aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cypress/cypress/integration/buckinghamshire.js14
-rw-r--r--CHANGELOG.md4
-rwxr-xr-xbin/createsuperuser2
-rwxr-xr-xbin/fixmystreet.com/buckinghamshire-flytipping2
-rwxr-xr-xbin/update-schema1
-rw-r--r--db/downgrade_0072---0071.sql21
-rw-r--r--db/schema.sql2
-rw-r--r--db/schema_0072-add-staff-contact-state.sql23
-rw-r--r--docs/Gemfile.lock336
-rw-r--r--perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm99
-rw-r--r--perllib/FixMyStreet/App/Controller/Location.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Offline.pm106
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm6
-rw-r--r--perllib/FixMyStreet/App/Controller/Root.pm2
-rw-r--r--perllib/FixMyStreet/App/Form/ManifestTheme.pm68
-rw-r--r--perllib/FixMyStreet/Cobrand/Bexley.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm1
-rw-r--r--perllib/FixMyStreet/Cobrand/FixMyStreet.pm47
-rw-r--r--perllib/FixMyStreet/Cobrand/IsleOfWight.pm16
-rw-r--r--perllib/FixMyStreet/Cobrand/TfL.pm2
-rw-r--r--perllib/FixMyStreet/DB/Result/AdminLog.pm6
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm1
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Contact.pm27
-rw-r--r--perllib/FixMyStreet/Geocode/Bexley.pm2
-rw-r--r--perllib/FixMyStreet/Geocode/Bing.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/Google.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/OSM.pm1
-rw-r--r--perllib/FixMyStreet/Geocode/Zurich.pm1
-rw-r--r--perllib/FixMyStreet/PhotoStorage.pm6
-rw-r--r--perllib/FixMyStreet/Script/CreateSuperuser.pm20
-rw-r--r--perllib/Memcached.pm4
-rw-r--r--t/app/controller/admin/manifesttheme.t340
-rw-r--r--t/app/controller/admin/users.t11
-rw-r--r--t/app/controller/around.t3
-rw-r--r--t/app/controller/offline.t19
-rw-r--r--t/app/controller/report_new.t203
-rw-r--r--t/app/controller/report_new_staff.t257
-rw-r--r--t/script/createsuperuser.t23
-rw-r--r--templates/web/base/admin/bodies/contact-form.html5
-rw-r--r--templates/web/base/admin/manifesttheme/form.html71
-rw-r--r--templates/web/base/admin/manifesttheme/index.html35
-rw-r--r--templates/web/base/common_header_tags.html6
-rw-r--r--templates/web/base/reports/_list-filters.html2
-rw-r--r--templates/web/buckinghamshire/report/form/user_name.html13
-rw-r--r--templates/web/fixmystreet.com/header_extra.html1
-rw-r--r--web/cobrands/fixmystreet/admin.js7
-rw-r--r--web/cobrands/sass/_admin.scss5
48 files changed, 1387 insertions, 460 deletions
diff --git a/.cypress/cypress/integration/buckinghamshire.js b/.cypress/cypress/integration/buckinghamshire.js
index d5b14100a..6e87da624 100644
--- a/.cypress/cypress/integration/buckinghamshire.js
+++ b/.cypress/cypress/integration/buckinghamshire.js
@@ -5,6 +5,7 @@ describe('flytipping', function() {
cy.fixture('roads.xml');
cy.route('**mapserver/bucks*Whole_Street*', 'fixture:roads.xml').as('roads-layer');
cy.route('/report/new/ajax*').as('report-ajax');
+ cy.route('/around/nearby*').as('around-ajax');
cy.visit('http://buckinghamshire.localhost:3001/');
cy.contains('Buckinghamshire');
cy.get('[name=pc]').type('SL9 0NX');
@@ -51,4 +52,17 @@ describe('flytipping', function() {
cy.get('[name=site_code]').should('have.value', '7300268');
});
+ it('uses the label "Full name" for the name field', function() {
+ cy.get('#map_box').click(290, 307);
+ cy.wait('@report-ajax');
+ cy.get('select:eq(4)').select('Flytipping');
+ cy.wait('@around-ajax');
+ cy.get('.js-hide-duplicate-suggestions:first').should('be.visible').click();
+
+ cy.get('[name=title]').type('Title');
+ cy.get('[name=detail]').type('Detail');
+ cy.get('.js-new-report-user-show').click();
+ cy.get('label[for=form_name]').should('contain', 'Full name');
+ });
+
});
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e7c47cb2..8d7a5714b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,8 @@
- Allow report as another user with only name.
- Allow staff users to sign other people up for alerts.
- Group categories on body page. #2850
+ - Add admin UI for managing web manifest themes. #2792
+ - Add a new "staff" contact state.
- New features:
- Categories can be listed under more than one group #2475
- OpenID Connect login support. #2523
@@ -70,6 +72,7 @@
- Inconsistent display of mark private checkbox for staff users
- Clear user categories when staff access is removed. #2815
- Only trigger one change event on initial popstate.
+ - Fix error when hiding a user's updates with no confirmed updates. #2898
- Development improvements:
- Upgrade the underlying framework and a number of other packages. #2473
- Add feature cobrand helper function.
@@ -149,6 +152,7 @@
- Make front page cache time configurable.
- Better working of /fakemapit/ under https.
- Improve Open311 error output on failing GET requests.
+ - Optionally log failed geocoder searches.
- Backwards incompatible changes:
- If you wish the default for the showname checkbox to be checked,
add `sub default_show_name { 1 }` to your cobrand file.
diff --git a/bin/createsuperuser b/bin/createsuperuser
index 98b36aa36..7a4946228 100755
--- a/bin/createsuperuser
+++ b/bin/createsuperuser
@@ -30,4 +30,4 @@ BEGIN {
use FixMyStreet;
use FixMyStreet::Script::CreateSuperuser;
-FixMyStreet::Script::CreateSuperuser::createsuperuser();
+exit FixMyStreet::Script::CreateSuperuser::createsuperuser(@ARGV);
diff --git a/bin/fixmystreet.com/buckinghamshire-flytipping b/bin/fixmystreet.com/buckinghamshire-flytipping
index 27548be88..70a37ea34 100755
--- a/bin/fixmystreet.com/buckinghamshire-flytipping
+++ b/bin/fixmystreet.com/buckinghamshire-flytipping
@@ -34,7 +34,7 @@ my $body = FixMyStreet::DB->resultset("Body")->for_areas(BUCKS_AREA_ID)->first;
die "Could not find Bucks body" unless $body;
my @districts = FixMyStreet::DB->resultset("Body")->for_areas(DISTRICT_IDS)->all;
-my @district_ids = map { $_->id } @districts;
+my @district_ids = map { $_->id } grep { $_->name ne 'TfL' } @districts;
die "Did not find all districts" unless @district_ids == 4;
find_problems(TIME_OPEN_ALERT, TIME_OPEN, 'Auto-closure', 1);
diff --git a/bin/update-schema b/bin/update-schema
index 7941bc542..8f31085c9 100755
--- a/bin/update-schema
+++ b/bin/update-schema
@@ -212,6 +212,7 @@ 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 '0072' if constraint_contains('contacts_state_check', 'staff');
return '0071' if table_exists('manifest_theme');
return '0070' if column_like('alert_type', "ref='new_problems'", 'head_title', '{{SITE_NAME}}');
return '0069' if constraint_contains('admin_log_object_type_check', 'template');
diff --git a/db/downgrade_0072---0071.sql b/db/downgrade_0072---0071.sql
new file mode 100644
index 000000000..aa649f63d
--- /dev/null
+++ b/db/downgrade_0072---0071.sql
@@ -0,0 +1,21 @@
+BEGIN;
+
+ALTER TABLE contacts DROP CONSTRAINT contacts_state_check;
+
+ALTER TABLE contacts ADD CONSTRAINT contacts_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'deleted'
+);
+
+ALTER TABLE contacts_history DROP CONSTRAINT contacts_history_state_check;
+
+ALTER TABLE contacts_history ADD CONSTRAINT contacts_history_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'deleted'
+);
+
+COMMIT;
diff --git a/db/schema.sql b/db/schema.sql
index 0e38ad862..ed21aded6 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -100,6 +100,7 @@ create table contacts (
state = 'unconfirmed'
or state = 'confirmed'
or state = 'inactive'
+ or state = 'staff'
or state = 'deleted'
),
@@ -137,6 +138,7 @@ create table contacts_history (
state = 'unconfirmed'
or state = 'confirmed'
or state = 'inactive'
+ or state = 'staff'
or state = 'deleted'
),
diff --git a/db/schema_0072-add-staff-contact-state.sql b/db/schema_0072-add-staff-contact-state.sql
new file mode 100644
index 000000000..efaac2d20
--- /dev/null
+++ b/db/schema_0072-add-staff-contact-state.sql
@@ -0,0 +1,23 @@
+BEGIN;
+
+ALTER TABLE contacts DROP CONSTRAINT contacts_state_check;
+
+ALTER TABLE contacts ADD CONSTRAINT contacts_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'staff'
+ or state = 'deleted'
+);
+
+ALTER TABLE contacts_history DROP CONSTRAINT contacts_history_state_check;
+
+ALTER TABLE contacts_history ADD CONSTRAINT contacts_history_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'inactive'
+ or state = 'staff'
+ or state = 'deleted'
+);
+
+COMMIT;
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index c51eb3c2e..ed1962744 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -1,205 +1,245 @@
GEM
remote: https://rubygems.org/
specs:
- activesupport (4.2.8)
- i18n (~> 0.7)
+ activesupport (6.0.2.1)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (>= 0.7, < 2)
minitest (~> 5.1)
- thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
- addressable (2.5.1)
- public_suffix (~> 2.0, >= 2.0.2)
+ zeitwerk (~> 2.2)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.12.2)
+ coffee-script-source (1.11.1)
colorator (1.1.0)
+ commonmarker (0.17.13)
+ ruby-enum (~> 0.5)
+ concurrent-ruby (1.1.6)
+ dnsruby (1.61.3)
+ addressable (~> 2.5)
em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
- ethon (0.10.1)
+ ethon (0.12.0)
ffi (>= 1.3.0)
- eventmachine (1.2.5)
+ eventmachine (1.2.7)
execjs (2.7.0)
- faraday (0.12.1)
+ faraday (1.0.0)
multipart-post (>= 1.2, < 3)
- ffi (1.9.18)
+ ffi (1.12.2)
forwardable-extended (2.6.0)
- gemoji (3.0.0)
- github-pages (145)
- activesupport (= 4.2.8)
- github-pages-health-check (= 1.3.4)
- jekyll (= 3.4.5)
- jekyll-avatar (= 0.4.2)
- jekyll-coffeescript (= 1.0.1)
+ gemoji (3.0.1)
+ github-pages (204)
+ github-pages-health-check (= 1.16.1)
+ jekyll (= 3.8.5)
+ jekyll-avatar (= 0.7.0)
+ jekyll-coffeescript (= 1.1.1)
+ jekyll-commonmark-ghpages (= 0.1.6)
jekyll-default-layout (= 0.1.4)
- jekyll-feed (= 0.9.2)
- jekyll-gist (= 1.4.0)
- jekyll-github-metadata (= 2.5.1)
- jekyll-mentions (= 1.2.0)
- jekyll-optional-front-matter (= 0.2.0)
+ jekyll-feed (= 0.13.0)
+ jekyll-gist (= 1.5.0)
+ jekyll-github-metadata (= 2.13.0)
+ jekyll-mentions (= 1.5.1)
+ jekyll-optional-front-matter (= 0.3.2)
jekyll-paginate (= 1.1.0)
- jekyll-readme-index (= 0.1.0)
- jekyll-redirect-from (= 0.12.1)
- jekyll-relative-links (= 0.4.1)
- jekyll-sass-converter (= 1.5.0)
- jekyll-seo-tag (= 2.2.3)
- jekyll-sitemap (= 1.0.0)
- jekyll-swiss (= 0.4.0)
- jekyll-theme-architect (= 0.0.4)
- jekyll-theme-cayman (= 0.0.4)
- jekyll-theme-dinky (= 0.0.4)
- jekyll-theme-hacker (= 0.0.4)
- jekyll-theme-leap-day (= 0.0.4)
- jekyll-theme-merlot (= 0.0.4)
- jekyll-theme-midnight (= 0.0.4)
- jekyll-theme-minimal (= 0.0.4)
- jekyll-theme-modernist (= 0.0.4)
- jekyll-theme-primer (= 0.3.1)
- jekyll-theme-slate (= 0.0.4)
- jekyll-theme-tactile (= 0.0.4)
- jekyll-theme-time-machine (= 0.0.4)
- jekyll-titles-from-headings (= 0.2.0)
- jemoji (= 0.8.0)
- kramdown (= 1.13.2)
- liquid (= 3.0.6)
- listen (= 3.0.6)
+ jekyll-readme-index (= 0.3.0)
+ jekyll-redirect-from (= 0.15.0)
+ jekyll-relative-links (= 0.6.1)
+ jekyll-remote-theme (= 0.4.1)
+ jekyll-sass-converter (= 1.5.2)
+ jekyll-seo-tag (= 2.6.1)
+ jekyll-sitemap (= 1.4.0)
+ jekyll-swiss (= 1.0.0)
+ jekyll-theme-architect (= 0.1.1)
+ jekyll-theme-cayman (= 0.1.1)
+ jekyll-theme-dinky (= 0.1.1)
+ jekyll-theme-hacker (= 0.1.1)
+ jekyll-theme-leap-day (= 0.1.1)
+ jekyll-theme-merlot (= 0.1.1)
+ jekyll-theme-midnight (= 0.1.1)
+ jekyll-theme-minimal (= 0.1.1)
+ jekyll-theme-modernist (= 0.1.1)
+ jekyll-theme-primer (= 0.5.4)
+ jekyll-theme-slate (= 0.1.1)
+ jekyll-theme-tactile (= 0.1.1)
+ jekyll-theme-time-machine (= 0.1.1)
+ jekyll-titles-from-headings (= 0.5.3)
+ jemoji (= 0.11.1)
+ kramdown (= 1.17.0)
+ liquid (= 4.0.3)
mercenary (~> 0.3)
- minima (= 2.1.1)
- rouge (= 1.11.1)
+ minima (= 2.5.1)
+ nokogiri (>= 1.10.4, < 2.0)
+ rouge (= 3.13.0)
terminal-table (~> 1.4)
- github-pages-health-check (1.3.4)
+ github-pages-health-check (1.16.1)
addressable (~> 2.3)
- net-dns (~> 0.8)
+ dnsruby (~> 1.60)
octokit (~> 4.0)
- public_suffix (~> 2.0)
- typhoeus (~> 0.7)
- html-pipeline (2.6.0)
+ public_suffix (~> 3.0)
+ typhoeus (~> 1.3)
+ html-pipeline (2.12.3)
activesupport (>= 2)
nokogiri (>= 1.4)
http_parser.rb (0.6.0)
- i18n (0.8.6)
- jekyll (3.4.5)
+ i18n (0.9.5)
+ concurrent-ruby (~> 1.0)
+ jekyll (3.8.5)
addressable (~> 2.4)
colorator (~> 1.0)
+ em-websocket (~> 0.5)
+ i18n (~> 0.7)
jekyll-sass-converter (~> 1.0)
- jekyll-watch (~> 1.1)
- kramdown (~> 1.3)
- liquid (~> 3.0)
+ jekyll-watch (~> 2.0)
+ kramdown (~> 1.14)
+ liquid (~> 4.0)
mercenary (~> 0.3.3)
pathutil (~> 0.9)
- rouge (~> 1.7)
+ rouge (>= 1.7, < 4)
safe_yaml (~> 1.0)
- jekyll-avatar (0.4.2)
- jekyll (~> 3.0)
- jekyll-coffeescript (1.0.1)
+ jekyll-avatar (0.7.0)
+ jekyll (>= 3.0, < 5.0)
+ jekyll-coffeescript (1.1.1)
coffee-script (~> 2.2)
+ coffee-script-source (~> 1.11.1)
+ jekyll-commonmark (1.3.1)
+ commonmarker (~> 0.14)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-commonmark-ghpages (0.1.6)
+ commonmarker (~> 0.17.6)
+ jekyll-commonmark (~> 1.2)
+ rouge (>= 2.0, < 4.0)
jekyll-default-layout (0.1.4)
jekyll (~> 3.0)
- jekyll-feed (0.9.2)
- jekyll (~> 3.3)
- jekyll-gist (1.4.0)
+ jekyll-feed (0.13.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-gist (1.5.0)
octokit (~> 4.2)
- jekyll-github-metadata (2.5.1)
- jekyll (~> 3.1)
+ jekyll-github-metadata (2.13.0)
+ jekyll (>= 3.4, < 5.0)
octokit (~> 4.0, != 4.4.0)
jekyll-livereload (0.2.2)
em-websocket (~> 0.5)
jekyll (~> 3.0)
- jekyll-mentions (1.2.0)
- activesupport (~> 4.0)
+ jekyll-mentions (1.5.1)
html-pipeline (~> 2.3)
- jekyll (~> 3.0)
- jekyll-optional-front-matter (0.2.0)
- jekyll (~> 3.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-optional-front-matter (0.3.2)
+ jekyll (>= 3.0, < 5.0)
jekyll-paginate (1.1.0)
- jekyll-readme-index (0.1.0)
- jekyll (~> 3.0)
- jekyll-redirect-from (0.12.1)
- jekyll (~> 3.3)
- jekyll-relative-links (0.4.1)
- jekyll (~> 3.3)
- jekyll-sass-converter (1.5.0)
+ jekyll-readme-index (0.3.0)
+ jekyll (>= 3.0, < 5.0)
+ jekyll-redirect-from (0.15.0)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-relative-links (0.6.1)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-remote-theme (0.4.1)
+ addressable (~> 2.0)
+ jekyll (>= 3.5, < 5.0)
+ rubyzip (>= 1.3.0)
+ jekyll-sass-converter (1.5.2)
sass (~> 3.4)
- jekyll-seo-tag (2.2.3)
- jekyll (~> 3.3)
- jekyll-sitemap (1.0.0)
- jekyll (~> 3.3)
- jekyll-swiss (0.4.0)
- jekyll-theme-architect (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-cayman (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-dinky (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-hacker (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-leap-day (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-merlot (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-midnight (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-minimal (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-modernist (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-primer (0.3.1)
- jekyll (~> 3.3)
- jekyll-theme-slate (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-tactile (0.0.4)
- jekyll (~> 3.3)
- jekyll-theme-time-machine (0.0.4)
- jekyll (~> 3.3)
- jekyll-titles-from-headings (0.2.0)
- jekyll (~> 3.3)
- jekyll-watch (1.5.0)
- listen (~> 3.0, < 3.1)
- jemoji (0.8.0)
- activesupport (~> 4.0)
+ jekyll-seo-tag (2.6.1)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-sitemap (1.4.0)
+ jekyll (>= 3.7, < 5.0)
+ jekyll-swiss (1.0.0)
+ jekyll-theme-architect (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-cayman (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-dinky (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-hacker (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-leap-day (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-merlot (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-midnight (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-minimal (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-modernist (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-primer (0.5.4)
+ jekyll (> 3.5, < 5.0)
+ jekyll-github-metadata (~> 2.9)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-slate (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-tactile (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-theme-time-machine (0.1.1)
+ jekyll (~> 3.5)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-titles-from-headings (0.5.3)
+ jekyll (>= 3.3, < 5.0)
+ jekyll-watch (2.2.1)
+ listen (~> 3.0)
+ jemoji (0.11.1)
gemoji (~> 3.0)
html-pipeline (~> 2.2)
- jekyll (>= 3.0)
- kramdown (1.13.2)
- liquid (3.0.6)
- listen (3.0.6)
- rb-fsevent (>= 0.9.3)
- rb-inotify (>= 0.9.7)
+ jekyll (>= 3.0, < 5.0)
+ kramdown (1.17.0)
+ liquid (4.0.3)
+ listen (3.2.1)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6)
- mini_portile2 (2.2.0)
- minima (2.1.1)
- jekyll (~> 3.3)
- minitest (5.10.2)
- multipart-post (2.0.0)
- net-dns (0.8.0)
- nokogiri (1.8.0)
- mini_portile2 (~> 2.2.0)
- octokit (4.7.0)
+ mini_portile2 (2.4.0)
+ minima (2.5.1)
+ jekyll (>= 3.5, < 5.0)
+ jekyll-feed (~> 0.9)
+ jekyll-seo-tag (~> 2.1)
+ minitest (5.14.0)
+ multipart-post (2.1.1)
+ nokogiri (1.10.8)
+ mini_portile2 (~> 2.4.0)
+ octokit (4.16.0)
+ faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
- pathutil (0.14.0)
+ pathutil (0.16.2)
forwardable-extended (~> 2.6)
- public_suffix (2.0.5)
- rb-fsevent (0.10.2)
- rb-inotify (0.9.10)
- ffi (>= 0.5.0, < 2)
- rouge (1.11.1)
- safe_yaml (1.0.4)
- sass (3.5.1)
+ public_suffix (3.1.1)
+ rb-fsevent (0.10.3)
+ rb-inotify (0.10.1)
+ ffi (~> 1.0)
+ rouge (3.13.0)
+ ruby-enum (0.7.2)
+ i18n
+ rubyzip (2.2.0)
+ safe_yaml (1.0.5)
+ sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
- sawyer (0.8.1)
- addressable (>= 2.3.5, < 2.6)
- faraday (~> 0.8, < 1.0)
+ sawyer (0.8.2)
+ addressable (>= 2.3.5)
+ faraday (> 0.8, < 2.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
- typhoeus (0.8.0)
- ethon (>= 0.8.0)
- tzinfo (1.2.3)
+ typhoeus (1.3.1)
+ ethon (>= 0.9.0)
+ tzinfo (1.2.6)
thread_safe (~> 0.1)
- unicode-display_width (1.3.0)
+ unicode-display_width (1.6.1)
+ zeitwerk (2.2.2)
PLATFORMS
ruby
@@ -209,4 +249,4 @@ DEPENDENCIES
jekyll-livereload
BUNDLED WITH
- 1.13.7
+ 1.16.1
diff --git a/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm b/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm
index 3be6e4594..82e6e591e 100644
--- a/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm
+++ b/perllib/DBIx/Class/FixMyStreet/EncodedColumn.pm
@@ -10,7 +10,7 @@ sub set_column {
my $self = shift;
if ($_[0] eq 'password') {
my $cobrand = $self->result_source->schema->cobrand;
- if ($cobrand->moniker eq 'tfl') {
+ if ($cobrand && $cobrand->moniker eq 'tfl') {
if (defined $_[1]) {
if (defined $_[2]) {
$self->set_extra_metadata(tfl_password => $_[1]);
diff --git a/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
new file mode 100644
index 000000000..9e3bdc33e
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Admin/ManifestTheme.pm
@@ -0,0 +1,99 @@
+package FixMyStreet::App::Controller::Admin::ManifestTheme;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+use FixMyStreet::App::Form::ManifestTheme;
+
+sub auto :Private {
+ my ($self, $c) = @_;
+
+ if ( $c->cobrand->moniker eq 'fixmystreet' ) {
+ $c->stash(rs => $c->model('DB::ManifestTheme')->search_rs({}), show_all => 1);
+ } else {
+ $c->stash(rs => $c->model('DB::ManifestTheme')->search_rs({ cobrand => $c->cobrand->moniker }));
+ }
+}
+
+sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+
+ unless ( $c->stash->{show_all} ) {
+ if ( $c->stash->{rs}->count ) {
+ $c->res->redirect($c->uri_for($self->action_for('edit'), [ $c->stash->{rs}->first->cobrand ]));
+ } else {
+ $c->res->redirect($c->uri_for($self->action_for('create')));
+ }
+ $c->detach;
+ }
+}
+
+sub item :PathPart('admin/manifesttheme') :Chained :CaptureArgs(1) {
+ my ($self, $c, $cobrand) = @_;
+
+ my $obj = $c->stash->{rs}->find({ cobrand => $cobrand })
+ or $c->detach('/page_error_404_not_found', []);
+ $c->stash(obj => $obj);
+}
+
+sub edit :PathPart('') :Chained('item') :Args(0) {
+ my ($self, $c) = @_;
+
+ my $form = $self->form($c, $c->stash->{obj});
+
+ # We need to do this after form processing, in case a form POST has deleted
+ # an icon.
+ $c->stash->{editing_manifest_theme} = $c->forward('/offline/_find_manifest_theme', [ $c->stash->{obj}->cobrand, 1 ]);
+
+ return $form;
+}
+
+
+sub create :Local :Args(0) {
+ my ($self, $c) = @_;
+
+ unless ( $c->stash->{show_all} || $c->stash->{rs}->count == 0) {
+ $c->res->redirect($c->uri_for($self->action_for('edit'), [ $c->stash->{rs}->first->cobrand ]));
+ $c->detach;
+ }
+
+ my $theme = $c->stash->{rs}->new_result({});
+ return $self->form($c, $theme);
+}
+
+sub form {
+ my ($self, $c, $theme) = @_;
+
+ if ($c->get_param('delete_theme')) {
+ $c->forward('_delete_all_manifest_icons');
+ $c->forward('/offline/_clear_manifest_theme_cache', [ $theme->cobrand ]);
+ $theme->delete;
+ $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', 'delete' ]);
+ $c->response->redirect($c->uri_for($self->action_for('index')));
+ $c->detach;
+ }
+
+ my $action = $theme->in_storage ? 'edit' : 'add';
+ my $form = FixMyStreet::App::Form::ManifestTheme->new( cobrand => $c->cobrand->moniker );
+ $c->stash(template => 'admin/manifesttheme/form.html', form => $form);
+ my $params = $c->req->params;
+ $params->{icon} = $c->req->upload('icon') if $params->{icon};
+ $form->process(item => $theme, params => $params);
+ return unless $form->validated;
+
+ $c->forward('/admin/log_edit', [ $theme->id, 'manifesttheme', $action ]);
+ $c->forward('/offline/_clear_manifest_theme_cache', [ $theme->cobrand ]);
+ $c->response->redirect($c->uri_for($self->action_for('index')));
+}
+
+sub _delete_all_manifest_icons :Private {
+ my ($self, $c) = @_;
+
+ my $theme = $c->forward('/offline/_find_manifest_theme', [ $c->stash->{obj}->cobrand, 1 ]);
+ foreach my $icon ( @{ $theme->{icons} } ) {
+ unlink FixMyStreet->path_to('web', $icon->{src});
+ }
+}
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm
index 8d5b0b147..81c2c33fc 100644
--- a/perllib/FixMyStreet/App/Controller/Location.pm
+++ b/perllib/FixMyStreet/App/Controller/Location.pm
@@ -6,6 +6,7 @@ BEGIN {extends 'Catalyst::Controller'; }
use Encode;
use FixMyStreet::Geocode;
+use Try::Tiny;
use Utils;
=head1 NAME
@@ -107,6 +108,25 @@ sub determine_location_from_pc : Private {
# pass errors back to the template
$c->stash->{location_error_pc_lookup} = 1;
$c->stash->{location_error} = $error;
+
+ # Log failure in a log db
+ try {
+ my $dbfile = FixMyStreet->path_to('../data/analytics.sqlite');
+ my $db = DBI->connect("dbi:SQLite:dbname=$dbfile", undef, undef) or die "$DBI::errstr\n";
+ my $sth = $db->prepare("INSERT INTO location_searches_with_no_results
+ (datetime, cobrand, geocoder, url, user_input)
+ VALUES (?, ?, ?, ?, ?)") or die $db->errstr . "\n";
+ my $rv = $sth->execute(
+ POSIX::strftime("%Y-%m-%d %H:%M:%S", localtime(time())),
+ $c->cobrand->moniker,
+ $c->cobrand->get_geocoder(),
+ $c->stash->{geocoder_url},
+ $pc,
+ );
+ } catch {
+ $c->log->debug("Unable to log to analytics.sqlite: $_");
+ };
+
return;
}
diff --git a/perllib/FixMyStreet/App/Controller/Offline.pm b/perllib/FixMyStreet/App/Controller/Offline.pm
index 57cbe201c..adb3de14d 100644
--- a/perllib/FixMyStreet/App/Controller/Offline.pm
+++ b/perllib/FixMyStreet/App/Controller/Offline.pm
@@ -33,42 +33,12 @@ sub manifest: Path("/.well-known/manifest.webmanifest") {
my ($self, $c) = @_;
$c->res->content_type('application/manifest+json');
- my $theme = $c->model('DB::ManifestTheme')->find({ cobrand => $c->cobrand->moniker });
- unless ( $theme ) {
- $theme = $c->model('DB::ManifestTheme')->new({
- name => $c->stash->{site_name},
- short_name => $c->stash->{site_name},
- background_colour => '#ffffff',
- theme_colour => '#ffd000',
- });
- }
-
- my @icons;
- my $uri = '/theme/' . $c->cobrand->moniker;
- my $theme_path = path(FixMyStreet->path_to('web' . $uri));
- $theme_path->visit(
- sub {
- my ($x, $y, $typ) = Image::Size::imgsize($_->stringify);
- push @icons, {
- src => join('/', $uri, $_->basename),
- sizes => join('x', $x, $y),
- type => $typ eq 'PNG' ? 'image/png' : $typ eq 'GIF' ? 'image/gif' : $typ eq 'JPG' ? 'image/jpeg' : '',
- };
- }
- );
-
- unless (@icons) {
- push @icons,
- { src => "/cobrands/fixmystreet/images/192.png", sizes => "192x192", type => "image/png" },
- { src => "/cobrands/fixmystreet/images/512.png", sizes => "512x512", type => "image/png" };
- }
-
my $data = {
- name => $theme->name,
- short_name => $theme->short_name,
- background_color => $theme->background_colour,
- theme_color => $theme->theme_colour,
- icons => \@icons,
+ name => $c->stash->{manifest_theme}->{name},
+ short_name => $c->stash->{manifest_theme}->{short_name},
+ background_color => $c->stash->{manifest_theme}->{background_colour},
+ theme_color => $c->stash->{manifest_theme}->{theme_colour},
+ icons => $c->stash->{manifest_theme}->{icons},
lang => $c->stash->{lang_code},
display => "minimal-ui",
start_url => "/?pwa",
@@ -82,6 +52,72 @@ sub manifest: Path("/.well-known/manifest.webmanifest") {
$c->res->body($json);
}
+sub _stash_manifest_theme : Private {
+ my ($self, $c, $cobrand) = @_;
+
+ $c->stash->{manifest_theme} = $c->forward('_find_manifest_theme', [ $cobrand ]);
+}
+
+sub _find_manifest_theme : Private {
+ my ($self, $c, $cobrand, $ignore_cache_and_defaults) = @_;
+
+ my $key = "manifest_theme:$cobrand";
+ # ignore_cache_and_defaults is only used in the admin, so no harm bypassing cache
+ my $manifest_theme = $ignore_cache_and_defaults ? undef : Memcached::get($key);
+
+ unless ( $manifest_theme ) {
+ my $theme = $c->model('DB::ManifestTheme')->find({ cobrand => $cobrand });
+ unless ( $theme ) {
+ $theme = $c->model('DB::ManifestTheme')->new({
+ name => $c->stash->{site_name},
+ short_name => $c->stash->{site_name},
+ background_colour => '#ffffff',
+ theme_colour => '#ffd000',
+ });
+ }
+
+ my @icons;
+ my $uri = '/theme/' . $cobrand;
+ my $theme_path = path(FixMyStreet->path_to('web' . $uri));
+ $theme_path->visit(
+ sub {
+ my ($x, $y, $typ) = Image::Size::imgsize($_->stringify);
+ push @icons, {
+ src => join('/', $uri, $_->basename),
+ sizes => join('x', $x, $y),
+ type => $typ eq 'PNG' ? 'image/png' : $typ eq 'GIF' ? 'image/gif' : $typ eq 'JPG' ? 'image/jpeg' : '',
+ };
+ }
+ );
+
+ unless (@icons || $ignore_cache_and_defaults) {
+ push @icons,
+ { src => "/cobrands/fixmystreet/images/192.png", sizes => "192x192", type => "image/png" },
+ { src => "/cobrands/fixmystreet/images/512.png", sizes => "512x512", type => "image/png" };
+ }
+
+ $manifest_theme = {
+ icons => \@icons,
+ background_colour => $theme->background_colour,
+ theme_colour => $theme->theme_colour,
+ name => $theme->name,
+ short_name => $theme->short_name,
+ };
+
+ unless ($ignore_cache_and_defaults) {
+ Memcached::set($key, $manifest_theme);
+ }
+ }
+
+ return $manifest_theme;
+}
+
+sub _clear_manifest_theme_cache : Private {
+ my ($self, $c, $cobrand ) = @_;
+
+ Memcached::delete("manifest_theme:$cobrand");
+}
+
__PACKAGE__->meta->make_immutable;
1;
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index 53d686e55..faa02f9fe 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -690,11 +690,7 @@ sub setup_categories_and_bodies : Private {
$c->cobrand->call_hook(munge_report_new_bodies => \%bodies);
- my $contacts #
- = $c #
- ->model('DB::Contact') #
- ->active
- ->search( { 'me.body_id' => [ keys %bodies ] }, { prefetch => 'body' } );
+ my $contacts = $c->model('DB::Contact')->for_new_reports($c, \%bodies);
my @contacts = $c->cobrand->categories_restriction($contacts)->all_sorted;
$c->cobrand->call_hook(munge_report_new_contacts => \@contacts);
diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm
index caaa260ff..71dcf8e27 100644
--- a/perllib/FixMyStreet/App/Controller/Root.pm
+++ b/perllib/FixMyStreet/App/Controller/Root.pm
@@ -42,6 +42,8 @@ sub auto : Private {
$c->forward('check_password_expiry');
$c->detach('/auth/redirect') if $c->cobrand->call_hook('check_login_disallowed');
+ $c->forward('/offline/_stash_manifest_theme', [ $c->cobrand->moniker ]);
+
return 1;
}
diff --git a/perllib/FixMyStreet/App/Form/ManifestTheme.pm b/perllib/FixMyStreet/App/Form/ManifestTheme.pm
new file mode 100644
index 000000000..aa2d467d6
--- /dev/null
+++ b/perllib/FixMyStreet/App/Form/ManifestTheme.pm
@@ -0,0 +1,68 @@
+package FixMyStreet::App::Form::ManifestTheme;
+
+use Path::Tiny;
+use File::Copy;
+use Digest::SHA qw(sha1_hex);
+use File::Basename;
+use HTML::FormHandler::Moose;
+use FixMyStreet::App::Form::I18N;
+use List::MoreUtils qw(uniq);
+extends 'HTML::FormHandler::Model::DBIC';
+use namespace::autoclean;
+
+has 'cobrand' => ( isa => 'Str', is => 'ro' );
+
+has '+widget_name_space' => ( default => sub { ['FixMyStreet::App::Form::Widget'] } );
+has '+widget_tags' => ( default => sub { { wrapper_tag => 'p' } } );
+has '+item_class' => ( default => 'ManifestTheme' );
+has_field 'cobrand' => ( type => 'Select', empty_select => 'Select a cobrand', required => 1 );
+has_field 'name' => ( required => 1 );
+has_field 'short_name' => ( required => 1 );
+has_field 'background_colour' => ( required => 0 );
+has_field 'theme_colour' => ( required => 0 );
+has_field 'icon' => ( required => 0, type => 'Upload', label => "Add icon" );
+has_field 'delete_icon' => ( type => 'Multiple' );
+
+sub _build_language_handle { FixMyStreet::App::Form::I18N->new }
+
+sub options_cobrand {
+ my @cobrands = uniq sort map { $_->{moniker} } FixMyStreet::Cobrand->available_cobrand_classes;
+ return map { $_ => $_ } @cobrands;
+}
+
+sub validate {
+ my $self = shift;
+
+ my $value = $self->value;
+ my $cobrand = $value->{cobrand} || $self->cobrand;
+ my $upload = $value->{icon};
+
+ if ( $upload ) {
+ if( $upload->type !~ /^image/ ) {
+ $self->field('icon')->add_error( _("File type not recognised. Please upload an image.") );
+ return;
+ }
+
+ my $uri = '/theme/' . $cobrand;
+ my $theme_path = path(FixMyStreet->path_to('web' . $uri));
+ $theme_path->mkpath;
+ FixMyStreet::PhotoStorage::base64_decode_upload(undef, $upload);
+ my ($p, $n, $ext) = fileparse($upload->filename, qr/\.[^.]*/);
+ my $key = sha1_hex($upload->slurp) . $ext;
+ my $out = path($theme_path, $key);
+ unless (copy($upload->tempname, $out)) {
+ $self->field('icon')->add_error( _("Sorry, we couldn't save your file(s), please try again.") );
+ return;
+ }
+ }
+
+ foreach my $delete_icon ( @{ $value->{delete_icon} } ) {
+ unlink FixMyStreet->path_to('web', $delete_icon);
+ }
+
+ return 1;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/Cobrand/Bexley.pm b/perllib/FixMyStreet/Cobrand/Bexley.pm
index 275d7dfaf..0586c18c4 100644
--- a/perllib/FixMyStreet/Cobrand/Bexley.pm
+++ b/perllib/FixMyStreet/Cobrand/Bexley.pm
@@ -212,7 +212,7 @@ sub dashboard_export_problems_add_columns {
my %groups;
if ($c->stash->{body}) {
- %groups = FixMyStreet::DB->resultset('Contact')->active->search({
+ %groups = FixMyStreet::DB->resultset('Contact')->search({
body_id => $c->stash->{body}->id,
})->group_lookup;
}
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 9851b4896..695487268 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -685,6 +685,7 @@ sub admin_pages {
$pages->{flagged} = [ _('Flagged'), 7 ];
$pages->{states} = [ _('States'), 8 ];
$pages->{config} = [ _('Configuration'), 9];
+ $pages->{manifesttheme} = [ _('Manifest Theme'), 11];
$pages->{user_import} = [ undef, undef ];
};
# And some that need special permissions
diff --git a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
index 97a0ab53a..a2dea1df4 100644
--- a/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
+++ b/perllib/FixMyStreet/Cobrand/FixMyStreet.pm
@@ -131,51 +131,8 @@ sub munge_load_and_group_problems {
return unless $where->{category} && $self->{c}->stash->{body}->name eq 'Isle of Wight Council';
- $where->{category} = $self->expand_triage_cat_list($where->{category});
-}
-
-sub expand_triage_cat_list {
- my ($self, $categories) = @_;
-
- my $b = $self->{c}->stash->{body};
-
- my $all_cats = $self->{c}->model('DB::Contact')->not_deleted->search(
- {
- body_id => $b->id,
- send_method => [{ '!=', 'Triage'}, undef]
- }
- );
-
- my %group_to_category;
- while ( my $cat = $all_cats->next ) {
- next unless $cat->get_extra_metadata('group');
- my $groups = $cat->get_extra_metadata('group');
- $groups = ref $groups eq 'ARRAY' ? $groups : [ $groups ];
- for my $group ( @$groups ) {
- $group_to_category{$group} //= [];
- push @{ $group_to_category{$group} }, $cat->category;
- }
- }
-
- my $cats = $self->{c}->model('DB::Contact')->not_deleted->search(
- {
- body_id => $b->id,
- category => $categories
- }
- );
-
- my @cat_names;
- while ( my $cat = $cats->next ) {
- if ( $cat->send_method && $cat->send_method eq 'Triage' ) {
- # include the category itself
- push @cat_names, $cat->category;
- push @cat_names, @{ $group_to_category{$cat->category} } if $group_to_category{$cat->category};
- } else {
- push @cat_names, $cat->category;
- }
- }
-
- return \@cat_names;
+ my $iow = FixMyStreet::Cobrand->get_class_for_moniker( 'isleofwight' )->new({ c => $self->{c} });
+ $where->{category} = $iow->expand_triage_cat_list($where->{category}, $self->{c}->stash->{body});
}
sub title_list {
diff --git a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
index a46b540ad..db0a20b9c 100644
--- a/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
+++ b/perllib/FixMyStreet/Cobrand/IsleOfWight.pm
@@ -142,7 +142,7 @@ sub munge_load_and_group_problems {
return unless $where->{category};
- $where->{category} = $self->expand_triage_cat_list($where->{category});
+ $where->{category} = $self->_expand_triage_cat_list($where->{category});
}
sub munge_around_filter_category_list {
@@ -151,17 +151,21 @@ sub munge_around_filter_category_list {
my $c = $self->{c};
return unless $c->stash->{filter_category};
- my $cat_names = $self->expand_triage_cat_list([ keys %{$c->stash->{filter_category}} ]);
+ my $cat_names = $self->_expand_triage_cat_list([ keys %{$c->stash->{filter_category}} ]);
$c->stash->{filter_category} = { map { $_ => 1 } @$cat_names };
}
+sub _expand_triage_cat_list {
+ my ($self, $categories) = @_;
+ my $b = $self->{c}->model('DB::Body')->for_areas( $self->council_area_id )->first;
+ return $self->expand_triage_cat_list($categories, $b);
+}
+
# this assumes that each Triage category has the same name as a group
# and uses this to generate a list of categories that a triage category
# could be triaged to
sub expand_triage_cat_list {
- my ($self, $categories) = @_;
-
- my $b = $self->{c}->model('DB::Body')->for_areas( $self->council_area_id )->first;
+ my ($self, $categories, $b) = @_;
my $all_cats = $self->{c}->model('DB::Contact')->not_deleted->search(
{
@@ -190,7 +194,7 @@ sub expand_triage_cat_list {
my @cat_names;
while ( my $cat = $cats->next ) {
- if ( $cat->send_method eq 'Triage' ) {
+ if ( $cat->send_method && $cat->send_method eq 'Triage' ) {
# include the category itself
push @cat_names, $cat->category;
push @cat_names, @{ $group_to_category{$cat->category} } if $group_to_category{$cat->category};
diff --git a/perllib/FixMyStreet/Cobrand/TfL.pm b/perllib/FixMyStreet/Cobrand/TfL.pm
index d1cfb8d42..40c2a5257 100644
--- a/perllib/FixMyStreet/Cobrand/TfL.pm
+++ b/perllib/FixMyStreet/Cobrand/TfL.pm
@@ -244,7 +244,7 @@ sub dashboard_export_problems_add_columns {
my %groups;
if ($c->stash->{body}) {
- %groups = FixMyStreet::DB->resultset('Contact')->active->search({
+ %groups = FixMyStreet::DB->resultset('Contact')->search({
body_id => $c->stash->{body}->id,
})->group_lookup;
}
diff --git a/perllib/FixMyStreet/DB/Result/AdminLog.pm b/perllib/FixMyStreet/DB/Result/AdminLog.pm
index 5564d829a..4c89138c9 100644
--- a/perllib/FixMyStreet/DB/Result/AdminLog.pm
+++ b/perllib/FixMyStreet/DB/Result/AdminLog.pm
@@ -91,6 +91,10 @@ sub link {
my $category = $self->object;
return "/admin/body/" . $category->body_id . '/' . $category->category;
}
+ if ($type eq 'manifesttheme') {
+ my $theme = $self->object;
+ return "/admin/manifesttheme/" . $theme->cobrand;
+ }
return '';
}
@@ -114,6 +118,7 @@ sub object_summary {
role => 'name',
template => 'title',
category => 'category',
+ manifesttheme => 'cobrand',
};
my $thing = $type_to_thing->{$self->object_type} || 'id';
@@ -130,6 +135,7 @@ sub object {
template => 'ResponseTemplate',
category => 'Contact',
update => 'Comment',
+ manifesttheme => 'ManifestTheme',
};
$type = $type_to_object->{$type} || ucfirst $type;
my $object = $self->result_source->schema->resultset($type)->find($id);
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index bac183271..b217bf96c 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -292,6 +292,7 @@ sub is_latest {
{ problem_id => $self->problem_id, state => 'confirmed' },
{ order_by => [ { -desc => 'confirmed' }, { -desc => 'id' } ] }
)->first;
+ return unless $latest_update;
return $latest_update->id == $self->id;
}
diff --git a/perllib/FixMyStreet/DB/ResultSet/Contact.pm b/perllib/FixMyStreet/DB/ResultSet/Contact.pm
index 1643f9931..eb502c190 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Contact.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Contact.pm
@@ -17,7 +17,7 @@ Filter down to not deleted contacts (so active or inactive).
sub not_deleted {
my $rs = shift;
- return $rs->search( { $rs->me('state') => { '!=' => 'deleted' } } );
+ return $rs->search( { $rs->me('state') => { -not_in => [ 'deleted', 'staff' ] } } );
}
sub active {
@@ -25,6 +25,31 @@ sub active {
$rs->search( { $rs->me('state') => [ 'unconfirmed', 'confirmed' ] } );
}
+sub for_new_reports {
+ my ($rs, $c, $bodies) = @_;
+ my $params = {
+ $rs->me('body_id') => [ keys %$bodies ],
+ };
+
+ if ($c->user_exists && $c->user->from_body) {
+ # Everything normal OR staff state in the user body
+ $params->{'-or'} = [
+ $rs->me('state') => [ 'unconfirmed', 'confirmed' ],
+ {
+ $rs->me('body_id') => $c->user->from_body->id,
+ $rs->me('state') => 'staff',
+ },
+ ];
+ } elsif ($c->user_exists && $c->user->is_superuser) {
+ # Everything normal OR any staff states
+ $params->{$rs->me('state')} = [ 'unconfirmed', 'confirmed', 'staff' ];
+ } else {
+ $params->{$rs->me('state')} = [ 'unconfirmed', 'confirmed' ];
+ }
+
+ $rs->search($params, { prefetch => 'body' });
+}
+
sub translated {
my $rs = shift;
my $schema = $rs->result_source->schema;
diff --git a/perllib/FixMyStreet/Geocode/Bexley.pm b/perllib/FixMyStreet/Geocode/Bexley.pm
index a70a42cd1..8a1a886bb 100644
--- a/perllib/FixMyStreet/Geocode/Bexley.pm
+++ b/perllib/FixMyStreet/Geocode/Bexley.pm
@@ -23,6 +23,8 @@ sub string {
my $js = query_layer($s);
return $osm unless $js && @{$js->{features}};
+ $c->stash->{geocoder_url} = $s;
+
my ( $error, @valid_locations, $latitude, $longitude, $address );
foreach (sort { $a->{properties}{ADDRESS} cmp $b->{properties}{ADDRESS} } @{$js->{features}}) {
my @lines = @{$_->{geometry}{coordinates}};
diff --git a/perllib/FixMyStreet/Geocode/Bing.pm b/perllib/FixMyStreet/Geocode/Bing.pm
index ee5e15f8c..1d39d911f 100644
--- a/perllib/FixMyStreet/Geocode/Bing.pm
+++ b/perllib/FixMyStreet/Geocode/Bing.pm
@@ -38,6 +38,7 @@ sub string {
$url .= '&userLocation=' . $params->{centre} if $params->{centre};
$url .= '&c=' . $params->{bing_culture} if $params->{bing_culture};
+ $c->stash->{geocoder_url} = $url;
my $js = FixMyStreet::Geocode::cache('bing', $url, 'key=' . FixMyStreet->config('BING_MAPS_API_KEY'));
if (!$js) {
return { error => _('Sorry, we could not parse that location. Please try again.') };
diff --git a/perllib/FixMyStreet/Geocode/Google.pm b/perllib/FixMyStreet/Geocode/Google.pm
index 455d9cec0..ffbad96ba 100644
--- a/perllib/FixMyStreet/Geocode/Google.pm
+++ b/perllib/FixMyStreet/Geocode/Google.pm
@@ -49,6 +49,7 @@ sub string {
$url .= '&components=' . $components if $components;
+ $c->stash->{geocoder_url} = $url;
my $args = 'key=' . FixMyStreet->config('GOOGLE_MAPS_API_KEY');
my $js = FixMyStreet::Geocode::cache('google', $url, $args, qr/"status"\s*:\s*"(OVER_QUERY_LIMIT|REQUEST_DENIED|INVALID_REQUEST|UNKNOWN_ERROR)"/);
if (!$js) {
diff --git a/perllib/FixMyStreet/Geocode/OSM.pm b/perllib/FixMyStreet/Geocode/OSM.pm
index b979e2a10..20e653cf6 100644
--- a/perllib/FixMyStreet/Geocode/OSM.pm
+++ b/perllib/FixMyStreet/Geocode/OSM.pm
@@ -47,6 +47,7 @@ sub string {
if $params->{country};
$url .= join('&', map { "$_=$query_params{$_}" } sort keys %query_params);
+ $c->stash->{geocoder_url} = $url;
my $js = FixMyStreet::Geocode::cache('osm', $url);
if (!$js) {
return { error => _('Sorry, we could not find that location.') };
diff --git a/perllib/FixMyStreet/Geocode/Zurich.pm b/perllib/FixMyStreet/Geocode/Zurich.pm
index 0b85ab7b2..b0c0b528e 100644
--- a/perllib/FixMyStreet/Geocode/Zurich.pm
+++ b/perllib/FixMyStreet/Geocode/Zurich.pm
@@ -97,6 +97,7 @@ sub string {
my $cache_dir = path(FixMyStreet->config('GEO_CACHE'), 'zurich')->absolute(FixMyStreet->path_to());
my $cache_file = $cache_dir->child(md5_hex($s));
my $result;
+ $c->stash->{geocoder_url} = $s;
if (-s $cache_file && -M $cache_file <= 7 && !FixMyStreet->config('STAGING_SITE')) {
$result = retrieve($cache_file);
} else {
diff --git a/perllib/FixMyStreet/PhotoStorage.pm b/perllib/FixMyStreet/PhotoStorage.pm
index 9b0e5c9c3..256d46361 100644
--- a/perllib/FixMyStreet/PhotoStorage.pm
+++ b/perllib/FixMyStreet/PhotoStorage.pm
@@ -58,8 +58,10 @@ sub base64_decode_upload {
print $fh $decoded;
close $fh
} else {
- $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
- $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ if ($c) {
+ $c->log->info('Couldn\'t open temp file to save base64 decoded image: ' . $!);
+ $c->stash->{photo_error} = _("Sorry, we couldn't save your file(s), please try again.");
+ }
return ();
}
}
diff --git a/perllib/FixMyStreet/Script/CreateSuperuser.pm b/perllib/FixMyStreet/Script/CreateSuperuser.pm
index 69d165abb..cbbea577a 100644
--- a/perllib/FixMyStreet/Script/CreateSuperuser.pm
+++ b/perllib/FixMyStreet/Script/CreateSuperuser.pm
@@ -7,19 +7,27 @@ use FixMyStreet;
use FixMyStreet::DB;
sub createsuperuser {
- die "Specify a single email address and optionally password to create a superuser or grant superuser status to." if (@ARGV < 1 || @ARGV > 2);
+ my ($email, $password) = @_;
- my $user = FixMyStreet::DB->resultset('User')->find_or_new({ email => $ARGV[0] });
+ unless ($email) {
+ warn "Specify a single email address and optionally password to create a superuser or grant superuser status to.\n";
+ return 1;
+ }
+
+ my $user = FixMyStreet::DB->resultset('User')->find_or_new({ email => $email });
if ( !$user->in_storage ) {
- die "Specify a password for this new user." if (@ARGV < 2);
- $user->password($ARGV[1]);
+ unless ($password) {
+ warn "Specify a password for this new user.\n";
+ return 1;
+ }
+ $user->password($password);
$user->is_superuser(1);
$user->insert;
} else {
$user->update({ is_superuser => 1 });
}
print $user->email . " is now a superuser.\n";
+ return 0;
}
-
-1; \ No newline at end of file
+1;
diff --git a/perllib/Memcached.pm b/perllib/Memcached.pm
index 05ceeb615..d03897e5a 100644
--- a/perllib/Memcached.pm
+++ b/perllib/Memcached.pm
@@ -29,4 +29,8 @@ sub set {
instance->set(@_);
}
+sub delete {
+ instance->delete(@_);
+}
+
1;
diff --git a/t/app/controller/admin/manifesttheme.t b/t/app/controller/admin/manifesttheme.t
new file mode 100644
index 000000000..c1b2d4542
--- /dev/null
+++ b/t/app/controller/admin/manifesttheme.t
@@ -0,0 +1,340 @@
+use Path::Tiny;
+use FixMyStreet::DB;
+use FixMyStreet::TestMech;
+
+my $mech = FixMyStreet::TestMech->new;
+
+my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1);
+
+$mech->log_in_ok( $superuser->email );
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'lincolnshire', 'tfl', 'fixmystreet' ],
+}, sub {
+
+ok $mech->host('lincolnshire.fixmystreet.com');
+
+subtest "theme link on cobrand admin goes to create form if no theme exists" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "no themes yet" );
+
+ $mech->get_ok("/admin");
+ $mech->follow_link_ok({ text => "Manifest Theme" });
+
+ is $mech->res->previous->code, 302, "got 302 for redirect";
+ is $mech->res->previous->base->path, "/admin/manifesttheme", "redirected from index";
+ is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page";
+};
+
+subtest "name and short_name are required fields" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "no themes yet" );
+
+ $mech->get_ok("/admin/manifesttheme/create");
+ $mech->content_lacks("Delete theme");
+
+ $mech->submit_form_ok({});
+ is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create page";
+ $mech->content_contains("field is required");
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme not created" );
+
+ $mech->get_ok("/admin/manifesttheme/create");
+ $mech->submit_form_ok({ with_fields => { short_name => "Lincs FMS" } });
+ is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create page";
+ $mech->content_contains("field is required", "name is required");
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme not created" );
+
+ $mech->get_ok("/admin/manifesttheme/create");
+ $mech->submit_form_ok({ with_fields => { name => "Lincolnshire FixMyStreet" } });
+ is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create page";
+ $mech->content_contains("field is required", "short_name is required");
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme not created" );
+};
+
+subtest "cobrand admin lets you create a new theme" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "no themes yet" );
+
+ $mech->get_ok("/admin/manifesttheme/create");
+ $mech->content_lacks("Delete theme");
+
+ my $fields = {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected to edit page";
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme was created" );
+
+ my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' });
+ is $theme->name, "Lincolnshire FixMyStreet";
+ is $theme->short_name, "Lincs FMS";
+ is $theme->background_colour, undef;
+
+ my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first;
+ is $log->object_id, $theme->id;
+ is $log->action, "add";
+ is $log->object_summary, "lincolnshire";
+ is $log->link, "/admin/manifesttheme/lincolnshire";
+};
+
+subtest "cobrand admin lets you update an existing theme" => sub {
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+
+ my $fields = {
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+
+ my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' });
+ is $theme->background_colour, "#663399";
+ is $theme->theme_colour, "rgb(102, 51, 153)";
+
+ my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first;
+ is $log->object_id, $theme->id;
+ is $log->action, "edit";
+};
+
+subtest "cobrand admin lets you add an icon to an existing theme" => sub {
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+
+ my $sample_jpeg = path(__FILE__)->parent->parent->child("sample.jpg");
+ ok $sample_jpeg->exists, "sample image $sample_jpeg exists";
+ my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg';
+
+ $mech->post( '/admin/manifesttheme/lincolnshire',
+ Content_Type => 'form-data',
+ Content => {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ cobrand => 'lincolnshire',
+ icon => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ],
+ },
+ );
+ ok $mech->success, 'Posted request successfully';
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ $mech->content_contains("<img src=\"/theme/lincolnshire/" . $icon_filename);
+ $mech->content_contains("<td class=\"icon-size\">133x100</td>");
+ my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename));
+ ok $icon_dest->exists, "Icon stored on disk";
+};
+
+subtest "cobrand admin lets you delete an icon from an existing theme" => sub {
+ my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg';
+ my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename));
+ ok $icon_dest->exists, "Icon exists on disk";
+
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+ my $fields = {
+ delete_icon => "/theme/lincolnshire/$icon_filename",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ $mech->content_lacks("<img src=\"/theme/lincolnshire/" . $icon_filename);
+ $mech->content_lacks("<td class=\"icon-size\">133x100</td>");
+ ok !$icon_dest->exists, "Icon removed from disk";
+};
+
+subtest "cobrand admin rejects non-images" => sub {
+ $mech->get_ok("/admin/manifesttheme/lincolnshire");
+
+ my $sample_pdf = path(__FILE__)->parent->parent->child("sample.pdf");
+ ok $sample_pdf->exists, "sample image $sample_pdf exists";
+
+ $mech->post( '/admin/manifesttheme/lincolnshire',
+ Content_Type => 'form-data',
+ Content => {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ cobrand => 'lincolnshire',
+ icon => [ $sample_pdf, undef, Content_Type => 'application/pdf' ],
+ },
+ );
+ ok $mech->success, 'Posted request successfully';
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ $mech->content_lacks("90f7a64043fb458d58de1a0703a6355e2856b15e.pdf");
+ $mech->content_contains("File type not recognised. Please upload an image.");
+};
+
+subtest "theme link on cobrand admin goes to edit form when theme exists" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" );
+
+ $mech->get_ok("/admin");
+ $mech->follow_link_ok({ text => "Manifest Theme" });
+
+ is $mech->res->previous->code, 302, "got 302 for redirect";
+ is $mech->res->previous->base->path, "/admin/manifesttheme", "redirected from index";
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected to edit page";
+};
+
+subtest "create page on cobrand admin redirects to edit form when theme exists" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" );
+
+ $mech->get_ok("/admin/manifesttheme/create");
+
+ is $mech->res->previous->code, 302, "got 302 for redirect";
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected to edit page";
+};
+
+subtest "can delete theme" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme exists" );
+
+ my $theme_id = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'lincolnshire' })->id;
+
+ # Add an icon so we can test it gets deleted when the theme is deleted
+ my $sample_jpeg = path(__FILE__)->parent->parent->child("sample.jpg");
+ ok $sample_jpeg->exists, "sample image $sample_jpeg exists";
+ my $icon_filename = '74e3362283b6ef0c48686fb0e161da4043bbcc97.jpg';
+
+ $mech->post( '/admin/manifesttheme/lincolnshire',
+ Content_Type => 'form-data',
+ Content => {
+ name => "Lincolnshire FixMyStreet",
+ short_name => "Lincs FMS",
+ background_colour => "#663399",
+ theme_colour => "rgb(102, 51, 153)",
+ cobrand => "lincolnshire",
+ icon => [ $sample_jpeg, undef, Content_Type => 'image/jpeg' ],
+ },
+ );
+ ok $mech->success, 'Posted request successfully';
+
+ is $mech->uri->path, '/admin/manifesttheme/lincolnshire', "redirected back to edit page";
+ my $icon_dest = path(FixMyStreet->path_to('web/theme/lincolnshire/', $icon_filename));
+ ok $icon_dest->exists, "Icon stored on disk";
+
+ $mech->submit_form_ok({ button => 'delete_theme' });
+ is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page";
+
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "theme deleted" );
+ ok !$icon_dest->exists, "Icon removed from disk";
+
+ my $log = $superuser->admin_logs->search({}, { order_by => { -desc => 'id' } })->first;
+ is $log->object_id, $theme_id;
+ is $log->action, "delete";
+};
+
+subtest "can't edit another cobrand's theme" => sub {
+ FixMyStreet::DB->resultset('ManifestTheme')->create({
+ cobrand => "tfl",
+ name => "Transport for London Street Care",
+ short_name => "TfL Street Care",
+ });
+
+ $mech->get("/admin/manifesttheme/tfl");
+ ok !$mech->res->is_success(), "want a bad response";
+ is $mech->res->code, 404, "got 404";
+};
+
+ok $mech->host('www.fixmystreet.com');
+
+subtest "fms cobrand lets you view all manifest themes" => sub {
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme already exists" );
+
+ $mech->get_ok("/admin");
+ $mech->follow_link_ok({ text => "Manifest Theme" });
+
+ is $mech->uri->path, '/admin/manifesttheme', "taken to list page";
+
+ $mech->content_contains("Transport for London Street Care");
+ $mech->content_contains("TfL Street Care");
+
+};
+
+subtest "fms cobrand lets you edit a cobrand's manifest theme" => sub {
+ $mech->get_ok("/admin/manifesttheme");
+ $mech->follow_link_ok({ url => "manifesttheme/tfl" }) or diag $mech->content;
+
+ my $fields = {
+ name => "Transport for London Report It",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/manifesttheme', "redirected back to list page";
+
+ my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'tfl' });
+ is $theme->name, "Transport for London Report It";
+
+};
+
+subtest "fms cobrand lets you create a new manifest theme" => sub {
+ $mech->get_ok("/admin/manifesttheme");
+ $mech->follow_link_ok({ text => "Create" });
+
+ my $fields = {
+ name => "FixMyStreet Pro",
+ short_name => "FMS Pro",
+ cobrand => "fixmystreet",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/manifesttheme', "redirected to list page";
+
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 2, "theme added" );
+ my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'fixmystreet' });
+ is $theme->name, "FixMyStreet Pro";
+};
+
+subtest "fms cobrand prevents you creating a duplicate theme" => sub {
+ $mech->get_ok("/admin/manifesttheme");
+ $mech->follow_link_ok({ text => "Create" });
+
+ my $fields = {
+ name => "FixMyStreet Pro",
+ short_name => "FMS Pro",
+ cobrand => "fixmystreet",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/manifesttheme/create', "stayed on create form";
+
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 2, "theme not added" );
+};
+
+subtest "fms cobrand prevents creating a duplicate by editing" => sub {
+ $mech->get_ok("/admin/manifesttheme");
+ $mech->follow_link_ok({ url => "manifesttheme/tfl" });
+
+ my $fields = {
+ cobrand => "fixmystreet",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/manifesttheme/tfl', "stayed on edit page";
+};
+
+};
+
+FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'fixamingata' ],
+}, sub {
+
+ok $mech->host("www.fixamingata.se"), "change host to FixaMinGata";
+
+subtest "single cobrand behaves correctly" => sub {
+ FixMyStreet::DB->resultset('ManifestTheme')->delete_all;
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 0, "themes all deleted" );
+
+ $mech->get_ok("/admin/manifesttheme");
+ is $mech->uri->path, '/admin/manifesttheme/create', "redirected to create page";
+
+ my $fields = {
+ name => "FixaMinGata Theme Test",
+ short_name => "FixaMinGata Short Name",
+ cobrand => "fixamingata",
+ };
+ $mech->submit_form_ok( { with_fields => $fields } );
+ is $mech->uri->path, '/admin/manifesttheme/fixamingata', "redirected to edit form page";
+ $mech->content_contains("FixaMinGata Theme Test");
+ $mech->content_contains("FixaMinGata Short Name");
+
+ is( FixMyStreet::DB->resultset('ManifestTheme')->count, 1, "theme added" );
+ my $theme = FixMyStreet::DB->resultset('ManifestTheme')->find({ cobrand => 'fixamingata' });
+ is $theme->name, "FixaMinGata Theme Test";
+};
+
+
+};
+
+done_testing();
diff --git a/t/app/controller/admin/users.t b/t/app/controller/admin/users.t
index 2e3ad9e5a..a36a4187a 100644
--- a/t/app/controller/admin/users.t
+++ b/t/app/controller/admin/users.t
@@ -564,7 +564,10 @@ subtest "Send login email from admin for unverified email" => sub {
};
subtest "Anonymizing user from admin" => sub {
- $mech->create_problems_for_body(4, 2237, 'Title');
+ my ($problem) = $mech->create_problems_for_body(4, 2237, 'Title');
+ $mech->create_comment_for_problem($problem, $user, $user->name, 'An update', 'f', 'confirmed', 'confirmed');
+ $mech->create_comment_for_problem($problem, $user, $user->name, '2nd update', 't', 'confirmed', 'fixed - user');
+ $mech->create_comment_for_problem($problem, $user, $user->name, '3rd update', 'f', 'unconfirmed', 'confirmed');
my $count_p = FixMyStreet::DB->resultset('Problem')->search({ user_id => $user->id })->count;
my $count_u = FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id })->count;
$mech->get_ok( '/admin/users/' . $user->id );
@@ -586,6 +589,12 @@ subtest "Hiding user's reports from admin" => sub {
is $c, $count_u;
};
+subtest "Hiding user with only unconfirmed updates does not error" => sub {
+ FixMyStreet::DB->resultset('Comment')->search({ user_id => $user->id, state => 'hidden' })->update({ state => 'unconfirmed' });
+ $mech->get_ok( '/admin/users/' . $user->id );
+ $mech->submit_form_ok({ button => 'hide_everywhere' });
+};
+
subtest "Logging user out" => sub {
my $mech2 = FixMyStreet::TestMech->new;
$mech2->log_in_ok($user->email);
diff --git a/t/app/controller/around.t b/t/app/controller/around.t
index bd2bf2cee..cd992270f 100644
--- a/t/app/controller/around.t
+++ b/t/app/controller/around.t
@@ -261,6 +261,9 @@ subtest 'check category, status and extra filtering works on /around' => sub {
$mech->get_ok( '/around?filter_category=Pothole&bbox=' . $bbox );
$mech->content_contains('<option value="Pothole" selected>');
$mech->content_contains('<optgroup label="Environment">');
+
+ $mech->get_ok( '/around?filter_group=Environment&bbox=' . $bbox );
+ $mech->content_contains('<option value="Flytipping" selected>');
};
$json = $mech->get_ok_json( '/around?ajax=1&filter_category=Pothole&bbox=' . $bbox );
diff --git a/t/app/controller/offline.t b/t/app/controller/offline.t
index d2a5009ec..876475264 100644
--- a/t/app/controller/offline.t
+++ b/t/app/controller/offline.t
@@ -1,5 +1,7 @@
use FixMyStreet::TestMech;
+use FixMyStreet::DB;
use Path::Tiny;
+use Memcached;
my $mech = FixMyStreet::TestMech->new;
@@ -11,6 +13,7 @@ FixMyStreet::override_config {
my $image_path = path('t/app/controller/sample.jpg');
$image_path->copy($theme_dir->child('sample.jpg'));
subtest 'manifest' => sub {
+ Memcached::delete("manifest_theme:test");
my $j = $mech->get_ok_json('/.well-known/manifest.webmanifest');
is $j->{name}, 'FixMyStreet', 'correct name';
is $j->{theme_color}, '#ffd000', 'correct theme colour';
@@ -20,6 +23,22 @@ FixMyStreet::override_config {
sizes => '133x100'
}, 'correct icon';
};
+ subtest 'themed manifest' => sub {
+ Memcached::delete("manifest_theme:test");
+ FixMyStreet::DB->resultset('ManifestTheme')->create({
+ cobrand => "test",
+ name => "My Test Cobrand FMS",
+ short_name => "Test FMS",
+ background_colour => "#ff00ff",
+ theme_colour => "#ffffff",
+ });
+
+ my $j = $mech->get_ok_json('/.well-known/manifest.webmanifest');
+ is $j->{name}, 'My Test Cobrand FMS', 'correctly overridden name';
+ is $j->{short_name}, 'Test FMS', 'correctly overridden short_name';
+ is $j->{background_color}, '#ff00ff', 'correctly overridden background colour';
+ is $j->{theme_color}, '#ffffff', 'correctly overridden theme colour';
+ };
$theme_dir->remove_tree;
};
diff --git a/t/app/controller/report_new.t b/t/app/controller/report_new.t
index 1f5ad2b52..b85bae43a 100644
--- a/t/app/controller/report_new.t
+++ b/t/app/controller/report_new.t
@@ -2185,207 +2185,4 @@ subtest "extra google analytics code displayed on email confirmation problem cre
};
};
-
-my $private_perms = $mech->create_user_ok('private_perms@example.org', name => 'private', from_body => $bodies[0]);
-subtest "report_mark_private allows users to mark reports as private" => sub {
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- BASE_URL => 'https://www.fixmystreet.com',
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $mech->log_out_ok;
-
- $private_perms->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'report_mark_private',
- });
-
- $mech->log_in_ok('private_perms@example.org');
- $mech->get_ok('/');
- $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
- "submit location" );
- $mech->follow_link_ok(
- { text_regex => qr/skip this step/i, },
- "follow 'skip this step' link"
- );
-
- $mech->submit_form_ok(
- {
- with_fields => {
- title => "Private report",
- detail => 'Private report details.',
- photo1 => '',
- name => 'Joe Bloggs',
- may_show_name => '1',
- phone => '07903 123 456',
- category => 'Trees',
- non_public => 1,
- }
- },
- "submit good details"
- );
-
- $mech->content_contains('Great work. Now spread the word', 'shown confirmation page');
- }
-};
-
-my $inspector = $mech->create_user_ok('inspector@example.org', name => 'inspector', from_body => $bodies[0]);
-foreach my $test (
- { non_public => 0 },
- { non_public => 1 },
-) {
- subtest "inspectors get redirected directly to the report page, non_public=$test->{non_public}" => sub {
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- BASE_URL => 'https://www.fixmystreet.com',
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $mech->log_out_ok;
-
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'planned_reports',
- });
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'report_inspect',
- });
-
- $mech->log_in_ok('inspector@example.org');
- $mech->get_ok('/');
- $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
- "submit location" );
- $mech->follow_link_ok(
- { text_regex => qr/skip this step/i, },
- "follow 'skip this step' link"
- );
-
- $mech->submit_form_ok(
- {
- with_fields => {
- title => "Inspector report",
- detail => 'Inspector report details.',
- photo1 => '',
- name => 'Joe Bloggs',
- may_show_name => '1',
- phone => '07903 123 456',
- category => 'Trees',
- non_public => $test->{non_public},
- }
- },
- "submit good details"
- );
-
- like $mech->uri->path, qr/\/report\/[0-9]+/, 'Redirects directly to report';
- }
- };
-}
-
-subtest "check map click ajax response for inspector" => sub {
- $mech->log_out_ok;
-
- my $extra_details;
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'planned_reports',
- });
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => 'report_inspect',
- });
-
- $mech->log_in_ok('inspector@example.org');
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
- };
- like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
- ok !$extra_details->{contribute_as}, 'no contribute as section';
-};
-
-subtest "check map click ajax response for inspector and uk cobrand" => sub {
- $mech->log_out_ok;
-
- my $extra_details;
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[4],
- permission_type => 'planned_reports',
- });
- $inspector->user_body_permissions->find_or_create({
- body => $bodies[4],
- permission_type => 'report_inspect',
- });
-
- $mech->log_in_ok('inspector@example.org');
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { bromley => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.402096&longitude=0.015784' );
- };
- like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
-};
-
-for my $test (
- {
- desc => 'map click ajax for contribute_as_another_user',
- permissions => {
- contribute_as_another_user => 1,
- contribute_as_anonymous_user => undef,
- contribute_as_body => undef,
- }
- },
- {
- desc => 'map click ajax for contribute_as_anonymous_user',
- permissions => {
- contribute_as_another_user => undef,
- contribute_as_anonymous_user => 1,
- contribute_as_body => undef,
- }
- },
- {
- desc => 'map click ajax for contribute_as_body',
- permissions => {
- contribute_as_another_user => undef,
- contribute_as_anonymous_user => undef,
- contribute_as_body => 1,
- }
- },
-) {
- subtest $test->{desc} => sub {
- $mech->log_out_ok;
- my $extra_details;
- (my $name = $test->{desc}) =~ s/.*(contri.*)/$1/;
- my $user = $mech->create_user_ok("$name\@example.org", name => 'test user', from_body => $bodies[0]);
- for my $p ( keys %{$test->{permissions}} ) {
- next unless $test->{permissions}->{$p};
- $user->user_body_permissions->find_or_create({
- body => $bodies[0],
- permission_type => $p,
- });
- }
- $mech->log_in_ok("$name\@example.org");
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
- };
- for my $p ( keys %{$test->{permissions}} ) {
- (my $key = $p) =~ s/contribute_as_//;
- is $extra_details->{contribute_as}->{$key}, $test->{permissions}->{$p}, "$key correctly set";
- }
-
- FixMyStreet::override_config {
- ALLOWED_COBRANDS => [ { fixmystreet => '.' } ],
- MAPIT_URL => 'http://mapit.uk/',
- }, sub {
- $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.754926&longitude=-1.256179' );
- };
- ok !$extra_details->{contribute_as}, 'no contribute as section for other council';
- };
-}
-
done_testing();
diff --git a/t/app/controller/report_new_staff.t b/t/app/controller/report_new_staff.t
new file mode 100644
index 000000000..ee9dff9e4
--- /dev/null
+++ b/t/app/controller/report_new_staff.t
@@ -0,0 +1,257 @@
+use FixMyStreet::TestMech;
+
+# disable info logs for this test run
+FixMyStreet::App->log->disable('info');
+END { FixMyStreet::App->log->enable('info'); }
+
+my $mech = FixMyStreet::TestMech->new;
+
+my %body_ids;
+for my $body (
+ { area_id => 2651, name => 'City of Edinburgh Council' },
+ { area_id => 2482, name => 'Bromley Council' },
+ { area_id => 2237, name => 'Oxfordshire County Council' },
+) {
+ my $body_obj = $mech->create_body_ok($body->{area_id}, $body->{name});
+ $body_ids{$body->{area_id}} = $body_obj->id;
+}
+
+# Let's make some contacts to send things to!
+$mech->create_contact_ok( body_id => $body_ids{2651}, category => 'Street lighting', email => 'highways@example.com' );
+my $edin_trees = $mech->create_contact_ok( body_id => $body_ids{2651}, category => 'Trees', email => 'trees@example.com' );
+$mech->create_contact_ok( body_id => $body_ids{2482}, category => 'Trees', email => 'trees@example.com' );
+$mech->create_contact_ok( body_id => $body_ids{2237}, category => 'Trees', email => 'trees-2247@example.com' );
+
+my $private_perms = $mech->create_user_ok('private_perms@example.org', name => 'private', from_body => $body_ids{2651});
+subtest "report_mark_private allows users to mark reports as private" => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ BASE_URL => 'https://www.fixmystreet.com',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->log_out_ok;
+
+ $private_perms->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'report_mark_private',
+ });
+
+ $mech->log_in_ok('private_perms@example.org');
+ $mech->get_ok('/');
+ $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
+ "submit location" );
+ $mech->follow_link_ok(
+ { text_regex => qr/skip this step/i, },
+ "follow 'skip this step' link"
+ );
+
+ $mech->submit_form_ok(
+ {
+ with_fields => {
+ title => "Private report",
+ detail => 'Private report details.',
+ photo1 => '',
+ name => 'Joe Bloggs',
+ may_show_name => '1',
+ phone => '07903 123 456',
+ category => 'Trees',
+ non_public => 1,
+ }
+ },
+ "submit good details"
+ );
+
+ $mech->content_contains('Great work. Now spread the word', 'shown confirmation page');
+ }
+};
+
+my $inspector = $mech->create_user_ok('inspector@example.org', name => 'inspector', from_body => $body_ids{2651});
+foreach my $test (
+ { non_public => 0 },
+ { non_public => 1 },
+) {
+ subtest "inspectors get redirected directly to the report page, non_public=$test->{non_public}" => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ BASE_URL => 'https://www.fixmystreet.com',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $mech->log_out_ok;
+
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'planned_reports',
+ });
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'report_inspect',
+ });
+
+ $mech->log_in_ok('inspector@example.org');
+ $mech->get_ok('/');
+ $mech->submit_form_ok( { with_fields => { pc => 'EH1 1BB' } },
+ "submit location" );
+ $mech->follow_link_ok(
+ { text_regex => qr/skip this step/i, },
+ "follow 'skip this step' link"
+ );
+
+ $mech->submit_form_ok(
+ {
+ with_fields => {
+ title => "Inspector report",
+ detail => 'Inspector report details.',
+ photo1 => '',
+ name => 'Joe Bloggs',
+ may_show_name => '1',
+ phone => '07903 123 456',
+ category => 'Trees',
+ non_public => $test->{non_public},
+ }
+ },
+ "submit good details"
+ );
+
+ like $mech->uri->path, qr/\/report\/[0-9]+/, 'Redirects directly to report';
+ }
+ };
+}
+
+subtest "check map click ajax response for inspector" => sub {
+ $mech->log_out_ok;
+
+ my $extra_details;
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'planned_reports',
+ });
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => 'report_inspect',
+ });
+
+ $mech->log_in_ok('inspector@example.org');
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ };
+ like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
+ ok !$extra_details->{contribute_as}, 'no contribute as section';
+};
+
+subtest "check map click ajax response for inspector and uk cobrand" => sub {
+ $mech->log_out_ok;
+
+ my $extra_details;
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2482},
+ permission_type => 'planned_reports',
+ });
+ $inspector->user_body_permissions->find_or_create({
+ body_id => $body_ids{2482},
+ permission_type => 'report_inspect',
+ });
+
+ $mech->log_in_ok('inspector@example.org');
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'bromley',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.402096&longitude=0.015784' );
+ };
+ like $extra_details->{category}, qr/data-prefill="0/, 'inspector prefill not set';
+};
+
+for my $test (
+ {
+ desc => 'map click ajax for contribute_as_another_user',
+ permissions => {
+ contribute_as_another_user => 1,
+ contribute_as_anonymous_user => undef,
+ contribute_as_body => undef,
+ }
+ },
+ {
+ desc => 'map click ajax for contribute_as_anonymous_user',
+ permissions => {
+ contribute_as_another_user => undef,
+ contribute_as_anonymous_user => 1,
+ contribute_as_body => undef,
+ }
+ },
+ {
+ desc => 'map click ajax for contribute_as_body',
+ permissions => {
+ contribute_as_another_user => undef,
+ contribute_as_anonymous_user => undef,
+ contribute_as_body => 1,
+ }
+ },
+) {
+ subtest $test->{desc} => sub {
+ $mech->log_out_ok;
+ my $extra_details;
+ (my $name = $test->{desc}) =~ s/.*(contri.*)/$1/;
+ my $user = $mech->create_user_ok("$name\@example.org", name => 'test user', from_body => $body_ids{2651});
+ for my $p ( keys %{$test->{permissions}} ) {
+ next unless $test->{permissions}->{$p};
+ $user->user_body_permissions->find_or_create({
+ body_id => $body_ids{2651},
+ permission_type => $p,
+ });
+ }
+ $mech->log_in_ok("$name\@example.org");
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ };
+ for my $p ( keys %{$test->{permissions}} ) {
+ (my $key = $p) =~ s/contribute_as_//;
+ is $extra_details->{contribute_as}->{$key}, $test->{permissions}->{$p}, "$key correctly set";
+ }
+
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => 'fixmystreet',
+ MAPIT_URL => 'http://mapit.uk/',
+ }, sub {
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=51.754926&longitude=-1.256179' );
+ };
+ ok !$extra_details->{contribute_as}, 'no contribute as section for other council';
+ };
+}
+
+subtest 'staff-only categories when reporting' => sub {
+ FixMyStreet::override_config {
+ MAPIT_URL => 'http://mapit.uk/',
+ MAPIT_TYPES => ['UTA'],
+ }, sub {
+ $inspector->update({ is_superuser => 1 });
+ $mech->log_in_ok('inspector@example.org');
+
+ $mech->get_ok('/admin/body/' . $body_ids{2651} . '/Trees');
+ $mech->submit_form_ok({ with_fields => { state => 'staff' } }, 'mark Trees as staff-only');
+ $edin_trees->discard_changes;
+ is $edin_trees->state, 'staff', 'category is staff only';
+
+ my $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting', 'Trees' ], 'Superuser can see staff-only category';
+
+ $inspector->update({ is_superuser => 0 });
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting', 'Trees' ], 'Body staff user can see staff-only category';
+
+ $inspector->update({ from_body => $body_ids{2482} });
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting' ], 'Different body staff user cannot see staff-only category';
+
+ $mech->log_out_ok;
+ $extra_details = $mech->get_ok_json( '/report/new/ajax?latitude=55.952055&longitude=-3.189579' );
+ is_deeply [ sort keys %{$extra_details->{by_category}} ], [ 'Street lighting' ], 'Normal user cannot see staff-only category';
+ };
+};
+
+done_testing;
diff --git a/t/script/createsuperuser.t b/t/script/createsuperuser.t
new file mode 100644
index 000000000..1d4826111
--- /dev/null
+++ b/t/script/createsuperuser.t
@@ -0,0 +1,23 @@
+use Test::More;
+use Test::Output;
+
+use_ok 'FixMyStreet::Script::CreateSuperuser';
+
+stderr_like { FixMyStreet::Script::CreateSuperuser::createsuperuser(); }
+ qr/Specify a single email address/, 'Email error shown';
+stderr_is { FixMyStreet::Script::CreateSuperuser::createsuperuser('test@example.org'); }
+ "Specify a password for this new user.\n", 'Password error shown';
+stdout_is { FixMyStreet::Script::CreateSuperuser::createsuperuser('test@example.org', 'password'); }
+ "test\@example.org is now a superuser.\n", 'Correct message shown';
+
+my $user = FixMyStreet::DB->resultset("User")->find({ email => 'test@example.org' });
+ok $user, 'user created';
+is $user->is_superuser, 1, 'is a superuser';
+
+$user->update({ is_superuser => 0 });
+stdout_is { FixMyStreet::Script::CreateSuperuser::createsuperuser('test@example.org'); }
+ "test\@example.org is now a superuser.\n", 'Correct message shown';
+$user->discard_changes;
+is $user->is_superuser, 1, 'is a superuser again';
+
+done_testing;
diff --git a/templates/web/base/admin/bodies/contact-form.html b/templates/web/base/admin/bodies/contact-form.html
index 921cb1380..b698fcea2 100644
--- a/templates/web/base/admin/bodies/contact-form.html
+++ b/templates/web/base/admin/bodies/contact-form.html
@@ -40,6 +40,11 @@
<label for="state-deleted">[% loc('Deleted') %]</label>
<p class="form-hint" id="state-deleted-hint">[% loc('Prevent new reports from using this category, <em>and</em> also remove it from map filters.') %]</p>
</div>
+ <div class="form-check form-check--inline">
+ <input type="radio" name="state" id="state-staff" aria-describedby="state-staff-hint" value="staff"[% ' checked' IF contact.state == 'staff' %]>
+ <label for="state-staff">[% loc('Staff only') %]</label>
+ <p class="form-hint" id="state-staff-hint">[% loc('Only staff users will be able to add reports in this category.') %]</p>
+ </div>
</fieldset>
<p class="form-check">
diff --git a/templates/web/base/admin/manifesttheme/form.html b/templates/web/base/admin/manifesttheme/form.html
new file mode 100644
index 000000000..6d02487a6
--- /dev/null
+++ b/templates/web/base/admin/manifesttheme/form.html
@@ -0,0 +1,71 @@
+[% INCLUDE 'admin/header.html' title=loc('Theme') -%]
+
+<form method="post" enctype="multipart/form-data">
+ <div class="admin-hint">
+ <p>[% loc("The <strong>name</strong> is a string that represents the name of the web application as it is usually displayed to the user (e.g., amongst a list of other applications, or as a label for an icon).") %]</p>
+ </div>
+ [% form.field('name').render | safe %]
+
+ <div class="admin-hint">
+ <p>[% loc("The <strong>short name</strong> is a string that represents the name of the web application displayed to the user if there is not enough space to display name (e.g., as a label for an icon on the phone home screen).") %]</p>
+ </div>
+ [% form.field('short_name').render | safe %]
+
+ <div class="admin-hint">
+ <p>[% loc("The <strong>theme colour</strong> defines the default theme colour for the application. This sometimes affects how the OS displays the site (e.g., on Android's task switcher, the theme colour surrounds the site). Colours should be specified with CSS syntax, e.g. <strong><code>#ff00ff</code></strong> or <strong><code>rgb(255, 0, 255)</code></strong> or a named colour like <strong><code>fuchsia</code></strong>.") %]</p>
+ </div>
+ [% form.field('theme_colour').render | safe %]
+
+ <div class="admin-hint">
+ <p>[% loc("The <strong>background colour</strong> defines a placeholder background colour for the application splash screen before it has loaded. Colours should be specified with CSS syntax, e.g. <strong><code>#ff00ff</code></strong> or <strong><code>rgb(255, 0, 255)</code></strong> or a named colour like <strong><code>fuchsia</code></strong>.") %]</p>
+ </div>
+ [% form.field('background_colour').render | safe %]
+
+ [% IF show_all %]
+ [% form.field('cobrand').render | safe %]
+ [% ELSE %]
+ <input type=hidden name=cobrand value='[% c.cobrand.moniker %]' />
+ [% END %]
+
+ <table>
+ <thead>
+ <tr>
+ <th>Icon</th>
+ <th>Size</th>
+ <th>Delete?</th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOREACH icon IN editing_manifest_theme.icons %]
+ <tr>
+ <td><img src="[% icon.src %]" /></td>
+ <td class="icon-size">[% icon.sizes %]</td>
+ <td><input type=checkbox name=delete_icon value='[% icon.src %]' /></td>
+ </tr>
+ [% END %]
+ <tr>
+ <td colspan=3>
+ <div class="admin-hint">
+ <p>[% loc("The <strong>icons</strong> are used when the application is installed to the user's home screen. Icons must be <strong>square</strong>, with <strong>512x512</strong>px and <strong>192x192</strong>px being the most common sizes.") %]</p>
+ </div>
+ [% form.field('icon').render | safe %]
+ </td>
+ </tr>
+ </tbody>
+ </table>
+
+ <p>
+ <input class="btn" type="submit" name="submit" value="[% loc('Save changes') %]">
+ </p>
+ [% IF form.item.id %]
+ <p>
+ <input class="btn-danger" type="submit" name="delete_theme" value="[% loc('Delete theme') %]" data-confirm="[% loc('Are you sure?') %]">
+ </p>
+ [% END %]
+</form>
+
+[% IF show_all %]
+ <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to themes list</a></p>
+[% END %]
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/base/admin/manifesttheme/index.html b/templates/web/base/admin/manifesttheme/index.html
new file mode 100644
index 000000000..df94c394f
--- /dev/null
+++ b/templates/web/base/admin/manifesttheme/index.html
@@ -0,0 +1,35 @@
+[% INCLUDE 'admin/header.html' title=loc('Themes') %]
+
+<table>
+ <thead>
+ <tr>
+ <th> [% loc('Cobrand') %] </th>
+ <th> [% loc('Name') %] </th>
+ <th> [% loc('Short Name') %] </th>
+ <th> [% loc('Background Colour') %] </th>
+ <th> [% loc('Theme Colour') %] </th>
+ <th> &nbsp; </th>
+ </tr>
+ </thead>
+ <tbody>
+ [% FOR theme IN rs.all %]
+ <tr>
+ <td>[% theme.cobrand %]</td>
+ <td>[% theme.name %]</td>
+ <td>[% theme.short_name %]</td>
+ <td>[% theme.background_colour %]</td>
+ <td>[% theme.theme_colour %]</td>
+ <td> <a href="[% c.uri_for(c.controller.action_for('edit'), [theme.cobrand]) %]" class="btn">[% loc('Edit') %]</a> </td>
+ </tr>
+ [% END %]
+ </tbody>
+</table>
+
+
+
+<p>
+ <a href="[% c.uri_for(c.controller.action_for('create')) %]">[% loc('Create') %]</a>
+ </p>
+
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/base/common_header_tags.html b/templates/web/base/common_header_tags.html
index 279f561df..6edcc63a4 100644
--- a/templates/web/base/common_header_tags.html
+++ b/templates/web/base/common_header_tags.html
@@ -2,6 +2,12 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<link rel="manifest" href="/.well-known/manifest.webmanifest">
+[% IF manifest_theme %]
+ <meta name='theme-color' content='[% manifest_theme.theme_colour %]'>
+ [% FOREACH icon IN manifest_theme.icons %]
+ <link rel="apple-touch-icon" sizes="[% icon.sizes %]" href="[% icon.src %]">
+ [% END %]
+[% END %]
[% IF csrf_token %]
<meta content="[% csrf_token %]" name="csrf-token" />
diff --git a/templates/web/base/reports/_list-filters.html b/templates/web/base/reports/_list-filters.html
index 3125f63b1..f5d1faa65 100644
--- a/templates/web/base/reports/_list-filters.html
+++ b/templates/web/base/reports/_list-filters.html
@@ -2,7 +2,7 @@
[% BLOCK category_options %]
[% FOR cat IN categories %]
- <option value="[% cat.category %]"[% ' selected' IF filter_category.${cat.category} OR ( filter_group AND ( cat.get_extra_metadata('group') == filter_group OR cat.category == filter_group ) ) %]>
+ <option value="[% cat.category %]"[% ' selected' IF filter_category.${cat.category} OR ( filter_group AND ( cat.get_extra_metadata('group').grep(filter_group).size OR cat.category == filter_group ) ) %]>
[% cat.category_display %]
[%~ IF cat.get_extra_metadata('help_text') %] ([% cat.get_extra_metadata('help_text') %])[% END ~%]
</option>
diff --git a/templates/web/buckinghamshire/report/form/user_name.html b/templates/web/buckinghamshire/report/form/user_name.html
new file mode 100644
index 000000000..0cefb1b1d
--- /dev/null
+++ b/templates/web/buckinghamshire/report/form/user_name.html
@@ -0,0 +1,13 @@
+<!-- user_name.html -->
+<label for="form_name">Full name
+[% TRY %]
+ [% INCLUDE 'report/form/after_name.html' %]
+ [% CATCH file %]
+[% END %]
+</label>
+[% IF field_errors.name %]
+ <p class='form-error'>[% field_errors.name %]</p>
+[% END %]
+<input type="text" class="form-control [% valid_class OR 'validName' %] js-form-name [% extra_class %]"
+ value="[% object.name || c.user.name | html %]" name="name" id="form_name">
+<!-- /user_name.html -->
diff --git a/templates/web/fixmystreet.com/header_extra.html b/templates/web/fixmystreet.com/header_extra.html
index 5292d4804..12d9fe9d4 100644
--- a/templates/web/fixmystreet.com/header_extra.html
+++ b/templates/web/fixmystreet.com/header_extra.html
@@ -10,7 +10,6 @@
<link rel="canonical" href="https://www.fixmystreet.com[% c.req.uri.path_query %]">
[% END %]
-<meta name='theme-color' content='#ffd000'>
<link rel="Shortcut Icon" type="image/x-icon" href="/cobrands/fixmystreet.com/favicon.ico">
[% INCLUDE 'tracking_code.html' %]
diff --git a/web/cobrands/fixmystreet/admin.js b/web/cobrands/fixmystreet/admin.js
index 8210f002f..52f4dd292 100644
--- a/web/cobrands/fixmystreet/admin.js
+++ b/web/cobrands/fixmystreet/admin.js
@@ -190,5 +190,12 @@ $(function(){
$('.js-metadata-items').on('click', '.js-metadata-option-remove', function(){
$(this).parents('.js-metadata-option').remove();
});
+
+ // On the manifest theme editing page we have tickboxes for deleting individual
+ // icons - ticking one of these should grey out that row to indicate it will be
+ // deleted upon form submission.
+ $("input[name=delete_icon]").change(function() {
+ $(this).closest("tr").toggleClass("is-deleted", this.checked);
+ });
});
diff --git a/web/cobrands/sass/_admin.scss b/web/cobrands/sass/_admin.scss
index b1f914ca8..4ae019776 100644
--- a/web/cobrands/sass/_admin.scss
+++ b/web/cobrands/sass/_admin.scss
@@ -61,7 +61,10 @@ $button_bg_col: #a1a1a1; // also search bar (tables)
}
tr.is-deleted {
background-color: #ffdddd;
- td.contact-category {
+ img {
+ filter: grayscale(1);
+ }
+ td.contact-category, td.icon-size {
text-decoration: line-through;
}
}