aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--db/schema.sql20
-rw-r--r--db/schema_0005-add_council_user_flag.sql6
-rw-r--r--db/schema_0006-alter_problem_state.sql19
-rw-r--r--db/schema_0007-add-comment-problem-state.sql16
-rw-r--r--db/schema_0008-add_user_object_to_admin_log.sql12
-rw-r--r--notes/states.txt74
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm87
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/JSON.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/My.pm7
-rw-r--r--perllib/FixMyStreet/App/Controller/Open311.pm17
-rw-r--r--perllib/FixMyStreet/App/Controller/Photo.pm2
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Questionnaire.pm27
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm34
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm10
-rw-r--r--perllib/FixMyStreet/App/Controller/Tokens.pm2
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm16
-rw-r--r--perllib/FixMyStreet/Cobrand/EmptyHomes.pm2
-rw-r--r--perllib/FixMyStreet/DB/Result/Comment.pm22
-rw-r--r--perllib/FixMyStreet/DB/Result/Problem.pm135
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm42
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/AlertType.pm15
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Nearby.pm2
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Problem.pm17
-rw-r--r--perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm2
-rw-r--r--perllib/FixMyStreet/Map/OSM.pm2
-rw-r--r--t/app/controller/admin.t166
-rw-r--r--t/app/controller/questionnaire.t68
-rw-r--r--t/app/controller/report_display.t97
-rw-r--r--t/app/controller/report_import.t122
-rw-r--r--t/app/controller/report_updates.t234
-rw-r--r--t/app/model/alert_type.t144
-rw-r--r--t/app/model/problem.t88
-rw-r--r--t/app/model/questionnaire.t106
-rw-r--r--t/map/tilma/original.t108
-rw-r--r--templates/web/default/admin/list_updates.html4
-rw-r--r--templates/web/default/admin/report_edit.html5
-rw-r--r--templates/web/default/admin/search_users.html29
-rw-r--r--templates/web/default/admin/update_edit.html17
-rw-r--r--templates/web/default/admin/user_edit.html21
-rw-r--r--templates/web/default/around/around_map_list_items.html2
-rw-r--r--templates/web/default/around/on_map_list_items.html2
-rw-r--r--templates/web/default/my/my.html5
-rw-r--r--templates/web/default/questionnaire/completed.html2
-rw-r--r--templates/web/default/questionnaire/index.html2
-rw-r--r--templates/web/default/report/display.html31
-rw-r--r--templates/web/default/report/updates.html4
-rw-r--r--web/cobrands/fiksgatami/css.css55
-rw-r--r--web/css/core.css427
-rw-r--r--web/css/core.scss12
-rw-r--r--web/css/main.css176
51 files changed, 2376 insertions, 149 deletions
diff --git a/db/schema.sql b/db/schema.sql
index 53c236dd6..99cf2832d 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -131,6 +131,7 @@ create table users (
name text,
phone text,
password text not null default '',
+ from_council integer, -- id of council user is from or null/0 if not
flagged boolean not null default 'f'
);
@@ -166,7 +167,13 @@ create table problem (
state text not null check (
state = 'unconfirmed'
or state = 'confirmed'
+ or state = 'investigating'
+ or state = 'planned'
+ or state = 'in progress'
+ or state = 'closed'
or state = 'fixed'
+ or state = 'fixed - council'
+ or state = 'fixed - user'
or state = 'hidden'
or state = 'partial'
),
@@ -281,7 +288,17 @@ create table comment (
lang text not null default 'en-gb',
cobrand_data text not null default '' check (cobrand_data ~* '^[a-z0-9]*$'), -- Extra data used in cobranded versions of the site
mark_fixed boolean not null,
- mark_open boolean not null default 'f'
+ mark_open boolean not null default 'f',
+ problem_state text check (
+ problem_state = 'confirmed'
+ or problem_state = 'investigating'
+ or problem_state = 'planned'
+ or problem_state = 'in progress'
+ or problem_state = 'closed'
+ or problem_state = 'fixed'
+ or problem_state = 'fixed - council'
+ or problem_state = 'fixed - user'
+ )
-- other fields? one to indicate whether this was written by the council
-- and should be highlighted in the display?
);
@@ -380,6 +397,7 @@ create table admin_log (
object_type text not null check (
object_type = 'problem'
or object_type = 'update'
+ or object_type = 'user'
),
object_id integer not null,
action text not null check (
diff --git a/db/schema_0005-add_council_user_flag.sql b/db/schema_0005-add_council_user_flag.sql
new file mode 100644
index 000000000..1ab1579b7
--- /dev/null
+++ b/db/schema_0005-add_council_user_flag.sql
@@ -0,0 +1,6 @@
+begin;
+
+ALTER table users
+ ADD COLUMN from_council integer;
+
+commit;
diff --git a/db/schema_0006-alter_problem_state.sql b/db/schema_0006-alter_problem_state.sql
new file mode 100644
index 000000000..6fada1eb8
--- /dev/null
+++ b/db/schema_0006-alter_problem_state.sql
@@ -0,0 +1,19 @@
+begin;
+
+ ALTER TABLE problem DROP CONSTRAINT problem_state_check;
+
+ ALTER TABLE problem ADD CONSTRAINT problem_state_check CHECK (
+ state = 'unconfirmed'
+ or state = 'confirmed'
+ or state = 'investigating'
+ or state = 'planned'
+ or state = 'in progress'
+ or state = 'closed'
+ or state = 'fixed'
+ or state = 'fixed - council'
+ or state = 'fixed - user'
+ or state = 'hidden'
+ or state = 'partial'
+ );
+
+commit;
diff --git a/db/schema_0007-add-comment-problem-state.sql b/db/schema_0007-add-comment-problem-state.sql
new file mode 100644
index 000000000..2a3a95ada
--- /dev/null
+++ b/db/schema_0007-add-comment-problem-state.sql
@@ -0,0 +1,16 @@
+begin;
+
+ ALTER TABLE comment ADD column problem_state text;
+
+ ALTER TABLE comment ADD CONSTRAINT comment_problem_state_check CHECK (
+ problem_state = 'confirmed'
+ or problem_state = 'investigating'
+ or problem_state = 'planned'
+ or problem_state = 'in progress'
+ or problem_state = 'closed'
+ or problem_state = 'fixed'
+ or problem_state = 'fixed - council'
+ or problem_state = 'fixed - user'
+ );
+
+commit;
diff --git a/db/schema_0008-add_user_object_to_admin_log.sql b/db/schema_0008-add_user_object_to_admin_log.sql
new file mode 100644
index 000000000..dd8f03645
--- /dev/null
+++ b/db/schema_0008-add_user_object_to_admin_log.sql
@@ -0,0 +1,12 @@
+begin;
+
+ ALTER TABLE admin_log DROP CONSTRAINT admin_log_object_type_check;
+
+ ALTER TABLE admin_log ADD CONSTRAINT admin_log_object_type_check CHECK (
+ object_type = 'problem'
+ or object_type = 'update'
+ or object_type = 'user'
+ );
+
+
+commit;
diff --git a/notes/states.txt b/notes/states.txt
new file mode 100644
index 000000000..b885b252b
--- /dev/null
+++ b/notes/states.txt
@@ -0,0 +1,74 @@
+Problems exist in four broad state categories:
+ unconfirmed - the report has been made but the user hasn't clicked
+the confirmation link.
+ open - the report has been confirmed
+ fixed - exactly what it says
+ closed - a registered user from a council has marked the problem as
+closed
+
+When a problem is created it will be unconfirmed, The problem
+becomes confirmed when the user clicks on the link sent to them in
+the confirmation email. At this point the problem is confirmed.
+
+If a problem is uploaded from a mobile app then it is initally
+created with a state of partial.
+
+If a user is logged in then any problem they create is confirmed
+automatically.
+
+If a council user is logged in then they can change the state of the
+problem to one of the following provifing they are from the council that
+the problem has been reported to:
+ Open ( a synonym for confirmed )
+ Investigating
+ Planned
+ In Progress
+ Fixed
+ Closed
+
+Updates from council users will have the name of the council they
+are from included in the meta information for that update.
+
+Any user of the sytem can mark a problem as fixed.
+
+If a problem has been marked as fixed then the user who created the
+problem can reopen the problem by checking the 'This problem has not
+been fixed' checkbox when submitting an update.
+
+If a problem has been closed it may only be re-opened by a council
+user although it can still be updated.
+
+Internally there are three states for fixed problems:
+ fixed : problems marked as fixed before the addition of extra states
+ fixed - user : marked as fixed by a standard site user
+ fixed - council : marked as fixed by a council user
+
+At the moment there is no visible difference between these fixed states.
+
+If you need to check the state of a problem then there are a set of
+utility functions in DB/Result/Problem.pm to do that. If you have a
+problem object then these can be called directly on it:
+
+ $problem->is_visible
+ $problem->is_fixed
+ $problem->is_open
+ $problem->is_closed
+
+The is_visible method returns true if a problem should be displayed
+on the site - i.e it has been confirmed.
+
+You can also get a list of the states in a particular category by
+calling one of the following class methods:
+
+ visible_states
+ open_states
+ closed_states
+ fixed_states
+
+Which will return a list of state strings in list context or a
+hash reference in scalar context which you can use like this:
+
+ if ( FixMyStreet::App->model('DB::Problem')->open_states->{$state}
+) {
+ print "$state is open\n";
+ }
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 405ee37ec..e5c0133cf 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -56,10 +56,12 @@ sub index : Path : Args(0) {
%prob_counts =
map { $_ => $prob_counts{$_} || 0 }
- qw(confirmed fixed unconfirmed hidden partial);
+ ('confirmed', 'investigating', 'in progress', 'closed', 'fixed - council',
+ 'fixed - user', 'fixed', 'unconfirmed', 'hidden',
+ 'partial', 'planned');
$c->stash->{problems} = \%prob_counts;
- $c->stash->{total_problems_live} =
- $prob_counts{confirmed} + $prob_counts{fixed};
+ $c->stash->{total_problems_live} += $prob_counts{$_}
+ for ( FixMyStreet::DB::Result::Problem->visible_states() );
$c->stash->{total_problems_users} = $c->cobrand->problems->unique_users;
my $comments = $c->model('DB::Comment')->summary_count( $site_restriction );
@@ -614,6 +616,36 @@ sub report_edit : Path('report_edit') : Args(1) {
return 1;
}
+sub search_users: Path('search_users') : Args(0) {
+ my ( $self, $c ) = @_;
+
+ $c->forward('check_page_allowed');
+
+ if (my $search = $c->req->param('search')) {
+ $c->stash->{searched} = 1;
+
+ my $search = $c->req->param('search');
+ my $isearch = '%' . $search . '%';
+
+ my $search_n = 0;
+ $search_n = int($search) if $search =~ /^\d+$/;
+
+ my $users = $c->model('DB::User')->search(
+ {
+ -or => [
+ email => { ilike => $isearch },
+ name => { ilike => $isearch },
+ from_council => $search_n,
+ ]
+ }
+ );
+
+ $c->stash->{users} = [ $users->all ];
+ }
+
+ return 1;
+}
+
sub update_edit : Path('update_edit') : Args(1) {
my ( $self, $c, $id ) = @_;
@@ -737,6 +769,51 @@ sub search_abuse : Path('search_abuse') : Args(0) {
return 1;
}
+sub user_edit : Path('user_edit') : Args(1) {
+ my ( $self, $c, $id ) = @_;
+
+ $c->forward('check_page_allowed');
+ $c->forward('get_token');
+
+ my $user = $c->model('DB::User')->find( { id => $id } );
+ $c->stash->{user} = $user;
+
+ my @area_types = $c->cobrand->area_types;
+ my $areas = mySociety::MaPit::call('areas', \@area_types);
+
+ my @councils_ids = sort { strcoll($areas->{$a}->{name}, $areas->{$b}->{name}) } keys %$areas;
+ @councils_ids = $c->cobrand->filter_all_council_ids_list( @councils_ids );
+
+ $c->stash->{council_ids} = \@councils_ids;
+ $c->stash->{council_details} = $areas;
+
+ if ( $c->req->param('submit') ) {
+ $c->forward('check_token');
+
+ my $edited = 0;
+
+ if ( $user->email ne $c->req->param('email') ||
+ $user->name ne $c->req->param('name' ) ||
+ $user->from_council != $c->req->param('council') ) {
+ $edited = 1;
+ }
+
+ $user->name( $c->req->param('name') );
+ $user->email( $c->req->param('email') );
+ $user->from_council( $c->req->param('council') || undef );
+ $user->update;
+
+ if ($edited) {
+ $c->forward( 'log_edit', [ $id, 'user', 'edit' ] );
+ }
+
+ $c->stash->{status_message} =
+ '<p><em>' . _('Updated!') . '</em></p>';
+ }
+
+ return 1;
+}
+
sub list_flagged : Path('list_flagged') : Args(0) {
my ( $self, $c ) = @_;
@@ -840,9 +917,11 @@ sub set_allowed_pages : Private {
'search_reports' => [_('Search Reports'), 2],
'timeline' => [_('Timeline'), 3],
'questionnaire' => [_('Survey Results'), 4],
+ 'search_users' => [_('Search Users'), 5],
'search_abuse' => [_('Search Abuse'), 5],
'list_flagged' => [_('List Flagged'), 6],
'stats' => [_('Stats'), 6],
+ 'user_edit' => [undef, undef],
'council_contacts' => [undef, undef],
'council_edit' => [undef, undef],
'report_edit' => [undef, undef],
@@ -890,7 +969,7 @@ not then display 404 page
sub check_token : Private {
my ( $self, $c ) = @_;
- if ( $c->req->param('token' ) ne $c->stash->{token} ) {
+ if ( !$c->req->param('token') || $c->req->param('token' ) ne $c->stash->{token} ) {
$c->detach( '/page_error_404_not_found', [ _('The requested URL was not found on this server.') ] );
}
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index 0f871508e..d3a4500c6 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -189,7 +189,7 @@ sub display_location : Private {
{
latitude => $p->latitude,
longitude => $p->longitude,
- colour => $p->state eq 'fixed' ? 'green' : 'red',
+ colour => $p->is_fixed ? 'green' : 'red',
id => $p->id,
title => $p->title,
}
diff --git a/perllib/FixMyStreet/App/Controller/JSON.pm b/perllib/FixMyStreet/App/Controller/JSON.pm
index a89fb3e6c..f3607341a 100644
--- a/perllib/FixMyStreet/App/Controller/JSON.pm
+++ b/perllib/FixMyStreet/App/Controller/JSON.pm
@@ -71,12 +71,12 @@ sub problems : Local {
}
# query the database
- my ( $state, $date_col );
+ my ( @state, $date_col );
if ( $type eq 'new_problems' ) {
- $state = 'confirmed';
+ @state = FixMyStreet::DB::Result::Problem->open_states();
$date_col = 'confirmed';
} elsif ( $type eq 'fixed_problems' ) {
- $state = 'fixed';
+ @state = FixMyStreet::DB::Result::Problem->fixed_states();
$date_col = 'lastupdate';
}
@@ -86,7 +86,7 @@ sub problems : Local {
'>=' => $start_dt,
'<=' => $end_dt + $one_day,
},
- state => $state,
+ state => [ @state ],
};
$query->{category} = $category if $category;
my @problems = $c->cobrand->problems->search( $query, {
diff --git a/perllib/FixMyStreet/App/Controller/My.pm b/perllib/FixMyStreet/App/Controller/My.pm
index 1021f7056..60e9dd09f 100644
--- a/perllib/FixMyStreet/App/Controller/My.pm
+++ b/perllib/FixMyStreet/App/Controller/My.pm
@@ -31,7 +31,7 @@ sub my : Path : Args(0) {
my $pins = [];
my $problems = {};
my $rs = $c->user->problems->search( {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
}, {
order_by => { -desc => 'confirmed' },
rows => 50
@@ -41,11 +41,12 @@ sub my : Path : Args(0) {
push @$pins, {
latitude => $problem->latitude,
longitude => $problem->longitude,
- colour => $problem->state eq 'fixed' ? 'green' : 'red',
+ colour => $problem->is_fixed ? 'green' : 'red',
id => $problem->id,
title => $problem->title,
};
- push @{ $problems->{$problem->state} }, $problem;
+ my $state = $problem->is_fixed ? 'fixed' : $problem->is_closed ? 'closed' : 'confirmed';
+ push @{ $problems->{$state} }, $problem;
}
$c->stash->{problems_pager} = $rs->pager;
$c->stash->{problems} = $problems;
diff --git a/perllib/FixMyStreet/App/Controller/Open311.pm b/perllib/FixMyStreet/App/Controller/Open311.pm
index 459ce12c9..46d7801f6 100644
--- a/perllib/FixMyStreet/App/Controller/Open311.pm
+++ b/perllib/FixMyStreet/App/Controller/Open311.pm
@@ -216,8 +216,11 @@ sub output_requests : Private {
# Look up categories for this council or councils
my $problems = $c->cobrand->problems->search( $criteria, $attr );
- my %statusmap = ( 'fixed' => 'closed',
- 'confirmed' => 'open');
+ my %statusmap = (
+ map( { $_ => 'open' } FixMyStreet::DB::Result::Problem->open_states() ),
+ map( { $_ => 'closed' } FixMyStreet::DB::Result::Problem->fixed_states() ),
+ 'closed' => 'closed'
+ );
my @problemlist;
my @councils;
@@ -309,13 +312,13 @@ sub get_requests : Private {
# Only provide access to the published reports
my $criteria = {
- state => [ 'fixed', 'confirmed' ]
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ]
};
my %rules = (
service_request_id => [ '=', 'id' ],
service_code => [ '=', 'category' ],
- status => [ '=', 'state' ],
+ status => [ 'IN', 'state' ],
start_date => [ '>=', 'confirmed' ],
end_date => [ '<', 'confirmed' ],
agency_responsible => [ '~', 'council' ],
@@ -329,8 +332,8 @@ sub get_requests : Private {
my $key = $rules{$param}[1];
if ( 'status' eq $param ) {
$value = {
- 'open' => 'confirmed',
- 'closed' => 'fixed'
+ 'open' => [ FixMyStreet::DB::Result::Problem->open_states() ],
+ 'closed' => [ FixMyStreet::DB::Result::Problem->fixed_states(), 'closed' ],
}->{$value};
} elsif ( 'agency_responsible' eq $param ) {
my @valuelist;
@@ -403,7 +406,7 @@ sub get_request : Private {
}
my $criteria = {
- state => [ 'fixed', 'confirmed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
id => $id,
};
$c->forward( 'output_requests', [ $criteria ] );
diff --git a/perllib/FixMyStreet/App/Controller/Photo.pm b/perllib/FixMyStreet/App/Controller/Photo.pm
index 17862aa0a..b4fa3a457 100644
--- a/perllib/FixMyStreet/App/Controller/Photo.pm
+++ b/perllib/FixMyStreet/App/Controller/Photo.pm
@@ -48,7 +48,7 @@ sub index :Path :Args(0) {
$c->detach( 'no_photo' ) if $id =~ /\D/;
@photo = $c->cobrand->problems->search( {
id => $id,
- state => [ 'confirmed', 'fixed', 'partial' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states(), 'partial' ],
photo => { '!=', undef },
} );
}
diff --git a/perllib/FixMyStreet/App/Controller/Questionnaire.pm b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
index a87aff330..f0cb02115 100755
--- a/perllib/FixMyStreet/App/Controller/Questionnaire.pm
+++ b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
@@ -41,7 +41,7 @@ sub check_questionnaire : Private {
$c->detach;
}
- unless ( $questionnaire->problem->state eq 'confirmed' || $questionnaire->problem->state eq 'fixed' ) {
+ unless ( $questionnaire->problem->is_visible ) {
$c->detach('missing_problem');
}
@@ -121,13 +121,19 @@ sub submit_creator_fixed : Private {
my $questionnaire = $c->model( 'DB::Questionnaire' )->find_or_new(
{
problem_id => $c->stash->{problem},
- old_state => 'confirmed',
- new_state => 'fixed',
+ # we want to look for any previous questionnaire here rather than one for
+ # this specific open state -> fixed transistion
+ old_state => [ FixMyStreet::DB::Result::Problem->open_states() ],
+ new_state => 'fixed - user',
}
);
unless ( $questionnaire->in_storage ) {
+ my $old_state = $c->flash->{old_state};
+ $old_state = 'confirmed' unless FixMyStreet::DB::Result::Problem->open_states->{$old_state};
+
$questionnaire->ever_reported( $c->stash->{reported} eq 'Yes' ? 1 : 0 );
+ $questionnaire->old_state( $old_state );
$questionnaire->whensent( \'ms_current_timestamp()' );
$questionnaire->whenanswered( \'ms_current_timestamp()' );
$questionnaire->insert;
@@ -149,8 +155,10 @@ sub submit_standard : Private {
my $problem = $c->stash->{problem};
my $old_state = $problem->state;
my $new_state = '';
- $new_state = 'fixed' if $c->stash->{been_fixed} eq 'Yes' && $old_state eq 'confirmed';
- $new_state = 'confirmed' if $c->stash->{been_fixed} eq 'No' && $old_state eq 'fixed';
+ $new_state = 'fixed - user' if $c->stash->{been_fixed} eq 'Yes' &&
+ FixMyStreet::DB::Result::Problem->open_states()->{$old_state};
+ $new_state = 'confirmed' if $c->stash->{been_fixed} eq 'No' &&
+ FixMyStreet::DB::Result::Problem->fixed_states()->{$old_state};
# Record state change, if there was one
if ( $new_state ) {
@@ -159,7 +167,8 @@ sub submit_standard : Private {
}
# If it's not fixed and they say it's still not been fixed, record time update
- if ( $c->stash->{been_fixed} eq 'No' && $old_state eq 'confirmed' ) {
+ if ( $c->stash->{been_fixed} eq 'No' &&
+ FixMyStreet::DB::Result::Problem->open_states->{$old_state} ) {
$problem->lastupdate( \'ms_current_timestamp()' );
}
@@ -186,7 +195,7 @@ sub submit_standard : Private {
user => $problem->user,
text => $update,
state => 'confirmed',
- mark_fixed => $new_state eq 'fixed' ? 1 : 0,
+ mark_fixed => $new_state eq 'fixed - user' ? 1 : 0,
mark_open => $new_state eq 'confirmed' ? 1 : 0,
lang => $c->stash->{lang_code},
cobrand => $c->cobrand->moniker,
@@ -236,7 +245,7 @@ sub process_questionnaire : Private {
if ($c->stash->{been_fixed} eq 'No' || $c->stash->{been_fixed} eq 'Unknown') && !$c->stash->{another};
push @errors, _('Please provide some explanation as to why you\'re reopening this report')
- if $c->stash->{been_fixed} eq 'No' && $c->stash->{problem}->state eq 'fixed' && !$c->stash->{update};
+ if $c->stash->{been_fixed} eq 'No' && $c->stash->{problem}->is_fixed() && !$c->stash->{update};
$c->forward('/report/new/process_photo');
push @errors, $c->stash->{photo_error}
@@ -288,7 +297,7 @@ sub display : Private {
pins => [ {
latitude => $problem->latitude,
longitude => $problem->longitude,
- colour => $problem->state eq 'fixed' ? 'green' : 'red',
+ colour => $problem->is_fixed() ? 'green' : 'red',
} ],
);
}
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index 501dd2b41..add9d1371 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -51,8 +51,11 @@ sub update_problem : Private {
my $update = $c->stash->{update};
my $problem = $c->stash->{problem} || $update->problem;
+ # we may need this if we display the questionnaire
+ my $old_state = $problem->state;
+
if ( $update->mark_fixed ) {
- $problem->state('fixed');
+ $problem->state('fixed - user');
if ( $update->user->id == $problem->user->id ) {
$problem->send_questionnaire(0);
@@ -65,6 +68,10 @@ sub update_problem : Private {
}
}
+ if ( $update->problem_state ) {
+ $problem->state( $update->problem_state );
+ }
+
if ( $update->mark_open && $update->user->id == $problem->user->id ) {
$problem->state('confirmed');
}
@@ -75,6 +82,7 @@ sub update_problem : Private {
$c->stash->{problem_id} = $problem->id;
if ($display_questionnaire) {
+ $c->flash->{old_state} = $old_state;
$c->detach('/questionnaire/creator_fixed');
}
@@ -145,7 +153,7 @@ sub process_update : Private {
my ( $self, $c ) = @_;
my %params =
- map { $_ => scalar $c->req->param($_) } ( 'update', 'name', 'fixed', 'reopen' );
+ map { $_ => scalar $c->req->param($_) } ( 'update', 'name', 'fixed', 'state', 'reopen' );
$params{update} =
Utils::cleanup_text( $params{update}, { allow_multiline => 1 } );
@@ -170,6 +178,12 @@ sub process_update : Private {
}
);
+ if ( $params{state} ) {
+ $params{state} = 'fixed - council'
+ if $params{state} eq 'fixed' && $c->user && $c->user->belongs_to_council( $update->problem->council );
+ $update->problem_state( $params{state} );
+ }
+
$c->stash->{update} = $update;
$c->stash->{add_alert} = $c->req->param('add_alert');
@@ -187,6 +201,22 @@ return false.
sub check_for_errors : Private {
my ( $self, $c ) = @_;
+ # they have to be an authority user to update the state
+ if ( $c->req->param('state') ) {
+ my $error = 0;
+ $error = 1 unless $c->user && $c->user->belongs_to_council( $c->stash->{update}->problem->council );
+
+ my $state = $c->req->param('state');
+ $error = 1 unless ( grep { $state eq $_ } ( qw/confirmed closed fixed investigating planned/, 'in progress', 'fixed', 'fixed - user', 'fixed - council' ) );
+
+ if ( $error ) {
+ $c->stash->{errors} ||= [];
+ push @{ $c->stash->{errors} }, _('There was a problem with your update. Please try again.');
+ return;
+ }
+
+ }
+
# let the model check for errors
$c->stash->{field_errors} ||= {};
my %field_errors = (
diff --git a/perllib/FixMyStreet/App/Controller/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index feafc4b77..f7fb5dec5 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -315,7 +315,7 @@ sub load_and_group_problems : Private {
my $page = $c->req->params->{p} || 1;
my $where = {
- state => [ 'confirmed', 'fixed' ]
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ]
};
if ($c->stash->{ward}) {
$where->{areas} = { 'like', '%,' . $c->stash->{ward}->{id} . ',%' };
@@ -411,14 +411,16 @@ sub add_row {
? 'unknown'
: ($problem->{age} > $fourweeks ? 'older' : 'new');
# Fixed problems are either old or new
- push @{$fixed->{$council}{$duration_str}}, $problem if $problem->{state} eq 'fixed';
+ push @{$fixed->{$council}{$duration_str}}, $problem if
+ exists FixMyStreet::DB::Result::Problem->fixed_states()->{$problem->{state}};
# Open problems are either unknown, older, or new
- push @{$open->{$council}{$type}}, $problem if $problem->{state} eq 'confirmed';
+ push @{$open->{$council}{$type}}, $problem if
+ exists FixMyStreet::DB::Result::Problem->open_states->{$problem->{state}};
push @$pins, {
latitude => $problem->{latitude},
longitude => $problem->{longitude},
- colour => $problem->{state} eq 'fixed' ? 'green' : 'red',
+ colour => FixMyStreet::DB::Result::Problem->fixed_states()->{$problem->{state}} ? 'green' : 'red',
id => $problem->{id},
title => $problem->{title},
};
diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm
index 26a1a1459..10f994d9f 100644
--- a/perllib/FixMyStreet/App/Controller/Tokens.pm
+++ b/perllib/FixMyStreet/App/Controller/Tokens.pm
@@ -75,7 +75,7 @@ sub confirm_problem : Path('/P') {
$c->authenticate( { email => $problem->user->email }, 'no_password' );
$c->set_session_cookie_expire(0);
- if ( $old_state eq 'confirmed' || $old_state eq 'fixed' ) {
+ if ( FixMyStreet::DB::Result::Problem->visible_states()->{$old_state} ) {
my $report_uri = $c->uri_for( '/report', $problem->id );
$c->res->redirect($report_uri);
}
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 134111076..69718f613 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -894,14 +894,24 @@ sub generate_problem_banner {
my ( $self, $problem ) = @_;
my $banner = {};
- if ($problem->state eq 'confirmed' && time() - $problem->lastupdate_local->epoch > 8*7*24*60*60) {
- $banner->{id} = 'unknown';
+ if ( $problem->is_open && time() - $problem->lastupdate_local->epoch > 8 * 7 * 24 * 60 * 60 )
+ {
+ $banner->{id} = 'unknown';
$banner->{text} = _('This problem is old and of unknown status.');
}
- if ($problem->state eq 'fixed') {
+ if ($problem->is_fixed) {
$banner->{id} = 'fixed';
$banner->{text} = _('This problem has been fixed') . '.';
}
+ if ($problem->is_closed) {
+ $banner->{id} = 'closed';
+ $banner->{text} = _('This problem has been closed') . '.';
+ }
+
+ if ( grep { $problem->state eq $_ } ( 'investigating', 'in progress', 'planned' ) ) {
+ $banner->{id} = 'progress';
+ $banner->{text} = _('This problem is in progress') . '.';
+ }
return $banner;
}
diff --git a/perllib/FixMyStreet/Cobrand/EmptyHomes.pm b/perllib/FixMyStreet/Cobrand/EmptyHomes.pm
index eda0b2882..189daee0c 100644
--- a/perllib/FixMyStreet/Cobrand/EmptyHomes.pm
+++ b/perllib/FixMyStreet/Cobrand/EmptyHomes.pm
@@ -112,7 +112,7 @@ sub generate_problem_banner {
my ( $self, $problem ) = @_;
my $banner = {};
- if ($problem->state eq 'fixed') {
+ if ($problem->is_fixed ) {
$banner->{id} = 'fixed';
$banner->{text} = _('This problem has been fixed') . '.';
}
diff --git a/perllib/FixMyStreet/DB/Result/Comment.pm b/perllib/FixMyStreet/DB/Result/Comment.pm
index ae152eb31..958194eb8 100644
--- a/perllib/FixMyStreet/DB/Result/Comment.pm
+++ b/perllib/FixMyStreet/DB/Result/Comment.pm
@@ -52,6 +52,8 @@ __PACKAGE__->add_columns(
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"anonymous",
{ data_type => "boolean", is_nullable => 0 },
+ "problem_state",
+ { data_type => "text", is_nullable => 1 },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to(
@@ -68,8 +70,8 @@ __PACKAGE__->belongs_to(
);
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-23 15:49:48
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:TYFusbxkOkAewaiZYZVJUA
+# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-27 10:07:32
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ilLn3dlagg5COdpZDmzrVQ
use DateTime::TimeZone;
use Image::Size;
@@ -147,6 +149,22 @@ sub get_photo_params {
return $photo;
}
+=head2 meta_problem_state
+
+Returns a string suitable for display in the update meta section.
+Mostly removes the '- council/user' bit from fixed states
+
+=cut
+
+sub meta_problem_state {
+ my $self = shift;
+
+ my $state = $self->problem_state;
+ $state =~ s/ -.*$//;
+
+ return $state;
+}
+
# we need the inline_constructor bit as we don't inherit from Moose
__PACKAGE__->meta->make_immutable( inline_constructor => 0 );
diff --git a/perllib/FixMyStreet/DB/Result/Problem.pm b/perllib/FixMyStreet/DB/Result/Problem.pm
index 6472b91db..987c92c64 100644
--- a/perllib/FixMyStreet/DB/Result/Problem.pm
+++ b/perllib/FixMyStreet/DB/Result/Problem.pm
@@ -121,6 +121,92 @@ use Utils;
with 'FixMyStreet::Roles::Abuser';
+=head2
+
+ @states = FixMyStreet::DB::Problem::open_states();
+
+Get a list or states that are regarded as open. If called in
+array context then returns an array of names, otherwise returns a
+HASHREF.
+
+=cut
+
+sub open_states {
+ my $states = {
+ 'confirmed' => 1,
+ 'investigating' => 1,
+ 'planned' => 1,
+ 'in progress' => 1,
+ };
+
+ return wantarray ? keys %{$states} : $states;
+}
+
+=head2
+
+ @states = FixMyStreet::DB::Problem::fixed_states();
+
+Get a list or states that should be regarded as fixed. If called in
+array context then returns an array of names, otherwise returns a
+HASHREF.
+
+=cut
+
+sub fixed_states {
+ my $states = {
+ 'fixed' => 1,
+ 'fixed - user' => 1,
+ 'fixed - council' => 1,
+ };
+
+ return wantarray ? keys %{ $states } : $states;
+}
+
+=head2
+
+ @states = FixMyStreet::DB::Problem::closed_states();
+
+Get a list or states that should be regarded as closed. If called in
+array context then returns an array of names, otherwise returns a
+HASHREF.
+
+=cut
+
+sub closed_states {
+ my $states = {
+ 'closed' => 1,
+ };
+
+ return wantarray ? keys %{$states} : $states;
+}
+
+
+=head2
+
+ @states = FixMyStreet::DB::Problem::visible_states();
+
+Get a list or states that should be visible on the site. If called in
+array context then returns an array of names, otherwise returns a
+HASHREF.
+
+=cut
+
+sub visible_states {
+ my $states = {
+ 'confirmed' => 1,
+ 'planned' => 1,
+ 'investigating' => 1,
+ 'in progress' => 1,
+ 'fixed' => 1,
+ 'fixed - council' => 1,
+ 'fixed - user' => 1,
+ 'closed' => 1,
+ };
+
+ return wantarray ? keys %{$states} : $states;
+}
+
+
my $tz = DateTime::TimeZone->new( name => "local" );
sub confirmed_local {
@@ -286,6 +372,55 @@ sub get_photo_params {
return $photo;
}
+=head2 is_open
+
+Returns 1 if the problem is in a open state otherwise 0.
+
+=cut
+
+sub is_open {
+ my $self = shift;
+
+ return exists $self->open_states->{ $self->state } ? 1 : 0;
+}
+
+
+=head2 is_fixed
+
+Returns 1 if the problem is in a fixed state otherwise 0.
+
+=cut
+
+sub is_fixed {
+ my $self = shift;
+
+ return exists $self->fixed_states->{ $self->state } ? 1 : 0;
+}
+
+=head2 is_closed
+
+Returns 1 if the problem is in a closed state otherwise 0.
+
+=cut
+
+sub is_closed {
+ my $self = shift;
+
+ return exists $self->closed_states->{ $self->state } ? 1 : 0;
+}
+
+=head2 is_visible
+
+Returns 1 if the problem should be displayed on the site otherwise 0.
+
+=cut
+
+sub is_visible {
+ my $self = shift;
+
+ return exists $self->visible_states->{ $self->state } ? 1 : 0;
+}
+
=head2 meta_line
Returns a string to be used on a problem report page, describing some of the
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index cf4fc56d5..56d726a8d 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -26,6 +26,8 @@ __PACKAGE__->add_columns(
{ data_type => "text", is_nullable => 1 },
"password",
{ data_type => "text", default_value => "", is_nullable => 0 },
+ "from_council",
+ { data_type => "integer", is_nullable => 1 },
"flagged",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
);
@@ -51,8 +53,8 @@ __PACKAGE__->has_many(
);
-# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-23 15:49:48
-# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:T2JK+KyfoE2hkCLgreq1XQ
+# Created by DBIx::Class::Schema::Loader v0.07010 @ 2011-06-27 10:25:21
+# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9IHuqRTcHZCqJeBAaiQxzw
__PACKAGE__->add_columns(
"password" => {
@@ -134,4 +136,40 @@ sub alert_for_problem {
} );
}
+sub council {
+ my $self = shift;
+
+ return '' unless $self->from_council;
+
+ my $key = 'council_name:' . $self->from_council;
+ my $result = Memcached::get($key);
+
+ unless ($result) {
+ my $area_info = mySociety::MaPit::call('area', $self->from_council);
+ $result = $area_info->{name};
+ Memcached::set($key, $result, 86400);
+ }
+
+ return $result;
+}
+
+=head2 belongs_to_council
+
+ $belongs_to_council = $user->belongs_to_council( $council_list );
+
+Returns true if the user belongs to the comma seperated list of council ids passed in
+
+=cut
+
+sub belongs_to_council {
+ my $self = shift;
+ my $council = shift;
+
+ my %councils = map { $_ => 1 } split ',', $council;
+
+ return 1 if $self->from_council && $councils{ $self->from_council };
+
+ return 0;
+}
+
1;
diff --git a/perllib/FixMyStreet/DB/ResultSet/AlertType.pm b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm
index 46009cb85..bf085e32a 100644
--- a/perllib/FixMyStreet/DB/ResultSet/AlertType.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm
@@ -50,6 +50,7 @@ sub email_alerts ($) {
# XXX Ugh - needs work
$query =~ s/\?/alert.parameter/ if ($query =~ /\?/);
$query =~ s/\?/alert.parameter2/ if ($query =~ /\?/);
+
$query = dbh()->prepare($query);
$query->execute();
my $last_alert_id;
@@ -74,10 +75,13 @@ sub email_alerts ($) {
}
# create problem status message for the templates
- $data{state_message} =
- $row->{state} eq 'fixed'
- ? _("This report is currently marked as fixed.")
- : _("This report is currently marked as open.");
+ if ( FixMyStreet::DB::Result::Problem::fixed_states()->{$row->{state}} ) {
+ $data{state_message} = _("This report is currently marked as fixed.");
+ } elsif ( FixMyStreet::DB::Result::Problem::closed_states()->{$row->{state}} ) {
+ $data{state_message} = _("This report is currently marked as closed.")
+ } else {
+ $data{state_message} = _("This report is currently marked as open.");
+ }
my $url = $cobrand->base_url_for_emails( $row->{alert_cobrand_data} );
if ($row->{item_text}) {
@@ -130,11 +134,12 @@ sub email_alerts ($) {
$d = mySociety::Locale::in_gb_locale {
sprintf("%f", int($d*10+0.5)/10);
};
+ my $states = "'" . join( "', '", FixMyStreet::DB::Result::Problem::visible_states() ) . "'";
my %data = ( template => $template, data => '', alert_id => $alert->id, alert_email => $alert->user->email, lang => $alert->lang, cobrand => $alert->cobrand, cobrand_data => $alert->cobrand_data );
my $q = "select problem.id, problem.title from problem_find_nearby(?, ?, ?) as nearby, problem, users
where nearby.problem_id = problem.id
and problem.user_id = users.id
- and problem.state in ('confirmed', 'fixed')
+ and problem.state in ($states)
and problem.confirmed >= ? and problem.confirmed >= ms_current_timestamp() - '7 days'::interval
and (select whenqueued from alert_sent where alert_sent.alert_id = ? and alert_sent.parameter::integer = problem.id) is null
and users.email <> ?
diff --git a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
index 3b3a3d90b..04089096e 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Nearby.pm
@@ -8,7 +8,7 @@ sub nearby {
my ( $rs, $c, $dist, $ids, $limit, $mid_lat, $mid_lon, $interval ) = @_;
my $params = {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem::visible_states() ],
};
$params->{'current_timestamp-lastupdate'} = { '<', \"'$interval'::interval" }
if $interval;
diff --git a/perllib/FixMyStreet/DB/ResultSet/Problem.pm b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
index ca329ab59..8c1f95bae 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Problem.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Problem.pm
@@ -21,7 +21,7 @@ sub recent_fixed {
my $result = Memcached::get($key);
unless ($result) {
$result = $rs->search( {
- state => 'fixed',
+ state => [ FixMyStreet::DB::Result::Problem->fixed_states() ],
lastupdate => { '>', \"current_timestamp-'1 month'::interval" },
} )->count;
Memcached::set($key, $result, 3600);
@@ -50,7 +50,7 @@ sub recent_new {
my $result = Memcached::get($key);
unless ($result) {
$result = $rs->search( {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
confirmed => { '>', \"current_timestamp-'$interval'::interval" },
} )->count;
Memcached::set($key, $result, 3600);
@@ -66,7 +66,7 @@ sub recent {
my $result = Memcached::get($key);
unless ($result) {
$result = [ $rs->search( {
- state => [ 'confirmed', 'fixed' ]
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ]
}, {
columns => [ 'id', 'title' ],
order_by => { -desc => 'confirmed' },
@@ -81,7 +81,7 @@ sub recent_photos {
my ( $rs, $num, $lat, $lon, $dist ) = @_;
my $probs;
my $query = {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
photo => { '!=', undef },
};
my $attrs = {
@@ -125,7 +125,7 @@ sub around_map {
$attr->{rows} = $limit if $limit;
my $q = {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
latitude => { '>=', $min_lat, '<', $max_lat },
longitude => { '>=', $min_lon, '<', $max_lon },
};
@@ -177,7 +177,7 @@ sub unique_users {
my ( $rs ) = @_;
return $rs->search( {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
}, {
select => [ { count => { distinct => 'user_id' } } ],
as => [ 'count' ]
@@ -187,11 +187,12 @@ sub unique_users {
sub categories_summary {
my ( $rs ) = @_;
+ my $fixed_case = "case when state IN ( '" . join( "', '", FixMyStreet::DB::Result::Problem->fixed_states() ) . "' ) then 1 else null end";
my $categories = $rs->search( {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem->visible_states() ],
whensent => { '<' => \"NOW() - INTERVAL '4 weeks'" },
}, {
- select => [ 'category', { count => 'id' }, { count => \"case when state='fixed' then 1 else null end" } ],
+ select => [ 'category', { count => 'id' }, { count => \$fixed_case } ],
as => [ 'category', 'c', 'fixed' ],
group_by => [ 'category' ],
result_class => 'DBIx::Class::ResultClass::HashRefInflator'
diff --git a/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm b/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
index e490c77a6..665e0e3e0 100644
--- a/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
+++ b/perllib/FixMyStreet/DB/ResultSet/Questionnaire.pm
@@ -19,7 +19,7 @@ sub send_questionnaires_period {
# Select all problems that need a questionnaire email sending
my $q_params = {
- state => [ 'confirmed', 'fixed' ],
+ state => [ FixMyStreet::DB::Result::Problem::visible_states() ],
whensent => [
'-and',
{ '!=', undef },
diff --git a/perllib/FixMyStreet/Map/OSM.pm b/perllib/FixMyStreet/Map/OSM.pm
index c848c9e5e..be185c35c 100644
--- a/perllib/FixMyStreet/Map/OSM.pm
+++ b/perllib/FixMyStreet/Map/OSM.pm
@@ -116,7 +116,7 @@ sub map_pins {
# id => $p->id,
# title => $p->title,
#}
- [ $p->latitude, $p->longitude, $p->state eq 'fixed' ? 'green' : 'red', $p->id, $p->title ]
+ [ $p->latitude, $p->longitude, $p->is_fixed ? 'green' : 'red', $p->id, $p->title ]
} @$around_map, @$nearby;
return (\@pins, $around_map_list, $nearby, $dist);
diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t
index 8d55bbc18..08cb4fb0d 100644
--- a/t/app/controller/admin.t
+++ b/t/app/controller/admin.t
@@ -77,11 +77,13 @@ my $alert = FixMyStreet::App->model('DB::Alert')->find_or_create(
);
subtest 'check summary counts' => sub {
- my $problems = FixMyStreet::App->model('DB::Problem')->search( { state => { -in => [qw/confirmed fixed/] } } );
+ my $problems = FixMyStreet::App->model('DB::Problem')->search( { state => { -in => [qw/confirmed fixed closed investigating planned/, 'in progress', 'fixed - user', 'fixed - council'] } } );
my $problem_count = $problems->count;
$problems->update( { cobrand => '' } );
+ FixMyStreet::App->model('DB::Problem')->search( { council => 2489 } )->update( { council => 1 } );
+
my $q = FixMyStreet::App->model('DB::Questionnaire')->find_or_new( { problem => $report, });
$q->whensent( \'ms_current_timestamp()' );
$q->in_storage ? $q->update : $q->insert;
@@ -130,6 +132,7 @@ subtest 'check summary counts' => sub {
$alert->cobrand('');
$alert->update;
+ FixMyStreet::App->model('DB::Problem')->search( { council => 1 } )->update( { council => 2489 } );
ok $mech->host('fixmystreet.com');
};
@@ -729,6 +732,76 @@ for my $test (
};
}
+for my $test (
+ {
+ desc => 'user is problem owner',
+ problem_user => $user,
+ update_user => $user,
+ update_fixed => 0,
+ update_reopen => 0,
+ update_state => undef,
+ user_council => undef,
+ content => 'user is problem owner',
+ },
+ {
+ desc => 'user is council user',
+ problem_user => $user,
+ update_user => $user2,
+ update_fixed => 0,
+ update_reopen => 0,
+ update_state => undef,
+ user_council => 2504,
+ content => 'user is from same council as problem - 2504',
+ },
+ {
+ desc => 'update changed problem state',
+ problem_user => $user,
+ update_user => $user2,
+ update_fixed => 0,
+ update_reopen => 0,
+ update_state => 'planned',
+ user_council => 2504,
+ content => 'Update changed problem state to planned',
+ },
+ {
+ desc => 'update marked problem as fixed',
+ problem_user => $user,
+ update_user => $user3,
+ update_fixed => 1,
+ update_reopen => 0,
+ update_state => undef,
+ user_council => undef,
+ content => 'Update marked problem as fixed',
+ },
+ {
+ desc => 'update reopened problem',
+ problem_user => $user,
+ update_user => $user,
+ update_fixed => 0,
+ update_reopen => 1,
+ update_state => undef,
+ user_council => undef,
+ content => 'Update reopened problem',
+ },
+) {
+ subtest $test->{desc} => sub {
+ $report->user( $test->{problem_user} );
+ $report->update;
+
+ $update->user( $test->{update_user} );
+ $update->problem_state( $test->{update_state} );
+ $update->mark_fixed( $test->{update_fixed} );
+ $update->mark_open( $test->{update_reopen} );
+ $update->update;
+
+ $test->{update_user}->from_council( $test->{user_council} );
+ $test->{update_user}->update;
+
+ $mech->get_ok('/admin/update_edit/' . $update->id );
+ $mech->content_contains( $test->{content} );
+ };
+}
+
subtest 'editing update email creates new user if required' => sub {
my $user = FixMyStreet::App->model('DB::User')->find(
{ email => 'test4@example.com' }
@@ -891,6 +964,97 @@ subtest 'show flagged entries' => sub {
$mech->content_contains( $user->email );
};
+subtest 'user search' => sub {
+ $mech->get_ok('/admin/search_users');
+ $mech->get_ok('/admin/search_users?search=' . $user->name);
+
+ $mech->content_contains( $user->name);
+ my $u_id = $user->id;
+ $mech->content_like( qr{user_edit/$u_id">Edit</a>} );
+
+ $mech->get_ok('/admin/search_users?search=' . $user->email);
+
+ $mech->content_like( qr{user_edit/$u_id">Edit</a>} );
+
+ $user->from_council(2509);
+ $user->update;
+ $mech->get_ok('/admin/search_users?search=2509' );
+ $mech->content_contains(2509);
+};
+
+$log_entries = FixMyStreet::App->model('DB::AdminLog')->search(
+ {
+ object_type => 'user',
+ object_id => $user->id
+ },
+ {
+ order_by => { -desc => 'id' },
+ }
+);
+
+is $log_entries->count, 0, 'no admin log entries';
+
+for my $test (
+ {
+ desc => 'edit user name',
+ fields => {
+ name => 'Test User',
+ email => 'test@example.com',
+ council => 2509,
+ },
+ changes => {
+ name => 'Changed User',
+ },
+ log_count => 1,
+ log_entries => [qw/edit/],
+ },
+ {
+ desc => 'edit user email',
+ fields => {
+ name => 'Changed User',
+ email => 'test@example.com',
+ council => 2509,
+ },
+ changes => {
+ email => 'changed@example.com',
+ },
+ log_count => 2,
+ log_entries => [qw/edit edit/],
+ },
+ {
+ desc => 'edit user council',
+ fields => {
+ name => 'Changed User',
+ email => 'changed@example.com',
+ council => 2509,
+ },
+ changes => {
+ council => 2607,
+ },
+ log_count => 3,
+ log_entries => [qw/edit edit edit/],
+ },
+) {
+ subtest $test->{desc} => sub {
+ $mech->get_ok( '/admin/user_edit/' . $user->id );
+
+ my $visible = $mech->visible_form_values;
+ is_deeply $visible, $test->{fields}, 'expected user';
+
+ my $expected = {
+ %{ $test->{fields} },
+ %{ $test->{changes} }
+ };
+
+ $mech->submit_form_ok( { with_fields => $expected } );
+
+ $visible = $mech->visible_form_values;
+ is_deeply $visible, $expected, 'user updated';
+
+ $mech->content_contains( 'Updated!' );
+ };
+}
+
$mech->delete_user( $user );
$mech->delete_user( $user2 );
$mech->delete_user( $user3 );
diff --git a/t/app/controller/questionnaire.t b/t/app/controller/questionnaire.t
index e56734bfc..af3b373ac 100644
--- a/t/app/controller/questionnaire.t
+++ b/t/app/controller/questionnaire.t
@@ -204,7 +204,12 @@ foreach my $test (
}
my $result;
- $result = 'fixed' if $test->{fields}{been_fixed} eq 'Yes';
+ $result = 'fixed - user'
+ if $test->{fields}{been_fixed} eq 'Yes'
+ && $test->{problem_state} ne 'fixed';
+ $result = 'fixed'
+ if $test->{fields}{been_fixed} eq 'Yes'
+ && $test->{problem_state} eq 'fixed';
$result = 'confirmed' if $test->{fields}{been_fixed} eq 'No';
$result = 'unknown' if $test->{fields}{been_fixed} eq 'Unknown';
@@ -214,7 +219,7 @@ foreach my $test (
# Check the right HTML page has been returned
$mech->content_like( qr/<title>[^<]*Questionnaire/m );
$mech->content_contains( 'glad to hear it&rsquo;s been fixed' )
- if $result eq 'fixed';
+ if $result =~ /fixed/;
$mech->content_contains( 'get some more information about the status of your problem' )
if $result eq 'unknown';
$mech->content_contains( "sorry to hear that" )
@@ -235,6 +240,16 @@ foreach my $test (
{ problem_id => $report->id }
);
is $c->text, $test->{fields}{update} || $test->{comment};
+ if ( $result =~ /fixed/ ) {
+ ok $c->mark_fixed, 'comment marked as fixed';
+ ok !$c->mark_open, 'comment not marked as open';
+ } elsif ( $result eq 'confirmed' ) {
+ ok $c->mark_open, 'comment marked as open';
+ ok !$c->mark_fixed, 'comment not marked as fixed';
+ } elsif ( $result eq 'unknown' ) {
+ ok !$c->mark_open, 'comment not marked as open';
+ ok !$c->mark_fixed, 'comment not marked as fixed';
+ }
}
# Reset questionnaire for next test
@@ -250,6 +265,55 @@ foreach my $test (
};
}
+
+for my $test (
+ {
+ state => 'confirmed',
+ fixed => 0
+ },
+ {
+ state => 'planned',
+ fixed => 0
+ },
+ {
+ state => 'in progress',
+ fixed => 0
+ },
+ {
+ state => 'investigating',
+ fixed => 0
+ },
+ {
+ state => 'closed',
+ fixed => 0
+ },
+ {
+ state => 'fixed',
+ fixed => 1
+ },
+ {
+ state => 'fixed - council',
+ fixed => 1
+ },
+ {
+ state => 'fixed - user',
+ fixed => 1
+ },
+) {
+ subtest "correct fixed text for state $test->{state}" => sub {
+ $report->state ( $test->{state} );
+ $report->update;
+
+ $mech->get_ok("/Q/" . $token->token);
+ $mech->title_like( qr/Questionnaire/ );
+ if ( $test->{fixed} ) {
+ $mech->content_contains('An update marked this problem as fixed');
+ } else {
+ $mech->content_lacks('An update marked this problem as fixed');
+ }
+ };
+}
+
# EHA extra checking
ok $mech->host("reportemptyhomes.com"), 'change host to reportemptyhomes';
diff --git a/t/app/controller/report_display.t b/t/app/controller/report_display.t
index 1f857a387..a70d5b9e9 100644
--- a/t/app/controller/report_display.t
+++ b/t/app/controller/report_display.t
@@ -235,6 +235,54 @@ for my $test (
banner_text => 'This problem has been fixed.',
fixed => 1
},
+ {
+ description => 'user fixed report',
+ date => DateTime->now,
+ state => 'fixed - user',
+ banner_id => 'fixed',
+ banner_text => 'This problem has been fixed.',
+ fixed => 1
+ },
+ {
+ description => 'council fixed report',
+ date => DateTime->now,
+ state => 'fixed - council',
+ banner_id => 'fixed',
+ banner_text => 'This problem has been fixed.',
+ fixed => 1
+ },
+ {
+ description => 'closed report',
+ date => DateTime->now,
+ state => 'closed',
+ banner_id => 'closed',
+ banner_text => 'This problem has been closed.',
+ fixed => 0
+ },
+ {
+ description => 'investigating report',
+ date => DateTime->now,
+ state => 'investigating',
+ banner_id => 'progress',
+ banner_text => 'This problem is in progress.',
+ fixed => 0
+ },
+ {
+ description => 'planned report',
+ date => DateTime->now,
+ state => 'planned',
+ banner_id => 'progress',
+ banner_text => 'This problem is in progress.',
+ fixed => 0
+ },
+ {
+ description => 'in progressreport',
+ date => DateTime->now,
+ state => 'in progress',
+ banner_id => 'progress',
+ banner_text => 'This problem is in progress.',
+ fixed => 0
+ },
) {
subtest "banner for $test->{description}" => sub {
$report->confirmed( $test->{date}->ymd . ' ' . $test->{date}->hms );
@@ -260,6 +308,55 @@ for my $test (
};
}
+for my $test (
+ {
+ desc => 'no state dropdown if user not from authority',
+ from_council => 0,
+ no_state => 1,
+ report_council => '2504',
+ },
+ {
+ desc => 'state dropdown if user from authority',
+ from_council => 2504,
+ no_state => 0,
+ report_council => '2504',
+ },
+ {
+ desc => 'no state dropdown if user not from same council as problem',
+ from_council => 2505,
+ no_state => 1,
+ report_council => '2504',
+ },
+ {
+ desc => 'state dropdown if user from authority and problem sent to multiple councils',
+ from_council => 2504,
+ no_state => 0,
+ report_council => '2504,2506',
+ },
+) {
+ subtest $test->{desc} => sub {
+ $mech->log_in_ok( $user->email );
+ $user->from_council( $test->{from_council} );
+ $user->update;
+
+ $report->discard_changes;
+ $report->council( $test->{report_council} );
+ $report->update;
+
+ $mech->get_ok("/report/$report_id");
+ my $fields = $mech->visible_form_values( 'updateForm' );
+ if ( $test->{no_state} ) {
+ ok !$fields->{state};
+ } else {
+ ok $fields->{state};
+ }
+ };
+}
+
+$report->discard_changes;
+$report->council( 2504 );
+$report->update;
+
# tidy up
$mech->delete_user('test@example.com');
done_testing();
diff --git a/t/app/controller/report_import.t b/t/app/controller/report_import.t
index 385445565..d6bff240c 100644
--- a/t/app/controller/report_import.t
+++ b/t/app/controller/report_import.t
@@ -264,69 +264,71 @@ subtest "Submit a correct entry (with location)" => sub {
subtest "Submit a correct entry (with location) to cobrand" => sub {
- skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 8 )
- unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{fiksgatami};
- mySociety::MaPit::configure('http://mapit.nuug.no/');
- ok $mech->host("fiksgatami.no"), 'change host to fiksgatami';
+ SKIP: {
+ skip( "Need 'fiksgatami' in ALLOWED_COBRANDS config", 20 )
+ unless FixMyStreet::App->config->{ALLOWED_COBRANDS} =~ m{fiksgatami};
+ mySociety::MaPit::configure('http://mapit.nuug.no/');
+ ok $mech->host("fiksgatami.no"), 'change host to fiksgatami';
- $mech->get_ok('/import');
-
- $mech->submit_form_ok( #
- {
- with_fields => {
- service => 'test-script',
- lat => '59',
- lon => '10',
- name => 'Test User ll',
- email => 'test-ll@example.com',
- subject => 'Test report ll',
- detail => 'This is a test report ll',
- photo => $sample_file,
- }
- },
- "fill in form"
- );
-
- is_deeply( $mech->import_errors, [], "got no errors" );
- is $mech->content, 'SUCCESS', "Got success response";
-
- # check that we have received the email
- $mech->email_count_is(1);
- my $email = $mech->get_email;
- $mech->clear_emails_ok;
-
- my ($token_url) = $email->body =~ m{(http://\S+)};
- ok $token_url, "Found a token url $token_url";
-
- # go to the token url
- $mech->get_ok($token_url);
-
- # check that we are on '/report/new'
- is $mech->uri->path, '/report/new', "sent to /report/new";
-
- # check that fields are prefilled for us
- is_deeply $mech->visible_form_values,
- {
- name => 'Test User ll',
- title => 'Test report ll',
- detail => 'This is a test report ll',
- photo => '',
- phone => '',
- may_show_name => '1',
- },
- "check imported fields are shown";
-
- my $user =
- FixMyStreet::App->model('DB::User')
- ->find( { email => 'test-ll@example.com' } );
- ok $user, "Found a user";
+ $mech->get_ok('/import');
- my $report = $user->problems->first;
- is $report->state, 'partial', 'is still partial';
- is $report->title, 'Test report ll', 'title is correct';
- is $report->lang, 'nb', 'language is correct';
+ $mech->submit_form_ok( #
+ {
+ with_fields => {
+ service => 'test-script',
+ lat => '59',
+ lon => '10',
+ name => 'Test User ll',
+ email => 'test-ll@example.com',
+ subject => 'Test report ll',
+ detail => 'This is a test report ll',
+ photo => $sample_file,
+ }
+ },
+ "fill in form"
+ );
- $mech->delete_user($user);
+ is_deeply( $mech->import_errors, [], "got no errors" );
+ is $mech->content, 'SUCCESS', "Got success response";
+
+ # check that we have received the email
+ $mech->email_count_is(1);
+ my $email = $mech->get_email;
+ $mech->clear_emails_ok;
+
+ my ($token_url) = $email->body =~ m{(http://\S+)};
+ ok $token_url, "Found a token url $token_url";
+
+ # go to the token url
+ $mech->get_ok($token_url);
+
+ # check that we are on '/report/new'
+ is $mech->uri->path, '/report/new', "sent to /report/new";
+
+ # check that fields are prefilled for us
+ is_deeply $mech->visible_form_values,
+ {
+ name => 'Test User ll',
+ title => 'Test report ll',
+ detail => 'This is a test report ll',
+ photo => '',
+ phone => '',
+ may_show_name => '1',
+ },
+ "check imported fields are shown";
+
+ my $user =
+ FixMyStreet::App->model('DB::User')
+ ->find( { email => 'test-ll@example.com' } );
+ ok $user, "Found a user";
+
+ my $report = $user->problems->first;
+ is $report->state, 'partial', 'is still partial';
+ is $report->title, 'Test report ll', 'title is correct';
+ is $report->lang, 'nb', 'language is correct';
+
+ $mech->delete_user($user);
+ }
};
done_testing();
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index 856e7d763..993c6758e 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -404,6 +404,203 @@ for my $test (
};
}
+$report->state('confirmed');
+$report->update;
+
+subtest 'check non authority user cannot change set state' => sub {
+ $mech->log_in_ok( $user->email );
+ $user->from_council( 0 );
+ $user->update;
+
+ $mech->get_ok("/report/$report_id");
+ $mech->submit_form_ok( {
+ form_number => 2,
+ fields => {
+ submit_update => 1,
+ id => $report_id,
+ name => $user->name,
+ rznvy => $user->email,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'this is a forbidden update',
+ state => 'fixed - council',
+ },
+ },
+ 'submitted with state',
+ );
+
+ is $mech->uri->path, "/report/update", "at /report/update";
+
+ my $errors = $mech->page_errors;
+ is_deeply $errors, [ 'There was a problem with your update. Please try again.' ], 'error message';
+
+ is $report->state, 'confirmed', 'state unchanged';
+};
+
+for my $state ( qw/unconfirmed hidden partial/ ) {
+ subtest "check that update cannot set state to $state" => sub {
+ $mech->log_in_ok( $user->email );
+ $user->from_council( 2504 );
+ $user->update;
+
+ $mech->get_ok("/report/$report_id");
+ $mech->submit_form_ok( {
+ form_number => 2,
+ fields => {
+ submit_update => 1,
+ id => $report_id,
+ name => $user->name,
+ rznvy => $user->email,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'this is a forbidden update',
+ state => $state,
+ },
+ },
+ 'submitted with state',
+ );
+
+ is $mech->uri->path, "/report/update", "at /report/update";
+
+ my $errors = $mech->page_errors;
+ is_deeply $errors, [ 'There was a problem with your update. Please try again.' ], 'error message';
+
+ is $report->state, 'confirmed', 'state unchanged';
+ };
+}
+
+for my $test (
+ {
+ desc => 'from authority user marks report as investigating',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to investigating',
+ state => 'investigating',
+ },
+ state => 'investigating',
+ },
+ {
+ desc => 'from authority user marks report as planned',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to planned',
+ state => 'planned',
+ },
+ state => 'planned',
+ },
+ {
+ desc => 'from authority user marks report as in progress',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to in progress',
+ state => 'in progress',
+ },
+ state => 'in progress',
+ },
+ {
+ desc => 'from authority user marks report as closed',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to closed',
+ state => 'closed',
+ },
+ state => 'closed',
+ },
+ {
+ desc => 'from authority user marks report as fixed',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to fixed',
+ state => 'fixed',
+ },
+ state => 'fixed - council',
+ },
+ {
+ desc => 'from authority user marks report as confirmed',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to confirmed',
+ state => 'confirmed',
+ },
+ state => 'confirmed',
+ },
+ {
+ desc => 'from authority user marks report sent to two councils as fixed',
+ fields => {
+ name => $user->name,
+ may_show_name => 1,
+ add_alert => 0,
+ photo => '',
+ update => 'Set state to fixed',
+ state => 'fixed',
+ },
+ state => 'fixed - council',
+ report_councils => '2504,2505',
+ },
+) {
+ subtest $test->{desc} => sub {
+ $report->comments->delete;
+ if ( $test->{ report_councils } ) {
+ $report->council( $test->{ report_councils } );
+ $report->update;
+ }
+
+ $mech->log_in_ok( $user->email );
+ $user->from_council( 2504 );
+ $user->update;
+
+ $mech->get_ok("/report/$report_id");
+
+ $mech->submit_form_ok(
+ {
+ with_fields => $test->{fields},
+ },
+ 'submit update'
+ );
+
+ $report->discard_changes;
+ my $update = $report->comments->first;
+ ok $update, 'found update';
+ is $update->text, $test->{fields}->{update}, 'update text';
+ is $update->problem_state, $test->{state}, 'problem state set';
+
+ my $update_meta = $mech->extract_update_metas;
+ like $update_meta->[0], qr/marked as $test->{fields}->{state}$/, 'update meta includes state change';
+ like $update_meta->[0], qr{Test User \(Westminster City Council\)}, 'update meta includes council name';
+ $mech->content_contains( 'Test User (<strong>Westminster City Council</strong>)', 'council name in bold');
+
+ $report->discard_changes;
+ is $report->state, $test->{state}, 'state set';
+ };
+}
+
+$user->from_council(0);
+$user->update;
+
+$report->state('confirmed');
+$report->council('2504');
+$report->update;
+
for my $test (
{
desc => 'submit an update for a registered user, signing in with wrong password',
@@ -738,6 +935,34 @@ foreach my $test (
},
changed => { update => 'Update from owner' },
initial_banner => '',
+ initial_state => 'confirmed',
+ alert => 1, # we signed up for alerts before, do not unsign us
+ anonymous => 0,
+ answered => 0,
+ path => '/report/update',
+ content =>
+"Thanks, glad to hear it's been fixed! Could we just ask if you have ever reported a problem to a council before?",
+ },
+ {
+ desc => 'logged in reporter submits update and marks in progress problem fixed',
+ initial_values => {
+ name => 'Test User',
+ may_show_name => 1,
+ add_alert => 1,
+ photo => '',
+ update => '',
+ fixed => undef,
+ },
+ email => 'test@example.com',
+ fields => {
+ submit_update => 1,
+ update => 'update from owner',
+ add_alert => undef,
+ fixed => 1,
+ },
+ changed => { update => 'Update from owner' },
+ initial_banner => ' This problem is in progress. ',
+ initial_state => 'in progress',
alert => 1, # we signed up for alerts before, do not unsign us
anonymous => 0,
answered => 0,
@@ -765,6 +990,7 @@ foreach my $test (
},
changed => { update => 'Update from owner' },
initial_banner => '',
+ initial_state => 'confirmed',
alert => 1, # we signed up for alerts before, do not unsign us
anonymous => 0,
answered => 1,
@@ -783,7 +1009,7 @@ foreach my $test (
ok( $_->delete, 'deleted comment ' . $_->id ) for $report->comments;
$report->discard_changes;
- $report->state('confirmed');
+ $report->state( $test->{initial_state} );
$report->update;
my $questionnaire;
@@ -825,6 +1051,8 @@ foreach my $test (
my $results = { %{ $test->{fields} }, %{ $test->{changed} }, };
+ $report->discard_changes;
+
my $update = $report->comments->first;
ok $update, 'found update';
is $update->text, $results->{update}, 'update text';
@@ -832,6 +1060,8 @@ foreach my $test (
is $update->state, 'confirmed', 'update confirmed';
is $update->anonymous, $test->{anonymous}, 'user anonymous';
+ is $report->state, 'fixed - user', 'report state';
+
SKIP: {
skip( 'not answering questionnaire', 5 ) if $questionnaire;
@@ -851,6 +1081,8 @@ foreach my $test (
ok $questionnaire, 'questionnaire exists';
ok $questionnaire->ever_reported, 'ever reported is yes';
+ is $questionnaire->old_state(), $test->{initial_state}, 'questionnaire old state';
+ is $questionnaire->new_state(), 'fixed - user', 'questionnaire new state';
};
if ($questionnaire) {
diff --git a/t/app/model/alert_type.t b/t/app/model/alert_type.t
new file mode 100644
index 000000000..c7bfe171c
--- /dev/null
+++ b/t/app/model/alert_type.t
@@ -0,0 +1,144 @@
+use strict;
+use warnings;
+use Test::More;
+use FixMyStreet::TestMech;
+
+mySociety::Locale::gettext_domain( 'FixMyStreet' );
+
+my $mech = FixMyStreet::TestMech->new();
+
+# this is the easiest way to make sure we're not going
+# to get any emails sent by data kicking about in the database
+FixMyStreet::App->model('DB::AlertType')->email_alerts();
+$mech->clear_emails_ok;
+
+my $user =
+ FixMyStreet::App->model('DB::User')
+ ->find_or_create( { email => 'test@example.com', name => 'Test User' } );
+ok $user, "created test user";
+
+my $user2 =
+ FixMyStreet::App->model('DB::User')
+ ->find_or_create( { email => 'commenter@example.com', name => 'Commenter' } );
+ok $user2, "created comment user";
+
+
+my $dt = DateTime->new(
+ year => 2011,
+ month => 04,
+ day => 16,
+ hour => 15,
+ minute => 47,
+ second => 23
+);
+
+my $report = FixMyStreet::App->model('DB::Problem')->find_or_create(
+ {
+ postcode => 'SW1A 1AA',
+ council => '2504',
+ areas => ',105255,11806,11828,2247,2504,',
+ category => 'Other',
+ title => 'Test 2',
+ detail => 'Test 2 Detail',
+ used_map => 't',
+ name => 'Test User',
+ anonymous => 'f',
+ state => 'closed',
+ confirmed => $dt->ymd . ' ' . $dt->hms,
+ lang => 'en-gb',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ send_questionnaire => 't',
+ latitude => '51.5016605453401',
+ longitude => '-0.142497580865087',
+ user_id => $user->id,
+ }
+);
+my $report_id = $report->id;
+ok $report, "created test report - $report_id";
+
+my $comment = FixMyStreet::App->model('DB::Comment')->find_or_create(
+ {
+ problem_id => $report_id,
+ user_id => $user2->id,
+ name => 'Other User',
+ mark_fixed => 'false',
+ text => 'This is some update text',
+ state => 'confirmed',
+ confirmed => $dt->ymd . ' ' . $dt->hms,
+ anonymous => 'f',
+ }
+);
+my $comment2 = FixMyStreet::App->model('DB::Comment')->find_or_create(
+ {
+ problem_id => $report_id,
+ user_id => $user2->id,
+ name => 'Other User',
+ mark_fixed => 'false',
+ text => 'This is other update text',
+ state => 'confirmed',
+ confirmed => $dt->ymd . ' ' . $dt->hms,
+ anonymous => 'f',
+ }
+);
+
+$comment->confirmed( \"ms_current_timestamp() - '3 days'::interval" );
+$comment->update;
+
+my $alert = FixMyStreet::App->model('DB::Alert')->find_or_create(
+ {
+ user => $user,
+ parameter => $report_id,
+ alert_type => 'new_updates',
+ whensubscribed => $dt->ymd . ' ' . $dt->hms,
+ confirmed => 1,
+ }
+);
+
+for my $test (
+ {
+ state => 'closed',
+ msg => 'This report is currently marked as closed',
+ },
+ {
+ state => 'fixed',
+ msg => 'This report is currently marked as fixed',
+ },
+ {
+ state => 'confirmed',
+ msg => 'This report is currently marked as open',
+ },
+) {
+ subtest "correct summary for state of $test->{state}" => sub {
+ $mech->clear_emails_ok;
+
+ my $sent = FixMyStreet::App->model('DB::AlertSent')->search(
+ {
+ alert_id => $alert->id,
+ parameter => $comment->id,
+ }
+ )->delete;
+
+ $report->state( $test->{state} );
+ $report->update;
+
+ FixMyStreet::App->model('DB::AlertType')->email_alerts();
+
+ $mech->email_count_is( 1 );
+ my $email = $mech->get_email;
+ my $msg = $test->{msg};
+ my $body = $email->body;
+
+ like $body, qr/$msg/, 'email says problem is ' . $test->{state};
+ like $body, qr{report/$report_id}, 'contains problem url';
+ like $body, qr/This is some update text/, 'contains update text';
+ unlike $body, qr/This is other update text/, 'does not contains other update text';
+
+ my $comments = $body =~ s/(------)//gs;
+ is $comments, 1, 'only 1 update';
+ };
+}
+
+done_testing();
+
diff --git a/t/app/model/problem.t b/t/app/model/problem.t
index 1b8804fce..4c6be6a8d 100644
--- a/t/app/model/problem.t
+++ b/t/app/model/problem.t
@@ -152,4 +152,92 @@ for my $test (
};
}
+for my $test (
+ {
+ state => 'partial',
+ is_visible => 0,
+ is_fixed => 0,
+ is_open => 0,
+ is_closed => 0,
+ },
+ {
+ state => 'hidden',
+ is_visible => 0,
+ is_fixed => 0,
+ is_open => 0,
+ is_closed => 0,
+ },
+ {
+ state => 'unconfirmed',
+ is_visible => 0,
+ is_fixed => 0,
+ is_open => 0,
+ is_closed => 0,
+ },
+ {
+ state => 'confirmed',
+ is_visible => 1,
+ is_fixed => 0,
+ is_open => 1,
+ is_closed => 0,
+ },
+ {
+ state => 'investigating',
+ is_visible => 1,
+ is_fixed => 0,
+ is_open => 1,
+ is_closed => 0,
+ },
+ {
+ state => 'planned',
+ is_visible => 1,
+ is_fixed => 0,
+ is_open => 1,
+ is_closed => 0,
+ },
+ {
+ state => 'in progress',
+ is_visible => 1,
+ is_fixed => 0,
+ is_open => 1,
+ is_closed => 0,
+ },
+ {
+ state => 'fixed',
+ is_visible => 1,
+ is_fixed => 1,
+ is_open => 0,
+ is_closed => 0,
+ },
+ {
+ state => 'fixed - council',
+ is_visible => 1,
+ is_fixed => 1,
+ is_open => 0,
+ is_closed => 0,
+ },
+ {
+ state => 'fixed - user',
+ is_visible => 1,
+ is_fixed => 1,
+ is_open => 0,
+ is_closed => 0,
+ },
+ {
+ state => 'closed',
+ is_visible => 1,
+ is_fixed => 0,
+ is_open => 0,
+ is_closed => 1,
+ },
+) {
+ subtest $test->{state} . ' is fixed/open/closed/visible' => sub {
+ $problem->state( $test->{state} );
+ is $problem->is_visible, $test->{is_visible}, 'is_visible';
+ is $problem->is_fixed, $test->{is_fixed}, 'is_fixed';
+ is $problem->is_closed, $test->{is_closed}, 'is_closed';
+ is $problem->is_open, $test->{is_open}, 'is_open';
+ };
+}
+
done_testing();
diff --git a/t/app/model/questionnaire.t b/t/app/model/questionnaire.t
new file mode 100644
index 000000000..60b52043a
--- /dev/null
+++ b/t/app/model/questionnaire.t
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use FixMyStreet;
+use FixMyStreet::TestMech;
+
+my $user = FixMyStreet::App->model('DB::User')->find_or_create( { email => 'test@example.com' } );
+
+my $problem = FixMyStreet::App->model('DB::Problem')->create(
+ {
+ postcode => 'EH99 1SP',
+ latitude => 1,
+ longitude => 1,
+ areas => 1,
+ title => 'to be sent',
+ detail => 'detail',
+ used_map => 1,
+ user_id => 1,
+ name => 'A Name',
+ state => 'confirmed',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ confirmed => \"ms_current_timestamp() - '5 weeks'::interval",
+ whensent => \"ms_current_timestamp() - '5 weeks'::interval",
+ user => $user,
+ anonymous => 0,
+ }
+);
+
+diag $problem->id;
+
+my $mech = FixMyStreet::TestMech->new;
+
+for my $test (
+ {
+ state => 'unconfirmed',
+ send_email => 0,
+ },
+ {
+ state => 'partial',
+ send_email => 0,
+ },
+ {
+ state => 'hidden',
+ send_email => 0,
+ },
+ {
+ state => 'confirmed',
+ send_email => 1,
+ },
+ {
+ state => 'investigating',
+ send_email => 1,
+ },
+ {
+ state => 'planned',
+ send_email => 1,
+ },
+ {
+ state => 'in progress',
+ send_email => 1,
+ },
+ {
+ state => 'fixed',
+ send_email => 1,
+ },
+ {
+ state => 'fixed - council',
+ send_email => 1,
+ },
+ {
+ state => 'fixed - user',
+ send_email => 1,
+ },
+ {
+ state => 'closed',
+ send_email => 1,
+ },
+) {
+ subtest "correct questionnaire behviour for state $test->{state}" => sub {
+ $problem->discard_changes;
+ $problem->state( $test->{state} );
+ $problem->send_questionnaire( 1 );
+ $problem->update;
+
+ $problem->questionnaires->delete;
+
+ $mech->email_count_is(0);
+
+ FixMyStreet::App->model('DB::Questionnaire')
+ ->send_questionnaires( { site => 'fixmystreet' } );
+
+ $mech->email_count_is( $test->{send_email} );
+
+ $mech->clear_emails_ok();
+ }
+}
+
+$mech->delete_user( $user );
+
+done_testing();
diff --git a/t/map/tilma/original.t b/t/map/tilma/original.t
new file mode 100644
index 000000000..3add7719e
--- /dev/null
+++ b/t/map/tilma/original.t
@@ -0,0 +1,108 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use FixMyStreet::App;
+use FixMyStreet::Map;
+use FixMyStreet::TestMech;
+use DateTime;
+use mySociety::Locale;
+
+my $mech = FixMyStreet::TestMech->new;
+
+mySociety::Locale::gettext_domain('FixMyStreet');
+
+FixMyStreet::Map::set_map_class();
+my $r = Catalyst::Request->new( { base => URI->new('/'), uri => URI->new('http://fixmystreet.com/test'), parameters => { bbox => '-7.6,49.7,-7.5,49.8' } } );
+
+my $c = FixMyStreet::App->new( {
+ request => $r,
+});
+
+$mech->delete_user('test@example.com');
+my $user =
+ FixMyStreet::App->model('DB::User')
+ ->find_or_create( { email => 'test@example.com', name => 'Test User' } );
+ok $user, "created test user";
+
+my $dt = DateTime->now();
+
+
+my $report = FixMyStreet::App->model('DB::Problem')->find_or_create(
+ {
+ postcode => 'SW1A 1AA',
+ council => '2504',
+ areas => ',105255,11806,11828,2247,2504,',
+ category => 'Other',
+ title => 'Test 2',
+ detail => 'Test 2 Detail',
+ used_map => 't',
+ name => 'Test User',
+ anonymous => 'f',
+ state => 'fixed',
+ confirmed => $dt->ymd . ' ' . $dt->hms,
+ lang => 'en-gb',
+ service => '',
+ cobrand => 'default',
+ cobrand_data => '',
+ send_questionnaire => 't',
+ latitude => '49.7668057243776',
+ longitude => '-7.55715980363992',
+ user_id => $user->id,
+ }
+);
+
+for my $test (
+ {
+ state => 'fixed',
+ colour => 'green',
+ },
+ {
+ state => 'fixed - user',
+ colour => 'green',
+ },
+ {
+ state => 'fixed - council',
+ colour => 'green',
+ },
+ {
+ state => 'confirmed',
+ colour => 'red',
+ },
+ {
+ state => 'investigating',
+ colour => 'red',
+ },
+ {
+ state => 'planned',
+ colour => 'red',
+ },
+ {
+ state => 'in progress',
+ colour => 'red',
+ },
+) {
+ subtest "pin colour for state $test->{state}" => sub {
+ $report->state($test->{state});
+ $report->update;
+
+ my ( $pins, $around_map_list, $nearby, $dist ) =
+ FixMyStreet::Map::map_pins( $c, 0, 0, 0, 0 );
+
+ ok $pins;
+ ok $around_map_list;
+ ok $nearby;
+ ok $dist;
+
+ my $id = $report->id;
+ my $colour = $test->{colour};
+
+ is $pins->[0][2], $colour, 'pin colour';
+ };
+}
+
+$mech->delete_user( $user );
+
+
+done_testing();
diff --git a/templates/web/default/admin/list_updates.html b/templates/web/default/admin/list_updates.html
index 92f41f348..4f27b9595 100644
--- a/templates/web/default/admin/list_updates.html
+++ b/templates/web/default/admin/list_updates.html
@@ -8,6 +8,8 @@
<th>[% loc('Email') %]</th>
<th>[% loc('Created') %]</th>
<th>[% loc('Anonymous') %]</th>
+ <th>[% loc('Owner') %]</th>
+ <th>[% loc('Council') %]</th>
<th>[% loc('Cobrand') %]</th>
<th>[% loc('Text') %]</th>
<th>*</th>
@@ -26,6 +28,8 @@
<td>[% update.user.email | html %]</td>
<td>[% PROCESS format_time time=update.created %]</td>
<td>[% IF update.anonymous %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</td>
+ <td>[% IF update.user.id == update.problem.user_id %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</td>
+ <td>[% IF update.user.belongs_to_council( update.problem.council ) %][% loc('Yes') %][% ELSE %][% loc('No') %][% END %]</td>
<td>[% update.cobrand %]<br>[% update.cobrand_data | html %]</td>
<td>[% update.text | html %]</td>
<td><a href="[% c.uri_for( 'update_edit', update.id ) %]">[% loc('Edit') %]</a></td>
diff --git a/templates/web/default/admin/report_edit.html b/templates/web/default/admin/report_edit.html
index 69d5d0b11..470ad311a 100644
--- a/templates/web/default/admin/report_edit.html
+++ b/templates/web/default/admin/report_edit.html
@@ -19,7 +19,10 @@
<option [% 'selected ' IF !problem.anonymous %]value="0">[% loc('No') %]</option>
</select></li>
<li><label for="state">[% loc('State:') %]</label> <select name="state" id="state">
- [% FOREACH state IN [ ['confirmed', loc('Open')], ['fixed', loc('Fixed')], ['hidden', loc('Hidden')], ['partial', loc('Partial')],['unconfirmed',loc('Unconfirmed')] ] %]
+ [% FOREACH state IN [ ['confirmed', loc('Open')], ['investigating',
+ loc('Investigating')], ['planned', loc('Planned')], ['in progress',
+ loc('In Progress')], ['closed', loc('Closed')], ['fixed', loc('Fixed')], ['fixed - user',
+ loc('Fixed - User')], ['fixed - council', loc('Fixed - Council')], ['hidden', loc('Hidden')], ['partial', loc('Partial')],['unconfirmed',loc('Unconfirmed')] ] %]
<option [% 'selected ' IF state.0 == problem.state %] value="[% state.0 %]">[% state.1 %]</option>
[% END %]
</select></li>
diff --git a/templates/web/default/admin/search_users.html b/templates/web/default/admin/search_users.html
new file mode 100644
index 000000000..43fdebf2b
--- /dev/null
+++ b/templates/web/default/admin/search_users.html
@@ -0,0 +1,29 @@
+[% INCLUDE 'admin/header.html' title=loc('Search Users') %]
+[% PROCESS 'admin/report_blocks.html' %]
+
+<form method="get" action="[% c.uri_for('search_users') %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+ <label for="search">[% loc('Search:') %]</label> <input type="text" name="search" size="30" id="search">
+</form>
+
+
+[% IF searched %]
+<table cellspacing="0" cellpadding="2" border="1">
+ <tr>
+ <th>[% loc('Name') %]</th>
+ <th>[% loc('Email') %]</th>
+ <th>[% loc('Council') %]</th>
+ <th>*</th>
+ </tr>
+[%- FOREACH user IN users %]
+ <tr>
+ <td>[% PROCESS value_or_nbsp value=user.name %]</td>
+ <td>[% PROCESS value_or_nbsp value=user.email %]</td>
+ <td>[% PROCESS value_or_nbsp value=user.from_council %]</td>
+ <td><a href="[% c.uri_for( 'user_edit', user.id ) %]">[% loc('Edit') %]</a></td>
+ </tr>
+[%- END -%]
+</table>
+
+[% END %]
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/default/admin/update_edit.html b/templates/web/default/admin/update_edit.html
index d4ac1b440..e1783fd7c 100644
--- a/templates/web/default/admin/update_edit.html
+++ b/templates/web/default/admin/update_edit.html
@@ -21,7 +21,22 @@
[% END %]
</select></li>
<li>[% loc('Name:') %] <input type='text' name='name' id='name' value='[% update.name | html %]'></li>
-<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% update.user.email | html %]'> [% PROCESS abuse_button %] [% PROCESS flag_button user=update.user %]</li>
+<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% update.user.email | html %]'>
+[%- IF update.user.from_council && update.user.from_council == update.problem.council %]
+[% ' (' _ tprintf(loc('user is from same council as problem - %d'), update.user.from_council ) _')' %]
+[% END -%]
+[%- IF update.user.id == update.problem.user.id %]
+[% ' (' _ loc('user is problem owner') _')' %]
+[% END -%]
+</li>
+[% IF update.problem_state %]
+<li>[% tprintf(loc('Update changed problem state to %s'), update.problem_state) %]</li>
+[% ELSIF update.mark_fixed %]
+<li>[% loc('Update marked problem as fixed') %]</li>
+[% ELSIF update.user.id == update.problem.user.id && update.mark_open %]
+<li>[% loc('Update reopened problem') %]</li>
+[% END %]
+[% PROCESS abuse_button %] [% PROCESS flag_button user=update.user %]</li>
<li>[% loc('Cobrand:') %] [% update.cobrand %]</li>
<li>[% loc('Cobrand data:') %] [% update.cobrand_data %]</li>
<li>[% loc('Created:') %] [% PROCESS format_time time=update.created %]</li>
diff --git a/templates/web/default/admin/user_edit.html b/templates/web/default/admin/user_edit.html
new file mode 100644
index 000000000..7db8f5c63
--- /dev/null
+++ b/templates/web/default/admin/user_edit.html
@@ -0,0 +1,21 @@
+[% INCLUDE 'admin/header.html' title=tprintf(loc('Editing user %d'), user.id ) -%]
+[% PROCESS 'admin/report_blocks.html' %]
+
+[% status_message %]
+
+<form method="post" action="[% c.uri_for( 'user_edit', user.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
+ <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="submit" value="1" >
+<ul>
+<li>[% loc('Name:') %] <input type='text' name='name' id='name' value='[% user.name | html %]'></li>
+<li>[% loc('Email:') %] <input type='text' id='email' name='email' value='[% user.email | html %]'></li>
+<li>[% loc('Council:') %] <select id='council' name='council'>
+ <option value=''>[% loc('No council') %]</option>
+[% FOR council IN council_ids %]
+ <option value="[% council %]"[% ' selected' IF council == user.from_council %]>[% council_details.$council.name %]</option>
+[% END %]
+</select>
+</ul>
+<input type="submit" name="Submit changes" value="[% loc('Submit changes') %]" ></form>
+
+[% INCLUDE 'admin/footer.html' %]
diff --git a/templates/web/default/around/around_map_list_items.html b/templates/web/default/around/around_map_list_items.html
index a98f25dbe..e248ce3ed 100644
--- a/templates/web/default/around/around_map_list_items.html
+++ b/templates/web/default/around/around_map_list_items.html
@@ -6,7 +6,7 @@
<li>
<a href="[% c.uri_for('/report', p.problem.id ) %]">[% p.problem.title | html %]</a>
<small>[% prettify_epoch( p.problem.confirmed_local.epoch, 1 ) %], [% dist %]km</small>
- [% IF p.problem.state == 'fixed' %]
+ [% IF p.problem.is_fixed %]
<small>[% loc('(fixed)') %]</small>
[% END %]
</li>
diff --git a/templates/web/default/around/on_map_list_items.html b/templates/web/default/around/on_map_list_items.html
index 2db7d00d2..245be7190 100644
--- a/templates/web/default/around/on_map_list_items.html
+++ b/templates/web/default/around/on_map_list_items.html
@@ -3,7 +3,7 @@
<li>
<a href="[% c.uri_for('/report', p.id ) %]">[% p.title | html %]</a>
<small>[% prettify_epoch( p.confirmed_local.epoch, 1 ) %]</small>
- [% IF p.state == 'fixed' %]
+ [% IF p.is_fixed %]
<small>[% loc('(fixed)') %]</small>
[% END %]
</li>
diff --git a/templates/web/default/my/my.html b/templates/web/default/my/my.html
index 2147ef5f2..b96823df2 100644
--- a/templates/web/default/my/my.html
+++ b/templates/web/default/my/my.html
@@ -28,6 +28,11 @@
[% INCLUDE problem %]
[% END %]
+[% FOREACH p = problems.closed %]
+ [% IF loop.first %]<h2>[% loc('Closed reports') %]</h2>[% END %]
+ [% INCLUDE problem %]
+[% END %]
+
[%# FOREACH p = problems.unconfirmed;
IF loop.first;
'<h2>' _ loc('Unconfirmed reports') _ '</h2>';
diff --git a/templates/web/default/questionnaire/completed.html b/templates/web/default/questionnaire/completed.html
index 3762b25d0..fe896b383 100644
--- a/templates/web/default/questionnaire/completed.html
+++ b/templates/web/default/questionnaire/completed.html
@@ -10,7 +10,7 @@
get some more information about the status of your problem, please come back to the
site and leave an update.</p>') %]
-[% ELSIF new_state == 'confirmed' OR (!new_state AND problem.state == 'confirmed') %]
+[% ELSIF new_state == 'confirmed' OR (!new_state AND problem.is_open) %]
[% tprintf( loc('<p style="font-size:150%%">We&rsquo;re sorry to hear that. We have two suggestions: why not try
<a href="%s">writing direct to your councillor(s)</a>
diff --git a/templates/web/default/questionnaire/index.html b/templates/web/default/questionnaire/index.html
index d463ff2f9..a7e5ad60c 100644
--- a/templates/web/default/questionnaire/index.html
+++ b/templates/web/default/questionnaire/index.html
@@ -59,7 +59,7 @@ href="http://www.emptyhomes.com/getinvolved/campaign.html">http://www.emptyhomes
[% END %]
<p>
-[% loc('An update marked this problem as fixed.') IF problem.state == 'fixed' %]
+[% loc('An update marked this problem as fixed.') IF problem.is_fixed %]
[% loc('Has this problem been fixed?') %]
</p>
diff --git a/templates/web/default/report/display.html b/templates/web/default/report/display.html
index 4948f8808..fe26ec60d 100644
--- a/templates/web/default/report/display.html
+++ b/templates/web/default/report/display.html
@@ -85,16 +85,29 @@
<textarea name="update" id="form_update" rows="7" cols="30">[% update.text | html %]</textarea>
</div>
- [% IF problem.state == 'fixed' AND c.user_exists AND c.user.id == problem.user_id %]
- <div class="checkbox">
- <input type="checkbox" name="reopen" id="form_reopen" value="1"[% ' checked' IF update.mark_open %]>
- <label for="form_reopen">[% loc('This problem has not been fixed') %]</label>
- </div>
- [% ELSIF problem.state != 'fixed' %]
- <div class="checkbox">
- <input type="checkbox" name="fixed" id="form_fixed" value="1"[% ' checked' IF update.mark_fixed %]>
- <label for="form_fixed">[% loc('This problem has been fixed') %]</label>
+ [% IF c.user && c.user.belongs_to_council( problem.council ) %]
+ <div class="form-field">
+ <label for="form_state">[% loc( 'State:' ) %]</label>
+ <select name="state" id="form_state">
+ [% FOREACH state IN [ ['confirmed', loc('Open')], ['investigating',
+ loc('Investigating')], ['planned', loc('Planned')], ['in progress',
+ loc('In Progress')], ['closed', loc('Closed')], ['fixed', loc('Fixed')] ] %]
+ <option [% 'selected ' IF state.0 == problem.state %] value="[% state.0 %]">[% state.1 %]</option>
+ [% END %]
+ </select>
</div>
+ [% ELSE %]
+ [% IF problem.is_fixed AND c.user_exists AND c.user.id == problem.user_id %]
+ <div class="checkbox">
+ <input type="checkbox" name="reopen" id="form_reopen" value="1"[% ' checked' IF update.mark_open %]>
+ <label for="form_reopen">[% loc('This problem has not been fixed') %]</label>
+ </div>
+ [% ELSIF !problem.is_fixed %]
+ <div class="checkbox">
+ <input type="checkbox" name="fixed" id="form_fixed" value="1"[% ' checked' IF update.mark_fixed %]>
+ <label for="form_fixed">[% loc('This problem has been fixed') %]</label>
+ </div>
+ [% END %]
[% END %]
[% IF c.cobrand.allow_photo_upload %]
diff --git a/templates/web/default/report/updates.html b/templates/web/default/report/updates.html
index 910430114..803ed197e 100644
--- a/templates/web/default/report/updates.html
+++ b/templates/web/default/report/updates.html
@@ -6,12 +6,16 @@
<div><div class="problem-update"><p><a name="update_[% update.id %]"></a><em>
[% IF update.anonymous || update.name == '' %]
[% tprintf( loc( 'Posted anonymously at %s' ), prettify_epoch( update.confirmed_local.epoch ) ) -%]
+ [%- ELSIF update.user.from_council %]
+ [% user_name = update.user.name | html %]
+ [% tprintf( loc( 'Posted by %s (<strong>%s</strong>) at %s' ), user_name, update.user.council, prettify_epoch( update.confirmed_local.epoch ) ) -%]
[%- ELSE %]
[% tprintf( loc( 'Posted by %s at %s' ), update.name, prettify_epoch( update.confirmed_local.epoch ) ) | html -%]
[%- END -%]
[%- c.cobrand.extra_update_meta_text(update) -%]
[%- ", " _ loc( 'marked as fixed' ) IF update.mark_fixed %]
[%- ", " _ loc( 'reopened' ) IF update.mark_open %]
+ [%- ", " _ tprintf(loc( 'marked as %s' ), update.meta_problem_state) IF update.problem_state %]
</em></p>
[% IF c.cobrand.allow_update_reporting %]
diff --git a/web/cobrands/fiksgatami/css.css b/web/cobrands/fiksgatami/css.css
index 54513fb34..6c2a4e16b 100644
--- a/web/cobrands/fiksgatami/css.css
+++ b/web/cobrands/fiksgatami/css.css
@@ -43,6 +43,13 @@ select, input, textarea {
#mysociety #front_stats div {
background-color: #99bfe1;
}
+#mysociety p.promo {
+ border-top: 1px solid #bbb;
+ border-bottom: 1px solid #bbb;
+ background-color: #eee;
+ text-align: center;
+ padding: 0 0.5em;
+}
#header {
font-size: 200%;
@@ -73,11 +80,28 @@ select, input, textarea {
position: relative;
margin: 0 auto;
max-width: 60em;
+ overflow: auto;
}
/* Can't put the margin in #mysociety because of above IE craziness */
#wrapper {
- margin: 2em;
+ margin: 1em 2em;
+}
+
+#meta {
+ list-style-type: none;
+ margin: 0.25em 0 0 1em;
+ padding: 0;
+ font-size: 0.875em;
+}
+#meta li {
+ display: inline;
+ margin: 0;
+ padding: 0 0 0 0.25em;
+ border-left: solid 1px #1a4f7f;
+}
+#meta li:first-child {
+ border-left: none;
}
.v {
@@ -108,6 +132,12 @@ select, input, textarea {
#navigation a:hover, #navigation a:active {
background-color: #1a4f7f;
color: #99bfe1;
+ -moz-border-radius-topleft: 0.5em;
+ -webkit-border-top-left-radius: 0.5em;
+ border-radius-top-left: 0.5em;
+ -moz-border-radius-topright: 0.5em;
+ -webkit-border-top-right-radius: 0.5em;
+ border-radius-top-right: 0.5em;
}
#nav_new a {
@@ -125,12 +155,23 @@ select, input, textarea {
#footer {
clear: both;
- text-align: right;
- font-size: 83%;
- border-top: solid 1px #1a4f7f;
- display: table;
- margin: 2em 0 1em auto;
- padding: 2px 4px;
+ text-align: center;
+ border-top: solid 2px #ccc;
+ width: 50%;
+ margin: 1em auto 0;
+ padding: 0;
+ color: #333333;
+}
+#footer .l, #footer .r {
+ margin-top: 0;
+ text-align: left;
+ width: 45%;
+}
+#footer .l {
+ float: left;
+}
+#footer .r {
+ float: right;
}
body {
diff --git a/web/css/core.css b/web/css/core.css
index 054f82cc3..309d8a9cb 100644
--- a/web/css/core.css
+++ b/web/css/core.css
@@ -1 +1,426 @@
-#mysociety blockquote{border-left:solid 4px #666666;padding-left:0.5em}#mysociety blockquote h2,#mysociety blockquote p{margin:0}#mysociety dt{font-weight:bold;margin-top:0.5em}#mysociety .gone{color:#666666;background-color:#cccccc}#mysociety p.dev-site-notice,#mysociety p.error{text-align:center;color:#cc0000;font-size:larger}#mysociety ul{padding:0 0 0 1.5em;margin:0}#mysociety ul.error{color:#cc0000;background-color:#ffeeee;padding-right:4px;text-align:left;font-size:larger}#mysociety div.form-error{color:#cc0000;margin:5px 1em 5px 1em;padding:2px 5px 2px 5px;float:left;background-color:#ffeeee;text-align:left}#mysociety div.form-field{clear:both}#mysociety #advert_thin{width:50%;margin:1em auto;text-align:center;border-top:dotted 1px #999999}#mysociety #advert_hfymp{border-top:dotted 1px #999999;text-align:center}#mysociety p#expl{text-align:center;font-size:150%;margin:0 2em}#mysociety #postcodeForm{display:table;_width:33em;text-align:center;font-size:150%;margin:1em auto;padding:1em;-moz-border-radius:1em;-webkit-border-radius:1em;border-radius:1em}#mysociety #postcodeForm label{float:none;padding-right:0}#mysociety #postcodeForm #submit{font-size:83%}#mysociety #front_intro{float:left;width:48%}#mysociety #front_intro p{clear:both;margin-top:0}#mysociety #front_stats div{text-align:center;width:5.5em;-moz-border-radius:0.5em;-webkit-border-radius:0.5em;border-radius:0.5em;float:left;margin:0 1em 1em}#mysociety #front_stats div big{font-size:150%;display:block}#mysociety #front_recent{float:right;width:48%;margin-bottom:1em}#mysociety #front_recent img,#mysociety #alert_recent img{margin-right:0.25em;margin-bottom:0.25em}#mysociety #front_recent > h2:first-child,#mysociety #front_intro > h2:first-child{margin-top:0}#mysociety form{margin:0}#mysociety label{float:left;text-align:right;padding-right:0.5em;width:5em}#mysociety fieldset,#mysociety #fieldset{border:none;padding:0.5em}#mysociety fieldset div,#mysociety #fieldset div{margin-top:2px;clear:left}#mysociety legend{display:none}#mysociety #fieldset div.checkbox,#mysociety #problem_submit{padding-left:5.5em}#mysociety #fieldset div.checkbox label,#mysociety label.n{float:none;text-align:left;padding-right:0;width:auto;cursor:pointer;cursor:hand}#mysociety #questionnaire label,#mysociety #alerts label{float:none}#mysociety .confirmed{background-color:#ccffcc;border:solid 2px #009900;padding:5px;text-align:center}#mysociety #form_sign_in_yes{float:left;width:47%;padding-right:1%;border-right:solid 1px #999999;margin-bottom:1em}#mysociety #form_sign_in_no,#mysociety #fieldset #form_sign_in_no{float:right;width:47%;padding-left:1%;clear:none;margin-bottom:1em}#mysociety #map_box{float:right;width:502px;position:relative;padding-left:20px;background-color:#ffffff}#mysociety p#copyright{float:right;text-align:right;margin:0 0 1em 0;font-size:78%}#mysociety #map{border:solid 1px #000000;width:500px;height:500px;overflow:hidden;position:relative;background-color:#f1f1f1}#mysociety #drag{position:absolute;width:500px;height:500px;right:0;top:0}#mysociety #drag input,#mysociety #drag img{position:absolute;border:none}#mysociety #drag input{cursor:crosshair;background-color:#cccccc}#mysociety #drag img{cursor:move}#mysociety #drag img.pin{z-index:100;background-color:inherit}#mysociety #drag a img.pin{cursor:pointer;cursor:hand}#mysociety form#mapForm #map{cursor:pointer}#mysociety form#mapForm .olTileImage{cursor:crosshair}#mysociety #text_map{margin:0 530px 1em 0;padding:5px;text-align:center;position:relative;padding-left:0.5em;text-align:left;margin-top:0;font-size:110%;background-color:#eeeeee;-moz-border-radius-topleft:1em;-moz-border-radius-bottomleft:1em;-webkit-border-top-left-radius:1em;-webkit-border-bottom-left-radius:1em;border-top-left-radius:1em;border-bottom-left-radius:1em}#mysociety #text_map_arrow{display:block;position:absolute;top:0;right:-28px;width:0;height:0;line-height:0;font-size:0;border-style:solid;border-width:26px 14px 26px 14px;border-color:#fff #fff #fff #eee}#mysociety #text_no_map{margin-top:0}#mysociety #sub_map_links{float:right;clear:right;margin-top:0}#mysociety #fixed{margin:0 530px 1em 0;padding:5px;text-align:center;position:relative;background-color:#ccffcc;border:solid 2px #009900}#mysociety #unknown{margin:0 530px 1em 0;padding:5px;text-align:center;position:relative;background-color:#ffcccc;border:solid 2px #990000}#mysociety #updates div{padding:0 0 0.5em;margin:0 0 0.25em;border-bottom:dotted 1px #5e552b}#mysociety #updates div .problem-update,#mysociety #updates div .update-text{padding:0;margin:0;border-bottom:0}#mysociety #updates p{margin:0}#mysociety #nearby_lists h2{margin-top:1em;margin-bottom:0}#mysociety #nearby_lists li small{color:#666666}#mysociety #alert_links{float:right}#mysociety #alert_links_area{padding-left:0.5em;margin:0;color:#666;font-size:smaller}#mysociety #rss_alert{text-decoration:none}#mysociety #rss_alert span{text-decoration:underline}#mysociety #email_alert_box{display:none;position:absolute;padding:3px;font-size:83%;border:solid 1px #7399C3;background-color:#eeeeff;color:#000000}#mysociety #email_alert_box p{margin:0}#mysociety .council_sent_info{font-size:smaller}#mysociety #rss_items{width:62%;float:left}#mysociety #rss_rhs{border-left:1px dashed #999;width:36%;float:right;padding:0 0 0 0.5em;margin:0 0 1em 0.5em}#mysociety #rss_box{padding:10px;border:1px solid #999999}#mysociety #rss_feed{list-style-type:none;margin-bottom:2em}#mysociety #rss_feed li{margin-bottom:1em}#mysociety #alert_or{font-style:italic;font-size:125%;margin:0}#mysociety #rss_list{float:left;width:47%}#mysociety #rss_list ul{list-style-type:none}#mysociety #rss_buttons{float:right;width:35%;text-align:center;margin-bottom:2em}#mysociety #rss_local{margin-left:1.5em;margin-bottom:0}#mysociety #rss_local_alt{margin:0 0 2em 4em}#mysociety #alert_photos{text-align:center;float:right;width:150px;margin-left:0.5em}#mysociety #alert_photos h2{font-size:100%}#mysociety #alert_photos img{margin-bottom:0.25em}#mysociety #col_problems,#mysociety #col_fixed{float:left;width:48%;margin-right:1em}#mysociety .contact-details{font-size:80%;margin-top:2em}.olControlAttribution{bottom:3px !important;left:3px}.olControlPermalink{bottom:3px !important;right:3px}@media print{#mysociety #map_box{float:none;margin:0 auto}#mysociety #mysociety{max-width:none}#mysociety #side{margin-right:0}}
+#mysociety blockquote {
+ border-left: solid 4px #666666;
+ padding-left: 0.5em;
+}
+#mysociety blockquote h2, #mysociety blockquote p {
+ margin: 0;
+}
+#mysociety dt {
+ font-weight: bold;
+ margin-top: 0.5em;
+}
+#mysociety .gone {
+ color: #666666;
+ background-color: #cccccc;
+}
+#mysociety p.dev-site-notice, #mysociety p.error {
+ text-align: center;
+ color: #cc0000;
+ font-size: larger;
+}
+#mysociety ul {
+ padding: 0 0 0 1.5em;
+ margin: 0;
+}
+#mysociety ul.error {
+ color: #cc0000;
+ background-color: #ffeeee;
+ padding-right: 4px;
+ text-align: left;
+ font-size: larger;
+}
+#mysociety div.form-error {
+ color: #cc0000;
+ margin: 5px 1em 5px 1em;
+ padding: 2px 5px 2px 5px;
+ float: left;
+ background-color: #ffeeee;
+ text-align: left;
+}
+#mysociety div.form-field {
+ clear: both;
+}
+#mysociety #advert_thin {
+ width: 50%;
+ margin: 1em auto;
+ text-align: center;
+ border-top: dotted 1px #999999;
+}
+#mysociety #advert_hfymp {
+ border-top: dotted 1px #999999;
+ text-align: center;
+}
+#mysociety p#expl {
+ text-align: center;
+ font-size: 150%;
+ margin: 0 2em;
+}
+#mysociety #postcodeForm {
+ display: table;
+ _width: 33em;
+ text-align: center;
+ font-size: 150%;
+ margin: 1em auto;
+ padding: 1em;
+ -moz-border-radius: 1em;
+ -webkit-border-radius: 1em;
+ border-radius: 1em;
+}
+#mysociety #postcodeForm label {
+ float: none;
+ padding-right: 0;
+}
+#mysociety #postcodeForm #submit {
+ font-size: 83%;
+}
+#mysociety #front_intro {
+ float: left;
+ width: 48%;
+}
+#mysociety #front_intro p {
+ clear: both;
+ margin-top: 0;
+}
+#mysociety #front_stats div {
+ text-align: center;
+ width: 5.5em;
+ -moz-border-radius: 0.5em;
+ -webkit-border-radius: 0.5em;
+ border-radius: 0.5em;
+ float: left;
+ margin: 0 1em 1em;
+}
+#mysociety #front_stats div big {
+ font-size: 150%;
+ display: block;
+}
+#mysociety #front_recent {
+ float: right;
+ width: 48%;
+ margin-bottom: 1em;
+}
+#mysociety #front_recent img, #mysociety #alert_recent img {
+ margin-right: 0.25em;
+ margin-bottom: 0.25em;
+}
+#mysociety #front_recent > h2:first-child, #mysociety #front_intro > h2:first-child {
+ margin-top: 0;
+}
+#mysociety form {
+ margin: 0;
+}
+#mysociety label {
+ float: left;
+ text-align: right;
+ padding-right: 0.5em;
+ width: 5em;
+}
+#mysociety fieldset, #mysociety #fieldset {
+ border: none;
+ padding: 0.5em;
+}
+#mysociety fieldset div, #mysociety #fieldset div {
+ margin-top: 2px;
+ clear: left;
+}
+#mysociety legend {
+ display: none;
+}
+#mysociety #fieldset div.checkbox, #mysociety #problem_submit {
+ padding-left: 5.5em;
+}
+#mysociety #fieldset div.checkbox label, #mysociety label.n {
+ float: none;
+ text-align: left;
+ padding-right: 0;
+ width: auto;
+ cursor: pointer;
+ cursor: hand;
+}
+#mysociety #questionnaire label, #mysociety #alerts label {
+ float: none;
+}
+#mysociety .confirmed {
+ background-color: #ccffcc;
+ border: solid 2px #009900;
+ padding: 5px;
+ text-align: center;
+}
+#mysociety #form_sign_in_yes {
+ float: left;
+ width: 47%;
+ padding-right: 1%;
+ border-right: solid 1px #999999;
+ margin-bottom: 1em;
+}
+#mysociety #form_sign_in_no, #mysociety #fieldset #form_sign_in_no {
+ float: right;
+ width: 47%;
+ padding-left: 1%;
+ clear: none;
+ margin-bottom: 1em;
+}
+#mysociety #map_box {
+ float: right;
+ width: 502px;
+ position: relative;
+ padding-left: 20px;
+ background-color: #ffffff;
+}
+#mysociety p#copyright {
+ float: right;
+ text-align: right;
+ margin: 0 0 1em 0;
+ font-size: 78%;
+}
+#mysociety #map {
+ border: solid 1px #000000;
+ width: 500px;
+ height: 500px;
+ overflow: hidden;
+ position: relative;
+ background-color: #f1f1f1;
+}
+#mysociety #drag {
+ position: absolute;
+ width: 500px;
+ height: 500px;
+ right: 0;
+ top: 0;
+}
+#mysociety #drag input, #mysociety #drag img {
+ position: absolute;
+ border: none;
+}
+#mysociety #drag input {
+ cursor: crosshair;
+ background-color: #cccccc;
+}
+#mysociety #drag img {
+ cursor: move;
+}
+#mysociety #drag img.pin {
+ z-index: 100;
+ background-color: inherit;
+}
+#mysociety #drag a img.pin {
+ cursor: pointer;
+ cursor: hand;
+}
+#mysociety form#mapForm #map {
+ cursor: pointer;
+}
+#mysociety form#mapForm .olTileImage {
+ cursor: crosshair;
+}
+#mysociety #text_map {
+ margin: 0 530px 1em 0;
+ padding: 5px;
+ text-align: center;
+ position: relative;
+ padding-left: 0.5em;
+ text-align: left;
+ margin-top: 0;
+ font-size: 110%;
+ background-color: #eeeeee;
+ -moz-border-radius-topleft: 1em;
+ -moz-border-radius-bottomleft: 1em;
+ -webkit-border-top-left-radius: 1em;
+ -webkit-border-bottom-left-radius: 1em;
+ border-top-left-radius: 1em;
+ border-bottom-left-radius: 1em;
+}
+#mysociety #text_map_arrow {
+ display: block;
+ position: absolute;
+ top: 0;
+ right: -28px;
+ width: 0;
+ height: 0;
+ line-height: 0;
+ font-size: 0;
+ border-style: solid;
+ border-width: 26px 14px 26px 14px;
+ border-color: #fff #fff #fff #eee;
+}
+#mysociety #text_no_map {
+ margin-top: 0;
+}
+#mysociety #sub_map_links {
+ float: right;
+ clear: right;
+ margin-top: 0;
+}
+#mysociety #fixed {
+ margin: 0 530px 1em 0;
+ padding: 5px;
+ text-align: center;
+ position: relative;
+ background-color: #ccffcc;
+ border: solid 2px #009900;
+}
+#mysociety #unknown {
+ margin: 0 530px 1em 0;
+ padding: 5px;
+ text-align: center;
+ position: relative;
+ background-color: #ffcccc;
+ border: solid 2px #990000;
+}
+#mysociety #closed {
+ margin: 0 530px 1em 0;
+ padding: 5px;
+ text-align: center;
+ position: relative;
+ background-color: #ccccff;
+ border: solid 2px #000099;
+}
+#mysociety #progress {
+ margin: 0 530px 1em 0;
+ padding: 5px;
+ text-align: center;
+ position: relative;
+ background-color: #ffffcc;
+ border: solid 2px #999900;
+}
+#mysociety #updates div {
+ padding: 0 0 0.5em;
+ margin: 0 0 0.25em;
+ border-bottom: dotted 1px #5e552b;
+}
+#mysociety #updates div .problem-update, #mysociety #updates div .update-text {
+ padding: 0;
+ margin: 0;
+ border-bottom: 0;
+}
+#mysociety #updates p {
+ margin: 0;
+}
+#mysociety #nearby_lists h2 {
+ margin-top: 1em;
+ margin-bottom: 0;
+}
+#mysociety #nearby_lists li small {
+ color: #666666;
+}
+#mysociety #alert_links {
+ float: right;
+}
+#mysociety #alert_links_area {
+ padding-left: 0.5em;
+ margin: 0;
+ color: #666;
+ font-size: smaller;
+}
+#mysociety #rss_alert {
+ text-decoration: none;
+}
+#mysociety #rss_alert span {
+ text-decoration: underline;
+}
+#mysociety #email_alert_box {
+ display: none;
+ position: absolute;
+ padding: 3px;
+ font-size: 83%;
+ border: solid 1px #7399C3;
+ background-color: #eeeeff;
+ color: #000000;
+}
+#mysociety #email_alert_box p {
+ margin: 0;
+}
+#mysociety .council_sent_info {
+ font-size: smaller;
+}
+#mysociety #rss_items {
+ width: 62%;
+ float: left;
+}
+#mysociety #rss_rhs {
+ border-left: 1px dashed #999;
+ width: 36%;
+ float: right;
+ padding: 0 0 0 0.5em;
+ margin: 0 0 1em 0.5em;
+}
+#mysociety #rss_box {
+ padding: 10px;
+ border: 1px solid #999999;
+}
+#mysociety #rss_feed {
+ list-style-type: none;
+ margin-bottom: 2em;
+}
+#mysociety #rss_feed li {
+ margin-bottom: 1em;
+}
+#mysociety #alert_or {
+ font-style: italic;
+ font-size: 125%;
+ margin: 0;
+}
+#mysociety #rss_list {
+ float: left;
+ width: 47%;
+}
+#mysociety #rss_list ul {
+ list-style-type: none;
+}
+#mysociety #rss_buttons {
+ float: right;
+ width: 35%;
+ text-align: center;
+ margin-bottom: 2em;
+}
+#mysociety #rss_local {
+ margin-left: 1.5em;
+ margin-bottom: 0;
+}
+#mysociety #rss_local_alt {
+ margin: 0 0 2em 4em;
+}
+#mysociety #alert_photos {
+ text-align: center;
+ float: right;
+ width: 150px;
+ margin-left: 0.5em;
+}
+#mysociety #alert_photos h2 {
+ font-size: 100%;
+}
+#mysociety #alert_photos img {
+ margin-bottom: 0.25em;
+}
+#mysociety #col_problems, #mysociety #col_fixed {
+ float: left;
+ width: 48%;
+ margin-right: 1em;
+}
+#mysociety .contact-details {
+ font-size: 80%;
+ margin-top: 2em;
+}
+
+.olControlAttribution {
+ bottom: 3px !important;
+ left: 3px;
+}
+
+.olControlPermalink {
+ bottom: 3px !important;
+ right: 3px;
+}
+
+@media print {
+ #mysociety #map_box {
+ float: none;
+ margin: 0 auto;
+ }
+ #mysociety #mysociety {
+ max-width: none;
+ }
+ #mysociety #side {
+ margin-right: 0;
+ }
+}
diff --git a/web/css/core.scss b/web/css/core.scss
index 2a1bc008a..5fafe625b 100644
--- a/web/css/core.scss
+++ b/web/css/core.scss
@@ -322,6 +322,18 @@ $map_width: 500px;
border: solid 2px #990000;
}
+ #closed {
+ @include problem-banner;
+ background-color: #ccccff;
+ border: solid 2px #000099;
+ }
+
+ #progress {
+ @include problem-banner;
+ background-color: #ffffcc;
+ border: solid 2px #999900;
+ }
+
#updates {
div {
padding: 0 0 0.5em;
diff --git a/web/css/main.css b/web/css/main.css
index 87b84c5ed..a3fe3b400 100644
--- a/web/css/main.css
+++ b/web/css/main.css
@@ -1 +1,175 @@
-a:link{color:#0000ff}a:visited{color:#000099}a:hover,a:active{color:#ff0000}body{font-family:"Gill Sans", "Gill Sans MT", Helvetica, Arial, sans-serif;margin:0;padding:0}h1{margin:0;font-size:175%}h2{font-size:140%}select,input,textarea{font-size:99%}#mysociety a.unsuitable-report{font-size:small}#mysociety blockquote{border-left:solid 4px #5e552b}#mysociety .a{color:#000000;background-color:#f3e5a5}#mysociety #postcodeForm{background-color:#e3d595}#mysociety #front_stats div{background-color:#e3d595}#mysociety p.promo{border-top:1px solid #bbb;border-bottom:1px solid #bbb;background-color:#eee;text-align:center;padding:0 0.5em}#header{font-size:200%;font-weight:bold;border-bottom:solid 2px #5e552b;margin:0;padding:0.15em 0.5em;background-color:#e3d595;color:#5e552b}#header a:link,#header a:visited{color:#5e552b;background-color:#e3d595;text-decoration:none}#header a:active,#header a:hover{text-decoration:underline}#my{color:#4e451b;background-color:#e3d595}#mysociety{width:100%;position:relative;margin:0 auto;max-width:60em;overflow:auto}#wrapper{margin:1em 2em}#meta{list-style-type:none;margin:0.25em 0 0 1em;padding:0;font-size:0.875em}#meta li{display:inline;margin:0;padding:0 0 0 0.25em;border-left:solid 1px #5e552b}#meta li:first-child{border-left:none}.v{display:none}#navigation{position:absolute;top:1em;right:1em;padding:0;margin:0;list-style-type:none}#navigation li{display:inline;padding:0;margin:0}#navigation a{display:-moz-inline-box;display:inline-block;padding:0.4em 1em}#navigation a:link,#navigation a:visited{color:#5e552b}#navigation a:hover,#navigation a:active{background-color:#5e552b;color:#e3d595;-moz-border-radius-topleft:0.5em;-webkit-border-top-left-radius:0.5em;border-radius-top-left:0.5em;-moz-border-radius-topright:0.5em;-webkit-border-top-right-radius:0.5em;border-radius-top-right:0.5em}#nav_new a{background-image:url("/i/new.png");background-repeat:no-repeat;background-position:100% 0}#logo{border:none;position:absolute;top:3.3em;right:10px}#footer{clear:both;text-align:center;border-top:solid 2px #ccc;width:50%;margin:1em auto 0;padding:0;color:#333333}#footer .l,#footer .r{margin-top:0;text-align:left;width:45%}#footer .l{float:left}#footer .r{float:right}
+a:link {
+ color: #0000ff;
+}
+a:visited {
+ color: #000099;
+}
+a:hover, a:active {
+ color: #ff0000;
+}
+
+body {
+ font-family: "Gill Sans", "Gill Sans MT", Helvetica, Arial, sans-serif;
+ margin: 0;
+ padding: 0;
+}
+
+h1 {
+ margin: 0;
+ font-size: 175%;
+}
+
+h2 {
+ font-size: 140%;
+}
+
+select, input, textarea {
+ font-size: 99%;
+}
+
+#mysociety a.unsuitable-report {
+ font-size: small;
+}
+#mysociety blockquote {
+ border-left: solid 4px #5e552b;
+}
+#mysociety .a {
+ color: #000000;
+ background-color: #f3e5a5;
+}
+#mysociety #postcodeForm {
+ background-color: #e3d595;
+}
+#mysociety #front_stats div {
+ background-color: #e3d595;
+}
+#mysociety p.promo {
+ border-top: 1px solid #bbb;
+ border-bottom: 1px solid #bbb;
+ background-color: #eee;
+ text-align: center;
+ padding: 0 0.5em;
+}
+
+#header {
+ font-size: 200%;
+ font-weight: bold;
+ border-bottom: solid 2px #5e552b;
+ margin: 0;
+ padding: 0.15em 0.5em;
+ background-color: #e3d595;
+ color: #5e552b;
+}
+#header a:link, #header a:visited {
+ color: #5e552b;
+ background-color: #e3d595;
+ text-decoration: none;
+}
+#header a:active, #header a:hover {
+ text-decoration: underline;
+}
+
+#my {
+ color: #4e451b;
+ background-color: #e3d595;
+}
+
+#mysociety {
+ width: 100%;
+ /* Must specify a width or IE goes crazy wrong! */
+ position: relative;
+ margin: 0 auto;
+ max-width: 60em;
+ overflow: auto;
+}
+
+/* Can't put the margin in #mysociety because of above IE craziness */
+#wrapper {
+ margin: 1em 2em;
+}
+
+#meta {
+ list-style-type: none;
+ margin: 0.25em 0 0 1em;
+ padding: 0;
+ font-size: 0.875em;
+}
+#meta li {
+ display: inline;
+ margin: 0;
+ padding: 0 0 0 0.25em;
+ border-left: solid 1px #5e552b;
+}
+#meta li:first-child {
+ border-left: none;
+}
+
+.v {
+ display: none;
+}
+
+#navigation {
+ position: absolute;
+ top: 1em;
+ right: 1em;
+ padding: 0;
+ margin: 0;
+ list-style-type: none;
+}
+#navigation li {
+ display: inline;
+ padding: 0;
+ margin: 0;
+}
+#navigation a {
+ display: -moz-inline-box;
+ display: inline-block;
+ padding: 0.4em 1em;
+}
+#navigation a:link, #navigation a:visited {
+ color: #5e552b;
+}
+#navigation a:hover, #navigation a:active {
+ background-color: #5e552b;
+ color: #e3d595;
+ -moz-border-radius-topleft: 0.5em;
+ -webkit-border-top-left-radius: 0.5em;
+ border-radius-top-left: 0.5em;
+ -moz-border-radius-topright: 0.5em;
+ -webkit-border-top-right-radius: 0.5em;
+ border-radius-top-right: 0.5em;
+}
+
+#nav_new a {
+ background-image: url("/i/new.png");
+ background-repeat: no-repeat;
+ background-position: 100% 0;
+}
+
+#logo {
+ border: none;
+ position: absolute;
+ top: 3.3em;
+ right: 10px;
+}
+
+#footer {
+ clear: both;
+ text-align: center;
+ border-top: solid 2px #ccc;
+ width: 50%;
+ margin: 1em auto 0;
+ padding: 0;
+ color: #333333;
+}
+#footer .l, #footer .r {
+ margin-top: 0;
+ text-align: left;
+ width: 45%;
+}
+#footer .l {
+ float: left;
+}
+#footer .r {
+ float: right;
+}