aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cypress/.gitignore2
-rw-r--r--.cypress/cypress.json4
-rw-r--r--.cypress/cypress/integration/simple_spec.js16
-rw-r--r--.travis.yml16
-rw-r--r--CHANGELOG.md8
-rwxr-xr-xVagrantfile2
-rwxr-xr-xbin/browser-tests129
-rwxr-xr-xbin/fetch-reports25
-rwxr-xr-xbin/fixmystreet.com/fixture106
-rwxr-xr-xbin/oxfordshire/archive-old-enquiries9
-rwxr-xr-xbin/setup-contacts204
-rwxr-xr-xbin/update-schema2
-rwxr-xr-xbin/zurich/fixture119
-rwxr-xr-xbin/zurich/overdue-alert4
-rw-r--r--cpanfile2
-rw-r--r--cpanfile.snapshot50
-rw-r--r--db/downgrade_0057---0056.sql5
-rw-r--r--db/downgrade_0058---0057.sql5
-rw-r--r--db/schema.sql2
-rw-r--r--db/schema_0057-fetch-problems.sql5
-rw-r--r--db/schema_0058-blank-updates-permitted.sql5
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm7
-rw-r--r--perllib/FixMyStreet/App/Controller/Open311.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm5
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm49
-rw-r--r--perllib/FixMyStreet/Cobrand/Rutland.pm46
-rw-r--r--perllib/FixMyStreet/Cobrand/UK.pm3
-rw-r--r--perllib/FixMyStreet/Cobrand/Zurich.pm573
-rw-r--r--perllib/FixMyStreet/DB/Factories.pm133
-rw-r--r--perllib/FixMyStreet/DB/Result/Body.pm8
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm14
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm4
-rw-r--r--perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm35
-rw-r--r--perllib/FixMyStreet/Script/Reports.pm4
-rw-r--r--perllib/FixMyStreet/SendReport/Open311.pm1
-rw-r--r--perllib/FixMyStreet/SendReport/Zurich.pm8
-rw-r--r--perllib/FixMyStreet/TestAppProve.pm90
-rw-r--r--perllib/Open311.pm50
-rw-r--r--perllib/Open311/GetServiceRequestUpdates.pm36
-rw-r--r--perllib/Open311/GetServiceRequests.pm168
-rw-r--r--perllib/Open311/GetUpdates.pm2
-rw-r--r--perllib/Open311/PopulateServiceList.pm2
-rwxr-xr-xsetenv.pl7
-rw-r--r--t/app/controller/report_display.t106
-rw-r--r--t/app/controller/report_new_open311.t5
-rw-r--r--t/app/script/archive_old_enquiries.t16
-rw-r--r--t/cobrand/rutland.t60
-rw-r--r--t/cobrand/zurich.t147
-rw-r--r--t/open311/getservicerequests.t301
-rw-r--r--t/open311/getservicerequestupdates.t48
-rw-r--r--t/open311/populate-service-list.t90
-rw-r--r--t/sendreport/open311.t81
-rw-r--r--templates/email/rutland/_email_color_overrides.html21
-rw-r--r--templates/email/warwickshire/archive.txt26
-rw-r--r--templates/web/base/admin/bodies.html2
-rw-r--r--templates/web/base/admin/list_updates.html6
-rw-r--r--templates/web/base/admin/open311-form-fields.html26
-rw-r--r--templates/web/base/admin/problem_row.html6
-rw-r--r--templates/web/base/admin/report-category.html4
-rwxr-xr-x[-rw-r--r--]templates/web/base/footer.html0
-rw-r--r--templates/web/base/report/_inspect.html2
-rw-r--r--templates/web/base/report/new/category_extras_fields.html3
-rwxr-xr-xtemplates/web/rutland/front/footer-marketing.html8
-rwxr-xr-xtemplates/web/rutland/site-name.html1
-rw-r--r--templates/web/zurich/admin/body.html30
-rw-r--r--templates/web/zurich/admin/contact-form.html25
-rw-r--r--templates/web/zurich/admin/header.html14
-rw-r--r--templates/web/zurich/admin/index-dm.html6
-rw-r--r--templates/web/zurich/admin/index-sdm.html2
-rw-r--r--templates/web/zurich/admin/index.html3
-rw-r--r--templates/web/zurich/admin/problem_row.html5
-rw-r--r--templates/web/zurich/admin/report_edit-sdm.html16
-rw-r--r--templates/web/zurich/admin/report_edit.html35
-rw-r--r--templates/web/zurich/admin/stats/index.html39
-rw-r--r--templates/web/zurich/report/_item.html4
-rw-r--r--templates/web/zurich/report/_main.html2
-rw-r--r--templates/web/zurich/report/updates.html6
-rw-r--r--web/cobrands/fixmystreet/assets.js172
-rw-r--r--web/cobrands/fixmystreet/fixmystreet.js18
-rw-r--r--web/cobrands/fixmystreet/staff.js46
-rwxr-xr-xweb/cobrands/rutland/RCCLogo.gifbin0 -> 74768 bytes
-rwxr-xr-xweb/cobrands/rutland/_colours.scss34
-rwxr-xr-xweb/cobrands/rutland/base.scss16
-rw-r--r--web/cobrands/rutland/images/email-logo.gifbin0 -> 3606 bytes
-rwxr-xr-xweb/cobrands/rutland/layout.scss15
-rw-r--r--web/cobrands/sass/_base.scss3
-rw-r--r--web/cobrands/zurich/base.scss6
-rw-r--r--web/cobrands/zurich/js.js2
-rw-r--r--web/cobrands/zurich/layout.scss8
-rw-r--r--web/js/map-OpenLayers.js7
92 files changed, 2308 insertions, 1160 deletions
diff --git a/.cypress/.gitignore b/.cypress/.gitignore
new file mode 100644
index 000000000..f677bfcd7
--- /dev/null
+++ b/.cypress/.gitignore
@@ -0,0 +1,2 @@
+/cypress/videos
+/cypress/screenshots
diff --git a/.cypress/cypress.json b/.cypress/cypress.json
new file mode 100644
index 000000000..dd3beceef
--- /dev/null
+++ b/.cypress/cypress.json
@@ -0,0 +1,4 @@
+{
+ "baseUrl": "http://localhost:3001",
+ "projectId": "y8vvs1"
+}
diff --git a/.cypress/cypress/integration/simple_spec.js b/.cypress/cypress/integration/simple_spec.js
new file mode 100644
index 000000000..39000a022
--- /dev/null
+++ b/.cypress/cypress/integration/simple_spec.js
@@ -0,0 +1,16 @@
+describe('My First Test', function() {
+ it('Visits the home page', function() {
+ cy.visit('/');
+ cy.contains('Go');
+ cy.get('[name=pc]').type('BS10 5EE');
+ cy.get('#postcodeForm').submit();
+ cy.url().should('include', '/around');
+ cy.get('#map_box').click(200, 200);
+ cy.get('[name=title]').type('Title');
+ cy.get('[name=detail]').type('Detail');
+ cy.get('[name=username]').type('user@example.org');
+ cy.get('[name=password_sign_in]').type('password');
+ cy.get('form').submit();
+ cy.get('form').submit();
+ });
+});
diff --git a/.travis.yml b/.travis.yml
index 9641e2819..de3ec7b29 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,9 +20,20 @@ perl:
# Params::Classify breaks in 5.26, and Lingua::EN::Tagger needs upgrading
# - "5.26"
+cache:
+ directories:
+ - ~/.npm
+ - node_modules
+
+matrix:
+ include:
+ - perl: "5.22"
+ env: "CYPRESS=1"
+
env:
global:
- "S3_BUCKET=fixmystreet-bundle-cache"
+ - "CYPRESS=0"
- secure: "llgWNfR/8pH0HjYpg+xhVxuqTaLC0GGUugfuINiUap7JxzjCZ2rlryxCXA4BCM8GUHa9wlYKhrKCSx+DM3EHRE0cLei7LNxAK1JSXLj3NihFQhqnq64tjDwGCSA4l7mlqErA7DK4Dpmh+hBp5f680akITAInM92CbwQZxLDYaCU="
- secure: "qW+WCgAF68itADxcbcq+nCnKx3vf3GX73HMfjfbkFFUsYmIR+ZaJ9yQMnGJwxIpCHTWLAeqyx4KO8N8T3GmNdKYzIMZemOzp4ED29YC31QOQeq1CwNp2hD5sq/o47d2BzXWwMYNvNXfxz1K6r2c6EMPUtu8X3B8ExZq1RzSFdXs="
@@ -38,10 +49,13 @@ addons:
install:
- .travis/install
- 'if [ "$TRAVIS_PERL_VERSION" = "5.24" ]; then cpanm --quiet --notest Devel::Cover::Report::Codecov; fi'
+ - 'if [ "$CYPRESS" = "1" ]; then npm install cypress; fi'
before_script:
- commonlib/bin/gettext-makemo FixMyStreet
- 'if [ "$TRAVIS_PERL_VERSION" = "5.24" ]; then export HARNESS_PERL_SWITCHES="-MDevel::Cover=+ignore,local/lib/perl5,commonlib,perllib/Catalyst/[^A],perllib/DBIx,perllib/Email,perllib/Template,^t"; fi'
-script: "script/test --jobs 3 t"
+script:
+ - 'if [ "$CYPRESS" = "0" ]; then script/test --jobs 3 t; fi'
+ - 'if [ "$CYPRESS" = "1" ]; then PATH=$(npm bin):$PATH bin/browser-tests run --record; fi'
after_success:
- .travis/after_script
- 'if [ "$TRAVIS_PERL_VERSION" = "5.24" ]; then cover --report codecov; fi'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26444fc34..a0e8bb1d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,17 @@
## Releases
* Unreleased
+ - New features
+ - Fetch problems over Open311 #1986
+ - Option to send multiple photos over Open311 #1986
+ - Allow Open311 service definitions to include automated
+ attributes #1986
+ - Optionally supress blank Open311 update errors #1986
- Front end improvements:
- Improve questionnaire process. #1939 #1998
- Increase size of "sub map links" (hide pins, permalink, etc) #2003
- Edge-to-edge email layout on narrow screens #2010
+ - Add default placeholder to report extra fields. #2027
- Bugfixes:
- Stop asset layers obscuring marker layer. #1999
- Don't delete hidden field values when inspecting reports. #1999
@@ -14,6 +21,7 @@
- Admin improvements:
- Inspectors can set non_public status of reports. #1992
- Default start date is shown on the dashboard.
+ - Users with 'user_edit' permission can search for users/reports. #2027
- Development improvements:
- Add HTML email previewer.
diff --git a/Vagrantfile b/Vagrantfile
index 536974693..b3647ad2b 100755
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -23,6 +23,8 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
config.vm.network :forwarded_port, guest: 3000, host: 3000
+ # And 3001 for the Cypress test server
+ config.vm.network :forwarded_port, guest: 3001, host: 3001
config.vm.synced_folder ".", "/home/vagrant/fixmystreet", :owner => "vagrant", :group => "vagrant"
diff --git a/bin/browser-tests b/bin/browser-tests
new file mode 100755
index 000000000..0c56be918
--- /dev/null
+++ b/bin/browser-tests
@@ -0,0 +1,129 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+BEGIN {
+ use File::Basename qw(dirname);
+ use File::Spec;
+ my $d = dirname(File::Spec->rel2abs($0));
+ require "$d/../setenv.pl";
+}
+
+use Getopt::Long ':config' => qw(pass_through auto_help);
+
+my $config_file = 'conf/general.yml-example';
+my $run_server;
+my $run_cypress;
+my $vagrant;
+GetOptions(
+ 'config=s' => \$config_file,
+ 'server' => \$run_server,
+ 'cypress' => \$run_cypress,
+ 'vagrant' => \$vagrant,
+);
+
+if ($vagrant) {
+ # Test inception
+ system('vagrant ssh -c "cd fixmystreet && bin/browser-tests --server 2>/dev/null" &');
+
+ require IO::Socket;
+ sub check_connection {
+ my $remote = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "localhost", PeerPort => 3001) or return;
+ $remote->autoflush(1);
+ print $remote "GET / HTTP/1.0\r\n\r\n";
+ while (<$remote>) { return 1; }
+ 0;
+ }
+
+ local $| = 1;
+ print 'Waiting for test server';
+ while (!check_connection()) {
+ print '.'; sleep 1;
+ }
+ print " done\n";
+ system('bin/browser-tests', '--cypress', @ARGV);
+ system('vagrant', 'ssh', '-c', 'kill $(cat /tmp/cypress-server.pid)');
+ exit;
+}
+
+if (!$run_server && !$run_cypress) {
+ # If asked for neither, run both
+ $run_server = $run_cypress = 1;
+}
+
+sub run {
+ my $cmd = shift @ARGV;
+ die "Must specify a cypress command\n" unless $cmd || !$run_cypress;
+
+ if ($run_server) {
+ require FixMyStreet::TestAppProve;
+ require t::Mock::MapIt;
+ my $config_out = FixMyStreet::TestAppProve->get_config({
+ config_file => $config_file,
+ # Want this to be like .com
+ ALLOWED_COBRANDS => [ 'fixmystreet' ],
+ MAPIT_URL => 'https://mapit.uk/',
+ });
+ $ENV{FMS_OVERRIDE_CONFIG} = $config_out;
+
+ # Set up, and load in some data
+ system('bin/make_css', 'fixmystreet.com');
+ system('bin/fixmystreet.com/fixture', '--coords', '51.532851,-2.284277', '--name', 'Borsetshire', '--area-id', 2608, '--commit');
+ }
+
+ my $pid;
+ if ($run_server && $run_cypress) {
+ $pid = fork();
+ die if not defined $pid;
+ }
+
+ if (($run_cypress && !$run_server) || $pid) {
+ # Parent, run the test runner (then kill the child)
+ my $exit = system("cypress", $cmd, '--config', 'fixturesFolder=false,pluginsFile=false,supportFile=false,blacklistHosts=[gaze.mysociety.org,*.openstreetmap.org]', '--project', '.cypress', @ARGV);
+ kill 'TERM', $pid if $pid;
+ exit $exit >> 8;
+ } else {
+ # Child, run the server on port 3001
+ local $ENV{FIXMYSTREET_APP_DEBUG} = 0;
+ require Plack::Runner;
+ my $runner = Plack::Runner->new;
+ $runner->parse_options('--listen', ':3001', '-s', 'Starman', '--env', 'deployment', '--pid', '/tmp/cypress-server.pid');
+ $runner->run;
+ }
+}
+
+run();
+
+
+__END__
+
+=head1 NAME
+
+browser-tests - Run Cypress browser tests, set up for FixMyStreet.
+
+=head1 SYNOPSIS
+
+browser-tests [options] [cypress options]
+
+ Options:
+ --config provide an override general.yml file
+ --server only run the test server, not cypress
+ --cypress only run cypress, not the test server
+ --vagrant run test server inside Vagrant, cypress outside
+ --help this help message
+
+Use browser-tests instead of running cypress directly, so that a clean
+database is set up for Cypress to use, not affecting your normal dev database.
+If you're running FixMyStreet in a VM, you can use this script to run the test
+server in the VM and Cypress outside of it.
+
+ $ browser-tests open # to run interactively
+ $ browser-tests run # run headlessly
+ $ browser-tests run --record --key # record and upload a run
+ $ browser-tests --vagrant run # run if you use Vagrant
+
+You need to have installed cypress already using npm, and it needs to be on
+your PATH.
+
+=cut
diff --git a/bin/fetch-reports b/bin/fetch-reports
new file mode 100755
index 000000000..665b4aff0
--- /dev/null
+++ b/bin/fetch-reports
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+#
+# This script utilises the Open311 extension explained at
+# https://github.com/mysociety/FixMyStreet/wiki/Open311-FMS---Proposed-differences-to-Open311
+# to fetch updates on service requests.
+
+use strict;
+use warnings;
+require 5.8.0;
+
+BEGIN {
+ use File::Basename qw(dirname);
+ use File::Spec;
+ my $d = dirname(File::Spec->rel2abs($0));
+ require "$d/../setenv.pl";
+}
+
+use CronFns;
+my ($verbose, $nomail) = CronFns::options();
+
+use Open311::GetServiceRequests;
+
+my $reports = Open311::GetServiceRequests->new( verbose => $verbose );
+
+$reports->fetch;
diff --git a/bin/fixmystreet.com/fixture b/bin/fixmystreet.com/fixture
index aceb75bd3..1aa85564f 100755
--- a/bin/fixmystreet.com/fixture
+++ b/bin/fixmystreet.com/fixture
@@ -16,12 +16,9 @@ BEGIN {
}
use List::Util qw(shuffle);
-use Path::Tiny;
use FixMyStreet;
use FixMyStreet::Cobrand;
use FixMyStreet::DB::Factories;
-use FixMyStreet::App::Model::PhotoSet;
-use DateTime::Format::Pg;
use Getopt::Long::Descriptive;
my ($opt, $usage) = describe_options(
@@ -35,32 +32,7 @@ my ($opt, $usage) = describe_options(
);
print($usage->text), exit if $opt->help;
-my $db = FixMyStreet::DB->schema->storage;
-$db->txn_begin;
-END {
- if ($db) {
- $opt->commit ? $db->txn_commit : $db->txn_rollback;
- }
-}
-if (!$opt->commit) {
- say "NOT COMMITTING TO DATABASE";
-}
-
-if ($opt->empty) {
- $db->dbh->do(q{
-DO
-$func$
-BEGIN
- EXECUTE
- (SELECT 'TRUNCATE TABLE ' || string_agg(quote_ident(tablename), ', ') || ' RESTART IDENTITY CASCADE '
- FROM pg_tables WHERE schemaname='public');
-END
-$func$;
-}) or die $!;
- $db->dbh->do( scalar FixMyStreet->path_to('db/fixture.sql')->slurp ) or die $!;
- $db->dbh->do( scalar FixMyStreet->path_to('db/generate_secret.sql')->slurp ) or die $!;
- say "Emptied database";
-}
+FixMyStreet::DB::Factories->setup($opt);
# Body + categories
my $categories = ['Potholes', 'Street lighting', 'Graffiti', 'Other'];
@@ -119,50 +91,6 @@ foreach (
# Problems
-my %titles = (
- 'Potholes' => ['Deep pothole', 'Small pothole', 'Pothole in cycle lane', 'Pothole on busy pavement', 'Large pothole', 'Sinking manhole'],
- 'Street lighting' => ['Faulty light', 'Street light not working', 'Lights out in tunnel', 'Light not coming on', 'Light not going off'],
- 'Graffiti' => ['Graffiti', 'Graffiti', 'Offensive graffiti', 'Graffiti on the bridge', 'Remove graffiti'],
- 'Other' => ['Loose drain cover', 'Flytipping on country lane', 'Vehicle blocking footpath', 'Hedge encroaching pavement', 'Full litter bins'],
-);
-my %photos = (
- 'Potholes' => [ '33717571655_46dfc6f65f_z.jpg', '37855543925_9dbbbecf41_z.jpg', '19119222668_a3c866d7c8_z.jpg', '12049724866_404b066875_z.jpg', '3705226606_eac71cf195_z.jpg', '6304445383_bd216ca892_z.jpg' ],
- 'Street lighting' => ['38110448864_fd71227247_z.jpg', '27050321819_ac123400eb_z.jpg', '35732107202_b790c61f63_z.jpg', '31889115854_01cdf38b0d_z.jpg', undef ],
- 'Graffiti' => ['12205918375_f37f0b27a9_z.jpg', '8895442578_376a9b0be0_z.jpg', '22998854352_17555b7536_z.jpg', '22593395257_3d48f23bfa_z.jpg', '20515339175_f4ed9fc1d9_z.jpg' ],
- 'Other' => ['14347396807_20737504f7_z.jpg', '14792525771_167bc20e3d_z.jpg', undef, '36296226976_a83a118ff8_z.jpg', '23222004240_273977b2b2_z.jpg'],
-);
-my %descriptions = (
- 'Potholes' => [
- '6” deep pothole in the very centre of the Bristol road; cars are swerving to avoid it. Please treat this as a matter of urgency.',
- 'It’s small but it’s a trip hazard. Right where people cross over to get into the school or church. About 3” across but will become larger if not attended to.',
- 'Just went over my handlebars as I didn’t see this pothole on Banbury road, just before the traffic lights. Dread to think what might have happened if the traffic had been busier.',
- 'I work in the cafe at 34 Clarington Avenue and we’ve had four people come in having tripped over in the last seven days. The pothole’s right outside the key-cutting shop, just near the alleyway.',
- 'This has been here, next to the side of the road, for a month',
- 'A manhole on the junction of Etherington Road is sinking into the road surface. Not only is it an accident waiting to happen but it’s making a terrible noise every time a car passes over it.',
- ],
- 'Street lighting' => [
- 'I saw a workman attempting to fix this streetlight over a week ago, and ever since then it’s come on in the daytime and gone off as soon as it gets dark. Come and sort it out please!',
- 'Every Tuesday night I have to walk across the carpark outside the station at around 9pm. Not a problem in summer but now the nights are drawing in I feel very unsafe. Please get the streetlight by the exit fixed as I’m sure I can’t be the only woman feeling vulnerable.',
- 'My toddler is too scared to go in now, as soon as you’re more than a few paces in it’s absolutely pitch black with no hope of seeing any puddles or worse on the floor. I think this needs seeing to as a priority. Thank you.',
- 'I think the lights in the multi storey carpark are motion sensitive but I’ve actually never seen them come on. Maybe the bulb needs replacing??',
- 'This streetlight is right outside my bedroom window. It is on 24 hours a day, even in blazing sunlight. Apart from the fact that it’s a waste of electricity, it makes my bedroom feel like an interrogation chamber. Please come and fix it.',
- ],
- 'Graffiti' => [
- 'Someone has scrawled a really offensive piece of graffiti (are they called ‘tags’??) on the side of the town hall. You might want to see about getting it cleaned off. Wouldn’t want my own children to see that, I’m sure others feel the same.',
- 'Can’t see the timetable at the bus shelter cos some idiot’s covered it all in red spray paint. Honestly. Kids of today.',
- 'Not gonna write down what it depicts cos I suspect that’d get caught in your profanity filter lol. But please do come and paint over this monstrosity before it causes an accident.',
- 'That same guy that’s graffitied all over town has gone and done the same on the passenger bridge over the tracks, you can see it as you come into the station. Ugly bit of garbage graffiti. Bit of a poor first impression for the town eh.',
- 'What’s the procedure for requesting a bit of graffiti be removed? There’s been a huge scrawl on the wall outside the club for months. Nice sentiment maybe but really brings the tone of the area down.',
- ],
- 'Other' => [
- 'Surprised me so much when I crossed the road I nearly took a tumble! Glad I didn’t fall in, this really needs securing now.',
- 'Some unmentionable has driven down Larker’s Lane and left a huge heap of old rubbish on the verge. Talk about ruining the view! Such a beautiful spot and these lowlifes come and dump their junk. Probably trying to avoid paying the tip.',
- 'Well someone on foot can just about squeeze through but good luck if you’ve got a pushchair or god forbid a wheelchair. Think someone’s abandoned this car; it hasn’t moved in weeks.',
- 'Awful trying to walk past after a rain shower, well any time really.',
- 'I think these need seeing to more frequently, they’re always full to overflowing by midday.',
- ],
-);
-
my ($location, $lat, $lon);
if ($opt->coords) {
$location = $opt->coords;
@@ -182,45 +110,27 @@ foreach (FixMyStreet::Cobrand->available_cobrand_classes) {
}
}
+my $cache_dir = path(FixMyStreet->config('UPLOAD_DIR'));
+$cache_dir->mkpath;
+
my $user = $users{'user@example.org'};
my $num = 20;
say "Created $num problems around '$location' in cobrand '$cobrand'";
-my $inaccurate_km = 0.01;
my $confirmed = DateTime->today->subtract(days => 1)->add(hours => 8);
my $problems = [];
for (1..$num) {
$confirmed->add(seconds => rand(7000));
my $category = $categories->[int(rand(@$categories))];
- my $titles = $titles{$category};
- my $descs = $descriptions{$category};
- my $rand = int(rand(@$titles));
-
- my $photo;
- if (my $file = $photos{$category}->[$rand]) {
- my $files = [ $file ];
- if ($category eq 'Graffiti') {
- push @$files, $photos{$category}->[int(rand(@$titles))];
- }
- $files = [ map { path(FixMyStreet->path_to("t/images/$_"))->slurp_raw } @$files ];
- my $photoset = FixMyStreet::App::Model::PhotoSet->new({
- data_items => $files,
- });
- $photo = $photoset->data;
- }
-
- push @$problems, FixMyStreet::DB::Factory::Problem->create({
+ push @$problems, FixMyStreet::DB::Factory::Problem->create_problem({
body => $body,
areas => ',' . $opt->area_id . ',',
user => $user,
postcode => $location,
- latitude => $lat + rand(2 * $inaccurate_km) - $inaccurate_km,
- longitude => $lon + rand(3 * $inaccurate_km) - 1.5 * $inaccurate_km,
+ latitude => $lat,
+ longitude => $lon,
category => $category,
cobrand => $cobrand,
- title => $titles->[$rand],
- detail => $descs->[$rand],
- photo_id => $photo,
- confirmed => DateTime::Format::Pg->format_datetime($confirmed),
+ confirmed => $confirmed,
});
}
diff --git a/bin/oxfordshire/archive-old-enquiries b/bin/oxfordshire/archive-old-enquiries
index 7fe66703a..e82aa984e 100755
--- a/bin/oxfordshire/archive-old-enquiries
+++ b/bin/oxfordshire/archive-old-enquiries
@@ -17,10 +17,11 @@ use Getopt::Long::Descriptive;
my ($opts, $usage) = describe_options(
'%c %o',
['commit|c', "actually close reports and send emails. Omitting this flag will do a dry-run"],
- ['body|b=s', "which body ID to close reports for"],
- ['cobrand=s', "which cobrand template to use for sent emails"],
- ['closure-cutoff=s', "Anything before this will be closed with no email"],
- ['email-cutoff=s', "Anything before this will be closed with an email sent to the reporter"],
+ ['body|b=s', "which body ID to close reports for", { required => 1 } ],
+ ['user|u=s', "which user ID to assign closure updates to", { required => 1 } ],
+ ['cobrand=s', "which cobrand template to use for sent emails", { required => 1 } ],
+ ['closure-cutoff=s', "Anything before this will be closed with no email", { required => 1 } ],
+ ['email-cutoff=s', "Anything before this will be closed with an email sent to the reporter", { required => 1 } ],
['limit|l=s', "limit to a certain number of reports/users to be closed"],
['help|h', "print usage message and exit" ],
);
diff --git a/bin/setup-contacts b/bin/setup-contacts
deleted file mode 100755
index d562ae71d..000000000
--- a/bin/setup-contacts
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/env perl
-
-use strict;
-use warnings;
-require 5.8.0;
-use feature 'say';
-
-BEGIN {
- use File::Basename qw(dirname);
- use File::Spec;
- my $d = dirname(File::Spec->rel2abs($0));
- require "$d/../setenv.pl";
-}
-
-use FixMyStreet::App;
-
-my $moniker = $ARGV[0];
-
-my $c = FixMyStreet::App->new();
-my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker($moniker)->new({ c => $c });
-$c->stash->{cobrand} = $cobrand;
-
-die "Not a staging site, bailing out" unless $c->config->{STAGING_SITE}; # TODO, allow override via --force
-say "Applying contacts for $cobrand";
-
-
-ensure_bodies();
-setup_contacts();
-
-=head2 setup_contacts, ensure_bodies
-
-routines to update extra data for contacts. These can be called by
-a script:
-
- bin/setup-contacts zurich
-
-=cut
-
-sub ensure_bodies {
- my @bodies = $cobrand->body_details_data;
-
- my $bodies_rs = $c->model('DB::Body');
-
- for my $body (@bodies) {
- # following should work (having added Unique name/parent constraint, but doesn't)
- # $bodies_rs->find_or_create( $body, { $parent ? ( key => 'body_name_parent_key' ) : () } );
- # let's keep it simple and just allow unique names
- next if $bodies_rs->search({ name => $body->{name} })->count;
- if (my $area_id = delete $body->{area_id}) {
- $body->{body_areas} = [ { area_id => $area_id } ];
- }
- my $parent = $body->{parent};
- if ($parent and ! ref $parent) {
- $body->{parent} = { name => $parent };
- }
- $bodies_rs->find_or_create( $body );
- }
-}
-
-sub setup_contacts {
- die "Not a staging site, bailing out" unless $c->config->{STAGING_SITE}; # TODO, allow override via --force
-
- my @contact_details = $cobrand->contact_details_data;
-
- for my $detail (@contact_details) {
- update_contact( $detail, $description );
- }
-}
-
-sub update_contact {
- my ($contact_data, $description) = @_;
-
- my $contact_rs = $c->model('DB::Contact');
-
- my $category = $contact_data->{category} or die "No category provided";
- $description ||= "Update contact";
-
- my $contact = ensure_contact($contact_data, $description)
- or return; # e.g. nothing returned if deleted
-
- if (my $fields = $contact_data->{fields}) {
-
- my @fields = map { get_field_extra($_) } @$fields;
- my $note = sprintf 'Fields edited by automated script%s', $description ? " ($description)" : '';
- $contact->set_extra_fields(@fields);
- $contact->set_inflated_columns({
- confirmed => 1,
- deleted => 0,
- editor => 'automated script',
- whenedited => \'NOW()',
- note => "Updated fields $description",
- });
- $contact->update;
- }
-}
-
-sub ensure_contact {
- my ($contact_data, $description) = @_;
-
- my $category = $contact_data->{category} or die "No category provided";
- $description ||= "Ensure contact exists $category";
-
- my $email = temp_email_to_update(); # will only be set if newly created
-
- my $body = get_body_for_contact($contact_data) or die "No body found for $category";
-
- my $contact_rs = $c->model('DB::Contact');
-
- my $category_details = $contact_data->{category_details} || {};
-
- if (my $old_name = delete $contact_data->{rename_from}) {
- if (my $old_category = $contact_rs->find({
- category => $old_name,
- , body => $body,
- })) {
- $old_category->update({
- category => $category,
- whenedited => \'NOW()',
- note => "Renamed $description",
- %{ $category_details || {} },
- });
- return $old_category;
- }
- }
-
- if ($contact_data->{delete}) {
- my $contact = $contact_rs->search({
- body_id => $body->id,
- category => $category,
- deleted => 0
- });
- if ($contact->count) {
- print sprintf "Deleting: %s\n", $category;
- $contact->update({
- deleted => 1,
- editor => 'automated script',
- whenedited => \'NOW()',
- note => "Deleted by script $description",
- });
- }
- return;
- }
-
- return $contact_rs->find_or_create(
- {
- body => $body,
- category => $category,
-
- confirmed => 1,
- deleted => 0,
- email => $email,
- editor => 'automated script',
- note => 'created by automated script',
- send_method => '',
- whenedited => \'NOW()',
- %{ $category_details || {} },
- },
- {
- key => 'contacts_body_id_category_idx'
- }
- );
-}
-
-sub get_field_extra {
- my ($field) = @_;
-
- my %default = (
- variable => 'true',
- order => '1',
- required => 'no',
- datatype => 'string',
- datatype_description => 'a string',
- );
-
- if (($field->{datatype} || '') eq 'boolean') {
- %default = (
- %default,
- datatype => 'singlevaluelist',
- datatype_description => 'Yes or No',
- values => { value => [
- { key => ['No'], name => ['No'] },
- { key => ['Yes'], name => ['Yes'] },
- ] },
- );
- }
-
- return { %default, %$field };
-}
-
-sub temp_email_to_update { 'test@example.com' }
-
-sub get_body_for_contact {
- my ($contact_data) = @_;
- if (my $body_name = $contact_data->{body_name}) {
- return $c->model('DB::Body')->find({ name => $body_name });
- }
- if ($cobrand->can('contact_details_data_body_default')) {
- return $cobrand->contact_details_data_body_default;
- }
- return;
- # TODO: for UK Councils use
- # $c->model('DB::Body')->find(id => $cobrand->council_id);
- # # NB: (or better that's the area in BodyAreas)
-}
diff --git a/bin/update-schema b/bin/update-schema
index fea316bd6..9660837c6 100755
--- a/bin/update-schema
+++ b/bin/update-schema
@@ -212,6 +212,8 @@ else {
# (assuming schema change files are never half-applied, which should be the case)
sub get_db_version {
return 'EMPTY' if ! table_exists('problem');
+ return '0058' if column_exists('body', 'blank_updates_permitted');
+ return '0057' if column_exists('body', 'fetch_problems');
return '0056' if column_exists('users', 'email_verified');
return '0055' if column_exists('response_priorities', 'is_default');
return '0054' if table_exists('state');
diff --git a/bin/zurich/fixture b/bin/zurich/fixture
new file mode 100755
index 000000000..e387c4fab
--- /dev/null
+++ b/bin/zurich/fixture
@@ -0,0 +1,119 @@
+#!/usr/bin/env perl
+#
+# This script will create a test body and its categories, covering the area
+# provided, and users associated with that body, which should help testing
+# of report interactions.
+
+use strict;
+use warnings;
+use v5.14;
+use utf8;
+
+BEGIN {
+ use File::Basename qw(dirname);
+ use File::Spec;
+ my $d = dirname(File::Spec->rel2abs($0));
+ require "$d/../../setenv.pl";
+}
+
+use FixMyStreet;
+use FixMyStreet::Cobrand::Zurich;
+use FixMyStreet::DB::Factories;
+use Getopt::Long::Descriptive;
+
+my ($opt, $usage) = describe_options(
+ '%c %o',
+ [ 'empty', "Empty all tables of the database first" ],
+ [ 'commit', "Actually commit changes to the database" ],
+ [ 'help', "print usage message and exit", { shortcircuit => 1 } ],
+);
+print($usage->text), exit if $opt->help;
+
+FixMyStreet::DB::Factories->setup($opt);
+
+my $cobrand = FixMyStreet::Cobrand::Zurich->new();
+$cobrand->db_state_migration;
+
+# Body + categories
+my $body = FixMyStreet::DB::Factory::Body->find_or_create({
+ name => 'Zürich',
+ body_areas => [],
+ contacts => [],
+});
+say "Created body " . $body->name;
+
+my $categories = ['Potholes', 'Street lighting', 'Graffiti', 'Other'];
+my $div = FixMyStreet::DB::Factory::Body->find_or_create({
+ name => 'Division 1',
+ parent => $body->id,
+ endpoint => 'division@example.org',
+ categories => $categories,
+ area_id => 423017,
+});
+say "Created body " . $div->name;
+
+my $contact = $div->contacts->first;
+$contact->set_extra_fields(
+ map { $_->{variable} = 'true'; $_->{datatype} = 'string'; $_ }
+ { code => 'strasse', description => 'Strasse', required => 'yes', },
+ { code => 'haus_nr', description => 'Haus-Nr.', },
+ { code => 'mast_nr', description => 'Mast-Nr.', }
+);
+$contact->update;
+
+my $subdiv = FixMyStreet::DB::Factory::Body->find_or_create({
+ name => 'Subdivision A',
+ parent => $div->id,
+ endpoint => 'subdivision@example.org',
+ body_areas => [],
+ contacts => [],
+});
+say "Created body " . $subdiv->name;
+
+my $ext = FixMyStreet::DB::Factory::Body->find_or_create({
+ name => 'External Body',
+ endpoint => 'external_body@example.org',
+ body_areas => [],
+ contacts => [],
+});
+say "Created body " . $ext->name;
+
+# Users
+say "Created users, all with password 'password':";
+my %users;
+foreach (
+ { name => 'Super', email => 'super@example.org', email_verified => 1, body => $body },
+ { name => 'DM', email_verified => 1, email => 'dm@example.org', body => $div },
+ { name => 'SDM', email_verified => 1, email => 'sdm@example.org', body => $subdiv },
+ { name => 'Wizard of Oz', email_verified => 1, email => 'admin@example.org', body => $body, is_superuser => 't' },
+ { name => "Norma User", email_verified => 1, email => 'user@example.org' },
+) {
+ $users{$_->{email}} = FixMyStreet::DB::Factory::User->find_or_create($_);
+ my $su = $_->{is_superuser} ? " (superuser)" : "";
+ say "* $_->{email}$su";
+}
+
+# Problems
+
+my $lat = 47.381416;
+my $lon = 8.531369;
+
+my $user = $users{'user@example.org'};
+my $num = 20;
+say "Created $num problems around '$lat,$lon' in cobrand '" . $cobrand->moniker . "'";
+my $confirmed = DateTime->today->subtract(days => 1)->add(hours => 8);
+my $problems = [];
+for (1..$num) {
+ $confirmed->add(seconds => rand(7000));
+ my $category = $categories->[int(rand(@$categories))];
+ push @$problems, FixMyStreet::DB::Factory::Problem->create_problem({
+ body => $div,
+ user => $user,
+ postcode => "$lat,$lon",
+ latitude => $lat,
+ longitude => $lon,
+ category => $category,
+ cobrand => $cobrand->moniker,
+ confirmed => $confirmed,
+ });
+}
diff --git a/bin/zurich/overdue-alert b/bin/zurich/overdue-alert
index f4fd0f4b7..7689c172f 100755
--- a/bin/zurich/overdue-alert
+++ b/bin/zurich/overdue-alert
@@ -32,9 +32,9 @@ exit if FixMyStreet::Cobrand::Zurich::is_public_holiday($now) or FixMyStreet::Co
my $cobrand = FixMyStreet::Cobrand->get_class_for_moniker('zurich')->new();
my %bodies = map { $_->id => $_ } FixMyStreet::DB->resultset("Body")->all;
-loop_through( 'alert-moderation-overdue.txt', 0, 1, [ 'unconfirmed' ] );
+loop_through( 'alert-moderation-overdue.txt', 0, 1, [ 'submitted' ] );
loop_through( 'alert-overdue.txt', 1, 6, 'in progress' );
-loop_through( 'alert-overdue.txt', 0, 6, ['confirmed', 'planned'] );
+loop_through( 'alert-overdue.txt', 0, 6, ['confirmed', 'feedback pending'] );
sub loop_through {
my ( $template, $include_parent, $days, $states ) = @_;
diff --git a/cpanfile b/cpanfile
index 707dcb006..8b22e2729 100644
--- a/cpanfile
+++ b/cpanfile
@@ -130,7 +130,7 @@ recommends 'Linux::Inotify2' if $^O eq 'linux';
recommends 'Mac::FSEvents' if $^O eq 'darwin';
# Modules used by the test suite
-requires 'Test::PostgreSQL';
+requires 'Test::PostgreSQL', '1.24';
requires 'CGI::Simple';
requires 'HTTP::Headers';
requires 'HTTP::Response';
diff --git a/cpanfile.snapshot b/cpanfile.snapshot
index 83d855c98..b118d9c93 100644
--- a/cpanfile.snapshot
+++ b/cpanfile.snapshot
@@ -3039,6 +3039,14 @@ DISTRIBUTIONS
ExtUtils::MakeMaker 0
Fcntl 0
POSIX 0
+ perl 5.004
+ File-Which-1.22
+ pathname: P/PL/PLICEASE/File-Which-1.22.tar.gz
+ provides:
+ File::Which 1.22
+ requirements:
+ ExtUtils::MakeMaker 0
+ perl 5.006
Filesys-Notify-Simple-0.10
pathname: M/MI/MIYAGAWA/Filesys-Notify-Simple-0.10.tar.gz
provides:
@@ -3048,6 +3056,22 @@ DISTRIBUTIONS
Test::More 0
Test::SharedFork 0
perl 5.008001
+ Function-Parameters-2.001003
+ pathname: M/MA/MAUKE/Function-Parameters-2.001003.tar.gz
+ provides:
+ Function::Parameters 2.001003
+ Function::Parameters::Info 2.001003
+ Function::Parameters::Param 2.001003
+ requirements:
+ Carp 0
+ ExtUtils::MakeMaker 0
+ File::Find 0
+ File::Spec 0
+ Scalar::Util 0
+ XSLoader 0
+ perl 5.014000
+ strict 0
+ warnings 0
Geography-NationalGrid-1.6
pathname: P/PK/PKENT/Geography-NationalGrid-1.6.tar.gz
provides:
@@ -6164,18 +6188,25 @@ DISTRIBUTIONS
Test::More 0
Test::Tester 0.107
perl 5.006
- Test-PostgreSQL-1.05
- pathname: T/TJ/TJC/Test-PostgreSQL-1.05.tar.gz
+ Test-PostgreSQL-1.24
+ pathname: T/TJ/TJC/Test-PostgreSQL-1.24.tar.gz
provides:
- Test::PostgreSQL 1.05
+ Test::PostgreSQL 1.24
requirements:
- Class::Accessor::Lite 0
DBD::Pg 0
DBI 0
ExtUtils::MakeMaker 6.59
+ File::Spec 0
+ File::Which 0
+ Function::Parameters 0
+ Moo 0
+ POSIX 0
Test::SharedFork 0.06
- Time::HiRes 0
- perl 5.008
+ Tie::Hash::Method 0
+ Try::Tiny 0
+ Types::Standard 0
+ User::pwent 0
+ perl 5.014
Test-Requires-0.06
pathname: T/TO/TOKUHIROM/Test-Requires-0.06.tar.gz
provides:
@@ -6406,6 +6437,13 @@ DISTRIBUTIONS
Scalar::Util 0
Sub::Quote 0
overload 0
+ Tie-Hash-Method-0.02
+ pathname: Y/YV/YVES/Tie-Hash-Method-0.02.tar.gz
+ provides:
+ Tie::Hash::Method 0.02
+ requirements:
+ ExtUtils::MakeMaker 0
+ Test::More 0
Tie-IxHash-1.23
pathname: C/CH/CHORNY/Tie-IxHash-1.23.tar.gz
provides:
diff --git a/db/downgrade_0057---0056.sql b/db/downgrade_0057---0056.sql
new file mode 100644
index 000000000..a87488e41
--- /dev/null
+++ b/db/downgrade_0057---0056.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body DROP fetch_problems;
+
+COMMIT;
diff --git a/db/downgrade_0058---0057.sql b/db/downgrade_0058---0057.sql
new file mode 100644
index 000000000..1ce8f527c
--- /dev/null
+++ b/db/downgrade_0058---0057.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body DROP blank_updates_permitted;
+
+COMMIT;
diff --git a/db/schema.sql b/db/schema.sql
index f2197dc52..739090480 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -54,6 +54,8 @@ create table body (
suppress_alerts boolean not null default 'f',
can_be_devolved boolean not null default 'f',
send_extended_statuses boolean not null default 'f',
+ fetch_problems boolean not null default 'f',
+ blank_updates_permitted boolean not null default 'f',
deleted boolean not null default 'f'
);
diff --git a/db/schema_0057-fetch-problems.sql b/db/schema_0057-fetch-problems.sql
new file mode 100644
index 000000000..7419eb032
--- /dev/null
+++ b/db/schema_0057-fetch-problems.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body ADD fetch_problems boolean default 'f' not null;
+
+COMMIT;
diff --git a/db/schema_0058-blank-updates-permitted.sql b/db/schema_0058-blank-updates-permitted.sql
new file mode 100644
index 000000000..8b6710bc0
--- /dev/null
+++ b/db/schema_0058-blank-updates-permitted.sql
@@ -0,0 +1,5 @@
+BEGIN;
+
+ALTER TABLE body ADD blank_updates_permitted boolean default 'f' not null;
+
+COMMIT;
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 253840082..c12fdf9b9 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -340,6 +340,7 @@ sub update_contacts : Private {
}
$c->forward('update_extra_fields', [ $contact ]);
+ $c->forward('contact_cobrand_extra_fields', [ $contact ]);
if ( %errors ) {
$c->stash->{updated} = _('Please correct the errors below');
@@ -444,6 +445,8 @@ sub body_params : Private {
my %defaults = map { $_ => '' } @fields;
%defaults = ( %defaults,
send_comments => 0,
+ fetch_problems => 0,
+ blank_updates_permitted => 0,
suppress_alerts => 0,
comment_user_id => undef,
send_extended_statuses => 0,
@@ -1008,11 +1011,18 @@ sub report_edit_location : Private {
my ($lat, $lon) = map { Utils::truncate_coordinate($_) } $problem->latitude, $problem->longitude;
if ( $c->stash->{latitude} != $lat || $c->stash->{longitude} != $lon ) {
+ # The two actions below change the stash, setting things up for e.g. a
+ # new report. But here we're only doing it in order to check the found
+ # bodies match; we don't want to overwrite the existing report data if
+ # this lookup is bad. So let's save the stash and restore it after the
+ # comparison.
+ my $safe_stash = { %{$c->stash} };
$c->forward('/council/load_and_check_areas', []);
$c->forward('/report/new/setup_categories_and_bodies');
my %allowed_bodies = map { $_ => 1 } @{$problem->bodies_str_ids};
my @new_bodies = @{$c->stash->{bodies_to_list}};
my $bodies_match = grep { exists( $allowed_bodies{$_} ) } @new_bodies;
+ $c->stash($safe_stash);
return unless $bodies_match;
$problem->latitude($c->stash->{latitude});
$problem->longitude($c->stash->{longitude});
@@ -1035,7 +1045,6 @@ sub categories_for_point : Private {
# Remove the "Pick a category" option
shift @{$c->stash->{category_options}} if @{$c->stash->{category_options}};
- $c->stash->{category_options_copy} = $c->stash->{category_options};
$c->stash->{categories_hash} = { map { $_->{name} => 1 } @{$c->stash->{category_options}} };
}
@@ -1610,6 +1619,15 @@ sub user_edit : Path('user_edit') : Args(1) {
return 1;
}
+sub contact_cobrand_extra_fields : Private {
+ my ( $self, $c, $contact ) = @_;
+
+ my $extra_fields = $c->cobrand->call_hook('contact_extra_fields');
+ foreach ( @$extra_fields ) {
+ $contact->set_extra_metadata( $_ => $c->get_param("extra[$_]") );
+ }
+}
+
sub user_cobrand_extra_fields : Private {
my ( $self, $c ) = @_;
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 661579f95..7cdf150aa 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -345,6 +345,8 @@ Generates a CSV output, given a 'csv' stash hashref containing:
* columns: an arrayref of the columns (looked up in the row's as_hashref, plus
the following: user_name_display, acknowledged, fixed, closed, wards,
local_coords_x, local_coords_y, url).
+* extra_data: If present, a function that is passed the report and returns a
+hashref of extra data to include that can be used by 'columns'.
=cut
@@ -398,6 +400,11 @@ sub generate_csv : Private {
$report->local_coords;
$hashref->{url} = join '', $c->cobrand->base_url_for_report($report), $report->url;
+ if (my $fn = $c->stash->{csv}->{extra_data}) {
+ my $extra = $fn->($report);
+ $hashref = { %$hashref, %$extra };
+ }
+
$csv->combine(
@{$hashref}{
@{$c->stash->{csv}->{columns}}
diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm
index ddc976f2d..b50728b77 100644
--- a/perllib/FixMyStreet/App/Controller/Open311.pm
+++ b/perllib/FixMyStreet/App/Controller/Open311.pm
@@ -308,6 +308,7 @@ sub get_requests : Private {
# Only provide access to the published reports
my $states = FixMyStreet::DB::Result::Problem->visible_states();
delete $states->{unconfirmed};
+ delete $states->{submitted};
my $criteria = {
state => [ keys %$states ]
};
@@ -410,6 +411,7 @@ sub get_request : Private {
my $states = FixMyStreet::DB::Result::Problem->visible_states();
delete $states->{unconfirmed};
+ delete $states->{submitted};
my $criteria = {
state => [ keys %$states ],
id => $id,
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 7062f481f..df4dc5f77 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -221,9 +221,15 @@ sub format_problem_for_display : Private {
if ( $c->stash->{ajax} ) {
$c->res->content_type('application/json; charset=utf-8');
+
+ # encode_json doesn't like DateTime objects, so strip them out
+ my $report_hashref = $c->cobrand->problem_as_hashref( $problem, $c );
+ delete $report_hashref->{created};
+ delete $report_hashref->{confirmed};
+
my $content = encode_json(
{
- report => $c->cobrand->problem_as_hashref( $problem, $c ),
+ report => $report_hashref,
updates => $c->cobrand->updates_as_hashref( $problem, $c ),
}
);
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 888110429..e4e82f091 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -967,6 +967,7 @@ sub process_report : Private {
# set defaults that make sense
$report->state('unconfirmed');
+ $report->state('submitted') if $c->cobrand->moniker eq 'zurich';
# save the cobrand and language related information
$report->cobrand( $c->cobrand->moniker );
@@ -1002,7 +1003,7 @@ sub set_report_extras : Private {
foreach my $contact (@$contacts) {
my $metas = $contact->get_metadata_for_input;
foreach my $field ( @$metas ) {
- if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) {
+ if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field)) {
unless ( $c->get_param($param_prefix . $field->{code}) ) {
$c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
}
@@ -1019,7 +1020,7 @@ sub set_report_extras : Private {
my $metas = $extra_fields->get_extra_fields;
$param_prefix = "extra[" . $extra_fields->id . "]";
foreach my $field ( @$metas ) {
- if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field->{code})) {
+ if ( lc( $field->{required} ) eq 'true' && !$c->cobrand->category_extra_hidden($field)) {
unless ( $c->get_param($param_prefix . $field->{code}) ) {
$c->stash->{field_errors}->{ $field->{code} } = _('This information is required');
}
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index c6ca5c56b..8c4d8be53 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -667,6 +667,7 @@ sub admin_pages {
$pages->{responsepriority_edit} = [ undef, undef ];
};
if ( $user->has_body_permission_to('user_edit') ) {
+ $pages->{reports} = [ _('Reports'), 2 ];
$pages->{users} = [ _('Users'), 6 ];
$pages->{user_edit} = [ undef, undef ];
}
@@ -727,7 +728,7 @@ sub available_permissions {
# trusted => _("Trusted to make reports that don't need to be inspected"),
},
_("Users") => {
- user_edit => _("Edit other users' details"),
+ user_edit => _("Edit users' details/search for their reports"),
user_manage_permissions => _("Edit other users' permissions"),
user_assign_body => _("Grant access to the admin"),
user_assign_areas => _("Assign users to areas"), # future use
@@ -1159,50 +1160,6 @@ sub jurisdiction_id_example {
return $self->moniker;
}
-=item body_details_data
-
-Returns a list of bodies to create with ensure_body. These
-are mostly just passed to ->find_or_create, but there is some
-pre-processing so that you can enter:
-
- area_id => 123,
- parent => 'Big Town',
-
-instead of
-
- body_areas => [ { area_id => 123 } ],
- parent => { name => 'Big Town' },
-
-For example:
-
- return (
- {
- name => 'Big Town',
- },
- {
- name => 'Small town',
- parent => 'Big Town',
- area_id => 1234,
- },
-
-
-=cut
-
-sub body_details_data {
- return ();
-}
-
-=item contact_details_data
-
-Returns a list of contact_data to create with setup_contacts.
-See Zurich for an example.
-
-=cut
-
-sub contact_details_data {
- return ()
-}
-
=item lookup_by_ref_regex
Returns a regex to match postcode form input against to determine if a lookup
@@ -1221,7 +1178,7 @@ Return true if an Open311 service attribute should be a hidden field.
sub category_extra_hidden {
my ($self, $meta) = @_;
- return 0;
+ return 0;
}
=item reputation_increment_states/reputation_decrement_states
diff --git a/perllib/FixMyStreet/Cobrand/Rutland.pm b/perllib/FixMyStreet/Cobrand/Rutland.pm
new file mode 100644
index 000000000..c0f9dd197
--- /dev/null
+++ b/perllib/FixMyStreet/Cobrand/Rutland.pm
@@ -0,0 +1,46 @@
+package FixMyStreet::Cobrand::Rutland;
+use base 'FixMyStreet::Cobrand::UKCouncils';
+
+use strict;
+use warnings;
+
+sub council_area_id { return 2600; }
+sub council_area { return 'Rutland'; }
+sub council_name { return 'Rutland County Council'; }
+sub council_url { return 'rutland'; }
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+
+ my $extra = $row->get_extra_fields;
+ push @$extra, { name => 'external_id', value => $row->id };
+ push @$extra, { name => 'title', value => $row->title };
+ push @$extra, { name => 'description', value => $row->detail };
+
+ if ($h->{closest_address}) {
+ push @$extra, { name => 'closest_address', value => $h->{closest_address} }
+ }
+ $row->set_extra_fields( @$extra );
+
+ $params->{multi_photos} = 1;
+}
+
+sub example_places {
+ return ( 'LE15 6HP', 'High Street', 'Oakham' );
+}
+
+sub disambiguate_location {
+ my $self = shift;
+ my $string = shift;
+
+ return {
+ bounds => [52.524755166940075, -0.8217480325342802, 52.7597945702699, -0.4283542728893742]
+ };
+}
+
+sub pin_colour {
+ my ( $self, $p, $context ) = @_;
+ return 'green' if $p->is_fixed || $p->is_closed;
+ return 'yellow';
+}
+1;
diff --git a/perllib/FixMyStreet/Cobrand/UK.pm b/perllib/FixMyStreet/Cobrand/UK.pm
index e1f5e565f..0ecb6a7c6 100644
--- a/perllib/FixMyStreet/Cobrand/UK.pm
+++ b/perllib/FixMyStreet/Cobrand/UK.pm
@@ -396,7 +396,8 @@ sub lookup_by_ref_regex {
sub category_extra_hidden {
my ($self, $meta) = @_;
- return 1 if $meta eq 'usrn' || $meta eq 'asset_id';
+ return 1 if $meta->{code} eq 'usrn' || $meta->{code} eq 'asset_id';
+ return 1 if $meta->{automated} eq 'hidden_field';
return 0;
}
diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm
index 06244ccd4..c8af63987 100644
--- a/perllib/FixMyStreet/Cobrand/Zurich.pm
+++ b/perllib/FixMyStreet/Cobrand/Zurich.pm
@@ -10,7 +10,6 @@ use DateTime::Format::Pg;
use strict;
use warnings;
-use feature 'say';
use utf8;
=head1 NAME
@@ -56,8 +55,7 @@ you already have, and the countres set so that they shouldn't in future.
=cut
sub setup_states {
- FixMyStreet::DB::Result::Problem->visible_states_add('unconfirmed');
- FixMyStreet::DB::Result::Problem->visible_states_remove('investigating');
+ FixMyStreet::DB::Result::Problem->visible_states_remove('not contactable');
}
sub shorten_recency_if_new_greater_than_fixed {
@@ -67,7 +65,7 @@ sub shorten_recency_if_new_greater_than_fixed {
sub pin_colour {
my ( $self, $p, $context ) = @_;
return 'green' if $p->is_fixed || $p->is_closed;
- return 'red' if $p->state eq 'unconfirmed' || $p->state eq 'confirmed';
+ return 'red' if $p->state eq 'submitted' || $p->state eq 'confirmed';
return 'yellow';
}
@@ -98,35 +96,11 @@ sub prettify_dt {
return Utils::prettify_dt( $dt, 'zurich' );
}
-# problem already has a concept of is_fixed/is_closed, but Zurich has different
-# workflow for this here.
-#
-# TODO: look at more elegant way of doing this, for example having ::DB::Problem
-# consider cobrand specific state config?
-
-sub zurich_closed_states {
- my $states = {
- 'fixed - council' => 1,
- 'closed' => 1, # extern
- 'hidden' => 1,
- 'investigating' => 1, # wish
- 'unable to fix' => 1, # jurisdiction unknown
- 'partial' => 1, # not contactable
- };
-
- return wantarray ? keys %{ $states } : $states;
-}
-
-sub problem_is_closed {
- my ($self, $problem) = @_;
- return exists $self->zurich_closed_states->{ $problem->state } ? 1 : 0;
-}
-
sub zurich_public_response_states {
my $states = {
'fixed - council' => 1,
- 'closed' => 1, # extern
- 'unable to fix' => 1, # jurisdiction unknown
+ 'external' => 1,
+ 'wish' => 1,
};
return wantarray ? keys %{ $states } : $states;
@@ -134,9 +108,9 @@ sub zurich_public_response_states {
sub zurich_user_response_states {
my $states = {
+ 'jurisdiction unknown' => 1,
'hidden' => 1,
- 'investigating' => 1, # wish
- 'partial' => 1, # not contactable
+ 'not contactable' => 1,
};
return wantarray ? keys %{ $states } : $states;
@@ -160,43 +134,33 @@ sub problem_as_hashref {
my $hashref = $problem->as_hashref( $ctx );
- if ( $problem->state eq 'unconfirmed' ) {
- for my $var ( qw( photo detail state state_t is_fixed meta ) ) {
+ if ( $problem->state eq 'submitted' ) {
+ for my $var ( qw( photo is_fixed meta ) ) {
delete $hashref->{ $var };
}
$hashref->{detail} = _('This report is awaiting moderation.');
$hashref->{title} = _('This report is awaiting moderation.');
- $hashref->{state} = 'submitted';
- $hashref->{state_t} = _('Submitted');
$hashref->{banner_id} = 'closed';
} else {
+ if ( $problem->state eq 'confirmed' || $problem->state eq 'external' ) {
+ $hashref->{banner_id} = 'closed';
+ } elsif ( $problem->is_fixed || $problem->is_closed ) {
+ $hashref->{banner_id} = 'fixed';
+ } else {
+ $hashref->{banner_id} = 'progress';
+ }
+
if ( $problem->state eq 'confirmed' ) {
$hashref->{state} = 'open';
$hashref->{state_t} = _('Open');
- $hashref->{banner_id} = 'closed';
- } elsif ( $problem->state eq 'closed' ) {
- $hashref->{state} = 'extern'; # is this correct?
- $hashref->{banner_id} = 'closed';
- $hashref->{state_t} = _('Extern');
- } elsif ( $problem->state eq 'unable to fix' ) {
- $hashref->{state} = 'jurisdiction unknown'; # is this correct?
- $hashref->{state_t} = _('Jurisdiction Unknown');
- $hashref->{banner_id} = 'fixed'; # green
- } elsif ( $problem->state eq 'partial' ) {
- $hashref->{state} = 'not contactable'; # is this correct?
- $hashref->{state_t} = _('Not contactable');
- # no banner_id as hidden
- } elsif ( $problem->state eq 'investigating' ) {
- $hashref->{state} = 'wish'; # is this correct?
- $hashref->{state_t} = _('Wish');
+ } elsif ( $problem->state eq 'wish' ) {
+ $hashref->{state_t} = _('Closed');
} elsif ( $problem->is_fixed ) {
$hashref->{state} = 'closed';
- $hashref->{banner_id} = 'fixed';
$hashref->{state_t} = _('Closed');
- } elsif ( $problem->state eq 'in progress' || $problem->state eq 'planned' ) {
+ } elsif ( $problem->state eq 'feedback pending' ) {
$hashref->{state} = 'in progress';
- $hashref->{state_t} = _('In progress');
- $hashref->{banner_id} = 'progress';
+ $hashref->{state_t} = FixMyStreet::DB->resultset("State")->display('in progress');
}
}
@@ -210,13 +174,13 @@ sub updates_as_hashref {
my $hashref = {};
- if ( $problem->state eq 'fixed - council' || $problem->state eq 'closed' ) {
+ if ($self->problem_has_public_response($problem)) {
$hashref->{update_pp} = $self->prettify_dt( $problem->lastupdate );
- if ( $problem->state eq 'fixed - council' ) {
+ if ( $problem->state ne 'external' ) {
$hashref->{details} = FixMyStreet::App::View::Web::add_links(
$problem->get_extra_metadata('public_response') || '' );
- } elsif ( $problem->state eq 'closed' ) {
+ } else {
$hashref->{details} = sprintf( _('Assigned to %s'), $problem->body($ctx)->name );
}
}
@@ -229,6 +193,7 @@ sub updates_as_hashref {
# boolean whether that indexed photo can be shown.
sub allow_photo_display {
my ( $self, $r, $num ) = @_;
+ return unless $r;
my $publish_photo;
if (blessed $r) {
$publish_photo = $r->get_extra_metadata('publish_photo');
@@ -250,10 +215,6 @@ sub allow_photo_display {
return $i + 1;
}
-sub show_unconfirmed_reports {
- 1;
-}
-
sub get_body_sender {
my ( $self, $body, $category ) = @_;
return { method => 'Zurich' };
@@ -324,25 +285,23 @@ sub overdue {
return 0 unless $w;
# call with previous state
- if ( $problem->state eq 'unconfirmed' ) {
+ if ( $problem->state eq 'submitted' ) {
# One working day
$w = add_days( $w, 1 );
return $w < DateTime->now() ? 1 : 0;
- } elsif ( $problem->state eq 'confirmed' || $problem->state eq 'in progress' || $problem->state eq 'planned' ) {
+ } elsif ( $problem->state eq 'confirmed' || $problem->state eq 'in progress' || $problem->state eq 'feedback pending' ) {
# States which affect the subdiv_overdue statistic. TODO: this may no longer be required
# Six working days from creation
$w = add_days( $w, 6 );
return $w < DateTime->now() ? 1 : 0;
# call with new state
- } elsif ( $self->problem_is_closed($problem) ) {
+ } else {
# States which affect the closed_overdue statistic
# Five working days from moderation (so 6 from creation)
$w = add_days( $w, 6 );
return $w < DateTime->now() ? 1 : 0;
- } else {
- return 0;
}
}
@@ -430,6 +389,13 @@ sub admin_pages {
'users' => [_('Users'), 3],
'user_edit' => [undef, undef],
};
+
+ # There are some pages that only super users can see
+ if ($self->{c}->user->is_superuser) {
+ $pages->{states} = [ _('States'), 8 ];
+ $pages->{config} = [ _('Configuration'), 9];
+ };
+
return $pages if $type eq 'super';
}
@@ -471,14 +437,14 @@ sub admin {
$order .= ' desc' if $dir;
# XXX No multiples or missing bodies
- $c->stash->{unconfirmed} = $c->cobrand->problems->search({
- state => [ 'unconfirmed', 'confirmed' ],
+ $c->stash->{submitted} = $c->cobrand->problems->search({
+ state => [ 'submitted', 'confirmed' ],
bodies_str => $c->stash->{body}->id,
}, {
order_by => $order,
});
$c->stash->{approval} = $c->cobrand->problems->search({
- state => 'planned',
+ state => 'feedback pending',
bodies_str => $c->stash->{body}->id,
}, {
order_by => $order,
@@ -486,7 +452,7 @@ sub admin {
my $page = $c->get_param('p') || 1;
$c->stash->{other} = $c->cobrand->problems->search({
- state => { -not_in => [ 'unconfirmed', 'confirmed', 'planned' ] },
+ state => { -not_in => [ 'submitted', 'confirmed', 'feedback pending' ] },
bodies_str => \@all,
}, {
order_by => $order,
@@ -512,7 +478,7 @@ sub admin {
order_by => $order
} );
$c->stash->{reports_unpublished} = $c->cobrand->problems->search( {
- state => 'planned',
+ state => 'feedback pending',
bodies_str => $body->parent->id,
}, {
order_by => $order
@@ -529,6 +495,15 @@ sub admin {
}
}
+sub category_options {
+ my ($self, $c) = @_;
+ my @categories = $c->model('DB::Contact')->not_deleted->all;
+ $c->stash->{category_options} = [ map { {
+ name => $_->category, value => $_->category,
+ abbreviation => $_->get_extra_metadata('abbreviation'),
+ } } @categories ];
+}
+
sub admin_report_edit {
my $self = shift;
my $c = $self->{c};
@@ -539,6 +514,8 @@ sub admin_report_edit {
if ($type ne 'super') {
my %allowed_bodies = map { $_->id => 1 } ( $body->bodies->all, $body );
+ # SDMs can see parent reports but not edit them
+ $allowed_bodies{$body->parent->id} = 1 if $type eq 'sdm';
$c->detach( '/page_error_404_not_found' )
unless $allowed_bodies{$problem->bodies_str};
}
@@ -550,8 +527,7 @@ sub admin_report_edit {
$c->stash->{bodies} = \@bodies;
# Can change category to any other
- my @categories = $c->model('DB::Contact')->not_deleted->all;
- $c->stash->{category_options} = [ map { { name => $_->category, value => $_->category } } @categories ];
+ $self->category_options($c);
} elsif ($type eq 'dm') {
@@ -565,8 +541,7 @@ sub admin_report_edit {
$c->stash->{bodies} = \@bodies;
# Can change category to any other
- my @categories = $c->model('DB::Contact')->not_deleted->all;
- $c->stash->{category_options} = [ map { { name => $_->category, value => $_->category } } @categories ];
+ $self->category_options($c);
}
@@ -636,8 +611,7 @@ sub admin_report_edit {
my $state = $c->get_param('state') || '';
my $oldstate = $problem->state;
- my $closure_states = $self->zurich_closed_states;
- delete $closure_states->{'fixed - council'}; # may not be needed?
+ my $closure_states = { map { $_ => 1 } FixMyStreet::DB::Result::Problem->closed_states(), FixMyStreet::DB::Result::Problem->hidden_states() };
my $old_closure_state = $problem->get_extra_metadata('closure_status') || '';
@@ -662,19 +636,19 @@ sub admin_report_edit {
$self->update_admin_log($c, $problem, "Changed category from $old_cat to $new_cat");
$redirect = 1 if $cat->body_id ne $body->id;
} elsif ( $oldstate ne $state and $closure_states->{$state} and
- $oldstate ne 'planned' || $old_closure_state ne $state)
+ $oldstate ne 'feedback pending' || $old_closure_state ne $state)
{
# for these states
- # - closed (Extern)
- # - investigating (Wish)
+ # - external
+ # - wish
# - hidden
- # - partial (Not contactable)
- # - unable to fix (Jurisdiction unknown)
- # we divert to planned (Rueckmeldung ausstehend) and set closure_status to the requested state
+ # - not contactable
+ # - jurisdiction unknown
+ # we divert to feedback pending (Rueckmeldung ausstehend) and set closure_status to the requested state
# From here, the DM can reply to the user, triggering the setting of problem to correct state
$problem->set_extra_metadata( closure_status => $state );
- $self->set_problem_state($c, $problem, 'planned');
- $state = 'planned';
+ $self->set_problem_state($c, $problem, 'feedback pending');
+ $state = 'feedback pending';
$problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) );
} elsif ( my $subdiv = $c->get_param('body_subdivision') ) {
@@ -687,18 +661,18 @@ sub admin_report_edit {
} else {
if ($state) {
- if ($oldstate eq 'unconfirmed' and $state ne 'unconfirmed') {
+ if ($oldstate eq 'submitted' and $state ne 'submitted') {
# only set this for the first state change
$problem->set_extra_metadata_if_undefined( moderated_overdue => $self->overdue( $problem ) );
}
$self->set_problem_state($c, $problem, $state)
unless $closure_states->{$state};
- # we'll defer to 'planned' clause below to change the state
+ # we'll defer to 'feedback pending' clause below to change the state
}
}
- if ($problem->state eq 'planned') {
+ if ($problem->state eq 'feedback pending') {
# Rueckmeldung ausstehend
# override $state from the metadata set above
$state = $problem->get_extra_metadata('closure_status') || '';
@@ -711,7 +685,7 @@ sub admin_report_edit {
$moderated++;
$closed++;
}
- elsif ($state =~/^(closed|investigating)$/) { # Extern | Wish
+ elsif ($state =~/^(external|wish)$/) {
$moderated++;
# Nested if instead of `and` because in these cases, we *don't*
# want to close unless we have body_external (so we don't want
@@ -724,7 +698,7 @@ sub admin_report_edit {
if ($problem->external_body && $c->get_param('publish_response')) {
$problem->whensent( undef );
$self->set_problem_state($c, $problem, $state);
- my $template = ($state eq 'investigating') ? 'problem-wish.txt' : 'problem-external.txt';
+ my $template = ($state eq 'wish') ? 'problem-wish.txt' : 'problem-external.txt';
_admin_send_email( $c, $template, $problem );
$redirect = 0;
$closed++;
@@ -770,7 +744,7 @@ sub admin_report_edit {
# send external_message if provided and state is *now* Wish|Extern
# e.g. was already, or was set in the Rueckmeldung ausstehend clause above.
if ( my $external_message = $c->get_param('external_message')
- and $problem->state =~ /^(closed|investigating)$/)
+ and $problem->state =~ /^(external|wish)$/)
{
my $external = $problem->external_body;
my $external_body = $c->model('DB::Body')->find($external)
@@ -782,7 +756,7 @@ sub admin_report_edit {
$problem->add_to_comments( {
text => (
sprintf '(%s %s) %s',
- $state eq 'closed' ?
+ $state eq 'external' ?
_('Forwarded to external body') :
_('Forwarded wish to external body'),
$external_body->name,
@@ -841,10 +815,13 @@ sub admin_report_edit {
if ($type eq 'sdm') {
+ my $editable = $type eq 'sdm' && $body->id eq $problem->bodies_str;
+ $c->stash->{sdm_disabled} = $editable ? '' : 'disabled';
+
# Has cut-down edit template for adding update and sending back up only
$c->stash->{template} = 'admin/report_edit-sdm.html';
- if ($c->get_param('send_back') or $c->get_param('not_contactable')) {
+ if ($editable && $c->get_param('send_back') or $c->get_param('not_contactable')) {
# SDM can send back a report either to be assigned to a different
# subdivision, or because the customer was not contactable.
# We handle these in the same way but with different statuses.
@@ -856,8 +833,8 @@ sub admin_report_edit {
$problem->bodies_str( $body->parent->id );
if ($not_contactable) {
# we can't directly set state, but mark the closure_status for DM to confirm.
- $self->set_problem_state($c, $problem, 'planned');
- $problem->set_extra_metadata( closure_status => 'partial');
+ $self->set_problem_state($c, $problem, 'feedback pending');
+ $problem->set_extra_metadata( closure_status => 'not contactable');
}
else {
$self->set_problem_state($c, $problem, 'confirmed');
@@ -870,7 +847,7 @@ sub admin_report_edit {
# Make sure the problem's time_spent is updated
$self->update_admin_log($c, $problem);
$c->res->redirect( '/admin/summary' );
- } elsif ($c->get_param('submit')) {
+ } elsif ($editable && $c->get_param('submit')) {
$c->forward('/auth/check_csrf_token');
my $db_update = 0;
@@ -904,7 +881,7 @@ sub admin_report_edit {
$problem->set_extra_metadata( subdiv_overdue => $self->overdue( $problem ) );
$problem->bodies_str( $body->parent->id );
$problem->whensent( undef );
- $self->set_problem_state($c, $problem, 'planned');
+ $self->set_problem_state($c, $problem, 'feedback pending');
$problem->update;
$c->res->redirect( '/admin/summary' );
}
@@ -932,61 +909,52 @@ sub stash_states {
my @states = (
{
# Erfasst
- state => 'unconfirmed',
- trans => _('Submitted'),
- unconfirmed => 1,
+ state => 'submitted',
+ submitted => 1,
hidden => 1,
},
{
# Aufgenommen
state => 'confirmed',
- trans => _('Open'),
- unconfirmed => 1,
+ submitted => 1,
},
{
# Unsichtbar (hidden)
state => 'hidden',
- trans => _('Hidden'),
- unconfirmed => 1,
+ submitted => 1,
hidden => 1,
},
{
# Extern
- state => 'closed',
- trans => _('Extern'),
+ state => 'external',
},
{
# Zustaendigkeit unbekannt
- state => 'unable to fix',
- trans => _('Jurisdiction unknown'),
+ state => 'jurisdiction unknown',
},
{
- # Wunsch (hidden)
- state => 'investigating',
- trans => _('Wish'),
+ # Wunsch
+ state => 'wish',
},
{
# Nicht kontaktierbar (hidden)
- state => 'partial',
- trans => _('Not contactable'),
+ state => 'not contactable',
},
);
- my %state_trans = map { $_->{state} => $_->{trans} } @states;
my $state = $problem->state;
# Rueckmeldung ausstehend may also indicate the status it's working towards.
push @states, do {
- if ($state eq 'planned' and my $closure_status = $problem->get_extra_metadata('closure_status')) {
+ if ($state eq 'feedback pending' and my $closure_status = $problem->get_extra_metadata('closure_status')) {
{
state => $closure_status,
- trans => sprintf '%s (%s)', _('Planned'), $state_trans{$closure_status},
+ trans => sprintf 'Rückmeldung ausstehend (%s)', FixMyStreet::DB->resultset("State")->display($closure_status),
};
}
else {
{
- state => 'planned',
- trans => _('Planned'),
+ state => 'feedback pending',
};
}
};
@@ -994,25 +962,22 @@ sub stash_states {
if ($state eq 'in progress') {
push @states, {
state => 'in progress',
- trans => _('In progress'),
};
}
elsif ($state eq 'fixed - council') {
push @states, {
state => 'fixed - council',
- trans => _('Closed'),
};
}
- elsif ($state =~/^(hidden|unconfirmed)$/) {
+ elsif ($state =~/^(hidden|submitted)$/) {
@states = grep { $_->{$state} } @states;
}
$c->stash->{states} = \@states;
- $c->stash->{states_trans} = { map { $_->{state} => $_->{trans} } @states }; # [% states_trans.${problem.state} %]
# stash details about the public response
$c->stash->{default_public_response} = "\nFreundliche Grüsse\n\nIhre Stadt Zürich\n";
$c->stash->{show_publish_response} =
- ($problem->state eq 'planned');
+ ($problem->state eq 'feedback pending');
}
=head2 _admin_send_email
@@ -1044,7 +1009,7 @@ sub _admin_send_email {
sub munge_sendreport_params {
my ($self, $row, $h, $params) = @_;
- if ($row->state =~ /^(closed|investigating)$/) {
+ if ($row->state =~ /^(external|wish)$/) {
# we attach images to reports sent to external bodies
my $photoset = $row->get_photoset();
my $num = $photoset->num_images
@@ -1114,138 +1079,34 @@ sub admin_stats {
my $self = shift;
my $c = $self->{c};
- my %date_params;
+ my %optional_params;
my $ym = $c->get_param('ym');
my ($m, $y) = $ym ? ($ym =~ /^(\d+)\.(\d+)$/) : ();
$c->stash->{ym} = $ym;
if ($y && $m) {
$c->stash->{start_date} = DateTime->new( year => $y, month => $m, day => 1 );
$c->stash->{end_date} = $c->stash->{start_date} + DateTime::Duration->new( months => 1 );
- $date_params{created} = {
+ $optional_params{created} = {
'>=', DateTime::Format::Pg->format_datetime($c->stash->{start_date}),
'<', DateTime::Format::Pg->format_datetime($c->stash->{end_date}),
};
}
+ my $cat = $c->stash->{category} = $c->get_param('category');
+ $optional_params{category} = $cat if $cat;
+
my %params = (
- %date_params,
+ %optional_params,
state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
);
if ( $c->get_param('export') ) {
- my $problems = $c->model('DB::Problem')->search(
- {%date_params},
- {
- join => 'admin_log_entries',
- distinct => 1,
- columns => [
- 'id', 'created',
- 'latitude', 'longitude',
- 'cobrand', 'category',
- 'state', 'user_id',
- 'external_body',
- 'title', 'detail',
- 'photo',
- 'whensent', 'lastupdate',
- 'service',
- 'extra',
- { sum_time_spent => { sum => 'admin_log_entries.time_spent' } },
- ]
- }
- );
- my @fields = (
- 'Report ID',
- 'Created',
- 'Sent to Agency',
- 'Last Updated',
- 'E',
- 'N',
- 'Category',
- 'Status',
- 'Closure Status',
- 'UserID',
- 'External Body',
- 'Time Spent',
- 'Title',
- 'Detail',
- 'Media URL',
- 'Interface Used',
- 'Council Response',
- 'Strasse',
- 'Mast-Nr.',
- 'Haus-Nr.',
- 'Hydranten-Nr.',
- );
-
- my $body = "";
- require Text::CSV;
- my $csv = Text::CSV->new({ binary => 1 });
-
- if ($csv->combine(@fields)) {
- $body .= $csv->string . "\n";
- }
- else {
- $body .= sprintf "{{error emitting CSV line: %s}}\n", $csv->error_diag;
- }
-
- while ( my $report = $problems->next ) {
- my $external_body;
- my $body_name = "";
- if ( $external_body = $report->body($c) ) {
- $body_name = $external_body->name || '[Unknown body]';
- }
-
- my $detail = $report->detail;
- my $public_response = $report->get_extra_metadata('public_response') || '';
- my $metas = $report->get_extra_fields();
- my %extras;
- foreach my $field (@$metas) {
- $extras{$field->{name}} = $field->{value};
- }
-
- # replace newlines with HTML <br/> element
- $detail =~ s{\r?\n}{ <br/> }g;
- $public_response =~ s{\r?\n}{ <br/> }g if $public_response;
-
- # Assemble photo URL, if report has a photo
- my $photo_to_display = $c->cobrand->allow_photo_display($report);
- my $media_url = (@{$report->photos} && $photo_to_display)
- ? $c->cobrand->base_url . $report->photos->[$photo_to_display-1]->{url}
- : '';
-
- my @columns = (
- $report->id,
- $report->created,
- $report->whensent,
- $report->lastupdate,
- $report->local_coords, $report->category,
- $report->state,
- $report->get_extra_metadata('closure_status') || '',
- $report->user_id,
- $body_name,
- $report->get_column('sum_time_spent') || 0,
- $report->title,
- $detail,
- $media_url,
- $report->service || 'Web interface',
- $public_response,
- $extras{'strasse'} || '',
- $extras{'mast_nr'} || '',
- $extras{'haus_nr'} || '',
- $extras{'hydranten_nr'} || ''
- );
- if ($csv->combine(@columns)) {
- $body .= $csv->string . "\n";
- }
- else {
- $body .= sprintf "{{error emitting CSV line: %s}}\n", $csv->error_diag;
- }
- }
- $c->res->content_type('text/csv; charset=utf-8');
- $c->res->header('Content-Disposition' => 'attachment; filename=stats.csv');
- $c->res->body($body);
+ return $self->export_as_csv($c, \%optional_params);
}
+ # Can change category to any other
+ $self->category_options($c);
+
# Total reports (non-hidden)
my $total = $c->model('DB::Problem')->search( \%params )->count;
# Device for apps (iOS/Android)
@@ -1255,17 +1116,17 @@ sub admin_stats {
group_by => [ 'service' ],
});
# Reports solved
- my $solved = $c->model('DB::Problem')->search( { state => 'fixed - council', %date_params } )->count;
+ my $solved = $c->model('DB::Problem')->search( { state => 'fixed - council', %optional_params } )->count;
# Reports marked as spam
- my $hidden = $c->model('DB::Problem')->search( { state => 'hidden', %date_params } )->count;
+ my $hidden = $c->model('DB::Problem')->search( { state => 'hidden', %optional_params } )->count;
# Reports assigned to third party
- my $closed = $c->model('DB::Problem')->search( { state => 'closed', %date_params } )->count;
+ my $external = $c->model('DB::Problem')->search( { state => 'external', %optional_params } )->count;
# Reports moderated within 1 day
- my $moderated = $c->model('DB::Problem')->search( { extra => { like => '%moderated_overdue,I1:0%' }, %date_params } )->count;
+ my $moderated = $c->model('DB::Problem')->search( { extra => { like => '%moderated_overdue,I1:0%' }, %optional_params } )->count;
# Reports solved within 5 days (sent back from subdiv)
my $subdiv_dealtwith = $c->model('DB::Problem')->search( { extra => { like => '%subdiv_overdue,I1:0%' }, %params } )->count;
- # Reports solved within 5 days (marked as 'fixed - council', 'closed', or 'hidden'
- my $fixed_in_time = $c->model('DB::Problem')->search( { extra => { like => '%closed_overdue,I1:0%' }, %date_params } )->count;
+ # Reports solved within 5 days (marked as 'fixed - council', 'external', or 'hidden'
+ my $fixed_in_time = $c->model('DB::Problem')->search( { extra => { like => '%closed_overdue,I1:0%' }, %optional_params } )->count;
# Reports per category
my $per_category = $c->model('DB::Problem')->search( \%params, {
select => [ 'category', { count => 'id' } ],
@@ -1294,7 +1155,7 @@ sub admin_stats {
reports_total => $total,
reports_solved => $solved,
reports_spam => $hidden,
- reports_assigned => $closed,
+ reports_assigned => $external,
reports_moderated => $moderated,
reports_dealtwith => $fixed_in_time,
reports_category_changed => $changed,
@@ -1309,6 +1170,96 @@ sub admin_stats {
return 1;
}
+sub export_as_csv {
+ my ($self, $c, $params) = @_;
+ my $csv = $c->stash->{csv} = {
+ problems => $c->model('DB::Problem')->search_rs(
+ $params,
+ {
+ join => 'admin_log_entries',
+ distinct => 1,
+ columns => [
+ 'id', 'created',
+ 'latitude', 'longitude',
+ 'cobrand', 'category',
+ 'state', 'user_id',
+ 'external_body',
+ 'title', 'detail',
+ 'photo',
+ 'whensent', 'lastupdate',
+ 'service',
+ 'extra',
+ { sum_time_spent => { sum => 'admin_log_entries.time_spent' } },
+ ]
+ }
+ ),
+ headers => [
+ 'Report ID', 'Created', 'Sent to Agency', 'Last Updated',
+ 'E', 'N', 'Category', 'Status', 'Closure Status',
+ 'UserID', 'User email', 'User phone', 'User name',
+ 'External Body', 'Time Spent', 'Title', 'Detail',
+ 'Media URL', 'Interface Used', 'Council Response',
+ 'Strasse', 'Mast-Nr.', 'Haus-Nr.', 'Hydranten-Nr.',
+ ],
+ columns => [
+ 'id', 'created', 'whensent',' lastupdate', 'local_coords_x',
+ 'local_coords_y', 'category', 'state', 'closure_status',
+ 'user_id', 'user_email', 'user_phone', 'user_name',
+ 'body_name', 'sum_time_spent', 'title', 'detail',
+ 'media_url', 'service', 'public_response',
+ 'strasse', 'mast_nr',' haus_nr', 'hydranten_nr',
+ ],
+ extra_data => sub {
+ my $report = shift;
+
+ my $body_name = "";
+ if ( my $external_body = $report->body($c) ) {
+ $body_name = $external_body->name || '[Unknown body]';
+ }
+
+ my $detail = $report->detail;
+ my $public_response = $report->get_extra_metadata('public_response') || '';
+ my $metas = $report->get_extra_fields();
+ my %extras;
+ foreach my $field (@$metas) {
+ $extras{$field->{name}} = $field->{value};
+ }
+
+ # replace newlines with HTML <br/> element
+ $detail =~ s{\r?\n}{ <br/> }g;
+ $public_response =~ s{\r?\n}{ <br/> }g if $public_response;
+
+ # Assemble photo URL, if report has a photo
+ my $photo_to_display = $c->cobrand->allow_photo_display($report);
+ my $media_url = (@{$report->photos} && $photo_to_display)
+ ? $c->cobrand->base_url . $report->photos->[$photo_to_display-1]->{url}
+ : '';
+
+ return {
+ whensent => $report->whensent,
+ lastupdate => $report->lastupdate,
+ user_id => $report->user_id,
+ user_email => $report->user->email || '',
+ user_phone => $report->user->phone || '',
+ user_name => $report->name,
+ closure_status => $report->get_extra_metadata('closure_status') || '',
+ body_name => $body_name,
+ sum_time_spent => $report->get_column('sum_time_spent') || 0,
+ detail => $detail,
+ media_url => $media_url,
+ service => $report->service || 'Web interface',
+ public_response => $public_response,
+ strasse => $extras{'strasse'} || '',
+ mast_nr => $extras{'mast_nr'} || '',
+ haus_nr => $extras{'haus_nr'} || '',
+ hydranten_nr => $extras{'hydranten_nr'} || ''
+ };
+ },
+ filename => 'stats',
+ };
+ $c->forward('/dashboard/generate_csv');
+}
+
sub problem_confirm_email_extras {
my ($self, $report) = @_;
my $confirmed_reports = $report->user->problems->search({
@@ -1318,102 +1269,44 @@ sub problem_confirm_email_extras {
$self->{c}->stash->{email_confirmed} = $confirmed_reports;
}
-sub body_details_data {
- return (
- {
- name => 'Stadt Zurich'
- },
- {
- name => 'Elektrizitäwerk Stadt Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'ERZ Entsorgung + Recycling Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Fachstelle Graffiti',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Grün Stadt Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Tiefbauamt Stadt Zürich',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- {
- name => 'Dienstabteilung Verkehr',
- parent => 'Stadt Zurich',
- area_id => 423017,
- },
- );
-}
+sub reports_per_page { return 20; }
-sub contact_details_data {
- return (
- {
- category => 'Beleuchtung/Uhren',
- body_name => 'Elektrizitätswerk Stadt Zürich',
- fields => [
- {
- code => 'strasse',
- description => 'Strasse',
- datatype => 'string',
- required => 'yes',
- },
- {
- code => 'haus_nr',
- description => 'Haus-Nr.',
- datatype => 'string',
- },
- {
- code => 'mast_nr',
- description => 'Mast-Nr.',
- datatype => 'string',
- }
- ],
- },
- {
- category => 'Brunnen/Hydranten',
- # body_name ???
- fields => [
- {
- code => 'hydranten_nr',
- description => 'Hydranten-Nr.',
- datatype => 'string',
- },
- ],
- },
- {
- category => "Grünflächen/Spielplätze",
- body_name => 'Grün Stadt Zürich',
- rename_from => "Tiere/Grünflächen",
- },
- {
- category => 'Spielplatz/Sitzbank',
- body_name => 'Grün Stadt Zürich',
- delete => 1,
- },
+sub singleton_bodies_str { 1 }
+
+sub contact_extra_fields { [ 'abbreviation' ] };
+
+sub db_state_migration {
+ my $rs = FixMyStreet::DB->resultset('State');
+
+ # Create new states needed
+ $rs->create({ label => 'submitted', type => 'open', name => 'Erfasst' });
+ $rs->create({ label => 'feedback pending', type => 'open', name => 'Rückmeldung ausstehend' });
+ $rs->create({ label => 'wish', type => 'closed', name => 'Wunsch' });
+ $rs->create({ label => 'external', type => 'closed', name => 'Extern' });
+ $rs->create({ label => 'jurisdiction unknown', type => 'closed', name => 'Zuständigkeit unbekannt' });
+ $rs->create({ label => 'not contactable', type => 'closed', name => 'Nicht kontaktierbar' });
+
+ # And update used current ones to have correct name
+ $rs->find({ label => 'in progress' })->update({ name => 'In Bearbeitung' });
+ $rs->find({ label => 'fixed' })->update({ name => 'Beantwortet' });
+
+ # Move reports to correct new state
+ my %state_move = (
+ unconfirmed => 'submitted',
+ closed => 'external',
+ investigating => 'wish',
+ 'unable to fix' => 'jurisdiction unknown',
+ planned => 'feedback pending',
+ partial => 'not contactable',
);
-}
+ foreach (keys %state_move) {
+ FixMyStreet::DB->resultset('Problem')->search({ state => $_ })->update({ state => $state_move{$_} });
+ }
-sub contact_details_data_body_default {
- my ($self) = @_;
- # temporary measure to assign un-bodied contacts to parent
- # (this isn't at all how things will be setup in live, but is
- # handy during dev.)
- return $self->{c}->model('DB::Body')->find({ name => 'Stadt Zurich' });
+ # Delete unused standard states from the database
+ for ('action scheduled', 'duplicate', 'not responsible', 'internal referral', 'planned', 'investigating', 'unable to fix') {
+ $rs->find({ label => $_ })->delete;
+ }
}
-sub reports_per_page { return 20; }
-
-sub singleton_bodies_str { 1 }
-
1;
diff --git a/perllib/FixMyStreet/DB/Factories.pm b/perllib/FixMyStreet/DB/Factories.pm
index 0e99608e1..66148ad61 100644
--- a/perllib/FixMyStreet/DB/Factories.pm
+++ b/perllib/FixMyStreet/DB/Factories.pm
@@ -1,5 +1,46 @@
+use strict;
+use warnings;
+use v5.14;
+
use FixMyStreet::DB;
+package FixMyStreet::DB::Factories;
+
+my $db;
+my $opt;
+
+END {
+ if ($db) {
+ $opt->commit ? $db->txn_commit : $db->txn_rollback;
+ }
+}
+sub setup {
+ my $cls = shift;
+
+ $opt = shift;
+ $db = FixMyStreet::DB->schema->storage;
+ $db->txn_begin;
+ if (!$opt->commit) {
+ say "NOT COMMITTING TO DATABASE";
+ }
+
+ if ($opt->empty) {
+ $db->dbh->do(q{
+DO
+$func$
+BEGIN
+ EXECUTE
+ (SELECT 'TRUNCATE TABLE ' || string_agg(quote_ident(tablename), ', ') || ' RESTART IDENTITY CASCADE '
+ FROM pg_tables WHERE schemaname='public');
+END
+$func$;
+}) or die $!;
+ $db->dbh->do( scalar FixMyStreet->path_to('db/fixture.sql')->slurp ) or die $!;
+ $db->dbh->do( scalar FixMyStreet->path_to('db/generate_secret.sql')->slurp ) or die $!;
+ say "Emptied database";
+ }
+}
+
package FixMyStreet::DB::Factory::Base;
use parent "DBIx::Class::Factory";
@@ -19,6 +60,10 @@ sub find_or_create {
package FixMyStreet::DB::Factory::Problem;
use parent "DBIx::Class::Factory";
+use Path::Tiny;
+use DateTime::Format::Pg;
+use FixMyStreet;
+use FixMyStreet::App::Model::PhotoSet;
__PACKAGE__->resultset(FixMyStreet::DB->resultset("Problem"));
@@ -43,6 +88,94 @@ __PACKAGE__->fields({
category => 'Other',
});
+sub data {
+ my $self = shift;
+
+ my %titles = (
+ 'Potholes' => ['Deep pothole', 'Small pothole', 'Pothole in cycle lane', 'Pothole on busy pavement', 'Large pothole', 'Sinking manhole'],
+ 'Street lighting' => ['Faulty light', 'Street light not working', 'Lights out in tunnel', 'Light not coming on', 'Light not going off'],
+ 'Graffiti' => ['Graffiti', 'Graffiti', 'Offensive graffiti', 'Graffiti on the bridge', 'Remove graffiti'],
+ 'Other' => ['Loose drain cover', 'Flytipping on country lane', 'Vehicle blocking footpath', 'Hedge encroaching pavement', 'Full litter bins'],
+ );
+ my %photos = (
+ 'Potholes' => [ '33717571655_46dfc6f65f_z.jpg', '37855543925_9dbbbecf41_z.jpg', '19119222668_a3c866d7c8_z.jpg', '12049724866_404b066875_z.jpg', '3705226606_eac71cf195_z.jpg', '6304445383_bd216ca892_z.jpg' ],
+ 'Street lighting' => ['38110448864_fd71227247_z.jpg', '27050321819_ac123400eb_z.jpg', '35732107202_b790c61f63_z.jpg', '31889115854_01cdf38b0d_z.jpg', undef ],
+ 'Graffiti' => ['12205918375_f37f0b27a9_z.jpg', '8895442578_376a9b0be0_z.jpg', '22998854352_17555b7536_z.jpg', '22593395257_3d48f23bfa_z.jpg', '20515339175_f4ed9fc1d9_z.jpg' ],
+ 'Other' => ['14347396807_20737504f7_z.jpg', '14792525771_167bc20e3d_z.jpg', undef, '36296226976_a83a118ff8_z.jpg', '23222004240_273977b2b2_z.jpg'],
+ );
+ my %descriptions = (
+ 'Potholes' => [
+ '6” deep pothole in the very centre of the Bristol road; cars are swerving to avoid it. Please treat this as a matter of urgency.',
+ 'It’s small but it’s a trip hazard. Right where people cross over to get into the school or church. About 3” across but will become larger if not attended to.',
+ 'Just went over my handlebars as I didn’t see this pothole on Banbury road, just before the traffic lights. Dread to think what might have happened if the traffic had been busier.',
+ 'I work in the cafe at 34 Clarington Avenue and we’ve had four people come in having tripped over in the last seven days. The pothole’s right outside the key-cutting shop, just near the alleyway.',
+ 'This has been here, next to the side of the road, for a month',
+ 'A manhole on the junction of Etherington Road is sinking into the road surface. Not only is it an accident waiting to happen but it’s making a terrible noise every time a car passes over it.',
+ ],
+ 'Street lighting' => [
+ 'I saw a workman attempting to fix this streetlight over a week ago, and ever since then it’s come on in the daytime and gone off as soon as it gets dark. Come and sort it out please!',
+ 'Every Tuesday night I have to walk across the carpark outside the station at around 9pm. Not a problem in summer but now the nights are drawing in I feel very unsafe. Please get the streetlight by the exit fixed as I’m sure I can’t be the only woman feeling vulnerable.',
+ 'My toddler is too scared to go in now, as soon as you’re more than a few paces in it’s absolutely pitch black with no hope of seeing any puddles or worse on the floor. I think this needs seeing to as a priority. Thank you.',
+ 'I think the lights in the multi storey carpark are motion sensitive but I’ve actually never seen them come on. Maybe the bulb needs replacing??',
+ 'This streetlight is right outside my bedroom window. It is on 24 hours a day, even in blazing sunlight. Apart from the fact that it’s a waste of electricity, it makes my bedroom feel like an interrogation chamber. Please come and fix it.',
+ ],
+ 'Graffiti' => [
+ 'Someone has scrawled a really offensive piece of graffiti (are they called ‘tags’??) on the side of the town hall. You might want to see about getting it cleaned off. Wouldn’t want my own children to see that, I’m sure others feel the same.',
+ 'Can’t see the timetable at the bus shelter cos some idiot’s covered it all in red spray paint. Honestly. Kids of today.',
+ 'Not gonna write down what it depicts cos I suspect that’d get caught in your profanity filter lol. But please do come and paint over this monstrosity before it causes an accident.',
+ 'That same guy that’s graffitied all over town has gone and done the same on the passenger bridge over the tracks, you can see it as you come into the station. Ugly bit of garbage graffiti. Bit of a poor first impression for the town eh.',
+ 'What’s the procedure for requesting a bit of graffiti be removed? There’s been a huge scrawl on the wall outside the club for months. Nice sentiment maybe but really brings the tone of the area down.',
+ ],
+ 'Other' => [
+ 'Surprised me so much when I crossed the road I nearly took a tumble! Glad I didn’t fall in, this really needs securing now.',
+ 'Some unmentionable has driven down Larker’s Lane and left a huge heap of old rubbish on the verge. Talk about ruining the view! Such a beautiful spot and these lowlifes come and dump their junk. Probably trying to avoid paying the tip.',
+ 'Well someone on foot can just about squeeze through but good luck if you’ve got a pushchair or god forbid a wheelchair. Think someone’s abandoned this car; it hasn’t moved in weeks.',
+ 'Awful trying to walk past after a rain shower, well any time really.',
+ 'I think these need seeing to more frequently, they’re always full to overflowing by midday.',
+ ],
+ );
+
+ return {
+ titles => \%titles,
+ descriptions => \%descriptions,
+ photos => \%photos,
+ };
+}
+
+sub create_problem {
+ my $self = shift;
+ my $params = shift;
+
+ my $data = $self->data;
+ my $category = $params->{category};
+ my $inaccurate_km = 0.01;
+
+ my $titles = $data->{titles}{$category};
+ my $descs = $data->{descriptions}{$category};
+ my $rand = int(rand(@$titles));
+
+ my $photo;
+ if (my $file = $data->{photos}{$category}->[$rand]) {
+ my $files = [ $file ];
+ if ($category eq 'Graffiti') {
+ push @$files, $data->{photos}{$category}->[int(rand(@$titles))];
+ }
+ $files = [ map { path(FixMyStreet->path_to("t/images/$_"))->slurp_raw } @$files ];
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new({
+ data_items => $files,
+ });
+ $photo = $photoset->data;
+ }
+
+ $params->{latitude} += rand(2 * $inaccurate_km) - $inaccurate_km;
+ $params->{longitude} += rand(3 * $inaccurate_km) - 1.5 * $inaccurate_km,
+ $params->{title} = $titles->[$rand];
+ $params->{detail} = $descs->[$rand];
+ $params->{photo_id} = $photo;
+ $params->{confirmed} = DateTime::Format::Pg->format_datetime($params->{confirmed});
+ return $self->create($params);
+}
+
#######################
package FixMyStreet::DB::Factory::Body;
diff --git a/perllib/FixMyStreet/DB/Result/Body.pm b/perllib/FixMyStreet/DB/Result/Body.pm
index 07bea276c..a9df1aeb7 100644
--- a/perllib/FixMyStreet/DB/Result/Body.pm
+++ b/perllib/FixMyStreet/DB/Result/Body.pm
@@ -44,6 +44,10 @@ __PACKAGE__->add_columns(
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"external_url",
{ data_type => "text", is_nullable => 1 },
+ "fetch_problems",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
+ "blank_updates_permitted",
+ { data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->has_many(
@@ -118,8 +122,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07035 @ 2017-02-13 15:11:11
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BOJANVwg3kR/1VjDq0LykA
+# Created by DBIx::Class::Schema::Loader v0.07035 @ 2018-03-01 12:27:28
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dzqgZI1wkGDPS2PfJgDEIg
use Moo;
use namespace::clean;
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index c73f7efca..2deeb3084 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -341,7 +341,7 @@ around service => sub {
sub title_safe {
my $self = shift;
- return _('Awaiting moderation') if $self->cobrand eq 'zurich' && $self->state eq 'unconfirmed';
+ return _('Awaiting moderation') if $self->cobrand eq 'zurich' && $self->state eq 'submitted';
return $self->title;
}
@@ -509,6 +509,18 @@ sub tokenised_url {
return "/M/". $token->token;
}
+=head2 is_hidden
+
+Returns 1 if the problem is in an hidden state otherwise 0.
+
+=cut
+
+sub is_hidden {
+ my $self = shift;
+
+ return exists $self->hidden_states->{ $self->state } ? 1 : 0;
+}
+
=head2 is_open
Returns 1 if the problem is in a open state otherwise 0.
diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
index 458efa179..ebc6e6c1b 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Problem.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
@@ -118,8 +118,8 @@ sub _recent {
my $key = $photos ? 'recent_photos' : 'recent';
$key .= ":$site_key:$num";
- # unconfirmed might be returned for e.g. Zurich, but would mean in moderation, so no photo
- my @states = grep { $_ ne 'unconfirmed' } FixMyStreet::DB::Result::Problem->visible_states();
+ # submitted might be returned for e.g. Zurich, but would mean in moderation, so no photo
+ my @states = grep { $_ ne 'submitted' } FixMyStreet::DB::Result::Problem->visible_states();
my $query = {
non_public => 0,
state => \@states,
diff --git a/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm b/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
index 5d1d45379..31876d83d 100644
--- a/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
+++ b/perllib/FixMyStreet/Script/ArchiveOldEnquiries.pm
@@ -14,10 +14,6 @@ use FixMyStreet::Email;
my $opts = {
commit => 0,
- body => '2237',
- cobrand => 'oxfordshire',
- closure_cutoff => "2015-01-01 00:00:00",
- email_cutoff => "2016-01-01 00:00:00",
};
sub query {
@@ -84,11 +80,7 @@ sub archive {
});
printf("Closing %d old reports, without sending emails: ", $problems_to_close->count);
-
- if ( $opts->{commit} ) {
- $problems_to_close->update({ state => 'closed', send_questionnaire => 0 });
- }
-
+ close_problems($problems_to_close);
printf("done.\n")
}
@@ -132,10 +124,31 @@ sub send_email_and_close {
unless ( $email_error ) {
printf("done.\n Closing reports: ");
-
- $problems->update({ state => 'closed', send_questionnaire => 0 });
+ close_problems($problems);
printf("done.\n");
} else {
printf("error! Not closing reports for this user.\n")
}
}
+
+sub close_problems {
+ return unless $opts->{commit};
+
+ my $problems = shift;
+ while (my $problem = $problems->next) {
+ my $timestamp = \'current_timestamp';
+ $problem->add_to_comments( {
+ text => '',
+ created => $timestamp,
+ confirmed => $timestamp,
+ user_id => $opts->{user},
+ name => _('an administrator'),
+ mark_fixed => 0,
+ anonymous => 0,
+ state => 'confirmed',
+ problem_state => 'closed',
+ extra => { is_superuser => 1 },
+ } );
+ $problem->update({ state => 'closed', send_questionnaire => 0 });
+ }
+}
diff --git a/perllib/FixMyStreet/Script/Reports.pm b/perllib/FixMyStreet/Script/Reports.pm
index be3c4c69a..b8c3d6d0d 100644
--- a/perllib/FixMyStreet/Script/Reports.pm
+++ b/perllib/FixMyStreet/Script/Reports.pm
@@ -29,7 +29,7 @@ sub send(;$) {
my $site = $site_override || CronFns::site($base_url);
my $states = [ FixMyStreet::DB::Result::Problem::open_states() ];
- $states = [ 'unconfirmed', 'confirmed', 'in progress', 'planned', 'closed', 'investigating' ] if $site eq 'zurich';
+ $states = [ 'submitted', 'confirmed', 'in progress', 'feedback pending', 'external', 'wish' ] if $site eq 'zurich';
my $unsent = $rs->search( {
state => $states,
whensent => undef,
@@ -88,6 +88,8 @@ sub send(;$) {
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 . $row->photos->[0]->{url_full};
+ my @all_images = map { $email_base_url . $_->{url_full} } @{ $row->photos };
+ $h{all_image_urls} = \@all_images;
} else {
$h{has_photo} = '';
$h{image_url} = '';
diff --git a/perllib/FixMyStreet/SendReport/Open311.pm b/perllib/FixMyStreet/SendReport/Open311.pm
index ecda0bca1..98637dd1f 100644
--- a/perllib/FixMyStreet/SendReport/Open311.pm
+++ b/perllib/FixMyStreet/SendReport/Open311.pm
@@ -28,6 +28,7 @@ sub send {
send_notpinpointed => 0,
use_service_as_deviceid => 0,
extended_description => 1,
+ multi_photos => 0,
);
my $cobrand = $body->get_cobrand_handler || $row->get_cobrand_logged;
diff --git a/perllib/FixMyStreet/SendReport/Zurich.pm b/perllib/FixMyStreet/SendReport/Zurich.pm
index b38981d94..59adfd688 100644
--- a/perllib/FixMyStreet/SendReport/Zurich.pm
+++ b/perllib/FixMyStreet/SendReport/Zurich.pm
@@ -44,15 +44,15 @@ sub get_template {
my ( $self, $row ) = @_;
my $template;
- if ( $row->state eq 'unconfirmed' || $row->state eq 'confirmed' ) {
+ if ( $row->state eq 'submitted' || $row->state eq 'confirmed' ) {
$template = 'submit.txt';
} elsif ( $row->state eq 'in progress' ) {
$template = 'submit-in-progress.txt';
- } elsif ( $row->state eq 'planned' ) {
+ } elsif ( $row->state eq 'feedback pending' ) {
$template = 'submit-feedback-pending.txt';
- } elsif ( $row->state eq 'investigating' ) {
+ } elsif ( $row->state eq 'wish' ) {
$template = 'submit-external-wish.txt';
- } elsif ( $row->state eq 'closed' ) {
+ } elsif ( $row->state eq 'external' ) {
$template = 'submit-external.txt';
if ( $row->extra->{third_personal} ) {
$template = 'submit-external-personal.txt';
diff --git a/perllib/FixMyStreet/TestAppProve.pm b/perllib/FixMyStreet/TestAppProve.pm
index d549b0148..049f7da6c 100644
--- a/perllib/FixMyStreet/TestAppProve.pm
+++ b/perllib/FixMyStreet/TestAppProve.pm
@@ -33,6 +33,59 @@ END {
cleanup();
}
+my $pg;
+
+sub spin_up_database {
+ warn "Spinning up a Pg cluster/database...\n";
+ $pg = Test::PostgreSQL->new();
+
+ warn sprintf "# Connected to %s\n", $pg->dsn;
+
+ my $dbh = DBI->connect($pg->dsn);
+
+ my $tmpwarn = $SIG{__WARN__};
+ $SIG{__WARN__} =
+ sub { print STDERR @_ if $_[0] !~ m/NOTICE: CREATE TABLE/; };
+ $dbh->do( path('db/schema.sql')->slurp ) or die $!;
+ $dbh->do( path('db/fixture.sql')->slurp ) or die $!;
+ $dbh->do( path('db/generate_secret.sql')->slurp ) or die $!;
+ $SIG{__WARN__} = $tmpwarn;
+
+ return {
+ FMS_DB_PORT => $pg->port,
+ FMS_DB_NAME => 'test',
+ FMS_DB_USER => 'postgres',
+ FMS_DB_HOST => 'localhost',
+ FMS_DB_PASS => '',
+ };
+}
+
+sub get_config {
+ my $cls = shift;
+ my $extra_config = shift;
+ my $config_file = delete $extra_config->{config_file};
+ my $db_config_file = delete $extra_config->{db_config_file};
+
+ my $config = YAML::Load( path($config_file)->slurp );
+ if ($db_config_file) {
+ my $db_config = YAML::Load( path($db_config_file)->slurp );
+ $config->{FMS_DB_PORT} = $db_config->{FMS_DB_PORT};
+ $config->{FMS_DB_NAME} = $db_config->{FMS_DB_NAME};
+ $config->{FMS_DB_USER} = $db_config->{FMS_DB_USER};
+ $config->{FMS_DB_HOST} = $db_config->{FMS_DB_HOST};
+ $config->{FMS_DB_PASS} = $db_config->{FMS_DB_PASS};
+ } else {
+ my $new_db_config = $cls->spin_up_database();
+ $config = { %$config, %$new_db_config };
+ }
+
+ $config = { %$config, %$extra_config };
+
+ my $config_out = "general-test-autogenerated.$$.yml";
+ path("conf/$config_out")->spew( YAML::Dump($config) );
+ return $config_out;
+}
+
sub run {
my ($class, @args) = @_;
local @ARGV = @args;
@@ -53,42 +106,7 @@ sub run {
'state=s@' => \@state,
);
- my $config = YAML::Load( path($config_file)->slurp );
- my $pg;
- if ($db_config_file) {
- my $db_config = YAML::Load( path($db_config_file)->slurp );
- $config->{FMS_DB_PORT} = $db_config->{FMS_DB_PORT};
- $config->{FMS_DB_NAME} = $db_config->{FMS_DB_NAME};
- $config->{FMS_DB_USER} = $db_config->{FMS_DB_USER};
- $config->{FMS_DB_HOST} = $db_config->{FMS_DB_HOST};
- $config->{FMS_DB_PASS} = $db_config->{FMS_DB_PASS};
- }
- else {
- warn "Spinning up a Pg cluster/database...\n";
- $pg = Test::PostgreSQL->new();
-
- warn sprintf "# Connected to %s\n", $pg->dsn;
-
- my $dbh = DBI->connect($pg->dsn);
-
- my $tmpwarn = $SIG{__WARN__};
- $SIG{__WARN__} =
- sub { print STDERR @_ if $_[0] !~ m/NOTICE: CREATE TABLE/; };
- $dbh->do( path('db/schema.sql')->slurp ) or die $!;
- $dbh->do( path('db/fixture.sql')->slurp ) or die $!;
- $dbh->do( path('db/generate_secret.sql')->slurp ) or die $!;
- $SIG{__WARN__} = $tmpwarn;
-
- $config->{FMS_DB_PORT} = $pg->port;
- $config->{FMS_DB_NAME} = 'test';
- $config->{FMS_DB_USER} = 'postgres';
- $config->{FMS_DB_HOST} = 'localhost';
- $config->{FMS_DB_PASS} = '';
- }
-
- my $config_out = "general-test-autogenerated.$$.yml";
- path("conf/$config_out")->spew( YAML::Dump($config) );
-
+ my $config_out = $class->get_config({ config_file => $config_file, db_config_file => $db_config_file });
local $ENV{FMS_OVERRIDE_CONFIG} = $config_out;
my $prove = App::Prove->new;
diff --git a/perllib/Open311.pm b/perllib/Open311.pm
index 44af0b134..577de31ea 100644
--- a/perllib/Open311.pm
+++ b/perllib/Open311.pm
@@ -32,6 +32,7 @@ has use_service_as_deviceid => ( is => 'ro', isa => Bool, default => 0 );
has use_extended_updates => ( is => 'ro', isa => Bool, default => 0 );
has extended_statuses => ( is => 'ro', isa => Bool, default => 0 );
has always_send_email => ( is => 'ro', isa => Bool, default => 0 );
+has multi_photos => ( is => 'ro', isa => Bool, default => 0 );
before [
qw/get_service_list get_service_meta_info get_service_requests get_service_request_updates
@@ -163,7 +164,11 @@ sub _populate_service_request_params {
}
if ( $extra->{image_url} ) {
- $params->{media_url} = $extra->{image_url};
+ if ( $self->multi_photos ) {
+ $params->{media_url} = $extra->{all_image_urls};
+ } else {
+ $params->{media_url} = $extra->{image_url};
+ }
}
if ( $self->use_service_as_deviceid && $problem->service ) {
@@ -213,14 +218,20 @@ sub _generate_service_request_description {
sub get_service_requests {
my $self = shift;
- my $report_ids = shift;
+ my $args = shift;
my $params = {};
- if ( $report_ids ) {
- $params->{service_request_id} = join ',', @$report_ids;
+ if ( $args->{report_ids} ) {
+ $params->{service_request_id} = join ',', @{$args->{report_ids}};
+ delete $args->{report_ids};
}
+ $params = {
+ %$params,
+ %$args
+ };
+
my $service_request_xml = $self->_get( $self->endpoints->{requests}, $params || undef );
return $self->_get_xml_object( $service_request_xml );
}
@@ -291,6 +302,37 @@ sub post_service_request_update {
return 0;
}
+sub add_media {
+ my ($self, $url, $object) = @_;
+
+ my $ua = LWP::UserAgent->new;
+ my $res = $ua->get($url);
+ if ( $res->is_success && $res->content_type eq 'image/jpeg' ) {
+ my $photoset = FixMyStreet::App::Model::PhotoSet->new({
+ data_items => [ $res->decoded_content ],
+ });
+ $object->photo($photoset->data);
+ }
+}
+
+sub map_state {
+ my $self = shift;
+ my $incoming_state = shift;
+
+ $incoming_state = lc($incoming_state);
+ $incoming_state =~ s/_/ /g;
+
+ my %state_map = (
+ fixed => 'fixed - council',
+ 'not councils responsibility' => 'not responsible',
+ 'no further action' => 'unable to fix',
+ open => 'confirmed',
+ closed => 'fixed - council',
+ );
+
+ return $state_map{$incoming_state} || $incoming_state;
+}
+
sub _populate_service_request_update_params {
my $self = shift;
my $comment = shift;
diff --git a/perllib/Open311/GetServiceRequestUpdates.pm b/perllib/Open311/GetServiceRequestUpdates.pm
index 2620b176a..f2a319f15 100644
--- a/perllib/Open311/GetServiceRequestUpdates.pm
+++ b/perllib/Open311/GetServiceRequestUpdates.pm
@@ -12,6 +12,7 @@ has end_date => ( is => 'ro', default => sub { undef } );
has suppress_alerts => ( is => 'rw', default => 0 );
has verbose => ( is => 'ro', default => 0 );
has schema => ( is =>'ro', lazy => 1, default => sub { FixMyStreet::DB->schema->connect } );
+has blank_updates_permitted => ( is => 'rw', default => 0 );
Readonly::Scalar my $AREA_ID_BROMLEY => 2482;
Readonly::Scalar my $AREA_ID_OXFORDSHIRE => 2237;
@@ -49,6 +50,7 @@ sub fetch {
}
$self->suppress_alerts( $body->suppress_alerts );
+ $self->blank_updates_permitted( $body->blank_updates_permitted );
$self->system_user( $body->comment_user );
$self->update_comments( $o, $body );
}
@@ -107,7 +109,7 @@ sub update_comments {
my $c = $p->comments->search( { external_id => $request->{update_id} } );
if ( !$c->first ) {
- my $state = $self->map_state( $request->{status} );
+ my $state = $open311->map_state( $request->{status} );
my $comment = $self->schema->resultset('Comment')->new(
{
problem => $p,
@@ -124,16 +126,8 @@ sub update_comments {
}
);
- if ($request->{media_url}) {
- my $ua = LWP::UserAgent->new;
- my $res = $ua->get($request->{media_url});
- if ( $res->is_success && $res->content_type eq 'image/jpeg' ) {
- my $photoset = FixMyStreet::App::Model::PhotoSet->new({
- data_items => [ $res->decoded_content ],
- });
- $comment->photo($photoset->data);
- }
- }
+ $open311->add_media($request->{media_url}, $comment)
+ if $request->{media_url};
# if the comment is older than the last update
# do not change the status of the problem as it's
@@ -191,26 +185,10 @@ sub comment_text_for_request {
return $template->text;
}
+ return "" if $self->blank_updates_permitted;
+
print STDERR "Couldn't determine update text for $request->{update_id} (report " . $problem->id . ")\n";
return "";
}
-sub map_state {
- my $self = shift;
- my $incoming_state = shift;
-
- $incoming_state = lc($incoming_state);
- $incoming_state =~ s/_/ /g;
-
- my %state_map = (
- fixed => 'fixed - council',
- 'not councils responsibility' => 'not responsible',
- 'no further action' => 'unable to fix',
- open => 'confirmed',
- closed => 'fixed - council'
- );
-
- return $state_map{$incoming_state} || $incoming_state;
-}
-
1;
diff --git a/perllib/Open311/GetServiceRequests.pm b/perllib/Open311/GetServiceRequests.pm
new file mode 100644
index 000000000..2a82c64a1
--- /dev/null
+++ b/perllib/Open311/GetServiceRequests.pm
@@ -0,0 +1,168 @@
+package Open311::GetServiceRequests;
+
+use Moo;
+use Open311;
+use FixMyStreet::DB;
+use FixMyStreet::App::Model::PhotoSet;
+use DateTime::Format::W3CDTF;
+
+has system_user => ( is => 'rw' );
+has start_date => ( is => 'ro', default => sub { undef } );
+has end_date => ( is => 'ro', default => sub { undef } );
+has verbose => ( is => 'ro', default => 0 );
+has schema => ( is =>'ro', lazy => 1, default => sub { FixMyStreet::DB->schema->connect } );
+
+sub fetch {
+ my $self = shift;
+
+ my $bodies = $self->schema->resultset('Body')->search(
+ {
+ send_method => 'Open311',
+ fetch_problems => 1,
+ comment_user_id => { '!=', undef },
+ endpoint => { '!=', '' },
+ }
+ );
+
+ while ( my $body = $bodies->next ) {
+
+ my $o = Open311->new(
+ endpoint => $body->endpoint,
+ api_key => $body->api_key,
+ jurisdiction => $body->jurisdiction,
+ );
+
+ $self->system_user( $body->comment_user );
+ $self->create_problems( $o, $body );
+ }
+}
+
+sub create_problems {
+ my ( $self, $open311, $body ) = @_;
+
+ my $args = {};
+
+ if ( $self->start_date || $self->end_date ) {
+ return 0 unless $self->start_date && $self->end_date;
+
+
+ $args->{start_date} = DateTime::Format::W3CDTF->format_datetime( $self->start_date );
+ $args->{end_date} = DateTime::Format::W3CDTF->format_datetime( $self->end_date );
+ }
+
+ my $requests = $open311->get_service_requests( $args );
+
+ unless ( $open311->success ) {
+ warn "Failed to fetch ServiceRequest Updates for " . $body->name . ":\n" . $open311->error
+ if $self->verbose;
+ return 0;
+ }
+
+ my $contacts = $self->schema->resultset('Contact')
+ ->active
+ ->search( { body_id => $body->id } );
+
+ for my $request (@{$requests->{request}}) {
+ # no point importing if we can't put it on the map
+ unless ($request->{service_request_id} && $request->{lat} && $request->{long}) {
+ warn "Not creating request '$request->{description}' for @{[$body->name]} as missing one of id, lat or long"
+ if $self->verbose;
+ next;
+ }
+ my $request_id = $request->{service_request_id};
+
+ my %params;
+ $params{generation} = mySociety::Config::get('MAPIT_GENERATION')
+ if mySociety::Config::get('MAPIT_GENERATION');
+
+ my ($latitude, $longitude) = ( $request->{lat}, $request->{long} );
+ my $all_areas =
+ mySociety::MaPit::call( 'point',
+ "4326/$longitude,$latitude", %params );
+
+ # skip if it doesn't look like it's for this body
+ my @areas = grep { $all_areas->{$_->area_id} } $body->body_areas;
+ unless (@areas) {
+ warn "Not creating request id $request_id for @{[$body->name]} as outside body area"
+ if $self->verbose;
+ next;
+ }
+
+ my $updated_time = eval {
+ DateTime::Format::W3CDTF->parse_datetime(
+ $request->{updated_datetime} || ""
+ )->set_time_zone(
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone
+ );
+ };
+ if ($@) {
+ warn "Not creating problem $request_id for @{[$body->name]}, bad update time"
+ if $self->verbose;
+ next;
+ }
+
+ my $updated = DateTime::Format::W3CDTF->format_datetime(
+ $updated_time->clone->set_time_zone('UTC')
+ );
+ if ($args->{start_date} && $args->{end_date} && ($updated lt $args->{start_date} || $updated gt $args->{end_date}) ) {
+ warn "Problem id $request_id for @{[$body->name]} has an invalid time, not creating"
+ if $self->verbose;
+ next;
+ }
+
+ my $created_time = eval {
+ DateTime::Format::W3CDTF->parse_datetime(
+ $request->{requested_datetime} || ""
+ )->set_time_zone(
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone
+ );
+ };
+ $created_time = $updated_time if $@;
+
+ my $problems;
+ my $criteria = {
+ external_id => $request_id,
+ };
+ $problems = $self->schema->resultset('Problem')->to_body($body)->search( $criteria );
+
+ my @contacts = grep { $request->{service_code} eq $_->category } $contacts->all;
+ my $contact = $contacts[0] ? $contacts[0]->category : 'Other';
+
+ my $state = $open311->map_state($request->{status});
+
+ unless (my $p = $problems->first) {
+ my $problem = $self->schema->resultset('Problem')->new(
+ {
+ user => $self->system_user,
+ external_id => $request_id,
+ detail => $request->{description},
+ title => $request->{title} || $request->{service_name} . ' problem',
+ anonymous => 0,
+ name => $self->system_user->name,
+ confirmed => $created_time,
+ created => $created_time,
+ lastupdate => $updated_time,
+ whensent => $created_time,
+ state => $state,
+ postcode => '',
+ used_map => 1,
+ latitude => $request->{lat},
+ longitude => $request->{long},
+ areas => ',' . $body->id . ',',
+ bodies_str => $body->id,
+ send_method_used => 'Open311',
+ category => $contact,
+ }
+ );
+
+ $open311->add_media($request->{media_url}, $problem)
+ if $request->{media_url};
+
+ $problem->insert();
+ }
+ }
+
+ return 1;
+}
+
+1;
diff --git a/perllib/Open311/GetUpdates.pm b/perllib/Open311/GetUpdates.pm
index 1b1e339e3..f62acf4a8 100644
--- a/perllib/Open311/GetUpdates.pm
+++ b/perllib/Open311/GetUpdates.pm
@@ -41,7 +41,7 @@ sub get_updates {
sub update_reports {
my ( $self, $report_ids, $open311, $body ) = @_;
- my $service_requests = $open311->get_service_requests( $report_ids );
+ my $service_requests = $open311->get_service_requests( { report_ids => $report_ids } );
my $requests = $service_requests->{request};
for my $request (@$requests) {
diff --git a/perllib/Open311/PopulateServiceList.pm b/perllib/Open311/PopulateServiceList.pm
index 30d888eb4..af89e3169 100644
--- a/perllib/Open311/PopulateServiceList.pm
+++ b/perllib/Open311/PopulateServiceList.pm
@@ -267,6 +267,8 @@ sub _add_meta_to_contact {
@meta = grep { ! $ignore{ $_->{ code } } } @meta;
}
+ @meta = grep { !defined $_->{automated} || $_->{ automated } eq 'hidden_field' } @meta;
+
$contact->set_extra_fields(@meta);
$contact->update;
}
diff --git a/setenv.pl b/setenv.pl
index 39dcc04b0..f1e9cf53b 100755
--- a/setenv.pl
+++ b/setenv.pl
@@ -25,12 +25,9 @@ for ( "$root/commonlib/perllib", "$root/perllib" ) {
$ENV{PERL5LIB} = "$_:$ENV{PERL5LIB}";
}
-# need to make sure we fetch this after our libs are in INC as some
-# vendor provided versions are old an incompatible with Moose
-use List::MoreUtils 'uniq';
-
# also set the path to our scripts etc
-$ENV{PATH} = join ':', uniq "$root/bin", split( m/:/, $ENV{PATH} );
+my %seen;
+$ENV{PATH} = join ':', grep { not $seen{$_}++ } "$root/bin", split( m/:/, $ENV{PATH} );
# we might want to require this file to configure something like a CGI script
if ( $0 eq __FILE__ ) {
diff --git a/t/app/controller/report_display.t b/t/app/controller/report_display.t
index f0913fbd2..17b9180c1 100644
--- a/t/app/controller/report_display.t
+++ b/t/app/controller/report_display.t
@@ -115,6 +115,12 @@ subtest "duplicate reports are signposted correctly" => sub {
$report2->update;
};
+subtest "test /report/ajax" => sub {
+ my $json = $mech->get_ok_json( "/report/ajax/$report_id" );
+ is $json->{report}->{title}, "Test 2", "correct title";
+ is $json->{report}->{state}, "confirmed", "correct state";
+};
+
subtest "test a good report" => sub {
$mech->get_ok("/report/$report_id");
is $mech->uri->path, "/report/$report_id", "at /report/$report_id";
@@ -419,106 +425,6 @@ for my $test (
};
}
-subtest "Zurich unconfirmeds are 200" => sub {
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ 'zurich' ],
- MAP_TYPE => 'Zurich,OSM',
- }, sub {
- $mech->host( 'zurich.example.com' );
- ok $report->update( { state => 'unconfirmed' } ), 'unconfirm report';
- $mech->get_ok("/report/$report_id");
- $mech->content_contains( '&Uuml;berpr&uuml;fung ausstehend' );
- ok $report->update( { state => 'confirmed' } ), 'confirm report again';
- $mech->host( 'www.fixmystreet.com' );
- };
-};
-
-subtest "Zurich banners are displayed correctly" => sub {
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ 'zurich' ],
- MAP_TYPE => 'Zurich,OSM',
- }, sub {
- $mech->host( 'zurich.example.com' );
-
- for my $test (
- {
- description => 'new report',
- state => 'unconfirmed',
- banner_id => 'closed',
- banner_text => 'Erfasst'
- },
- {
- description => 'confirmed report',
- state => 'confirmed',
- banner_id => 'closed',
- banner_text => 'Aufgenommen',
- },
- {
- description => 'fixed report',
- state => 'fixed - council',
- banner_id => 'fixed',
- banner_text => 'Beantwortet',
- },
- {
- description => 'closed report',
- state => 'closed',
- banner_id => 'closed',
- banner_text => _('Extern'),
- },
- {
- description => 'in progress report',
- state => 'in progress',
- banner_id => 'progress',
- banner_text => 'In Bearbeitung',
- },
- {
- description => 'planned report',
- state => 'planned',
- banner_id => 'progress',
- banner_text => 'In Bearbeitung',
- },
- {
- description => 'planned report',
- state => 'planned',
- banner_id => 'progress',
- banner_text => 'In Bearbeitung',
- },
- {
- description => 'jurisdiction unknown',
- state => 'unable to fix',
- banner_id => 'fixed',
- # We can't use _('Jurisdiction Unknown') here because
- # TestMech::extract_problem_banner decodes the HTML entities before
- # the string is passed back.
- banner_text => 'Zust\x{e4}ndigkeit unbekannt',
- },
- ) {
- subtest "banner for $test->{description}" => sub {
- $report->state( $test->{state} );
- $report->update;
-
- $mech->get_ok("/report/$report_id");
- is $mech->uri->path, "/report/$report_id", "at /report/$report_id";
- my $banner = $mech->extract_problem_banner;
- if ( $banner->{text} ) {
- $banner->{text} =~ s/^ //g;
- $banner->{text} =~ s/ $//g;
- }
-
- is $banner->{id}, $test->{banner_id}, 'banner id';
- if ($test->{banner_text}) {
- like_string( $banner->{text}, qr/$test->{banner_text}/i, 'banner text is ' . $test->{banner_text} );
- } else {
- is $banner->{text}, $test->{banner_text}, 'banner text';
- }
-
- };
- }
-
- $mech->host( 'www.fixmystreet.com' );
- };
-};
-
my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council');
my $oxfordshireuser = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $oxfordshire);
diff --git a/t/app/controller/report_new_open311.t b/t/app/controller/report_new_open311.t
index 0224e7e47..d79f7883c 100644
--- a/t/app/controller/report_new_open311.t
+++ b/t/app/controller/report_new_open311.t
@@ -64,7 +64,7 @@ foreach my $test (
},
changes => {
number => '',
- type => 'old',
+ type => '',
},
errors => [
'This information is required',
@@ -80,6 +80,7 @@ foreach my $test (
username => 'testopen311@example.com',
category => 'Street lighting',
number => 27,
+ type => 'old',
},
extra => [
{
@@ -146,7 +147,7 @@ foreach my $test (
}
->scrape( $mech->response );
- is_deeply $result->{option}, [ qw/old modern/], 'displayed streetlight type select';
+ is_deeply $result->{option}, [ "", qw/old modern/], 'displayed streetlight type select';
}
$new_values = {
diff --git a/t/app/script/archive_old_enquiries.t b/t/app/script/archive_old_enquiries.t
index e1adeec85..0475cb9ea 100644
--- a/t/app/script/archive_old_enquiries.t
+++ b/t/app/script/archive_old_enquiries.t
@@ -5,15 +5,18 @@ my $mech = FixMyStreet::TestMech->new();
$mech->clear_emails_ok;
-my $opts = {
- commit => 1,
-};
-
my $user = $mech->create_user_ok('test@example.com', name => 'Test User');
my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council');
my $west_oxon = $mech->create_body_ok(2420, 'West Oxfordshire District Council');
-$opts->{body} = $oxfordshire->id;
+my $opts = {
+ commit => 1,
+ body => $oxfordshire->id,
+ cobrand => 'oxfordshire',
+ closure_cutoff => "2015-01-01 00:00:00",
+ email_cutoff => "2016-01-01 00:00:00",
+ user => $user->id,
+};
subtest 'sets reports to the correct status' => sub {
FixMyStreet::override_config {
@@ -65,6 +68,9 @@ subtest 'sets reports to the correct status' => sub {
is $report4->state, 'closed', 'Report 4 has been set to closed';
is $report5->state, 'closed', 'Report 5 has been set to closed';
+ my $comment = $report1->comments->first;
+ is $comment->problem_state, 'closed';
+
is $report->state, 'confirmed', 'Recent report has been left alone';
};
};
diff --git a/t/cobrand/rutland.t b/t/cobrand/rutland.t
new file mode 100644
index 000000000..8943e64fc
--- /dev/null
+++ b/t/cobrand/rutland.t
@@ -0,0 +1,60 @@
+use CGI::Simple;
+use FixMyStreet::TestMech;
+use FixMyStreet::Script::Reports;
+my $mech = FixMyStreet::TestMech->new;
+
+# Create test data
+my $user = $mech->create_user_ok( 'rutland@example.com' );
+my $body = $mech->create_body_ok( 2482, 'Rutland County Council');
+my $contact = $mech->create_contact_ok(
+ body_id => $body->id,
+ category => 'Other',
+ email => 'LIGHT',
+);
+$contact->update;
+
+my @reports = $mech->create_problems_for_body( 1, $body->id, 'Test', {
+ cobrand => 'rutland',
+ user => $user,
+});
+my $report = $reports[0];
+
+for my $update ('in progress', 'unable to fix') {
+ FixMyStreet::DB->resultset('Comment')->find_or_create( {
+ problem_state => $update,
+ problem_id => $report->id,
+ user_id => $user->id,
+ name => 'User',
+ mark_fixed => 'f',
+ text => "This update marks it as $update",
+ state => 'confirmed',
+ confirmed => 'now()',
+ anonymous => 'f',
+ } );
+}
+
+subtest 'testing special Open311 behaviour', sub {
+ $report->set_extra_fields();
+ $report->update;
+ $body->update( { send_method => 'Open311', endpoint => 'http://rutland.endpoint.example.com', jurisdiction => 'FMS', api_key => 'test', send_comments => 1 } );
+ my $test_data;
+ FixMyStreet::override_config {
+ STAGING_FLAGS => { send_reports => 1 },
+ ALLOWED_COBRANDS => [ 'fixmystreet', 'rutland' ],
+ }, sub {
+ $test_data = FixMyStreet::Script::Reports::send();
+ };
+ $report->discard_changes;
+ ok $report->whensent, 'Report marked as sent';
+ is $report->send_method_used, 'Open311', 'Report sent via Open311';
+ is $report->external_id, 248, 'Report has right external ID';
+
+ my $req = $test_data->{test_req_used};
+ my $c = CGI::Simple->new($req->content);
+ is $c->param('attribute[title]'), $report->title, 'Request had title';
+ is $c->param('attribute[description]'), $report->detail, 'Request had description';
+ is $c->param('attribute[external_id]'), $report->id, 'Request had correct ID';
+ is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction';
+};
+
+done_testing();
diff --git a/t/cobrand/zurich.t b/t/cobrand/zurich.t
index 4ae9a0cde..eccb0c8eb 100644
--- a/t/cobrand/zurich.t
+++ b/t/cobrand/zurich.t
@@ -23,6 +23,7 @@ my $mech = FixMyStreet::TestMech->new;
use FixMyStreet;
my $cobrand = FixMyStreet::Cobrand::Zurich->new();
+$cobrand->db_state_migration;
my $sample_file = path(__FILE__)->parent->parent->child("app/controller/sample.jpg");
ok $sample_file->exists, "sample file $sample_file exists";
@@ -48,7 +49,7 @@ sub reset_report_state {
$report->unset_extra_metadata('closed_overdue');
$report->unset_extra_metadata('closure_status');
$report->whensent(undef);
- $report->state('unconfirmed');
+ $report->state('submitted');
$report->created($created) if $created;
$report->update;
}
@@ -109,7 +110,7 @@ subtest "set up superuser" => sub {
};
my @reports = $mech->create_problems_for_body( 1, $division->id, 'Test', {
- state => 'unconfirmed',
+ state => 'submitted',
confirmed => undef,
cobrand => 'zurich',
areas => ',423017,',
@@ -125,6 +126,89 @@ FixMyStreet::override_config {
$mech->content_contains('&Uuml;berpr&uuml;fung ausstehend')
or die $mech->content;
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'zurich' ],
+ MAP_TYPE => 'Zurich,OSM',
+}, sub {
+ my $json = $mech->get_ok_json( '/report/ajax/' . $report->id );
+ is $json->{report}->{title}, "&Uuml;berpr&uuml;fung ausstehend", "correct title";
+ is $json->{report}->{state}, "submitted", "correct state";
+};
+
+subtest "Banners are displayed correctly" => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'zurich' ],
+ MAP_TYPE => 'Zurich,OSM',
+ }, sub {
+ for my $test (
+ {
+ description => 'new report',
+ state => 'submitted',
+ banner_id => 'closed',
+ banner_text => 'Erfasst'
+ },
+ {
+ description => 'confirmed report',
+ state => 'confirmed',
+ banner_id => 'closed',
+ banner_text => 'Aufgenommen',
+ },
+ {
+ description => 'fixed report',
+ state => 'fixed - council',
+ banner_id => 'fixed',
+ banner_text => 'Beantwortet',
+ },
+ {
+ description => 'closed report',
+ state => 'external',
+ banner_id => 'closed',
+ banner_text => 'Extern',
+ },
+ {
+ description => 'in progress report',
+ state => 'in progress',
+ banner_id => 'progress',
+ banner_text => 'In Bearbeitung',
+ },
+ {
+ description => 'planned report',
+ state => 'feedback pending',
+ banner_id => 'progress',
+ banner_text => 'In Bearbeitung',
+ },
+ {
+ description => 'jurisdiction unknown',
+ state => 'jurisdiction unknown',
+ banner_id => 'fixed',
+ banner_text => 'Zust\x{e4}ndigkeit unbekannt',
+ },
+ ) {
+ subtest "banner for $test->{description}" => sub {
+ $report->state( $test->{state} );
+ $report->update;
+
+ $mech->get_ok("/report/" . $report->id);
+ is $mech->uri->path, "/report/" . $report->id, "at /report/" . $report->id;
+ my $banner = $mech->extract_problem_banner;
+ if ( $banner->{text} ) {
+ $banner->{text} =~ s/^ //g;
+ $banner->{text} =~ s/ $//g;
+ }
+
+ is $banner->{id}, $test->{banner_id}, 'banner id';
+ if ($test->{banner_text}) {
+ like_string( $banner->{text}, qr/$test->{banner_text}/i, 'banner text is ' . $test->{banner_text} );
+ } else {
+ is $banner->{text}, $test->{banner_text}, 'banner text';
+ }
+
+ };
+ }
+ $report->update({ state => 'submitted' });
+ };
+};
+
# Check logging in to deal with this report
FixMyStreet::override_config {
ALLOWED_COBRANDS => [ 'zurich' ],
@@ -158,7 +242,7 @@ subtest "changing of categories" => sub {
);
}
- # full Categories dropdown is hidden for unconfirmed reports
+ # full Categories dropdown is hidden for submitted reports
$report->update({ state => 'confirmed' });
# put report into known category
@@ -274,7 +358,7 @@ subtest "report_edit" => sub {
$report->discard_changes;
is ( $report->get_extra_metadata('moderated_overdue'), 0, 'Still marked moderated_overdue' );
is ( $report->get_extra_metadata('closed_overdue'), undef, "Marking hidden doesn't set closed_overdue..." );
- is ( $report->state, 'planned', 'Marking hidden actually sets state to planned');
+ is ( $report->state, 'feedback pending', 'Marking hidden actually sets state to feedback pending');
is ( $report->get_extra_metadata('closure_status'), 'hidden', 'Marking hidden sets closure_status to hidden');
is get_moderated_count(), 1, 'Check still counted moderated'
or diag $report->get_column('extra');
@@ -411,6 +495,12 @@ subtest 'SDM' => sub {
$mech->submit_form_ok( { button => 'no_more_updates' } );
is $mech->uri->path, '/admin/summary', "redirected now finished with report.";
+ # Can still view the edit page but can't change anything
+ $mech->get_ok( '/admin/report_edit/' . $report->id );
+ $mech->content_contains('<input disabled');
+ $mech->submit_form_ok( { with_fields => { status_update => 'This is a disallowed update.' } } );
+ $mech->content_lacks('This is a disallowed update');
+
$mech->get_ok( '/report/' . $report->id );
$mech->content_contains('In Bearbeitung');
$mech->content_contains('Test Test');
@@ -423,7 +513,7 @@ subtest 'SDM' => sub {
$mech->clear_emails_ok;
$report->discard_changes;
- is $report->state, 'planned', 'Report now in planned state';
+ is $report->state, 'feedback pending', 'Report now in feedback pending state';
subtest 'send_back' => sub {
FixMyStreet::override_config {
@@ -448,8 +538,8 @@ subtest 'SDM' => sub {
$mech->get_ok( '/admin/report_edit/' . $report->id );
$mech->submit_form_ok( { button => 'not_contactable', form_number => 2 } );
$report->discard_changes;
- is $report->state, 'planned', 'Report sent back to Rueckmeldung ausstehend state';
- is $report->get_extra_metadata('closure_status'), 'partial', 'Report sent back to partial (not_contactable) state';
+ is $report->state, 'feedback pending', 'Report sent back to Rueckmeldung ausstehend state';
+ is $report->get_extra_metadata('closure_status'), 'not contactable', 'Report sent back to not_contactable state';
is $report->bodies_str, $division->id, 'Report sent back to division';
};
};
@@ -465,7 +555,7 @@ FixMyStreet::override_config {
};
reset_report_state($report);
-$report->update({ state => 'planned' });
+$report->update({ state => 'feedback pending' });
$mech->content_contains( 'report_edit/' . $report->id );
$mech->content_contains( DateTime->now->strftime("%d.%m.%Y") );
@@ -500,9 +590,9 @@ like $email->header('From'), qr/do-not-reply\@example.org/, 'from line looks cor
like $email->body, qr/FINAL UPDATE/, 'body looks correct';
$mech->clear_emails_ok;
-# Assign planned (via confirmed), don't confirm email
+# Assign feedback pending (via confirmed), don't confirm email
@reports = $mech->create_problems_for_body( 1, $division->id, 'Second', {
- state => 'unconfirmed',
+ state => 'submitted',
confirmed => undef,
cobrand => 'zurich',
areas => ',423017,',
@@ -516,7 +606,7 @@ FixMyStreet::override_config {
$mech->get_ok( '/admin/report_edit/' . $report->id );
$mech->submit_form_ok( { with_fields => { state => 'confirmed' } } );
$mech->get_ok( '/admin/report_edit/' . $report->id );
- $mech->submit_form_ok( { with_fields => { state => 'planned' } } );
+ $mech->submit_form_ok( { with_fields => { state => 'feedback pending' } } );
$mech->get_ok( '/report/' . $report->id );
};
$mech->content_contains('In Bearbeitung');
@@ -543,7 +633,7 @@ $mech->email_count_is(0);
# Report assigned to third party
@reports = $mech->create_problems_for_body( 1, $division->id, 'Third', {
- state => 'unconfirmed',
+ state => 'submitted',
confirmed => undef,
cobrand => 'zurich',
areas => ',423017,',
@@ -558,8 +648,8 @@ subtest "external report triggers email" => sub {
}, sub {
# required to see body_external field
- $report->state('planned');
- $report->set_extra_metadata('closure_status' => 'closed');
+ $report->state('feedback pending');
+ $report->set_extra_metadata('closure_status' => 'external');
# Set the public_response manually here because the default one will have line breaks that get escaped as HTML, causing the comparison to fail.
$report->set_extra_metadata('public_response' => 'Freundliche Gruesse Ihre Stadt Zuerich');
$report->update;
@@ -575,7 +665,7 @@ subtest "external report triggers email" => sub {
$report->discard_changes;
$mech->get_ok( '/report/' . $report->id );
};
- is ($report->state, 'closed', 'Report was closed correctly');
+ is ($report->state, 'external', 'Report was closed correctly');
$mech->content_contains('Extern')
or die $mech->content;
$mech->content_contains('Third Test');
@@ -596,8 +686,8 @@ subtest "external report triggers email" => sub {
}, sub {
$mech->get_ok( '/admin' );
# required to see body_external field
- $report->state('planned');
- $report->set_extra_metadata('closure_status' => 'closed');
+ $report->state('feedback pending');
+ $report->set_extra_metadata('closure_status' => 'external');
$report->set_extra_metadata('public_response' => 'Freundliche Gruesse Ihre Stadt Zuerich');
$report->update;
@@ -632,10 +722,10 @@ subtest "external report triggers email" => sub {
}, sub {
# set as wish
$report->discard_changes;
- $report->state('planned');
- $report->set_extra_metadata('closure_status' => 'investigating');
+ $report->state('feedback pending');
+ $report->set_extra_metadata('closure_status' => 'wish');
$report->update;
- is ($report->state, 'planned', 'Sanity check') or die;
+ is ($report->state, 'feedback pending', 'Sanity check') or die;
$mech->get_ok( '/admin/report_edit/' . $report->id );
@@ -646,6 +736,9 @@ subtest "external report triggers email" => sub {
body_external => $external_body->id,
external_message => $EXTERNAL_MESSAGE,
} });
+ # Wishes publicly viewable
+ $mech->get_ok( '/report/' . $report->id );
+ $mech->content_contains('Freundliche Gruesse Ihre Stadt Zuerich');
};
send_reports_for_zurich();
$email = $mech->get_email;
@@ -665,12 +758,12 @@ subtest "external report triggers email" => sub {
}, sub {
# set as extern
reset_report_state($report);
- $report->state('planned');
- $report->set_extra_metadata('closure_status' => 'closed');
+ $report->state('feedback pending');
+ $report->set_extra_metadata('closure_status' => 'external');
$report->set_extra_metadata('email_confirmed' => 1);
$report->unset_extra_metadata('public_response');
$report->update;
- is ($report->state, 'planned', 'Sanity check') or die;
+ is ($report->state, 'feedback pending', 'Sanity check') or die;
$mech->get_ok( '/admin/report_edit/' . $report->id );
@@ -872,7 +965,7 @@ subtest "test admin_log" => sub {
# XXX: following is dependent on all of test up till now, rewrite to explicitly
# test which things need to be logged!
is scalar @entries, 4, 'State changes logged';
- is $entries[-1]->action, 'state change to closed', 'State change logged as expected';
+ is $entries[-1]->action, 'state change to external', 'State change logged as expected';
};
subtest 'email images to external partners' => sub {
@@ -892,7 +985,7 @@ subtest 'email images to external partners' => sub {
# The below email comparison must not have an external message.
$report->unset_extra_metadata('external_message');
$report->update({
- state => 'closed',
+ state => 'external',
photo => $fileid,
external_body => $external_body->id,
});
@@ -944,9 +1037,9 @@ subtest 'Status update shown as appropriate' => sub {
}, sub {
# ALL closed states must hide the public_response edit, and public ones
# must show the answer in blue.
- for (['planned', 1, 0, 0],
+ for (['feedback pending', 1, 0, 0],
['fixed - council', 0, 1, 0],
- ['closed', 0, 1, 0],
+ ['external', 0, 1, 0],
['hidden', 0, 0, 1])
{
my ($state, $update, $public, $user_response) = @$_;
diff --git a/t/open311/getservicerequests.t b/t/open311/getservicerequests.t
new file mode 100644
index 000000000..878c178ef
--- /dev/null
+++ b/t/open311/getservicerequests.t
@@ -0,0 +1,301 @@
+#!/usr/bin/env perl
+
+use FixMyStreet::TestMech;
+
+use_ok( 'Open311' );
+use_ok( 'Open311::GetServiceRequests' );
+use DateTime;
+use DateTime::Format::W3CDTF;
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $user = $mech->create_user_ok('system_user@example.com', name => 'test users');
+my $body = $mech->create_body_ok(2482, 'Bromley');
+my $contact = $mech->create_contact_ok( body_id => $body->id, category => 'sidewalks', email => 'sidewalks@example.com' );
+
+my $dtf = DateTime::Format::W3CDTF->new;
+
+my $requests_xml = qq{<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+<request>
+<service_request_id>638344</service_request_id>
+<status>open</status>
+<status_notes>This is a note.</status_notes>
+<service_name>Sidewalk and Curb Issues</service_name>
+<service_code>sidewalks</service_code>
+<description>This is a sidewalk problem</description>
+<agency_responsible></agency_responsible>
+<service_notice></service_notice>
+<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime>
+<updated_datetime>2010-04-14T06:37:38-08:00</updated_datetime>
+<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime>
+<lat>51.4021</lat>
+<long>0.01578</long>
+</request>
+<request>
+<service_request_id>638345</service_request_id>
+<status>investigating</status>
+<status_notes>This is a for a different issue.</status_notes>
+<service_name>Not Sidewalk and Curb Issues</service_name>
+<service_code>not_sidewalks</service_code>
+<description>This is a problem</description>
+<agency_responsible></agency_responsible>
+<service_notice></service_notice>
+<requested_datetime>2010-04-15T06:37:38-08:00</requested_datetime>
+<updated_datetime>2010-04-15T06:37:38-08:00</updated_datetime>
+<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime>
+<lat>51.4021</lat>
+<long>0.01578</long>
+</request>
+</service_requests>
+};
+
+my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $requests_xml }
+);
+
+subtest 'basic parsing checks' => sub {
+ my $update = Open311::GetServiceRequests->new( system_user => $user );
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+
+ my $p1_date = $dtf->parse_datetime('2010-04-14T06:37:38-08:00')
+ ->set_time_zone(
+ FixMyStreet->time_zone || FixMyStreet->local_time_zone
+ );
+
+ my $p = FixMyStreet::DB->resultset('Problem')->search(
+ { external_id => 638344 }
+ )->first;
+
+ ok $p, 'Found problem';
+ is $p->detail, 'This is a sidewalk problem', 'correct problem description';
+ is $p->created, $p1_date, 'Problem has correct creation date';
+ is $p->confirmed, $p1_date, 'Problem has correct confirmed date';
+ is $p->whensent, $p1_date, 'Problem has whensent set';
+ is $p->state, 'confirmed', 'correct problem state';
+ is $p->user->id, $user->id, 'user set to system user';
+ is $p->category, 'sidewalks', 'correct problem category';
+
+ my $p2 = FixMyStreet::DB->resultset('Problem')->search( { external_id => 638345 } )->first;
+ ok $p2, 'second problem found';
+ ok $p2->whensent, 'second problem marked sent';
+ is $p2->state, 'investigating', 'second problem correct state';
+ is $p2->category, 'Other', 'category falls back to Other';
+};
+
+subtest 'check problems not re-created' => sub {
+ my $update = Open311::GetServiceRequests->new( system_user => $user );
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+
+ my $count = FixMyStreet::DB->resultset('Problem')->count;
+
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+
+ my $after_count = FixMyStreet::DB->resultset('Problem')->count;
+
+ is $count, $after_count, "problems not re-created";
+};
+
+for my $test (
+ {
+ desc => 'problem with no id is not created',
+ detail => 'This is a problem with no service_code',
+ subs => { id => '', desc => 'This is a problem with service code' },
+ },
+ {
+ desc => 'problem with no lat is not created',
+ detail => 'This is a problem with no lat',
+ subs => { lat => '', desc => 'This is a problem with no lat' },
+ },
+ {
+ desc => 'problem with no long is not created',
+ detail => 'This is a problem with no long',
+ subs => { long => '', desc => 'This is a problem with no long' },
+ },
+ {
+ desc => 'problem with bad lat/long is not created',
+ detail => 'This is a problem with bad lat/long',
+ subs => { lat => '51', long => 0.1, desc => 'This is a problem with bad lat/long' },
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $xml = prepare_xml( $test->{subs} );
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $xml}
+ );
+
+ my $count = FixMyStreet::DB->resultset('Problem')->count;
+ my $update = Open311::GetServiceRequests->new( system_user => $user );
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+ my $after_count = FixMyStreet::DB->resultset('Problem')->count;
+
+ warn $count;
+ is $count, $after_count, "problems not created";
+
+ my $with_text = FixMyStreet::DB->resultset('Problem')->search( {
+ detail => $test->{detail}
+ } )->count;
+
+ is $with_text, 0, 'no matching problem created';
+ };
+}
+
+my $date = DateTime->new(
+ year => 2010,
+ month => 4,
+ day => 14,
+ hour => 6,
+ minute => 37
+);
+
+for my $test (
+ {
+ start_date => '1',
+ end_date => '',
+ desc => 'do not process if only a start_date',
+ subs => {},
+ },
+ {
+ start_date => '',
+ end_date => '1',
+ desc => 'do not process if only an end_date',
+ subs => {},
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $xml = prepare_xml( $test->{subs} );
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $xml}
+ );
+
+ my $update = Open311::GetServiceRequests->new(
+ start_date => $test->{start_date},
+ end_date => $test->{end_date},
+ system_user => $user,
+ );
+ my $ret = $update->create_problems( $o, $body );
+
+ is $ret, 0, 'failed correctly'
+ };
+}
+
+$date = DateTime->new(
+ year => 2010,
+ month => 4,
+ day => 14,
+ hour => 6,
+ minute => 37
+);
+
+for my $test (
+ {
+ start_date => $date->clone->add(hours => -2),
+ end_date => $date->clone->add(hours => -1),
+ desc => 'do not process if update time after end_date',
+ subs => {},
+ },
+ {
+ start_date => $date->clone->add(hours => 2),
+ end_date => $date->clone->add(hours => 4),
+ desc => 'do not process if update time before start_date',
+ subs => {},
+ },
+ {
+ start_date => $date->clone->add(hours => -2),
+ end_date => $date->clone->add(hours => 4),
+ desc => 'do not process if update time is bad',
+ subs => { update_time => '2010/12/12' },
+ },
+) {
+ subtest $test->{desc} => sub {
+ my $xml = prepare_xml( $test->{subs} );
+ my $o = Open311->new(
+ jurisdiction => 'mysociety',
+ endpoint => 'http://example.com',
+ test_mode => 1,
+ test_get_returns => { 'requests.xml' => $xml}
+ );
+
+ my $update = Open311::GetServiceRequests->new(
+ start_date => $test->{start_date},
+ end_date => $test->{end_date},
+ system_user => $user,
+ );
+ my $count = FixMyStreet::DB->resultset('Problem')->count;
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $update->create_problems( $o, $body );
+ };
+ my $after = FixMyStreet::DB->resultset('Problem')->count;
+
+ is $count, $after, 'problem not added';
+ };
+}
+
+sub prepare_xml {
+ my $replacements = shift;
+
+ my %defaults = (
+ desc => 'this is a problem',
+ lat => 51.4021,
+ long => 0.01578,
+ id => 123456,
+ update_time => '2010-04-14T06:37:38-08:00',
+ %$replacements
+ );
+
+ my $xml = qq[<?xml version="1.0" encoding="utf-8"?>
+<service_requests>
+<request>
+<service_request_id>XXX_ID</service_request_id>
+<status>open</status>
+<status_notes></status_notes>
+<service_name>Sidewalk and Curb Issues</service_name>
+<service_code>sidewalks</service_code>
+<description>XXX_DESC</description>
+<agency_responsible></agency_responsible>
+<service_notice></service_notice>
+<requested_datetime>2010-04-14T06:37:38-08:00</requested_datetime>
+<updated_datetime>XXX_UPDATE_TIME</updated_datetime>
+<expected_datetime>2010-04-15T06:37:38-08:00</expected_datetime>
+<lat>XXX_LAT</lat>
+<long>XXX_LONG</long>
+</request>
+</service_requests>
+];
+
+ for my $key (keys %defaults) {
+ my $string = 'XXX_' . uc $key;
+ $xml =~ s/$string/$defaults{$key}/;
+ }
+
+ return $xml;
+}
+
+done_testing();
diff --git a/t/open311/getservicerequestupdates.t b/t/open311/getservicerequestupdates.t
index da427e505..a53354685 100644
--- a/t/open311/getservicerequestupdates.t
+++ b/t/open311/getservicerequestupdates.t
@@ -1,6 +1,7 @@
#!/usr/bin/env perl
use FixMyStreet::Test;
+use Test::Output;
use CGI::Simple;
use LWP::Protocol::PSGI;
use t::Mock::Static;
@@ -782,6 +783,53 @@ foreach my $test ( {
}
}
+foreach my $test ( {
+ desc => 'normally blank text produces a warning',
+ num_alerts => 1,
+ blank_updates_permitted => 0,
+ },
+ {
+ desc => 'no warning if blank updates permitted',
+ num_alerts => 1,
+ blank_updates_permitted => 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>
+ <status>closed</status>
+ <description></description>
+ <updated_datetime>UPDATED_DATETIME</updated_datetime>
+ </request_update>
+ </service_requests_updates>
+ };
+
+ $problem->state( 'confirmed' );
+ $problem->lastupdate( $dt->clone->subtract( hours => 3 ) );
+ $problem->update;
+
+ $requests_xml =~ s/UPDATED_DATETIME/$dt/;
+
+ my $o = Open311->new( jurisdiction => 'mysociety', endpoint => 'http://example.com', test_mode => 1, test_get_returns => { 'servicerequestupdates.xml' => $requests_xml } );
+
+ my $update = Open311::GetServiceRequestUpdates->new(
+ system_user => $user,
+ blank_updates_permitted => $test->{blank_updates_permitted},
+ );
+
+ if ( $test->{blank_updates_permitted} ) {
+ stderr_is { $update->update_comments( $o, $bodies{2482} ) } '', 'No error message'
+ } else {
+ stderr_like { $update->update_comments( $o, $bodies{2482} ) } qr/Couldn't determine update text for/, 'Error message displayed'
+ }
+ $problem->discard_changes;
+ $problem->comments->delete;
+ }
+}
+
done_testing();
sub setup_xml {
diff --git a/t/open311/populate-service-list.t b/t/open311/populate-service-list.t
index 7d4f491c6..149fb4b2a 100644
--- a/t/open311/populate-service-list.t
+++ b/t/open311/populate-service-list.t
@@ -646,6 +646,96 @@ subtest 'check bromely skip code' => sub {
is_deeply $contact->get_extra_fields, $extra, 'all meta data saved for non bromley';
};
+subtest 'check automated meta skip code' => sub {
+ my $processor = Open311::PopulateServiceList->new();
+
+ 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>
+ <automated>server_set</automated>
+ <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>
+ <automated>hidden_field</automated>
+ <variable>true</variable>
+ <code>asset_id</code>
+ <datatype>string</datatype>
+ <required>true</required>
+ <datatype_description>Id of bin</datatype_description>
+ <order>1</order>
+ <description>Id of bin</description>
+ </attribute>
+ </attributes>
+</service_definition>
+ ';
+
+ my $contact = FixMyStreet::DB->resultset('Contact')->find_or_create(
+ {
+ body_id => 1,
+ email => '001',
+ category => 'Bins left out 24x7',
+ state => 'confirmed',
+ editor => $0,
+ whenedited => \'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 }
+ );
+
+ $processor->_current_open311( $o );
+ $processor->_current_body( $body );
+ $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'
+ },
+ {
+ automated => 'hidden_field',
+ variable => 'true',
+ code => 'asset_id',
+ datatype => 'string',
+ required => 'true',
+ datatype_description => 'Id of bin',
+ order => 1,
+ description => 'Id of bin'
+ } ];
+
+ $contact->discard_changes;
+
+ is_deeply $contact->get_extra_fields, $extra, 'only hidden automated meta data saved';
+};
+
sub get_standard_xml {
return qq{<?xml version="1.0" encoding="utf-8"?>
<services>
diff --git a/t/sendreport/open311.t b/t/sendreport/open311.t
index 1eb5535aa..23096aaac 100644
--- a/t/sendreport/open311.t
+++ b/t/sendreport/open311.t
@@ -1,4 +1,16 @@
+package FixMyStreet::Cobrand::Tester;
+
+use parent 'FixMyStreet::Cobrand::FixMyStreet';
+
+sub open311_config {
+ my ($self, $row, $h, $params) = @_;
+ $params->{multi_photos} = 1;
+}
+
+package main;
+
use CGI::Simple;
+use Path::Tiny;
use FixMyStreet::Script::Reports;
use FixMyStreet::TestMech;
my $mech = FixMyStreet::TestMech->new;
@@ -41,4 +53,73 @@ subtest 'testing Open311 behaviour', sub {
is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction';
};
+my ($photo_report) = $mech->create_problems_for_body( 1, $body->id, 'Test', {
+ cobrand => 'fixmystreet',
+ category => 'Potholes',
+ user => $user,
+});
+my $sample_file = path(__FILE__)->parent->parent->child("app/controller/sample.jpg");
+my $UPLOAD_DIR = File::Temp->newdir();
+my @files = map { $_ x 40 . ".jpeg" } (1..3);
+$sample_file->copy(path($UPLOAD_DIR, $_)) for @files;
+$photo_report->photo(join(',', @files));
+$photo_report->update;
+
+subtest 'test report with multiple photos only sends one', sub {
+ $body->update( { send_method => 'Open311', endpoint => 'http://endpoint.example.com', jurisdiction => 'FMS', api_key => 'test' } );
+ my $test_data;
+
+ FixMyStreet::override_config {
+ STAGING_FLAGS => { send_reports => 1 },
+ ALLOWED_COBRANDS => [ 'fixmystreet' ],
+ }, sub {
+ $test_data = FixMyStreet::Script::Reports::send();
+ };
+ $photo_report->discard_changes;
+ ok $photo_report->whensent, 'Report marked as sent';
+ is $photo_report->send_method_used, 'Open311', 'Report sent via Open311';
+ is $photo_report->external_id, 248, 'Report has right external ID';
+
+ my $req = $test_data->{test_req_used};
+ my $c = CGI::Simple->new($req->content);
+ is $c->param('attribute[easting]'), 529025, 'Request had easting';
+ is $c->param('attribute[northing]'), 179716, 'Request had northing';
+ is $c->param('attribute[fixmystreet_id]'), $photo_report->id, 'Request had correct ID';
+ is $c->param('jurisdiction_id'), 'FMS', 'Request had correct jurisdiction';
+ my @media = $c->param('media_url');
+ is_deeply \@media, [
+ 'http://www.example.org/photo/' . $photo_report->id .'.0.full.jpeg?11111111'
+ ], 'One photo in media_url';
+};
+
+$photo_report->whensent(undef);
+$photo_report->cobrand('tester');
+$photo_report->send_method_used('');
+$photo_report->update();
+
+subtest 'test sending multiple photos', sub {
+ $body->update( { send_method => 'Open311', endpoint => 'http://endpoint.example.com', jurisdiction => 'FMS', api_key => 'test' } );
+ my $test_data;
+
+ FixMyStreet::override_config {
+ STAGING_FLAGS => { send_reports => 1 },
+ ALLOWED_COBRANDS => [ 'tester' ],
+ }, sub {
+ $test_data = FixMyStreet::Script::Reports::send();
+ };
+ $photo_report->discard_changes;
+ ok $photo_report->whensent, 'Report marked as sent';
+ is $photo_report->send_method_used, 'Open311', 'Report sent via Open311';
+ is $photo_report->external_id, 248, 'Report has right external ID';
+
+ my $req = $test_data->{test_req_used};
+ my $c = CGI::Simple->new($req->content);
+ my @media = $c->param('media_url');
+ is_deeply \@media, [
+ 'http://www.example.org/photo/' . $photo_report->id .'.0.full.jpeg?11111111',
+ 'http://www.example.org/photo/' . $photo_report->id .'.1.full.jpeg?22222222',
+ 'http://www.example.org/photo/' . $photo_report->id .'.2.full.jpeg?33333333'
+ ], 'Multiple photos in media_url';
+};
+
done_testing();
diff --git a/templates/email/rutland/_email_color_overrides.html b/templates/email/rutland/_email_color_overrides.html
new file mode 100644
index 000000000..12ec97bc3
--- /dev/null
+++ b/templates/email/rutland/_email_color_overrides.html
@@ -0,0 +1,21 @@
+[%
+
+color_rutland_dark_green = '#265123'
+color_rutland_mid_green = '#A7B980'
+color_rutland_pale_green = '#DCE6C9'
+color_rutland_dark_grey = '#3C3C3C'
+
+body_font_family = "'PT Sans', Verdana, sans-serif"
+
+header_background_color = color_rutland_mid_green
+header_text_color = color_black
+
+secondary_column_background_color = color_rutland_pale_green
+
+button_background_color = color_rutland_dark_green
+button_text_color = color_white
+
+logo_width = "150" # pixel measurement, but without 'px' suffix
+logo_height = "77" # pixel measurement, but without 'px' suffix
+
+%]
diff --git a/templates/email/warwickshire/archive.txt b/templates/email/warwickshire/archive.txt
new file mode 100644
index 000000000..51ad37453
--- /dev/null
+++ b/templates/email/warwickshire/archive.txt
@@ -0,0 +1,26 @@
+Subject: Your reports on Warwickshire FixMyStreet
+
+Hello [% user.name %],
+
+FixMyStreet is being updated in Warwickshire to improve how problems get fixed.
+
+As part of these updates, we are closing old reports that appear to be resolved but remain open in the system.
+
+We noticed that you have [% report_count %] old [% nget('report', 'reports', report_count) %] on the system, which we've listed below.
+
+If your report is no longer an issue, you don't need to do anything.
+
+If you believe that your report is still a problem, you can reopen it by clicking or copying and pasting
+the link marked 'View report' by a report and leaving an update.
+
+[% FOR report IN reports %]
+
+[% report.title %]
+
+Reported [% report.time_ago %] ago.
+
+View report: [% cobrand.base_url_for_report( report ) %][% report.tokenised_url( user, { reopen => 'true' } ) %]#update_form
+
+----
+
+[% END %]
diff --git a/templates/web/base/admin/bodies.html b/templates/web/base/admin/bodies.html
index 9bd85940b..eab0f4c49 100644
--- a/templates/web/base/admin/bodies.html
+++ b/templates/web/base/admin/bodies.html
@@ -20,7 +20,7 @@
</p>
[% END %]
- <table cellspacing="0" cellpadding="2" border="1">
+ <table cellspacing="0" cellpadding="2" border="1" id="admin_bodies">
<tr>
<th>[% loc('Name') %]</th>
[% IF c.cobrand.moniker == 'zurich' %]
diff --git a/templates/web/base/admin/list_updates.html b/templates/web/base/admin/list_updates.html
index d759a2354..4b8b26d3c 100644
--- a/templates/web/base/admin/list_updates.html
+++ b/templates/web/base/admin/list_updates.html
@@ -39,7 +39,11 @@
<br>[% loc('Confirmed:') %] [% PROCESS format_time time=update.confirmed %]
</small></td>
<td>[% update.text | html %]</td>
- <td><a href="[% c.uri_for( 'update_edit', update.id ) %]">[% loc('Edit') %]</a></td>
+ <td>
+ [% IF c.user.has_permission_to('report_edit', update.problem.bodies_str_ids) %]
+ <a href="[% c.uri_for( 'update_edit', update.id ) %]">[% loc('Edit') %]</a>
+ [% END %]
+ </td>
</tr>
[% END -%]
</table>
diff --git a/templates/web/base/admin/open311-form-fields.html b/templates/web/base/admin/open311-form-fields.html
index d1067205c..6e6eb96f0 100644
--- a/templates/web/base/admin/open311-form-fields.html
+++ b/templates/web/base/admin/open311-form-fields.html
@@ -49,6 +49,17 @@
</p>
[% IF show_body_fields %]
+ <div class="admin-hint">
+ <p>
+ [% loc(
+ "Enabling this will suppress the error message that is normally emitted when an update has no description"
+ ) %]
+ </p>
+ </div>
+ <p>
+ <input type="checkbox" id="blank_updates_permitted" name="blank_updates_permitted"[% ' checked' IF object.blank_updates_permitted %]>
+ <label for="blank_updates_permitted" class="inline">[% loc('Permit blank updates') %]</label>
+ </p>
[%# These fields aren't shown for contacts %]
<div class="admin-hint">
<p>
@@ -68,6 +79,21 @@
<div class="admin-hint">
<p>
[% loc(
+ "Enable <strong>Open311 problem-fetching</strong> if you want to display reports created at
+ the endpoint to FixMyStreet. If you're not sure, you probably do not, so leave this unchecked.
+ For more information, see
+ <a href='https://www.mysociety.org/2013/02/20/open311-extended/' class='admin-offsite-link'>this article</a>."
+ ) %]
+ </p>
+ </div>
+ <p>
+ <input type="checkbox" id="fetch_problems" name="fetch_problems"[% ' checked' IF object.fetch_problems %]>
+ <label for="fetch_problems" class="inline">[% loc('Use Open311 problem fetching') %]</label>
+ </p>
+
+ <div class="admin-hint">
+ <p>
+ [% loc(
"If you've enabled Open311 update-sending above, you must identify which
FixMyStreet <strong>user</strong> will be attributed as the creator of those updates
when they are shown on the site. Enter the ID (number) of that user."
diff --git a/templates/web/base/admin/problem_row.html b/templates/web/base/admin/problem_row.html
index 446e94d66..99142af4e 100644
--- a/templates/web/base/admin/problem_row.html
+++ b/templates/web/base/admin/problem_row.html
@@ -38,6 +38,10 @@
[%- IF problem.is_closed %]<br>[% prettify_state('closed') %]: [% PROCESS format_time time=problem.lastupdate %][% END -%]
[%- IF problem.is_open %]<br>[% loc('Last&nbsp;update:') %] [% PROCESS format_time time=problem.lastupdate %][% END -%]
</small></td>
- <td><a href="[% c.uri_for( 'report_edit', problem.id ) %]">[% loc('Edit') %]</a></td>
+ <td>
+ [% IF c.user.has_permission_to('report_edit', problem.bodies_str_ids) %]
+ <a href="[% c.uri_for( 'report_edit', problem.id ) %]">[% loc('Edit') %]</a>
+ [% END %]
+ </td>
</tr>
[%- END -%]
diff --git a/templates/web/base/admin/report-category.html b/templates/web/base/admin/report-category.html
index a2290089b..2d9ffcdb1 100644
--- a/templates/web/base/admin/report-category.html
+++ b/templates/web/base/admin/report-category.html
@@ -4,9 +4,9 @@
<option selected value="[% problem.category | html %]">[% (problem.category_display OR '-') | html %]</option>
</optgroup>
[% END %]
- [% IF category_options_copy.size %]
+ [% IF category_options.size %]
<optgroup label="[% loc('Available categories') %]">
- [% FOREACH cat IN category_options_copy %]
+ [% FOREACH cat IN category_options %]
<option value="[% cat.name | html %]"[% ' selected' IF problem.category == cat.name %]>[% cat.value | html %]</option>
[% END %]
</optgroup>
diff --git a/templates/web/base/footer.html b/templates/web/base/footer.html
index e2bdbb01a..e2bdbb01a 100644..100755
--- a/templates/web/base/footer.html
+++ b/templates/web/base/footer.html
diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html
index 111e71283..6923dbb1a 100644
--- a/templates/web/base/report/_inspect.html
+++ b/templates/web/base/report/_inspect.html
@@ -58,7 +58,7 @@
[% INCLUDE 'admin/report-category.html' %]
</p>
- [% FOREACH category IN category_options_copy %]
+ [% FOREACH category IN category_options %]
[% cat_name = category.name;
cat_prefix = cat_name | lower | replace('[^a-z]', '');
cat_prefix = "category_" _ cat_prefix _ "_" %]
diff --git a/templates/web/base/report/new/category_extras_fields.html b/templates/web/base/report/new/category_extras_fields.html
index ca5cb1461..cf5cc37b4 100644
--- a/templates/web/base/report/new/category_extras_fields.html
+++ b/templates/web/base/report/new/category_extras_fields.html
@@ -1,7 +1,7 @@
[%- FOR meta IN metas %]
[%- meta_name = meta.code -%]
- [% IF c.cobrand.category_extra_hidden(meta_name) AND NOT show_hidden %]
+ [% IF c.cobrand.category_extra_hidden(meta) AND NOT show_hidden %]
<input type="hidden" value="[% report_meta.$meta_name.value | html %]" name="[% cat_prefix %][% meta_name %]" id="[% cat_prefix %]form_[% meta_name %]">
@@ -14,6 +14,7 @@
[% IF meta.variable != 'false' %]
[% IF meta.exists('values') %]
<select class="form-control" name="[% cat_prefix %][% meta_name %]" id="[% cat_prefix %]form_[% meta_name %]"[% meta.required == 'true' ? ' required' : '' %]>
+ <option value="">[% loc('-- Pick an option --') %]</option>
[% FOR option IN meta.values %]
<option value="[% option.key %]"[% IF option.key == report_meta.$meta_name.value %] selected[% END %]>[% option.name %]</option>
[% END %]
diff --git a/templates/web/rutland/front/footer-marketing.html b/templates/web/rutland/front/footer-marketing.html
new file mode 100755
index 000000000..7d998eba6
--- /dev/null
+++ b/templates/web/rutland/front/footer-marketing.html
@@ -0,0 +1,8 @@
+ <div class="tablewrapper bordered footer-marketing">
+ <p>
+ [% loc('') %]
+ </p>
+ <p>
+ [% loc('Powered by <a class="platform-logo" href="http://fixmystreet.org/">FixMyStreet Platform</a>') %]
+ </p>
+ </div>
diff --git a/templates/web/rutland/site-name.html b/templates/web/rutland/site-name.html
new file mode 100755
index 000000000..62188c84a
--- /dev/null
+++ b/templates/web/rutland/site-name.html
@@ -0,0 +1 @@
+Rutland County Council FixMyStreet
diff --git a/templates/web/zurich/admin/body.html b/templates/web/zurich/admin/body.html
index 468f7adff..11be6eef7 100644
--- a/templates/web/zurich/admin/body.html
+++ b/templates/web/zurich/admin/body.html
@@ -16,14 +16,16 @@
<th>[% loc('Last editor') %]</th>
<th>[% loc('Note') %]</th>
<th>[% loc('When edited') %]</th>
+ <th>Kürzel</th>
</tr>
[% WHILE ( cat = contacts.next ) %]
- <tr[% IF cat.deleted %] class="is-deleted"[% END %]>
+ <tr[% IF cat.state == 'deleted' %] class="is-deleted"[% END %]>
<td><a href="[% c.uri_for( 'body', body_id, cat.category ) %]">[% cat.category_display %]</a></td>
<td>[% cat.email | html %]</td>
<td>[% cat.editor %]</td>
<td>[% cat.note | html %]</td>
<td>[% PROCESS format_date this_date=cat.whenedited %]</td>
+ <td>[% cat.get_extra_metadata('abbreviation') %]</td>
</tr>
[% END %]
</table>
@@ -37,31 +39,7 @@
</div>
[% END %]
- <form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
-
- <p>
- <strong>[% loc('Category:') %] </strong>
- <input type="text" class="form-control" name="category" size="30" value="[% contact.category | html %]">
- </p>
-
- <p>
- <strong>[% loc('Email:') %] </strong>
- <input type="text" class="form-control" name="email" size="30" value="[% contact.email | html %]">
- </p>
-
- <input type="hidden" name="confirmed" value="1" id="confirmed">
-
- <p>
- <strong>[% loc('Note:') %] </strong>
- <textarea class="form-control" name="note" rows="3" cols="40"></textarea>
- </p>
-
- <p>
- <input type="hidden" name="posted" value="new" >
- <input type="hidden" name="token" value="[% csrf_token %]" >
- <input type="submit" class="btn" name="Create category" value="[% errors ? loc('Save changes') : loc('Create category') %]">
- </p>
- </form>
+ [% INCLUDE 'admin/contact-form.html' %]
[% END %]
diff --git a/templates/web/zurich/admin/contact-form.html b/templates/web/zurich/admin/contact-form.html
index 236b169d0..064eba3f0 100644
--- a/templates/web/zurich/admin/contact-form.html
+++ b/templates/web/zurich/admin/contact-form.html
@@ -1,11 +1,24 @@
<form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" id="category_edit">
- <p><strong>[% loc('Category:') %] </strong>[% contact.category_display | html %]
+
+ [% IF contact.in_storage %]
+ <h1>[% contact.category_display | html %]</h1>
<input type="hidden" name="category" value="[% contact.category | html %]" >
- <input type="hidden" name="token" value="[% csrf_token %]" >
+ [% ELSE %]
+ <p>
+ <strong>[% loc('Category:') %] </strong>
+ <input type="text" class="form-control" name="category" size="30" value="[% contact.category | html %]" required>
+ </p>
+ [% END %]
+
+ <p>
+ <strong>Kürzel</strong>
+ <input type="text" class="form-control" name="extra[abbreviation]" id="abbreviation" size="30" value="[% contact.get_extra_metadata('abbreviation') | html %]">
+ </p>
<p><strong>[% loc('Email:') %] </strong>
<input type="text" class="form-control" name="email" value="[% contact.email | html %]" size="30">
+ [% IF contact.in_storage %]
<p>
<label for="state">[% loc('State') %]</label>
<select name="state" id="state">
@@ -15,9 +28,15 @@
<input type="checkbox" name="photo_required" value="1" id="photo_required"[% ' checked' IF contact.get_extra_metadata('photo_required') %]>
<label class="inline" for="photo_required">[% loc('Photo required') %]</label>
</p>
+ [% ELSE %]
+ <input type="hidden" name="confirmed" value="1" id="confirmed">
+ [% END %]
<p><strong>[% loc('Note:') %] </strong><textarea class="form-control" name="note" rows="3" cols="40"></textarea>
<input type="hidden" name="posted" value="new">
- <p><input type="submit" class="btn" name="Save changes" value="[% loc('Save changes') %]">
+ <input type="hidden" name="token" value="[% csrf_token %]" >
+ <p><input type="submit" class="btn" name="Create category" value="[% contact.in_storage ? loc('Save changes') : loc('Create category') %]">
+ </p>
+
</form>
diff --git a/templates/web/zurich/admin/header.html b/templates/web/zurich/admin/header.html
index 40847f190..91ac24faa 100644
--- a/templates/web/zurich/admin/header.html
+++ b/templates/web/zurich/admin/header.html
@@ -1,20 +1,6 @@
[%
SET bodyclass = bodyclass || 'fullwidthpage';
INCLUDE 'header.html' admin = 1, bodyclass = bodyclass _ ' admin';
-
- states = {
- 'unconfirmed' = loc('Submitted'),
- 'confirmed' = loc('Open'),
- 'in progress' = loc('In progress'),
- 'planned' = loc('Planned'),
- 'fixed - council' = loc('Closed'),
- 'hidden' = loc('Hidden'),
- 'closed' = loc('Extern'),
- 'partial' = loc('Not contactable'),
- 'investigating' = loc('Wish'),
- 'unable to fix' = loc('Jurisdiction unknown'),
- 'fixed - council' = loc('Closed'),
- }
%]
<style type="text/css">
.adminhidden { color: #666666; }
diff --git a/templates/web/zurich/admin/index-dm.html b/templates/web/zurich/admin/index-dm.html
index e0b62d5d2..4d77cf264 100644
--- a/templates/web/zurich/admin/index-dm.html
+++ b/templates/web/zurich/admin/index-dm.html
@@ -4,10 +4,10 @@
[% status_message %]
<h2 id="submitted">[% loc('Submitted') %]</h2>
-[% INCLUDE list, problems = unconfirmed.all, hash = 'submitted' %]
+[% INCLUDE list, problems = submitted.all, hash = 'submitted' %]
-<h2 id="planned">[% loc('Planned') %]</h2>
-[% INCLUDE list, problems = approval.all, hash = 'planned' %]
+<h2 id="feedback_pending">Rückmeldung ausstehend</h2>
+[% INCLUDE list, problems = approval.all, hash = 'feedback_pending' %]
<h2 id="alle">[% loc('All reports') %]</h2>
[% INCLUDE list, problems = other.all, include_subdiv = 1, hash = 'alle' %]
diff --git a/templates/web/zurich/admin/index-sdm.html b/templates/web/zurich/admin/index-sdm.html
index 707bb2d9d..68a2fcf62 100644
--- a/templates/web/zurich/admin/index-sdm.html
+++ b/templates/web/zurich/admin/index-sdm.html
@@ -5,7 +5,7 @@
[% INCLUDE list, problems = reports_new.all, hash = 'new' %]
<h2 id="wait">[% loc('Reports awaiting approval') %]</h2>
-[% INCLUDE list, problems = reports_unpublished.all, no_edit = 1, hash = 'wait' %]
+[% INCLUDE list, problems = reports_unpublished.all, hash = 'wait' %]
<h2 id="alle">[% loc('Reports published') %]</h2>
[% INCLUDE list, problems = reports_published.all, no_edit = 1, hash = 'alle' %]
diff --git a/templates/web/zurich/admin/index.html b/templates/web/zurich/admin/index.html
index fb3609bb3..62cd1a52c 100644
--- a/templates/web/zurich/admin/index.html
+++ b/templates/web/zurich/admin/index.html
@@ -9,8 +9,7 @@
<h2>[% loc('Problem breakdown by state') %]</h2>
<ul>
[% FOREACH state IN problems.keys.sort %]
- [% NEXT IF NOT states.$state %]
- <li>[% problems.$state %] [% states.$state %]</li>
+ <li>[% problems.$state %] [% prettify_state(state) %]</li>
[% END %]
</ul>
diff --git a/templates/web/zurich/admin/problem_row.html b/templates/web/zurich/admin/problem_row.html
index a83e22b27..973d9f651 100644
--- a/templates/web/zurich/admin/problem_row.html
+++ b/templates/web/zurich/admin/problem_row.html
@@ -18,9 +18,10 @@
<td>[% PROCESS value_or_nbsp value=problem.category_display %]</td>
<td>[% PROCESS format_date this_date=problem.created %]</td>
<td>[% PROCESS format_date this_date=problem.lastupdate %]</td>
- <td> [% states.${problem.state} %][% IF problem.state == 'planned';
+ <td>[% prettify_state(problem.state) %]
+ [% IF problem.state == 'feedback pending';
SET cs=problem.get_extra_metadata('closure_status');
- IF cs %] ([% states.$cs %]) [% END; END %]</td>
+ IF cs %] ([% prettify_state(cs) %]) [% END; END %]</td>
[% IF include_subdiv %]
<td>
diff --git a/templates/web/zurich/admin/report_edit-sdm.html b/templates/web/zurich/admin/report_edit-sdm.html
index 0319fc565..d07629d01 100644
--- a/templates/web/zurich/admin/report_edit-sdm.html
+++ b/templates/web/zurich/admin/report_edit-sdm.html
@@ -73,7 +73,7 @@
<dd>[% problem.category_display | html %]</dd>
<dt class="print-only">[% loc('State:') %] <!-- Status --></dt>
- <dd class="print-only">[% states.${problem.state} %]</dd>
+ <dd class="print-only">[% prettify_state(problem.state) %]</dd>
<dt>[% loc('Time spent (in minutes):') %]</dt>
<dd>[% problem.get_time_spent %]</dd>
@@ -101,28 +101,28 @@
<div class="admin-report-edit admin-report-edit--interact">
-<p align="right" class="screen-only"><input type="submit" class="btn" name="send_back" value="[% loc('Not for my subdivision') %]"></p>
-<p align="right" class="screen-only"><input type="submit" class="btn" name="not_contactable" value="[% loc('Customer not contactable') %]"></p>
+<p align="right" class="screen-only"><input [% sdm_disabled %] type="submit" class="btn" name="send_back" value="[% loc('Not for my subdivision') %]"></p>
+<p align="right" class="screen-only"><input [% sdm_disabled %] type="submit" class="btn" name="not_contactable" value="[% loc('Customer not contactable') %]"></p>
<ul class="no-bullets screen-only">
<li>
<label for="new_internal_note">[% loc('New internal note:') %]</label>
- <textarea class="form-control" name='new_internal_note' id='new_internal_note' cols=60 rows=4></textarea>
+ <textarea [% sdm_disabled %] class="form-control" name='new_internal_note' id='new_internal_note' cols=60 rows=4></textarea>
</li>
<li>
<label for="status_update">[% loc('New note to DM:') %]</label>
- <textarea class="form-control" name='status_update' id='status_update' cols=60 rows=4></textarea>
+ <textarea [% sdm_disabled %] class="form-control" name='status_update' id='status_update' cols=60 rows=4></textarea>
</li>
</ul>
<p class="screen-only">
<label for="time_spent">[% loc('Time spent (in minutes):') %]</label>
- <input type="text" class="form-control" name="time_spent" id="form_time_spent" style="width: 4em" value="0">
+ <input [% sdm_disabled %] type="text" class="form-control" name="time_spent" id="form_time_spent" style="width: 4em" value="0">
</p>
<p class="clearfix screen-only">
- <input style="float:left" type="submit" class="btn" name="Submit changes" value="[% loc('Submit changes') %]" >
- <input style="float:right" type="submit" class="btn" name="no_more_updates" value="[% loc('No further updates') %]">
+ <input [% sdm_disabled %] style="float:left" type="submit" class="btn" name="Submit changes" value="[% loc('Submit changes') %]" >
+ <input [% sdm_disabled %] style="float:right" type="submit" class="btn" name="no_more_updates" value="[% loc('No further updates') %]">
</p>
[% INCLUDE 'admin/list_updates.html' %]
diff --git a/templates/web/zurich/admin/report_edit.html b/templates/web/zurich/admin/report_edit.html
index fd03fb044..ad14b2d45 100644
--- a/templates/web/zurich/admin/report_edit.html
+++ b/templates/web/zurich/admin/report_edit.html
@@ -27,7 +27,7 @@
<dd class="screen-only">&raquo; <a href="http://webgis.intra.stzh.ch/stapo/GoogleStreetView.asp?lat=[% problem.latitude %]&amp;lon=[% problem.longitude %]" target="_blank">[% loc('Street View') %]</a></dd>
- [% IF c.cobrand.problem_is_closed(problem) %]
+ [% IF problem.is_fixed || problem.is_closed || problem.is_hidden %]
<dt><span class="mock-label">[% loc('Details:') %]</span></dt>
<dd>[% problem.detail | html %]</dd>
[% IF problem.extra.original_detail %]
@@ -101,7 +101,7 @@
<dd>[% problem.category_display | html %]</dd>
<dt class="print-only">[% loc('State:') %] <!-- Status --></dt>
- <dd class="print-only">[% states_trans.${problem.state} %]</dd>
+ <dd class="print-only">[% prettify_state(problem.state) %]</dd>
<dt>[% loc('Time spent (in minutes):') %]</dt>
<dd>[% problem.get_time_spent %]</dd>
@@ -154,7 +154,8 @@
<select class="form-control" name="state" id="state" data-pstate="[% pstate %]">
<option value="">--</option>
[% FOREACH s IN states %]
- <option [% 'selected ' IF s.state == pstate %] value="[% s.state %]">[% s.trans %]</option>
+ <option [% 'selected ' IF s.state == pstate %] value="[% s.state %]">
+ [% IF s.trans; s.trans; ELSE; prettify_state(s.state); END %]</option>
[% END %]
</select>
</dd>
@@ -170,7 +171,8 @@
<select class="form-control" name="category" id="category">
<option value="">--</option>
[% FOREACH cat IN category_options %]
- <option value="[% cat.name %]">[% cat.value %]</option>
+ <option value="[% cat.name %]">[% cat.value ~%]
+ [% ' (' _ cat.abbreviation _ ')' IF cat.abbreviation %]</option>
[% END %]
</select>
</div>
@@ -195,15 +197,15 @@
</ul>
-[% IF problem.state == 'planned' %]
+[% IF problem.state == 'feedback pending' %]
[%# 3rd party messages sent for Extern/Wunsch states %]
[% SWITCH pstate %]
- [% CASE ['closed','investigating'] %]
+ [% CASE ['external','wish'] %]
<ul class="no-bullets screen-only">
<li class="assignation" id="assignation__external">
<span class="error hidden">[% loc('Please select a body.') %]</span>
<label for="body_external">
- [% IF pstate == 'closed' %]
+ [% IF pstate == 'external' %]
[% loc('Assign to external body:') %]
[% ELSE %]
[% loc('Assign to competent body:') %]
@@ -218,7 +220,7 @@
</select>
</li>
<li>
- [% IF pstate == 'closed' %]
+ [% IF pstate == 'external' %]
<input type="checkbox" name="third_personal" id="third_personal" value="1"[% ' checked' IF problem.extra.third_personal %]>
<label for="third_personal" class="inline">[% loc('Include reporter personal details') %]</label>
[% END %]
@@ -234,12 +236,11 @@
# (e.g. various pstates) %]
<ul class="no-bullets screen-only">
<li id="status_update_container"><label for="status_update">
- [% SWITCH pstate %]
- [% CASE ['hidden', 'investigating', 'partial'] %][%# Hidden/Wish/Not contactable %]
- [% loc('Reply to user:') %]
- [% CASE DEFAULT %]
- [% loc('Public response:') %]
- [% END %]
+ [% IF c.cobrand.zurich_user_response_states.$pstate %]
+ [% loc('Reply to user:') %]
+ [% ELSE %]
+ [% loc('Public response:') %]
+ [% END %]
</label>
[% INCLUDE 'admin/response_templates_select.html' for='status_update' %]
<textarea class="form-control" name='status_update' id='status_update' cols=60 rows=5>
@@ -252,7 +253,7 @@
<p align="right" class="screen-only">
[% IF show_publish_response %]
[%# While we call this 'publish_response', the response will not actually
- # be "published" for these cases: Wish / Hidden / Not contactable (for these,
+ # be "published" for these cases: Hidden / Not contactable (for these,
# only a private email will be sent to the user. However, in all cases,
# this is the end of processing, so we mark this with the same text used
# for 'No further updates %]
@@ -272,9 +273,9 @@
[% END %]
[% SWITCH problem.state %]
- [% CASE ['closed','investigating'] %]
+ [% CASE ['external','wish'] %]
<h2>
- [% IF problem.state == 'closed' %]
+ [% IF problem.state == 'external' %]
[% loc('Message to external body:') %]
[% ELSE %]
[% loc('Message to competent body:') %]
diff --git a/templates/web/zurich/admin/stats/index.html b/templates/web/zurich/admin/stats/index.html
index ce8e238f7..9f2906d0a 100644
--- a/templates/web/zurich/admin/stats/index.html
+++ b/templates/web/zurich/admin/stats/index.html
@@ -2,14 +2,12 @@
[% PROCESS 'admin/report_blocks.html' %]
[% USE date %]
-<p style="float:right"><a href="[% c.uri_with( { export=1 } ) %]">[% loc('All Reports as CSV') %]</a></p>
+<form method="get" action="[% c.uri_for('/admin/stats') %]">
+<div class="filters">
-[% IF start_date AND end_date %]
-<p><strong>[% tprintf( loc( 'All reports between %s and %s' ), start_date.ymd, end_date.ymd ) | html %]</strong></p>
-[% END %]
-
-<form method="get" action="[% c.uri_for('stats') %]">
-<p><select class="form-control" name="ym">
+<p>
+ <label for="ym">[% loc('Month') %]</label>
+ <select class="form-control" name="ym" id="ym">
<option value="">[% loc('All reports') %]</option>
[% FOR y IN [ 2013 .. date.format(date.now, '%Y') ];
SET max = 12;
@@ -21,11 +19,34 @@
<option[% ' selected' IF v == ym %]>[% v %]</option>
[% END %]
[% END %]
-</select>
+ </select>
+</p>
+
+<p>
+ <label for="category">[% loc('Category') %]</label>
+ <select class="form-control" name="category" id="category">
+ <option value="">--</option>
+ [% FOREACH cat IN category_options %]
+ <option value="[% cat.name %]"[% ' selected' IF cat.name == category %]>[% cat.value ~%]
+ [% ' (' _ cat.abbreviation _ ')' IF cat.abbreviation %]</option>
+ [% END %]
+ </select>
+</p>
+
+<p class="no-label">
+ <input type="submit" class="btn" value="[% loc('Go') %]">
+</p>
-<input type="submit" class="btn" value="[% loc('Go') %]">
+</div>
</form>
+<ul class="dashboard-options-tabs">
+ [% IF start_date AND end_date %]
+ <li><strong>[% tprintf( loc( 'All reports between %s and %s' ), start_date.ymd, end_date.ymd ) | html %]</strong></li>
+ [% END %]
+ <li class="pull-right"><a href="[% c.uri_with( { export=1 } ) %]">[% loc('All Reports as CSV') %]</a></li>
+</ul>
+
<ul>
<li>[% loc('Total') %]: [% reports_total || 0 %]
<li>[% loc('Closed') %]: [% reports_solved || 0 %]
diff --git a/templates/web/zurich/report/_item.html b/templates/web/zurich/report/_item.html
index ccae84271..f20b2021d 100644
--- a/templates/web/zurich/report/_item.html
+++ b/templates/web/zurich/report/_item.html
@@ -1,11 +1,11 @@
<li class="item-list__item item-list--reports__item">
<a href="[% c.uri_for('/report', problem.id ) %]">
[% photo_to_display = c.cobrand.allow_photo_display(problem) %]
- [% IF problem.state != 'unconfirmed' AND problem.photo AND photo_to_display %]
+ [% IF problem.state != 'submitted' AND problem.photo AND photo_to_display %]
[% photo_idx = photo_to_display - 1 ~%]
<img class="img" height="60" width="90" src="[% problem.photos.${photo_idx}.url_fp %]" alt="">
[% END %]
- [% IF problem.state != 'unconfirmed' %]
+ [% IF problem.state != 'submitted' %]
<h3 class="item-list__heading">[% problem.title | html %]</h3>
[% ELSE %]
<h3 class="item-list__heading"><em>[% loc('Awaiting moderation') %]</em></h3>
diff --git a/templates/web/zurich/report/_main.html b/templates/web/zurich/report/_main.html
index 912e8b0c9..c9225b4aa 100644
--- a/templates/web/zurich/report/_main.html
+++ b/templates/web/zurich/report/_main.html
@@ -5,7 +5,7 @@
[%- IF !problem.used_map %]<br>[% loc('there is no pin shown as the user did not use the map') %][% END %]
</p>
- [% IF problem.state != 'unconfirmed' %]
+ [% IF problem.state != 'submitted' %]
[% INCLUDE 'report/photo.html' object=problem %]
[% problem.detail | add_links | html_para %]
[% ELSE %]
diff --git a/templates/web/zurich/report/updates.html b/templates/web/zurich/report/updates.html
index 4489fea34..8a09d0bc7 100644
--- a/templates/web/zurich/report/updates.html
+++ b/templates/web/zurich/report/updates.html
@@ -1,15 +1,11 @@
-[% IF problem.state == 'fixed - council' OR problem.state == 'closed' %]
+[% IF c.cobrand.problem_has_public_response(problem) %]
<h4 class="static-with-rule">[% loc('Updates') %]</h4>
<ul class="item-list item-list--updates">
<li class="item-list__item item-list__item--updates">
<div class="item-list__update-wrap">
<div class="item-list__update-text">
<p class="meta-2">[% prettify_dt( problem.lastupdate, 'zurich' ) %]</p>
- [%# XXX following should honour zurich_closed_states instead? %]
- [% IF problem.state == 'fixed - council'
- || ( problem.external_body AND problem.state == 'closed' ) %]
[% problem.extra.public_response | add_links | html_para %]
- [% END %]
</div>
</div>
</li>
diff --git a/web/cobrands/fixmystreet/assets.js b/web/cobrands/fixmystreet/assets.js
index 26a13dc1d..14aab2db2 100644
--- a/web/cobrands/fixmystreet/assets.js
+++ b/web/cobrands/fixmystreet/assets.js
@@ -1,12 +1,97 @@
var fixmystreet = fixmystreet || {};
+/* Special USRN handling */
+
(function(){
-var selected_feature = null;
-var fault_popup = null;
var selected_usrn = null;
var usrn_field = null;
+fixmystreet.usrn = {
+ select: function(evt, lonlat) {
+ var usrn_providers = fixmystreet.map.getLayersBy('fixmystreet', {
+ test: function(options) {
+ return options && options.usrn;
+ }
+ });
+ if (usrn_providers.length) {
+ var usrn_layer = usrn_providers[0];
+ usrn_field = usrn_layer.fixmystreet.usrn.field;
+ var point = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
+ var feature = usrn_layer.getFeatureAtPoint(point);
+ if (feature == null) {
+ // The click wasn't directly over a road, try and find one
+ // nearby
+ feature = usrn_layer.getNearestFeature(point, 10);
+ }
+ if (feature !== null) {
+ selected_usrn = feature.attributes[usrn_layer.fixmystreet.usrn.attribute];
+ } else {
+ selected_usrn = null;
+ }
+ fixmystreet.usrn.update_field();
+ }
+ },
+
+ update_field: function() {
+ $("input[name="+usrn_field+"]").val(selected_usrn);
+ }
+};
+
+$(fixmystreet).on('maps:update_pin', fixmystreet.usrn.select);
+$(fixmystreet).on('assets:selected', fixmystreet.usrn.select);
+$(fixmystreet).on('report_new:category_change:extras_received', fixmystreet.usrn.update_field);
+
+})();
+
+(function(){
+
+var selected_feature = null;
+var fault_popup = null;
+
+/*
+ * Adds the layer to the map and sets up event handlers and whatnot.
+ * Called as part of fixmystreet.assets.init for each asset layer on the map.
+ */
+function init_asset_layer(layer, pins_layer) {
+ fixmystreet.map.addLayer(layer);
+ if (layer.fixmystreet.asset_category) {
+ fixmystreet.map.events.register( 'zoomend', layer, check_zoom_message_visibility);
+ }
+
+ // Don't cover the existing pins layer
+ if (pins_layer) {
+ layer.setZIndex(pins_layer.getZIndex()-1);
+ }
+
+ // Make sure the fault markers always appear beneath the linked assets
+ if (layer.fixmystreet.fault_layer) {
+ fixmystreet.map.addLayer(layer.fixmystreet.fault_layer);
+ layer.fixmystreet.fault_layer.setZIndex(layer.getZIndex()-1);
+ }
+
+ if (!layer.fixmystreet.always_visible) {
+ // Show/hide the asset layer when the category is chosen
+ $("#problem_form").on("change.category", "select#form_category", function(){
+ var category = $(this).val();
+ if (category == layer.fixmystreet.asset_category) {
+ layer.setVisibility(true);
+ if (layer.fixmystreet.fault_layer) {
+ layer.fixmystreet.fault_layer.setVisibility(true);
+ }
+ zoom_to_assets(layer);
+ } else {
+ layer.setVisibility(false);
+ if (layer.fixmystreet.fault_layer) {
+ layer.fixmystreet.fault_layer.setVisibility(false);
+ }
+ }
+ });
+ }
+
+}
+
+
function close_fault_popup() {
if (!!fault_popup) {
fixmystreet.map.removePopup(fault_popup);
@@ -37,7 +122,7 @@ function asset_selected(e) {
// Pick up the USRN for the location of this asset. NB we do this *before*
// handling the attributes on the selected feature in case the feature has
// its own USRN which should take precedence.
- fixmystreet.assets.select_usrn(lonlat);
+ $(fixmystreet).trigger('assets:selected', [ lonlat ]);
// Set the extra field to the value of the selected feature
$.each(this.fixmystreet.attributes, function (field_name, attribute_name) {
@@ -253,14 +338,15 @@ fixmystreet.assets = {
var protocol_options;
var protocol;
if (options.http_options !== undefined) {
- protocol_options = OpenLayers.Util.extend(options.http_options, {});
- if (protocol_options.format_class) {
- protocol_options.format = new protocol_options.format_class(protocol_options.format_options);
- } else {
- protocol_options.format = new OpenLayers.Format.GML({
- geometryName: options.geometryName
- });
+ protocol_options = options.http_options;
+ OpenLayers.Util.applyDefaults(options, {
+ format_class: OpenLayers.Format.GML,
+ format_options: {}
+ });
+ if (options.geometryName) {
+ options.format_options.geometryName = options.geometryName;
}
+ protocol_options.format = new options.format_class(options.format_options);
protocol = new OpenLayers.Protocol.HTTP(protocol_options);
} else {
protocol_options = {
@@ -374,25 +460,6 @@ fixmystreet.assets = {
if (select_feature_control) {
fixmystreet.assets.controls.push(select_feature_control);
}
-
- if (!asset_layer.fixmystreet.always_visible) {
- // Show/hide the asset layer when the category is chosen
- $("#problem_form").on("change.category", "select#form_category", function(){
- var category = $(this).val();
- if (category == options.asset_category) {
- asset_layer.setVisibility(true);
- if (asset_layer.fixmystreet.fault_layer) {
- asset_layer.fixmystreet.fault_layer.setVisibility(true);
- }
- zoom_to_assets(asset_layer);
- } else {
- asset_layer.setVisibility(false);
- if (asset_layer.fixmystreet.fault_layer) {
- asset_layer.fixmystreet.fault_layer.setVisibility(false);
- }
- }
- });
- }
},
init: function() {
@@ -418,58 +485,13 @@ fixmystreet.assets = {
var pins_layer = fixmystreet.map.getLayersByName("Pins")[0];
for (var i = 0; i < fixmystreet.assets.layers.length; i++) {
- var layer = fixmystreet.assets.layers[i];
- fixmystreet.map.addLayer(layer);
- if (layer.fixmystreet.asset_category) {
- fixmystreet.map.events.register( 'zoomend', layer, check_zoom_message_visibility);
- }
-
- // Don't cover the existing pins layer
- if (pins_layer) {
- layer.setZIndex(pins_layer.getZIndex()-1);
- }
-
- // Make sure the fault markers always appear beneath the linked assets
- if (layer.fixmystreet.fault_layer) {
- fixmystreet.map.addLayer(layer.fixmystreet.fault_layer);
- layer.fixmystreet.fault_layer.setZIndex(layer.getZIndex()-1);
- }
-
+ init_asset_layer(fixmystreet.assets.layers[i], pins_layer);
}
for (i = 0; i < fixmystreet.assets.controls.length; i++) {
fixmystreet.map.addControl(fixmystreet.assets.controls[i]);
fixmystreet.assets.controls[i].activate();
}
- },
-
- select_usrn: function(lonlat) {
- var usrn_providers = fixmystreet.map.getLayersBy('fixmystreet', {
- test: function(options) {
- return options && options.usrn;
- }
- });
- if (usrn_providers.length) {
- var usrn_layer = usrn_providers[0];
- usrn_field = usrn_layer.fixmystreet.usrn.field;
- var point = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
- var feature = usrn_layer.getFeatureAtPoint(point);
- if (feature == null) {
- // The click wasn't directly over a road, try and find one
- // nearby
- feature = usrn_layer.getNearestFeature(point, 10);
- }
- if (feature !== null) {
- selected_usrn = feature.attributes[usrn_layer.fixmystreet.usrn.attribute];
- } else {
- selected_usrn = null;
- }
- fixmystreet.assets.update_usrn_field();
- }
- },
-
- update_usrn_field: function() {
- $("input[name="+usrn_field+"]").val(selected_usrn);
}
};
diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js
index 1253af6b6..d85a5cdbc 100644
--- a/web/cobrands/fixmystreet/fixmystreet.js
+++ b/web/cobrands/fixmystreet/fixmystreet.js
@@ -151,8 +151,6 @@ function isR2L() {
});
})(jQuery);
-fixmystreet.hooks = fixmystreet.hooks || {};
-
fixmystreet.mobile_reporting = {
apply_ui: function() {
// Creates the "app-like" mobile reporting UI with full screen map
@@ -411,24 +409,20 @@ $.extend(fixmystreet.set_up, {
if ( data.category_extra ) {
if ( $category_meta.length ) {
$category_meta.replaceWith( data.category_extra );
+ // Preserve any existing values
+ $category_meta.find("[name]").each(function() {
+ $('#category_meta').find("[name="+this.name+"]").val(this.value);
+ });
} else {
$('#form_category_row').after( data.category_extra );
}
} else {
$category_meta.empty();
}
- if (fixmystreet.assets) {
- fixmystreet.assets.update_usrn_field();
- }
+ $(fixmystreet).trigger('report_new:category_change:extras_received');
});
- if (fixmystreet.hooks.update_problem_fields) {
- args.prefill_reports = $(this).data('prefill');
- args.role = $(this).data('role');
- args.body = $(this).data('body');
-
- fixmystreet.hooks.update_problem_fields(args);
- }
+ $(fixmystreet).trigger('report_new:category_change', [ $(this) ]);
});
},
diff --git a/web/cobrands/fixmystreet/staff.js b/web/cobrands/fixmystreet/staff.js
index 4577ab65e..dd65bc3ce 100644
--- a/web/cobrands/fixmystreet/staff.js
+++ b/web/cobrands/fixmystreet/staff.js
@@ -428,31 +428,33 @@ $.extend(fixmystreet.set_up, {
});
-$.extend(fixmystreet.hooks, {
- update_problem_fields: function(args) {
- if (args.prefill_reports && args.role == 'inspector') {
- var title = 'A ' + args.category + ' problem has been found';
- var description = 'A ' + args.category + ' problem has been found by ' + args.body;
-
- var $title_field = $('#form_title');
- var $description_field = $('#form_detail');
-
- if ($title_field.val().length === 0 || $title_field.data('autopopulated') === true) {
- $title_field.val(title);
- $title_field.data('autopopulated', true);
- }
-
- if ($description_field.val().length === 0 || $description_field.data('autopopulated') === true) {
- $description_field.val(description);
- $description_field.data('autopopulated', true);
- }
+$(fixmystreet).on('report_new:category_change', function(evt, $this) {
+ var category = $this.val();
+ var prefill_reports = $this.data('prefill');
+ var role = $this.data('role');
+ var body = $this.data('body');
+
+ if (prefill_reports && role == 'inspector') {
+ var title = 'A ' + category + ' problem has been found';
+ var description = 'A ' + category + ' problem has been found by ' + body;
+
+ var $title_field = $('#form_title');
+ var $description_field = $('#form_detail');
+
+ if ($title_field.val().length === 0 || $title_field.data('autopopulated') === true) {
+ $title_field.val(title);
+ $title_field.data('autopopulated', true);
+ }
- $('#form_title, #form_detail').on('keyup', function() {
- $(this).data('autopopulated', false);
- });
+ if ($description_field.val().length === 0 || $description_field.data('autopopulated') === true) {
+ $description_field.val(description);
+ $description_field.data('autopopulated', true);
}
- }
+ $('#form_title, #form_detail').on('keyup', function() {
+ $(this).data('autopopulated', false);
+ });
+ }
});
fixmystreet.maps = fixmystreet.maps || {};
diff --git a/web/cobrands/rutland/RCCLogo.gif b/web/cobrands/rutland/RCCLogo.gif
new file mode 100755
index 000000000..aeacf01f6
--- /dev/null
+++ b/web/cobrands/rutland/RCCLogo.gif
Binary files differ
diff --git a/web/cobrands/rutland/_colours.scss b/web/cobrands/rutland/_colours.scss
new file mode 100755
index 000000000..c3666ca17
--- /dev/null
+++ b/web/cobrands/rutland/_colours.scss
@@ -0,0 +1,34 @@
+/* LAYOUT */
+
+// If you are wanting a right-to-left layout, uncomment the following line.
+// $direction: right;
+
+/* COLOURS */
+
+$orange: #ff9900;
+$bluey: #6688ff;
+$RCCGreen: #a7b980;
+$RCCGreen_dark: #265123;
+$RCCbg: #F1F1F1;
+
+$primary: $RCCGreen;
+$primary_b: #000000;
+$primary_text: #222222;
+
+$base_bg: $RCCbg;
+$base_fg: #000;
+
+$map_nav_bg: $RCCbg;
+$nav_fg: #000;
+$nav_fg_hover: $primary;
+
+// Colour used for front page 'how to report a problem' steps
+$col_big_numbers: #ccc;
+
+$col_click_map: $RCCGreen_dark;
+
+$col_fixed_label: #00BD08;
+$col_fixed_label_dark: #4B8304;
+
+//$image-sprite: '/cobrands/rutland/RCCLogo.gif';
+
diff --git a/web/cobrands/rutland/base.scss b/web/cobrands/rutland/base.scss
new file mode 100755
index 000000000..4837e970a
--- /dev/null
+++ b/web/cobrands/rutland/base.scss
@@ -0,0 +1,16 @@
+@import "../sass/h5bp";
+@import "./_colours";
+@import "../sass/mixins";
+
+@import "../sass/base";
+
+
+#site-logo {
+ background: url("/cobrands/rutland/RCCLogo.gif");
+ background-size: contain;
+ height: 50px;
+ width: 110px;
+}
+
+
+
diff --git a/web/cobrands/rutland/images/email-logo.gif b/web/cobrands/rutland/images/email-logo.gif
new file mode 100644
index 000000000..bab9d2eef
--- /dev/null
+++ b/web/cobrands/rutland/images/email-logo.gif
Binary files differ
diff --git a/web/cobrands/rutland/layout.scss b/web/cobrands/rutland/layout.scss
new file mode 100755
index 000000000..eb0447be2
--- /dev/null
+++ b/web/cobrands/rutland/layout.scss
@@ -0,0 +1,15 @@
+@import "_colours";
+@import "../sass/layout";
+
+
+body.frontpage #site-header {
+ height: 10em;
+}
+
+
+body.frontpage #site-logo {
+ background: url("/cobrands/rutland/RCCLogo.gif");
+ background-size: contain;
+ height: 100px;
+ width: 220px;
+}
diff --git a/web/cobrands/sass/_base.scss b/web/cobrands/sass/_base.scss
index 4f46d685a..4e2a2fd5f 100644
--- a/web/cobrands/sass/_base.scss
+++ b/web/cobrands/sass/_base.scss
@@ -560,6 +560,9 @@ ul.error {
font-size: 1.25em;
border-bottom: 0.25em solid #333;
}
+ a:visited {
+ color: #333;
+ }
a:hover, span.hover {
background-color: #333;
color: #fff;
diff --git a/web/cobrands/zurich/base.scss b/web/cobrands/zurich/base.scss
index 101118a02..f526a8f1e 100644
--- a/web/cobrands/zurich/base.scss
+++ b/web/cobrands/zurich/base.scss
@@ -224,9 +224,9 @@ h4.static-with-rule {
margin-right: 0.2em;
}
- table tr.is-deleted {
- background-color: transparent;
- }
+}
+table#admin_bodies tr.is-deleted {
+ background-color: transparent;
}
.admin-label--inline {
diff --git a/web/cobrands/zurich/js.js b/web/cobrands/zurich/js.js
index d2909bb6d..8b30436d5 100644
--- a/web/cobrands/zurich/js.js
+++ b/web/cobrands/zurich/js.js
@@ -77,7 +77,7 @@ $(function() {
$('#assignation__category').show();
$('#assignation__subdivision').show();
}
- if ((state === 'closed') || (state === 'investigating')) {
+ if ((state === 'external') || (state === 'wish')) {
$('#assignation__external').show();
} else {
$('#assignation__external').hide();
diff --git a/web/cobrands/zurich/layout.scss b/web/cobrands/zurich/layout.scss
index d49f400e6..31469926f 100644
--- a/web/cobrands/zurich/layout.scss
+++ b/web/cobrands/zurich/layout.scss
@@ -283,6 +283,10 @@ body.mappage.admin {
}
}
+.admin-report-edit--interact {
+ margin-top: -1.25em; // To counteract the label's default top margin
+}
+
.admin {
.content {
margin: 2em 0 1em;
@@ -300,10 +304,6 @@ body.mappage.admin {
font-weight: bold;
padding-right: 0.333em;
}
- :first-child > label {
- // avoid empty space above first label in a container
- margin-top: 0;
- }
textarea {
min-height: 0;
}
diff --git a/web/js/map-OpenLayers.js b/web/js/map-OpenLayers.js
index 5ebb9a18e..0a1c947a0 100644
--- a/web/js/map-OpenLayers.js
+++ b/web/js/map-OpenLayers.js
@@ -59,12 +59,7 @@ $.extend(fixmystreet.utils, {
document.getElementById('fixmystreet.latitude').value = lat;
document.getElementById('fixmystreet.longitude').value = lon;
- // This tight coupling isn't ideal. A better solution would be for the
- // asset code to register an event handler somewhere, but the correct
- // place isn't apparent.
- if (fixmystreet.assets) {
- fixmystreet.assets.select_usrn(lonlat);
- }
+ $(fixmystreet).trigger('maps:update_pin', [ lonlat ]);
return {
'url': { 'lon': lon, 'lat': lat },