diff options
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’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’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; +} |