aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm77
-rw-r--r--perllib/FixMyStreet/App/Controller/Alert.pm6
-rw-r--r--perllib/FixMyStreet/App/Controller/Around.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth.pm113
-rw-r--r--perllib/FixMyStreet/App/Controller/Moderate.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm3
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/New.pm2
-rw-r--r--perllib/FixMyStreet/App/Controller/Report/Update.pm1
-rw-r--r--perllib/FixMyStreet/Cobrand/Zurich.pm6
-rw-r--r--perllib/FixMyStreet/DB/Result/User.pm27
-rw-r--r--t/app/controller/alert_new.t12
-rw-r--r--t/app/controller/auth.t56
-rw-r--r--t/app/controller/moderate.t76
-rw-r--r--t/app/controller/photo.t7
-rw-r--r--t/app/controller/report_updates.t80
-rw-r--r--t/cobrand/zurich.t11
-rw-r--r--templates/email/bromley/questionnaire.txt10
-rw-r--r--templates/email/default/change_email.txt11
-rw-r--r--templates/web/base/admin/body-form.html2
-rw-r--r--templates/web/base/admin/body.html4
-rw-r--r--templates/web/base/admin/category_edit.html2
-rw-r--r--templates/web/base/admin/report_edit.html2
-rw-r--r--templates/web/base/admin/update_edit.html2
-rw-r--r--templates/web/base/admin/user-form.html2
-rw-r--r--templates/web/base/alert/_list.html1
-rw-r--r--templates/web/base/alert/updates.html1
-rwxr-xr-xtemplates/web/base/around/display_location.html1
-rw-r--r--templates/web/base/auth/change_email.html37
-rw-r--r--templates/web/base/auth/change_password.html7
-rw-r--r--templates/web/base/auth/general.html11
-rw-r--r--templates/web/base/my/my.html1
-rw-r--r--templates/web/base/report/_main.html1
-rw-r--r--templates/web/base/report/display_tools.html2
-rw-r--r--templates/web/base/report/new/fill_in_details.html5
-rw-r--r--templates/web/base/report/update-form.html1
-rw-r--r--templates/web/base/report/update.html1
-rw-r--r--templates/web/bromley/report/display.html1
-rw-r--r--templates/web/eastsussex/report/update-form.html2
-rw-r--r--templates/web/seesomething/around/display_location.html1
-rw-r--r--templates/web/zurich/admin/body-form.html2
-rw-r--r--templates/web/zurich/admin/body.html2
-rw-r--r--templates/web/zurich/admin/report_edit-sdm.html2
-rw-r--r--templates/web/zurich/admin/report_edit.html2
-rw-r--r--templates/web/zurich/admin/template_edit.html2
-rw-r--r--templates/web/zurich/admin/update_edit.html2
-rw-r--r--web/js/dropzone.js.patch36
-rw-r--r--[-rwxr-xr-x]web/js/dropzone.min.js62
-rw-r--r--web/js/src/exif.js94
48 files changed, 566 insertions, 227 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index 4e288556f..e68eb00a5 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -215,7 +215,7 @@ sub bodies : Path('bodies') : Args(0) {
return;
}
- $c->forward( 'get_token' );
+ $c->forward( '/auth/get_csrf_token' );
my $edit_activity = $c->model('DB::ContactsHistory')->search(
undef,
@@ -232,7 +232,7 @@ sub bodies : Path('bodies') : Args(0) {
my $posted = $c->get_param('posted') || '';
if ( $posted eq 'body' ) {
$c->forward('check_for_super_user');
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $params = $c->forward('body_params');
my $body = $c->model('DB::Body')->create( $params );
@@ -289,7 +289,7 @@ sub body : Path('body') : Args(1) {
$c->stash->{body_id} = $body_id;
$c->forward( 'check_for_super_user' );
- $c->forward( 'get_token' );
+ $c->forward( '/auth/get_csrf_token' );
$c->forward( 'lookup_body' );
$c->forward( 'fetch_all_bodies' );
$c->forward( 'body_form_dropdowns' );
@@ -318,7 +318,7 @@ sub update_contacts : Private {
my $editor = $c->forward('get_user');
if ( $posted eq 'new' ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my %errors;
@@ -370,7 +370,7 @@ sub update_contacts : Private {
}
} elsif ( $posted eq 'update' ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my @categories = $c->get_param_list('confirmed');
@@ -393,7 +393,7 @@ sub update_contacts : Private {
$c->stash->{updated} = _('Values updated');
} elsif ( $posted eq 'body' ) {
$c->forward('check_for_super_user');
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $params = $c->forward( 'body_params' );
$c->stash->{body}->update( $params );
@@ -476,7 +476,7 @@ sub category_edit : Path('body') : Args(2) {
$c->stash->{body_id} = $body_id;
- $c->forward( 'get_token' );
+ $c->forward( '/auth/get_csrf_token' );
$c->forward( 'lookup_body' );
my $contact = $c->stash->{body}->contacts->search( { category => $category } )->first;
@@ -643,7 +643,7 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->stash->{problem} = $problem;
- $c->forward('get_token');
+ $c->forward('/auth/get_csrf_token');
if ( $c->cobrand->moniker eq 'zurich' ) {
$c->stash->{page} = 'admin';
@@ -689,7 +689,7 @@ sub report_edit : Path('report_edit') : Args(1) {
->all ];
if ( $c->get_param('resend') ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
$problem->whensent(undef);
$problem->update();
@@ -699,7 +699,7 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->forward( 'log_edit', [ $id, 'problem', 'resend' ] );
}
elsif ( $c->get_param('mark_sent') ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
$problem->whensent(\'current_timestamp');
$problem->update();
$c->stash->{status_message} = '<p><em>' . _('That problem has been marked as sent.') . '</em></p>';
@@ -717,7 +717,7 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->forward('ban_user');
}
elsif ( $c->get_param('submit') ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $done = 0;
my $edited = 0;
@@ -917,7 +917,7 @@ sub users: Path('users') : Args(0) {
}
} else {
- $c->forward('get_token');
+ $c->forward('/auth/get_csrf_token');
$c->forward('fetch_all_bodies');
# Admin users by default
@@ -942,7 +942,7 @@ sub update_edit : Path('update_edit') : Args(1) {
$c->detach( '/page_error_404_not_found' )
unless $update;
- $c->forward('get_token');
+ $c->forward('/auth/get_csrf_token');
$c->stash->{update} = $update;
@@ -965,7 +965,7 @@ sub update_edit : Path('update_edit') : Args(1) {
$c->stash->{update}->discard_changes;
}
elsif ( $c->get_param('submit') ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $old_state = $update->state;
my $new_state = $c->get_param('state');
@@ -1047,12 +1047,12 @@ sub user_add : Path('user_edit') : Args(0) {
my ( $self, $c ) = @_;
$c->stash->{template} = 'admin/user_edit.html';
- $c->forward('get_token');
+ $c->forward('/auth/get_csrf_token');
$c->forward('fetch_all_bodies');
return unless $c->get_param('submit');
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
unless ($c->get_param('email')) {
$c->stash->{field_errors}->{email} = _('Please enter a valid email');
@@ -1084,7 +1084,7 @@ sub user_add : Path('user_edit') : Args(0) {
sub user_edit : Path('user_edit') : Args(1) {
my ( $self, $c, $id ) = @_;
- $c->forward('get_token');
+ $c->forward('/auth/get_csrf_token');
my $user = $c->model('DB::User')->find( { id => $id } );
$c->stash->{user} = $user;
@@ -1092,7 +1092,7 @@ sub user_edit : Path('user_edit') : Args(1) {
$c->forward('fetch_all_bodies');
if ( $c->get_param('submit') ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $edited = 0;
@@ -1120,12 +1120,7 @@ sub user_edit : Path('user_edit') : Args(1) {
my $existing_user = $c->model('DB::User')->search({ email => $user->email, id => { '!=', $user->id } })->first;
if ($existing_user) {
- foreach (qw(Problem Comment Alert)) {
- $c->model("DB::$_")
- ->search({ user_id => $user->id })
- ->update({ user_id => $existing_user->id });
- }
- $user->delete;
+ $existing_user->adopt($user);
$c->forward( 'log_edit', [ $id, 'user', 'merge' ] );
$c->res->redirect( $c->uri_for( 'user_edit', $existing_user->id ) );
} else {
@@ -1328,40 +1323,6 @@ sub get_user : Private {
return $user;
}
-=item get_token
-
-Generate a token based on user and secret
-
-=cut
-
-sub get_token : Private {
- my ( $self, $c ) = @_;
-
- my $secret = $c->model('DB::Secret')->get;
- my $user = $c->forward('get_user');
- my $token = sha1_hex($user . $secret);
- $c->stash->{token} = $token;
-
- return 1;
-}
-
-=item check_token
-
-Check that a token has been set on a request and it's the correct token. If
-not then display 404 page
-
-=cut
-
-sub check_token : Private {
- my ( $self, $c ) = @_;
-
- if ( !$c->get_param('token') || $c->get_param('token') ne $c->stash->{token} ) {
- $c->detach( '/page_error_404_not_found' );
- }
-
- return 1;
-}
-
=item log_edit
$c->forward( 'log_edit', [ $object_id, $object_type, $action_performed ] );
diff --git a/perllib/FixMyStreet/App/Controller/Alert.pm b/perllib/FixMyStreet/App/Controller/Alert.pm
index 45c26a195..5c9fbad1b 100644
--- a/perllib/FixMyStreet/App/Controller/Alert.pm
+++ b/perllib/FixMyStreet/App/Controller/Alert.pm
@@ -36,6 +36,8 @@ sub index : Path('') : Args(0) {
sub list : Path('list') : Args(0) {
my ( $self, $c ) = @_;
+ $c->forward('/auth/get_csrf_token');
+
return
unless $c->forward('setup_request')
&& $c->forward('prettify_pc')
@@ -112,6 +114,8 @@ Sign up to email alerts
sub subscribe_email : Private {
my ( $self, $c ) = @_;
+ $c->forward('/auth/check_csrf_token');
+
$c->stash->{errors} = [];
$c->forward('process_user');
@@ -141,6 +145,8 @@ sub subscribe_email : Private {
sub updates : Path('updates') : Args(0) {
my ( $self, $c ) = @_;
+ $c->forward('/auth/get_csrf_token');
+
$c->stash->{email} = $c->get_param('rznvy');
$c->stash->{problem_id} = $c->get_param('id');
}
diff --git a/perllib/FixMyStreet/App/Controller/Around.pm b/perllib/FixMyStreet/App/Controller/Around.pm
index b0340204a..b8f038ce3 100644
--- a/perllib/FixMyStreet/App/Controller/Around.pm
+++ b/perllib/FixMyStreet/App/Controller/Around.pm
@@ -156,6 +156,8 @@ sub display_location : Private {
# set the template to use
$c->stash->{template} = 'around/display_location.html';
+ $c->forward('/auth/get_csrf_token');
+
# get the lat,lng
my $latitude = $c->stash->{latitude};
my $longitude = $c->stash->{longitude};
diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm
index c5a6cf9bf..b564a988c 100644
--- a/perllib/FixMyStreet/App/Controller/Auth.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth.pm
@@ -6,8 +6,9 @@ BEGIN { extends 'Catalyst::Controller'; }
use Email::Valid;
use Net::Domain::TLD;
-use mySociety::AuthToken;
+use Digest::HMAC_SHA1 qw(hmac_sha1);
use JSON::MaybeXS;
+use MIME::Base64;
use Net::Facebook::Oauth2;
use Net::Twitter::Lite::WithAPIv1_1;
@@ -37,16 +38,17 @@ sub general : Path : Args(0) {
# all done unless we have a form posted to us
return unless $c->req->method eq 'POST';
- # decide which action to take
- $c->detach('facebook_sign_in') if $c->get_param('facebook_sign_in');
- $c->detach('twitter_sign_in') if $c->get_param('twitter_sign_in');
-
- my $clicked_password = $c->get_param('sign_in');
my $clicked_email = $c->get_param('email_sign_in');
+ my $data_address = $c->get_param('email');
my $data_password = $c->get_param('password_sign_in');
my $data_email = $c->get_param('name') || $c->get_param('password_register');
+ # decide which action to take
$c->detach('email_sign_in') if $clicked_email || ($data_email && !$data_password);
+ if (!$data_address && !$data_password && !$data_email) {
+ $c->detach('facebook_sign_in') if $c->get_param('facebook_sign_in');
+ $c->detach('twitter_sign_in') if $c->get_param('twitter_sign_in');
+ }
$c->forward( 'sign_in' )
&& $c->detach( 'redirect_on_signin', [ $c->get_param('r') ] );
@@ -137,6 +139,10 @@ sub email_sign_in : Private {
if $c->get_param('oauth_need_email') && $c->session->{oauth}{facebook_id};
$token_data->{twitter_id} = $c->session->{oauth}{twitter_id}
if $c->get_param('oauth_need_email') && $c->session->{oauth}{twitter_id};
+ if ($c->stash->{current_user}) {
+ $token_data->{old_email} = $c->stash->{current_user}->email;
+ $token_data->{r} = 'auth/change_email/success';
+ }
my $token_obj = $c->model('DB::Token')->create({
scope => 'email_sign_in',
@@ -144,7 +150,8 @@ sub email_sign_in : Private {
});
$c->stash->{token} = $token_obj->token;
- $c->send_email( 'login.txt', { to => $good_email } );
+ my $template = $c->stash->{email_template} || 'login.txt';
+ $c->send_email( $template, { to => $good_email } );
$c->stash->{template} = 'auth/token.html';
}
@@ -175,17 +182,40 @@ sub token : Path('/M') : Args(1) {
return;
}
- # Sign out in case we are another user
- $c->logout();
-
# find or create the user related to the token.
my $data = $token_obj->data;
- my $user = $c->model('DB::User')->find_or_create( { email => $data->{email} } );
+
+ if ($data->{old_email} && (!$c->user_exists || $c->user->email ne $data->{old_email})) {
+ $c->stash->{token_not_found} = 1;
+ return;
+ }
+
+ # sign out in case we are another user
+ $c->logout();
+
+ my $user = $c->model('DB::User')->find_or_new({ email => $data->{email} });
+
+ if ($data->{old_email}) {
+ # Were logged in as old_email, want to switch to email ($user)
+ if ($user->in_storage) {
+ my $old_user = $c->model('DB::User')->find({ email => $data->{old_email} });
+ if ($old_user) {
+ $old_user->adopt($user);
+ $user = $old_user;
+ $user->email($data->{email});
+ }
+ } else {
+ # Updating to a new (to the db) email address, easier!
+ $user = $c->model('DB::User')->find({ email => $data->{old_email} });
+ $user->email($data->{email});
+ }
+ }
+
$user->name( $data->{name} ) if $data->{name};
$user->password( $data->{password}, 1 ) if $data->{password};
$user->facebook_id( $data->{facebook_id} ) if $data->{facebook_id};
$user->twitter_id( $data->{twitter_id} ) if $data->{twitter_id};
- $user->update;
+ $user->update_or_insert;
$c->authenticate( { email => $user->email }, 'no_password' );
# send the user to their page
@@ -414,12 +444,13 @@ sub change_password : Local {
$c->detach( 'redirect' ) unless $c->user;
- # FIXME - CSRF check here
- # FIXME - minimum criteria for passwords (length, contain number, etc)
+ $c->forward('get_csrf_token');
# If not a post then no submission
return unless $c->req->method eq 'POST';
+ $c->forward('check_csrf_token');
+
# get the passwords
my $new = $c->get_param('new_password') // '';
my $confirm = $c->get_param('confirm') // '';
@@ -443,6 +474,60 @@ sub change_password : Local {
}
+=head2 change_email
+
+Let the user change their email.
+
+=cut
+
+sub change_email : Local {
+ my ( $self, $c ) = @_;
+
+ $c->detach( 'redirect' ) unless $c->user;
+
+ $c->forward('get_csrf_token');
+
+ # If not a post then no submission
+ return unless $c->req->method eq 'POST';
+
+ $c->forward('check_csrf_token');
+ $c->stash->{current_user} = $c->user;
+ $c->stash->{email_template} = 'change_email.txt';
+ $c->forward('email_sign_in');
+}
+
+sub get_csrf_token : Private {
+ my ( $self, $c ) = @_;
+
+ my $time = $c->stash->{csrf_time} || time();
+ my $hash = hmac_sha1("$time-" . ($c->sessionid || ""), $c->model('DB::Secret')->get);
+ $hash = encode_base64($hash, "");
+ $hash =~ s/=$//;
+ my $token = "$time-$hash";
+ $c->stash->{csrf_token} = $token unless $c->stash->{csrf_time};
+ return $token;
+}
+
+sub check_csrf_token : Private {
+ my ( $self, $c ) = @_;
+
+ my $token = $c->get_param('token') || "";
+ $token =~ s/ /+/g;
+ my ($time) = $token =~ /^(\d+)-[0-9a-zA-Z+\/]+$/;
+ $c->stash->{csrf_time} = $time;
+ $c->detach('no_csrf_token')
+ unless $time
+ && $time > time() - 3600
+ && $token eq $c->forward('get_csrf_token');
+ delete $c->stash->{csrf_time};
+}
+
+sub no_csrf_token : Private {
+ my ($self, $c) = @_;
+ $c->stash->{message} = _('Unknown error');
+ $c->stash->{template} = 'errors/generic.html';
+}
+
=head2 sign_out
Log the user out. Tell them we've done so.
diff --git a/perllib/FixMyStreet/App/Controller/Moderate.pm b/perllib/FixMyStreet/App/Controller/Moderate.pm
index 77a3346dc..2d23417b9 100644
--- a/perllib/FixMyStreet/App/Controller/Moderate.pm
+++ b/perllib/FixMyStreet/App/Controller/Moderate.pm
@@ -57,6 +57,8 @@ sub report : Chained('moderate') : PathPart('report') : CaptureArgs(1) {
$c->detach unless $c->user_exists;
$c->detach unless $c->user->has_permission_to(moderate => $problem->bodies_str);
+ $c->forward('/auth/check_csrf_token');
+
my $original = $problem->find_or_new_related( moderation_original_data => {
title => $problem->title,
detail => $problem->detail,
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index b3e546c2c..89df4a52d 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -72,6 +72,7 @@ sub ajax : Path('ajax') : Args(1) {
sub _display : Private {
my ( $self, $c, $id ) = @_;
+ $c->forward('/auth/get_csrf_token');
$c->forward( 'load_problem_or_display_error', [ $id ] );
$c->forward( 'load_updates' );
$c->forward( 'format_problem_for_display' );
@@ -249,6 +250,8 @@ users too about this change, at which point we can delete:
sub delete :Local :Args(1) {
my ( $self, $c, $id ) = @_;
+ $c->forward('/auth/check_csrf_token');
+
$c->forward( 'load_problem_or_display_error', [ $id ] );
my $p = $c->stash->{problem};
diff --git a/perllib/FixMyStreet/App/Controller/Report/New.pm b/perllib/FixMyStreet/App/Controller/Report/New.pm
index af9ca50b5..9779a5e2a 100644
--- a/perllib/FixMyStreet/App/Controller/Report/New.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/New.pm
@@ -81,6 +81,7 @@ sub report_new : Path : Args(0) {
# create the report - loading a partial if available
$c->forward('initialize_report');
+ $c->forward('/auth/get_csrf_token');
# work out the location for this report and do some checks
# Also show map if we're just updating the filters
@@ -96,6 +97,7 @@ sub report_new : Path : Args(0) {
# deal with the user and report and check both are happy
return unless $c->forward('check_form_submitted');
+ $c->forward('/auth/check_csrf_token');
$c->forward('process_user');
$c->forward('process_report');
$c->forward('/photo/process_photo');
diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm
index 275a300bd..d03797f56 100644
--- a/perllib/FixMyStreet/App/Controller/Report/Update.pm
+++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm
@@ -25,6 +25,7 @@ sub report_update : Path : Args(0) {
$c->forward('check_form_submitted')
or $c->go( '/report/display', [ $c->stash->{problem}->id ] );
+ $c->forward('/auth/check_csrf_token');
$c->forward('process_update');
$c->forward('process_user');
$c->forward('/photo/process_photo');
diff --git a/perllib/FixMyStreet/Cobrand/Zurich.pm b/perllib/FixMyStreet/Cobrand/Zurich.pm
index 987f0bb67..2e4a167db 100644
--- a/perllib/FixMyStreet/Cobrand/Zurich.pm
+++ b/perllib/FixMyStreet/Cobrand/Zurich.pm
@@ -541,7 +541,7 @@ sub admin_report_edit {
# If super or dm check that the token is correct before proceeding
if ( ($type eq 'super' || $type eq 'dm') && $c->get_param('submit') ) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
}
# All types of users can add internal notes
@@ -807,7 +807,7 @@ sub admin_report_edit {
# subdivision, or because the customer was not contactable.
# We handle these in the same way but with different statuses.
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $not_contactable = $c->get_param('not_contactable');
@@ -829,7 +829,7 @@ sub admin_report_edit {
$self->update_admin_log($c, $problem);
$c->res->redirect( '/admin/summary' );
} elsif ($c->get_param('submit')) {
- $c->forward('check_token');
+ $c->forward('/auth/check_csrf_token');
my $db_update = 0;
if ( $c->get_param('latitude') != $problem->latitude || $c->get_param('longitude') != $problem->longitude ) {
diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm
index 054be3644..7356969d1 100644
--- a/perllib/FixMyStreet/DB/Result/User.pm
+++ b/perllib/FixMyStreet/DB/Result/User.pm
@@ -239,9 +239,30 @@ sub has_permission_to {
return $permission ? 1 : undef;
}
-sub print {
- my $self = shift;
- return '[' . (join '-', @_) . ']';
+sub adopt {
+ my ($self, $other) = @_;
+
+ return if $self->id == $other->id;
+
+ # Move most things from $other to $self
+ foreach (qw(Problem Comment Alert AdminLog )) {
+ $self->result_source->schema->resultset($_)
+ ->search({ user_id => $other->id })
+ ->update({ user_id => $self->id });
+ }
+
+ # It's possible the user permissions for the other user exist, so
+ # try updating, and then delete anyway.
+ foreach ($self->result_source->schema->resultset("UserBodyPermission")
+ ->search({ user_id => $other->id })->all) {
+ eval {
+ $_->update({ user_id => $self->id });
+ };
+ $_->delete if $@;
+ }
+
+ # Delete the now empty user
+ $other->delete;
}
1;
diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t
index 777d733e2..06932f70a 100644
--- a/t/app/controller/alert_new.t
+++ b/t/app/controller/alert_new.t
@@ -7,6 +7,10 @@ use FixMyStreet::App;
my $mech = FixMyStreet::TestMech->new;
+$mech->log_in_ok('test@example.com');
+$mech->get_ok('/alert/subscribe?id=1');
+my ($csrf) = $mech->content =~ /name="token" value="([^"]*)"/;
+
foreach my $test (
{
email => 'test@example.com',
@@ -71,7 +75,7 @@ foreach my $test (
$mech->delete_user($user);
}
- $mech->get_ok( $test->{uri} );
+ $mech->get_ok( $test->{uri} . "&token=$csrf" );
$mech->content_contains( $test->{content} );
$user =
@@ -113,7 +117,7 @@ foreach my $test (
my $existing_id = $alert->id;
my $existing_token = $url_token;
- $mech->get_ok( $test->{uri} );
+ $mech->get_ok( $test->{uri} . "&token=$csrf" );
$email = $mech->get_email;
ok $email, 'got a second email';
@@ -165,7 +169,7 @@ foreach my $test (
# clear existing data so we can be sure we're creating it
ok $alert->delete() if $alert && !$test->{exist};
- $mech->get_ok( '/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location' );
+ $mech->get_ok( '/alert/subscribe?type=local&rznvy=test-new@example.com&feed=area:1000:A_Location&token=' . $csrf );
$alert = FixMyStreet::App->model('DB::Alert')->find(
{
@@ -262,7 +266,7 @@ for my $test (
FixMyStreet::App->model('DB::Abuse')
->find_or_create( { email => $test->{email} } );
- $mech->get_ok( $test->{uri} );
+ $mech->get_ok( $test->{uri} . "&token=$csrf" );
$mech->content_contains( $test->{content} );
$user =
diff --git a/t/app/controller/auth.t b/t/app/controller/auth.t
index 235a3af7e..60f22acfb 100644
--- a/t/app/controller/auth.t
+++ b/t/app/controller/auth.t
@@ -7,11 +7,13 @@ use FixMyStreet::TestMech;
my $mech = FixMyStreet::TestMech->new;
my $test_email = 'test@example.com';
+my $test_email2 = 'test@example.net';
my $test_password = 'foobar';
$mech->delete_user($test_email);
END {
$mech->delete_user($test_email);
+ $mech->delete_user($test_email2);
done_testing();
}
@@ -63,7 +65,6 @@ $mech->not_logged_in_ok;
# check that we got one email
{
- $mech->email_count_is(1);
my $email = $mech->get_email;
$mech->clear_emails_ok;
is $email->header('Subject'), "Your FixMyStreet account details",
@@ -116,7 +117,6 @@ $mech->not_logged_in_ok;
# follow link and change password - check not prompted for old password
$mech->not_logged_in_ok;
- $mech->email_count_is(1);
my $email = $mech->get_email;
$mech->clear_emails_ok;
my ($link) = $email->body =~ m{(http://\S+)};
@@ -128,7 +128,7 @@ $mech->not_logged_in_ok;
ok my $form = $mech->form_name('change_password'),
"found change password form";
is_deeply [ sort grep { $_ } map { $_->name } $form->inputs ], #
- [ 'confirm', 'new_password' ],
+ [ 'confirm', 'new_password', 'token' ],
"check we got expected fields (ie not old_password)";
# check the various ways the form can be wrong
@@ -175,6 +175,54 @@ $mech->not_logged_in_ok;
ok $user->password, "user now has a password";
}
+subtest "Test change email page" => sub {
+ # Still signed in from the above test
+ $mech->get_ok('/my');
+ $mech->follow_link_ok({url => '/auth/change_email'});
+ $mech->submit_form_ok(
+ { with_fields => { email => "" } },
+ "submit blank change email form"
+ );
+ $mech->content_contains( 'Please enter your email', "found expected error" );
+ $mech->submit_form_ok({ with_fields => { email => $test_email2 } }, "change_email to $test_email2");
+ is $mech->uri->path, '/auth/change_email', "still on change email page";
+ $mech->content_contains( 'Now check your email', "found check your email" );
+ my $email = $mech->get_email;
+ $mech->clear_emails_ok;
+ my ($link) = $email->body =~ m{(http://\S+)};
+ $mech->get_ok($link);
+ is $mech->uri->path, '/auth/change_email/success', "redirected to the change_email page";
+ $mech->content_contains('successfully confirmed');
+ ok(FixMyStreet::App->model('DB::User')->find( { email => $test_email2 } ), "got a user");
+
+ ok(FixMyStreet::App->model('DB::User')->create( { email => $test_email } ), "created old user");
+ $mech->submit_form_ok({ with_fields => { email => $test_email } },
+ "change_email back to $test_email"
+ );
+ is $mech->uri->path, '/auth/change_email', "still on change email page";
+ $mech->content_contains( 'Now check your email', "found check your email" );
+ $email = $mech->get_email;
+ $mech->clear_emails_ok;
+ ($link) = $email->body =~ m{(http://\S+)};
+ $mech->get_ok($link);
+ is $mech->uri->path, '/auth/change_email/success', "redirected to the change_email page";
+ $mech->content_contains('successfully confirmed');
+
+ # Test you can't click the link if logged out
+ $mech->submit_form_ok({ with_fields => { email => $test_email } },
+ "change_email back to $test_email"
+ );
+ is $mech->uri->path, '/auth/change_email', "still on change email page";
+ $mech->content_contains( 'Now check your email', "found check your email" );
+ $email = $mech->get_email;
+ $mech->clear_emails_ok;
+ ($link) = $email->body =~ m{(http://\S+)};
+ $mech->log_out_ok;
+ $mech->get_ok($link);
+ isnt $mech->uri->path, '/auth/change_email/success', "not redirected to the change_email page";
+ $mech->content_contains('Sorry');
+};
+
foreach my $remember_me ( '1', '0' ) {
subtest "sign in using valid details (remember_me => '$remember_me')" => sub {
$mech->get_ok('/auth');
@@ -188,7 +236,7 @@ foreach my $remember_me ( '1', '0' ) {
},
button => 'sign_in',
},
- "sign in with '$test_email' & '$test_password"
+ "sign in with '$test_email' & '$test_password'"
);
is $mech->uri->path, '/my', "redirected to correct page";
diff --git a/t/app/controller/moderate.t b/t/app/controller/moderate.t
index b79f50e73..38216c708 100644
--- a/t/app/controller/moderate.t
+++ b/t/app/controller/moderate.t
@@ -8,6 +8,7 @@ use FixMyStreet::App;
use Data::Dumper;
my $mech = FixMyStreet::TestMech->new;
+$mech->host('www.example.org');
my $BROMLEY_ID = 2482;
my $body = $mech->create_body_ok( $BROMLEY_ID, 'Bromley Council' );
@@ -92,11 +93,12 @@ my %problem_prepopulated = (
subtest 'Problem moderation' => sub {
subtest 'Post modify title and text' => sub {
- $mech->post_ok('/moderate/report/' . $report->id, {
+ $mech->get_ok($REPORT_URL);
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_title => 'Good good',
problem_detail => 'Good good improved',
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$report->discard_changes;
@@ -105,11 +107,11 @@ subtest 'Problem moderation' => sub {
};
subtest 'Revert title and text' => sub {
- $mech->post_ok('/moderate/report/' . $report->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_revert_title => 1,
problem_revert_detail => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$report->discard_changes;
@@ -120,18 +122,18 @@ subtest 'Problem moderation' => sub {
subtest 'Make anonymous' => sub {
$mech->content_lacks('Reported anonymously');
- $mech->post_ok('/moderate/report/' . $report->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_show_name => 0,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_contains('Reported anonymously');
- $mech->post_ok('/moderate/report/' . $report->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_show_name => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_lacks('Reported anonymously');
@@ -140,18 +142,18 @@ subtest 'Problem moderation' => sub {
subtest 'Hide photo' => sub {
$mech->content_contains('Photo of this report');
- $mech->post_ok('/moderate/report/' . $report->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_show_photo => 0,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_lacks('Photo of this report');
- $mech->post_ok('/moderate/report/' . $report->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_show_photo => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_contains('Photo of this report');
@@ -160,10 +162,10 @@ subtest 'Problem moderation' => sub {
subtest 'Hide report' => sub {
$mech->clear_emails_ok;
- my $resp = $mech->post('/moderate/report/' . $report->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_hide => 1,
- });
+ }});
$mech->base_unlike( qr{/report/}, 'redirected to front page' );
$report->discard_changes;
@@ -185,22 +187,23 @@ $mech->content_lacks('Posted anonymously', 'sanity check');
subtest 'Problem 2' => sub {
my $REPORT2_URL = '/report/' . $report2->id ;
- $mech->post_ok('/moderate/report/' . $report2->id, {
+ $mech->get_ok($REPORT2_URL);
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_title => 'Good good',
problem_detail => 'Good good improved',
- });
+ }});
$mech->base_like( qr{\Q$REPORT2_URL\E} );
$report2->discard_changes;
is $report2->title, 'Good [...] good';
is $report2->detail, 'Good [...] good [...]improved';
- $mech->post_ok('/moderate/report/' . $report2->id, {
+ $mech->submit_form_ok({ with_fields => {
%problem_prepopulated,
problem_revert_title => 1,
problem_revert_detail => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT2_URL\E} );
$report2->discard_changes;
@@ -229,13 +232,12 @@ my $update = create_update();
subtest 'updates' => sub {
- my $MODERATE_UPDATE_URL = sprintf '/moderate/report/%d/update/%d', $report->id, $update->id;
-
subtest 'Update modify text' => sub {
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->get_ok($REPORT_URL);
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_detail => 'update good good good',
- }) or die $mech->content;
+ }}) or die $mech->content;
$mech->base_like( qr{\Q$REPORT_URL\E} );
$update->discard_changes;
@@ -243,10 +245,10 @@ subtest 'updates' => sub {
};
subtest 'Revert text' => sub {
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_revert_detail => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$update->discard_changes;
@@ -258,18 +260,18 @@ subtest 'updates' => sub {
$mech->content_lacks('Posted anonymously')
or die sprintf '%d (%d)', $update->id, $report->comments->count;
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_show_name => 0,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_contains('Posted anonymously');
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_show_name => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_lacks('Posted anonymously');
@@ -283,18 +285,18 @@ subtest 'updates' => sub {
$mech->content_contains('Photo of this report')
or die $mech->content;
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_show_photo => 0,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_lacks('Photo of this report');
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_show_photo => 1,
- });
+ }});
$mech->base_like( qr{\Q$REPORT_URL\E} );
$mech->content_contains('Photo of this report');
@@ -303,10 +305,10 @@ subtest 'updates' => sub {
subtest 'Hide comment' => sub {
$mech->content_contains('update good good bad good');
- $mech->post_ok( $MODERATE_UPDATE_URL, {
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_hide => 1,
- });
+ }});
$mech->content_lacks('update good good bad good');
};
@@ -316,11 +318,11 @@ subtest 'updates' => sub {
my $update2 = create_update();
subtest 'Update 2' => sub {
- my $MODERATE_UPDATE2_URL = sprintf '/moderate/report/%d/update/%d', $report->id, $update2->id;
- $mech->post_ok( $MODERATE_UPDATE2_URL, {
+ $mech->get_ok($REPORT_URL);
+ $mech->submit_form_ok({ with_fields => {
%update_prepopulated,
update_detail => 'update good good good',
- }) or die $mech->content;
+ }}) or die $mech->content;
$update2->discard_changes;
is $update2->text, 'update good good [...] good',
diff --git a/t/app/controller/photo.t b/t/app/controller/photo.t
index 425e3c4df..4cec82c44 100644
--- a/t/app/controller/photo.t
+++ b/t/app/controller/photo.t
@@ -40,11 +40,15 @@ subtest "Check multiple upload worked" => sub {
# submit the main form
# can't post_ok as we lose the Content_Type header
# (TODO rewrite with HTTP::Request::Common and request_ok)
+ $mech->get_ok('/report/new?lat=53.4031156&lon=-2.9840579');
+ my ($csrf) = $mech->content =~ /name="token" value="([^"]*)"/;
+
$mech->post( '/report/new',
Content_Type => 'form-data',
Content =>
{
submit_problem => 1,
+ token => $csrf,
title => 'Test',
lat => 53.4031156, lon => -2.9840579, # in Liverpool
pc => 'L1 4LN',
@@ -57,9 +61,6 @@ subtest "Check multiple upload worked" => sub {
email => 'test@example.com',
phone => '',
category => 'Street lighting',
- #password_sign_in => '',
- #password_register => '',
- #remember_me => undef,
}
);
ok $mech->success, 'Made request with multiple photo upload';
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index 7b4bf7854..2a3c7c0b3 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -510,20 +510,14 @@ subtest 'check non authority user cannot change set state' => sub {
$user->update;
$mech->get_ok("/report/$report_id");
- $mech->post_ok( "/report/update", {
- submit_update => 1,
- id => $report_id,
- name => $user->name,
- may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
- update => 'this is a forbidden update',
- state => 'fixed - council',
+ $mech->submit_form_ok( {
+ form_id => 'form_update_form',
+ fields => {
+ may_show_name => 1,
+ update => 'this is a forbidden update',
+ state => 'fixed - council',
},
- 'submitted with state',
- );
+ }, 'submitted with state');
is $mech->uri->path, "/report/update", "at /report/update";
@@ -540,20 +534,14 @@ for my $state ( qw/unconfirmed hidden partial/ ) {
$user->update;
$mech->get_ok("/report/$report_id");
- $mech->post_ok( "/report/update", {
- submit_update => 1,
- id => $report_id,
- name => $user->name,
- may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
- update => 'this is a forbidden update',
- state => $state,
+ $mech->submit_form_ok( {
+ form_id => 'form_update_form',
+ fields => {
+ may_show_name => 1,
+ update => 'this is a forbidden update',
+ state => $state,
},
- 'submitted with state',
- );
+ }, 'submitted with state');
is $mech->uri->path, "/report/update", "at /report/update";
@@ -570,10 +558,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to investigating',
state => 'investigating',
},
@@ -584,10 +568,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to in progress',
state => 'in progress',
},
@@ -598,10 +578,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to fixed',
state => 'fixed',
},
@@ -612,10 +588,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to action scheduled',
state => 'action scheduled',
},
@@ -626,10 +598,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to unable to fix',
state => 'unable to fix',
},
@@ -640,10 +608,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to internal referral',
state => 'internal referral',
},
@@ -655,10 +619,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to not responsible',
state => 'not responsible',
},
@@ -670,10 +630,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to duplicate',
state => 'duplicate',
},
@@ -685,10 +641,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to internal referral',
state => 'internal referral',
},
@@ -700,10 +652,6 @@ for my $test (
fields => {
name => $user->name,
may_show_name => 1,
- add_alert => undef,
- photo1 => '',
- photo2 => '',
- photo3 => '',
update => 'Set state to fixed',
state => 'fixed',
},
diff --git a/t/cobrand/zurich.t b/t/cobrand/zurich.t
index cf66136e5..a595f48c9 100644
--- a/t/cobrand/zurich.t
+++ b/t/cobrand/zurich.t
@@ -810,17 +810,12 @@ subtest "photo must be supplied for categories that require it" => sub {
MAPIT_ID_WHITELIST => [ 423017 ],
MAP_TYPE => 'Zurich,OSM',
}, sub {
- $mech->post_ok( '/report/new', {
+ $mech->get_ok('/report/new?lat=47.381817&lon=8.529156');
+ $mech->submit_form_ok({ with_fields => {
detail => 'Problem-Bericht',
- lat => 47.381817,
- lon => 8.529156,
email => 'user@example.org',
- pc => '',
- name => '',
category => 'Graffiti - photo required',
- photo => '',
- submit_problem => 1,
- });
+ }});
is $mech->res->code, 200, "missing photo shouldn't return anything but 200";
$mech->content_contains(_("Photo is required."), 'response should contain photo error message');
};
diff --git a/templates/email/bromley/questionnaire.txt b/templates/email/bromley/questionnaire.txt
index b9d428b98..5c0bd2957 100644
--- a/templates/email/bromley/questionnaire.txt
+++ b/templates/email/bromley/questionnaire.txt
@@ -1,8 +1,9 @@
-Subject: Questionnaire about '[% title %]'
+Subject: Questionnaire about your report: '[% title %]'
-Hi [% name %],
+Hello [% name %],
-[% created %] ago, you reported a problem. To keep the site
+[% created %] ago, you reported a problem, the details of
+which are at the end of this email. To keep the site
up to date and relevant, please fill in a short questionnaire
updating the status of your problem:
@@ -13,8 +14,7 @@ mailbox, please do not reply.
[% signature %]
-
-Your problem was as follows:
+Your report was as follows:
[% title %]
diff --git a/templates/email/default/change_email.txt b/templates/email/default/change_email.txt
new file mode 100644
index 000000000..0c5aeac14
--- /dev/null
+++ b/templates/email/default/change_email.txt
@@ -0,0 +1,11 @@
+Subject: Updating your [% INCLUDE 'site-name.txt' | trim %] email address
+
+Please click on the link below to confirm you wish to update your
+email address on [% INCLUDE 'site-name.txt' | trim %].
+
+[% c.uri_for_action( 'auth/token', token ) %]
+
+[% INCLUDE 'signature.txt' %]
+
+This email was sent automatically, from an unmonitored email account - so
+please do not reply to it.
diff --git a/templates/web/base/admin/body-form.html b/templates/web/base/admin/body-form.html
index 7acfbfdd5..8c4956f7f 100644
--- a/templates/web/base/admin/body-form.html
+++ b/templates/web/base/admin/body-form.html
@@ -236,7 +236,7 @@
<p>
<input type="hidden" name="posted" value="body">
- <input type="hidden" name="token" value="[% token %]">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="submit" value="[% body ? loc('Update body') : loc('Add body') %]">
</p>
</form>
diff --git a/templates/web/base/admin/body.html b/templates/web/base/admin/body.html
index d5e575666..15802fc44 100644
--- a/templates/web/base/admin/body.html
+++ b/templates/web/base/admin/body.html
@@ -97,7 +97,7 @@
<p>
<input type="hidden" name="posted" value="update">
- <input type="hidden" name="token" value="[% token %]">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="submit" name="Update statuses" value="[% loc('Update statuses') %]">
</p>
</form>
@@ -202,7 +202,7 @@
<p>
<input type="hidden" name="posted" value="new" >
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="submit" name="Create category" value="[% errors ? loc('Save changes') : loc('Create category') %]" >
</p>
diff --git a/templates/web/base/admin/category_edit.html b/templates/web/base/admin/category_edit.html
index c0bd43ef5..6537fe028 100644
--- a/templates/web/base/admin/category_edit.html
+++ b/templates/web/base/admin/category_edit.html
@@ -22,7 +22,7 @@
<form method="post" action="[% c.uri_for('body', body_id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
<p><strong>[% loc('Category:') %] </strong>[% contact.category | html %]
<input type="hidden" name="category" value="[% contact.category | html %]" >
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
[% IF contact.extra %]
<p><strong>[% loc('Extra data:') %] </strong>
[% USE Dumper %]
diff --git a/templates/web/base/admin/report_edit.html b/templates/web/base/admin/report_edit.html
index c0cdead84..065c6c2ce 100644
--- a/templates/web/base/admin/report_edit.html
+++ b/templates/web/base/admin/report_edit.html
@@ -4,7 +4,7 @@
[% status_message %]
<form method="post" action="[% c.uri_for( 'report_edit', problem.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
<ul>
[%- cobrand_data = problem.cobrand_data;
diff --git a/templates/web/base/admin/update_edit.html b/templates/web/base/admin/update_edit.html
index a956bb2cb..06bee6010 100644
--- a/templates/web/base/admin/update_edit.html
+++ b/templates/web/base/admin/update_edit.html
@@ -4,7 +4,7 @@
[% status_message %]
<form method="post" action="[% c.uri_for( 'update_edit', update.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
<ul>
[%- cobrand_data = update.cobrand_data;
diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html
index 3956e8533..b863bf96a 100644
--- a/templates/web/base/admin/user-form.html
+++ b/templates/web/base/admin/user-form.html
@@ -1,5 +1,5 @@
<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="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
[% IF c.cobrand.moniker == 'zurich' AND field_errors.email %]
diff --git a/templates/web/base/alert/_list.html b/templates/web/base/alert/_list.html
index 395948248..65bba2fed 100644
--- a/templates/web/base/alert/_list.html
+++ b/templates/web/base/alert/_list.html
@@ -1,3 +1,4 @@
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="hidden" name="type" value="local">
<input type="hidden" name="pc" value="[% pc | html %]">
<input type="hidden" name="latitude" value="[% latitude | html %]">
diff --git a/templates/web/base/alert/updates.html b/templates/web/base/alert/updates.html
index 104bfa55a..ecaed37ca 100644
--- a/templates/web/base/alert/updates.html
+++ b/templates/web/base/alert/updates.html
@@ -23,6 +23,7 @@
<input class="green-btn" type="submit" value="[% loc('Subscribe') %]">
</div>
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="hidden" name="id" value="[% problem_id | html %]">
<input type="hidden" name="type" value="updates">
</fieldset>
diff --git a/templates/web/base/around/display_location.html b/templates/web/base/around/display_location.html
index 337b97b8e..0ae1aadf5 100755
--- a/templates/web/base/around/display_location.html
+++ b/templates/web/base/around/display_location.html
@@ -41,6 +41,7 @@
[% IF allow_creation %]
<form action="[% c.uri_for('/report/new') %]" method="post" name="mapForm" id="mapForm" enctype="multipart/form-data" class="validate" novalidate>
+ <input type="hidden" name="token" value="[% csrf_token %]">
[% IF c.req.params.map_override %]
<input type="hidden" name="map_override" value="[% c.req.params.map_override | html %]">
[% END %]
diff --git a/templates/web/base/auth/change_email.html b/templates/web/base/auth/change_email.html
new file mode 100644
index 000000000..58c864929
--- /dev/null
+++ b/templates/web/base/auth/change_email.html
@@ -0,0 +1,37 @@
+[% INCLUDE 'header.html', title = loc('Change email address'), bodyclass = 'authpage' %]
+
+<h1>[% loc('Change email address') %]</h1>
+
+[% IF c.req.args.0 == 'success' %]
+ <p class="form-success">[% loc('You have successfully confirmed your email address.') %]</p>
+[% END %]
+
+[% loc('Your email address') %]: [% c.user.email %]
+
+<form action="[% c.uri_for('change_email') %]" method="post" name="change_email">
+ <input type="hidden" name="token" value="[% csrf_token %]">
+
+ <fieldset>
+ [% IF email_error;
+ errors = {
+ missing = loc('Please enter your email'),
+ other = loc('Please check your email address is correct')
+ };
+ loc_email_error = errors.$email_error || errors.other;
+ %]
+ <div class="form-error">[% loc_email_error %]</div>
+ [% END %]
+
+ <div class="form-field">
+ <label for="email">[% loc('New email address:') %]</label>
+ <input type="email" name="email" id="email" value="[% email | html %]">
+ </div>
+ <div class="final-submit">
+ <input type="submit" value="[% loc('Change email address') %]">
+ </div>
+
+ </fieldset>
+</form>
+
+
+[% INCLUDE 'footer.html' %]
diff --git a/templates/web/base/auth/change_password.html b/templates/web/base/auth/change_password.html
index b4170c23e..44b695e0d 100644
--- a/templates/web/base/auth/change_password.html
+++ b/templates/web/base/auth/change_password.html
@@ -3,12 +3,14 @@
<h1>[% loc('Change password') %]</h1>
[% IF password_changed %]
- <p id="fixed">[% loc('Your password has been changed') %]</p>
+ <p class="form-success">[% loc('Your password has been changed') %]</p>
[% END %]
<form action="[% c.uri_for('change_password') %]" method="post" name="change_password" class="fieldset">
+ <input type="hidden" name="token" value="[% csrf_token %]">
+ <fieldset>
[% IF password_error;
errors = {
@@ -29,10 +31,11 @@
<label for="confirm">[% loc('Again:') %]</label>
<input type="password" name="confirm" value="[% confirm | html %]">
</div>
- <div class="checkbox">
+ <div class="final-submit">
<input type="submit" value="[% loc('Change password') %]">
</div>
+ </fieldset>
</form>
diff --git a/templates/web/base/auth/general.html b/templates/web/base/auth/general.html
index 253dc26a1..a8bf8f1e0 100644
--- a/templates/web/base/auth/general.html
+++ b/templates/web/base/auth/general.html
@@ -86,8 +86,15 @@
<input class="green-btn" type="submit" name="sign_in" value="[% loc('Sign in') %]">
</div>
- <input type="checkbox" id="remember_me" name="remember_me" value='1'[% ' checked' IF remember_me %]>
- <label class="inline n" for="remember_me">[% loc('Keep me signed in on this computer') %]</label>
+ <div class="checkbox-group">
+ <input type="checkbox" id="remember_me" name="remember_me" value='1'[% ' checked' IF remember_me %]>
+ <label class="inline n" for="remember_me">[% loc('Keep me signed in on this computer') %]</label>
+ </div>
+
+ <div class="general-notes">
+ <p><strong>[% loc('Forgotten your password?') %]</strong>
+ [% loc('Sign in by email instead, providing a new password. When you click the link in your email, your password will be updated.') %]</p>
+ </div>
</div>
[% END %]
diff --git a/templates/web/base/my/my.html b/templates/web/base/my/my.html
index b93a837ad..9ba9533f8 100644
--- a/templates/web/base/my/my.html
+++ b/templates/web/base/my/my.html
@@ -20,6 +20,7 @@
<p class="my-account-buttons">
<a href="/auth/change_password">[% loc('Change password') %]</a>
+ <a href="/auth/change_email">[% loc('Change email') %]</a>
<a href="/auth/sign_out">[% loc('Sign out') %]</a>
</p>
diff --git a/templates/web/base/report/_main.html b/templates/web/base/report/_main.html
index aaa167108..4821b3fa0 100644
--- a/templates/web/base/report/_main.html
+++ b/templates/web/base/report/_main.html
@@ -5,6 +5,7 @@
[% IF moderating %]
[% original = problem_original %]
<form method="post" action="/moderate/report/[% problem.id %]">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<p class="moderate-display">
<input type="button" class="btn moderate" value="moderate">
</p>
diff --git a/templates/web/base/report/display_tools.html b/templates/web/base/report/display_tools.html
index ed9537184..435bfcbc1 100644
--- a/templates/web/base/report/display_tools.html
+++ b/templates/web/base/report/display_tools.html
@@ -2,6 +2,7 @@
<ul id="key-tools">
[% IF c.user_exists AND c.cobrand.users_can_hide AND c.user.belongs_to_body( c.cobrand.council_id ) %]
<li><form method="post" action="/report/delete/[% problem.id %]" id="remove-from-site-form">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="submit" id="key-tool-report-abuse" class="abuse" value="Remove from site">
</form></li>
[% ELSIF c.cobrand.moniker != 'zurich' %]
@@ -50,6 +51,7 @@
<input type="email" name="rznvy" id="alert_rznvy" value="[% email | html %]" size="30" placeholder="[% loc('Your email') %]">
<input class="green-btn" type="submit" value="[% loc('Subscribe') %]">
</div>
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="hidden" name="id" value="[% problem.id %]">
<input type="hidden" name="type" value="updates">
</fieldset>
diff --git a/templates/web/base/report/new/fill_in_details.html b/templates/web/base/report/new/fill_in_details.html
index fc272b533..e980c6065 100644
--- a/templates/web/base/report/new/fill_in_details.html
+++ b/templates/web/base/report/new/fill_in_details.html
@@ -15,16 +15,15 @@
<input type="hidden" name="map_override" value="[% c.req.params.map_override | html %]">
[% END %]
- <input type="hidden" name="pc" value="[% pc | html %]">
-
[% ELSE %]
<form action="[% c.uri_for('/report/new') %]" method="post" name="mapSkippedForm"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %] class="validate">
- <input type="hidden" name="pc" value="[% pc | html %]">
<input type="hidden" name="skipped" value="1">
[% END %]
+ <input type="hidden" name="token" value="[% csrf_token %]">
+ <input type="hidden" name="pc" value="[% pc | html %]">
<input type="hidden" name="latitude" id="fixmystreet.latitude" value="[% latitude | html %]">
<input type="hidden" name="longitude" id="fixmystreet.longitude" value="[% longitude | html %]">
diff --git a/templates/web/base/report/update-form.html b/templates/web/base/report/update-form.html
index f6ce265bf..97e0df779 100644
--- a/templates/web/base/report/update-form.html
+++ b/templates/web/base/report/update-form.html
@@ -15,6 +15,7 @@
[% INCLUDE 'errors.html' %]
<form method="post" action="[% c.uri_for( '/report/update' ) %]" id="form_update_form" name="updateForm" class="validate"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %]>
+ <input type="hidden" name="token" value="[% csrf_token %]">
<fieldset>
[% IF NOT login_success AND NOT oauth_need_email %]
[% INCLUDE 'report/update/form_update.html' %]
diff --git a/templates/web/base/report/update.html b/templates/web/base/report/update.html
index a09913d39..aaad33b7a 100644
--- a/templates/web/base/report/update.html
+++ b/templates/web/base/report/update.html
@@ -8,6 +8,7 @@
<li class="item-list__item item-list__item--updates">
[% IF moderating; original_update = update.moderation_original_data %]
<form method="post" action="/moderate/report/[% problem.id %]/update/[% update.id %]">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="button" class="btn moderate moderate-display" value="moderate">
<div class="moderate-edit">
<input type="checkbox" class="hide-document" name="update_hide">
diff --git a/templates/web/bromley/report/display.html b/templates/web/bromley/report/display.html
index d46d310cd..f30824385 100644
--- a/templates/web/bromley/report/display.html
+++ b/templates/web/bromley/report/display.html
@@ -30,6 +30,7 @@
[% INCLUDE 'errors.html' %]
<form method="post" action="[% c.uri_for( '/report/update' ) %]" name="updateForm" class="validate"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %]>
+ <input type="hidden" name="token" value="[% csrf_token %]">
<fieldset>
<input type="hidden" name="submit_update" value="1">
<input type="hidden" name="id" value="[% problem.id | html %]">
diff --git a/templates/web/eastsussex/report/update-form.html b/templates/web/eastsussex/report/update-form.html
index e4fb47a45..b2c67890f 100644
--- a/templates/web/eastsussex/report/update-form.html
+++ b/templates/web/eastsussex/report/update-form.html
@@ -24,6 +24,7 @@
</p>
<form method="post" action="/report/new">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<input type="hidden" name="latitude" value="[% problem.latitude %]">
<input type="hidden" name="longitude" value="[% problem.longitude %]">
<input type="submit" class="green-btn" value="CREATE A NEW PROBLEM NEARBY">
@@ -56,6 +57,7 @@
[% INCLUDE 'errors.html' %]
<form method="post" action="[% c.uri_for( '/report/update' ) %]" id="form_update_form" name="updateForm" class="validate"[% IF c.cobrand.allow_photo_upload %] enctype="multipart/form-data"[% END %]>
+ <input type="hidden" name="token" value="[% csrf_token %]">
<fieldset>
<input type="hidden" name="submit_update" value="1">
<input type="hidden" name="id" value="[% problem.id | html %]">
diff --git a/templates/web/seesomething/around/display_location.html b/templates/web/seesomething/around/display_location.html
index 3ed7cac46..7886c3a5d 100644
--- a/templates/web/seesomething/around/display_location.html
+++ b/templates/web/seesomething/around/display_location.html
@@ -21,6 +21,7 @@
%]
<form action="[% c.uri_for('/report/new') %]" method="post" name="mapForm" id="mapForm" enctype="multipart/form-data" class="validate" novalidate>
+ <input type="hidden" name="token" value="[% csrf_token %]">
[% IF c.req.params.map_override %]
<input type="hidden" name="map_override" value="[% c.req.params.map_override | html %]">
[% END %]
diff --git a/templates/web/zurich/admin/body-form.html b/templates/web/zurich/admin/body-form.html
index ac2887159..966bdf799 100644
--- a/templates/web/zurich/admin/body-form.html
+++ b/templates/web/zurich/admin/body-form.html
@@ -47,7 +47,7 @@
<p>
<input type="hidden" name="posted" value="body">
- <input type="hidden" name="token" value="[% token %]">
+ <input type="hidden" name="token" value="[% csrf_token %]">
<p>
<input type="submit" value="[% body ? loc('Update body') : loc('Add body') %]">
</p>
diff --git a/templates/web/zurich/admin/body.html b/templates/web/zurich/admin/body.html
index 771f1e537..1a156773d 100644
--- a/templates/web/zurich/admin/body.html
+++ b/templates/web/zurich/admin/body.html
@@ -55,7 +55,7 @@
<p>
<input type="hidden" name="posted" value="new" >
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="submit" name="Create category" value="[% errors ? loc('Save changes') : loc('Create category') %]">
</p>
diff --git a/templates/web/zurich/admin/report_edit-sdm.html b/templates/web/zurich/admin/report_edit-sdm.html
index 3cdf3d8c3..b8de2a5ef 100644
--- a/templates/web/zurich/admin/report_edit-sdm.html
+++ b/templates/web/zurich/admin/report_edit-sdm.html
@@ -13,7 +13,7 @@
<div id="map_sidebar">
<form method="post" action="[% c.uri_for( 'report_edit', problem.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
<div class="admin-report-edit admin-report-edit--details">
diff --git a/templates/web/zurich/admin/report_edit.html b/templates/web/zurich/admin/report_edit.html
index 512ea4708..215373eca 100644
--- a/templates/web/zurich/admin/report_edit.html
+++ b/templates/web/zurich/admin/report_edit.html
@@ -15,7 +15,7 @@
[% pstate = problem.get_extra_metadata('closure_status') || problem.state %]
<form id="report_edit" method="post" action="[% c.uri_for( 'report_edit', problem.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
<div class="admin-report-edit admin-report-edit--details">
diff --git a/templates/web/zurich/admin/template_edit.html b/templates/web/zurich/admin/template_edit.html
index 1deda6a77..dbad55f08 100644
--- a/templates/web/zurich/admin/template_edit.html
+++ b/templates/web/zurich/admin/template_edit.html
@@ -25,7 +25,7 @@
<textarea name="text" class="required">[% rt.text |html %]</textarea>
</p>
<p>
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="submit" name="Edit templates" value="[% rt.id ? loc('Save changes') : loc('Create template') %]" >
</p>
[% IF rt.id %]
diff --git a/templates/web/zurich/admin/update_edit.html b/templates/web/zurich/admin/update_edit.html
index fbd96f3a5..adafff3a8 100644
--- a/templates/web/zurich/admin/update_edit.html
+++ b/templates/web/zurich/admin/update_edit.html
@@ -4,7 +4,7 @@
[% status_message %]
<form method="post" action="[% c.uri_for( 'update_edit', update.id ) %]" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
- <input type="hidden" name="token" value="[% token %]" >
+ <input type="hidden" name="token" value="[% csrf_token %]" >
<input type="hidden" name="submit" value="1" >
<ul>
<li><a href="[% c.uri_for_email( '/report', update.problem_id ) %]#update_[% update.id %]">[% loc('View report on site' )%]</a></li>
diff --git a/web/js/dropzone.js.patch b/web/js/dropzone.js.patch
new file mode 100644
index 000000000..030b56a6a
--- /dev/null
+++ b/web/js/dropzone.js.patch
@@ -0,0 +1,36 @@
+--- web/js/src/dropzone.orig.js 2016-06-17 21:29:47.000000000 +0100
++++ web/js/src/dropzone.js 2016-06-20 11:40:55.000000000 +0100
+@@ -1469,7 +1469,7 @@
+ return _results;
+ };
+
+- Dropzone.blacklistedBrowsers = [/opera.*Macintosh.*version\/12/i];
++ Dropzone.blacklistedBrowsers = [/opera.*(Windows Phone|Macintosh).*version\/12/i];
+
+ Dropzone.isBrowserSupported = function() {
+ var capableBrowser, regex, _i, _len, _ref;
+@@ -1679,7 +1679,23 @@
+ drawImageIOSFix = function(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
+ var vertSquashRatio;
+ vertSquashRatio = detectVerticalSquash(img);
+- return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
++ dh = dh / vertSquashRatio;
++
++ /* An improved version of http://stackoverflow.com/a/28356942/669631 */
++ var orientation = 0;
++ switch (EXIF.getData(img)) {
++ case 3: orientation = 2; break;
++ case 6: orientation = 1; break;
++ case 8: orientation = -1; break;
++ }
++ if (orientation) {
++ ctx.translate(dx + dw/2, dy + dh/2);
++ ctx.rotate(orientation * Math.PI / 2);
++ dx = -dw/2;
++ dy = -dh/2;
++ }
++
++ return ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
+ };
+
+
diff --git a/web/js/dropzone.min.js b/web/js/dropzone.min.js
index f460b6c50..ff19898e9 100755..100644
--- a/web/js/dropzone.min.js
+++ b/web/js/dropzone.min.js
@@ -1,2 +1,60 @@
-(function(){var a,b,c,d,e,f,g,h,i=[].slice,j={}.hasOwnProperty,k=function(a,b){function c(){this.constructor=a}for(var d in b)j.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a};g=function(){},b=function(){function a(){}return a.prototype.addEventListener=a.prototype.on,a.prototype.on=function(a,b){return this._callbacks=this._callbacks||{},this._callbacks[a]||(this._callbacks[a]=[]),this._callbacks[a].push(b),this},a.prototype.emit=function(){var a,b,c,d,e,f;if(d=arguments[0],a=2<=arguments.length?i.call(arguments,1):[],this._callbacks=this._callbacks||{},c=this._callbacks[d])for(e=0,f=c.length;f>e;e++)b=c[e],b.apply(this,a);return this},a.prototype.removeListener=a.prototype.off,a.prototype.removeAllListeners=a.prototype.off,a.prototype.removeEventListener=a.prototype.off,a.prototype.off=function(a,b){var c,d,e,f,g;if(!this._callbacks||0===arguments.length)return this._callbacks={},this;if(d=this._callbacks[a],!d)return this;if(1===arguments.length)return delete this._callbacks[a],this;for(e=f=0,g=d.length;g>f;e=++f)if(c=d[e],c===b){d.splice(e,1);break}return this},a}(),a=function(a){function c(a,b){var e,f,g;if(this.element=a,this.version=c.version,this.defaultOptions.previewTemplate=this.defaultOptions.previewTemplate.replace(/\n*/g,""),this.clickableElements=[],this.listeners=[],this.files=[],"string"==typeof this.element&&(this.element=document.querySelector(this.element)),!this.element||null==this.element.nodeType)throw new Error("Invalid dropzone element.");if(this.element.dropzone)throw new Error("Dropzone already attached.");if(c.instances.push(this),this.element.dropzone=this,e=null!=(g=c.optionsForElement(this.element))?g:{},this.options=d({},this.defaultOptions,e,null!=b?b:{}),this.options.forceFallback||!c.isBrowserSupported())return this.options.fallback.call(this);if(null==this.options.url&&(this.options.url=this.element.getAttribute("action")),!this.options.url)throw new Error("No URL provided.");if(this.options.acceptedFiles&&this.options.acceptedMimeTypes)throw new Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");this.options.acceptedMimeTypes&&(this.options.acceptedFiles=this.options.acceptedMimeTypes,delete this.options.acceptedMimeTypes),this.options.method=this.options.method.toUpperCase(),(f=this.getExistingFallback())&&f.parentNode&&f.parentNode.removeChild(f),this.options.previewsContainer!==!1&&(this.previewsContainer=this.options.previewsContainer?c.getElement(this.options.previewsContainer,"previewsContainer"):this.element),this.options.clickable&&(this.clickableElements=this.options.clickable===!0?[this.element]:c.getElements(this.options.clickable,"clickable")),this.init()}var d,e;return k(c,a),c.prototype.Emitter=b,c.prototype.events=["drop","dragstart","dragend","dragenter","dragover","dragleave","addedfile","addedfiles","removedfile","thumbnail","error","errormultiple","processing","processingmultiple","uploadprogress","totaluploadprogress","sending","sendingmultiple","success","successmultiple","canceled","canceledmultiple","complete","completemultiple","reset","maxfilesexceeded","maxfilesreached","queuecomplete"],c.prototype.defaultOptions={url:null,method:"post",withCredentials:!1,parallelUploads:2,uploadMultiple:!1,maxFilesize:256,paramName:"file",createImageThumbnails:!0,maxThumbnailFilesize:10,thumbnailWidth:120,thumbnailHeight:120,filesizeBase:1e3,maxFiles:null,params:{},clickable:!0,ignoreHiddenFiles:!0,acceptedFiles:null,acceptedMimeTypes:null,autoProcessQueue:!0,autoQueue:!0,addRemoveLinks:!1,previewsContainer:null,hiddenInputContainer:"body",capture:null,dictDefaultMessage:"Drop files here to upload",dictFallbackMessage:"Your browser does not support drag'n'drop file uploads.",dictFallbackText:"Please use the fallback form below to upload your files like in the olden days.",dictFileTooBig:"File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",dictInvalidFileType:"You can't upload files of this type.",dictResponseError:"Server responded with {{statusCode}} code.",dictCancelUpload:"Cancel upload",dictCancelUploadConfirmation:"Are you sure you want to cancel this upload?",dictRemoveFile:"Remove file",dictRemoveFileConfirmation:null,dictMaxFilesExceeded:"You can not upload any more files.",accept:function(a,b){return b()},init:function(){return g},forceFallback:!1,fallback:function(){var a,b,d,e,f,g;for(this.element.className=""+this.element.className+" dz-browser-not-supported",g=this.element.getElementsByTagName("div"),e=0,f=g.length;f>e;e++)a=g[e],/(^| )dz-message($| )/.test(a.className)&&(b=a,a.className="dz-message");return b||(b=c.createElement('<div class="dz-message"><span></span></div>'),this.element.appendChild(b)),d=b.getElementsByTagName("span")[0],d&&(null!=d.textContent?d.textContent=this.options.dictFallbackMessage:null!=d.innerText&&(d.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(a){var b,c,d;return b={srcX:0,srcY:0,srcWidth:a.width,srcHeight:a.height},c=a.width/a.height,b.optWidth=this.options.thumbnailWidth,b.optHeight=this.options.thumbnailHeight,null==b.optWidth&&null==b.optHeight?(b.optWidth=b.srcWidth,b.optHeight=b.srcHeight):null==b.optWidth?b.optWidth=c*b.optHeight:null==b.optHeight&&(b.optHeight=1/c*b.optWidth),d=b.optWidth/b.optHeight,a.height<b.optHeight||a.width<b.optWidth?(b.trgHeight=b.srcHeight,b.trgWidth=b.srcWidth):c>d?(b.srcHeight=a.height,b.srcWidth=b.srcHeight*d):(b.srcWidth=a.width,b.srcHeight=b.srcWidth/d),b.srcX=(a.width-b.srcWidth)/2,b.srcY=(a.height-b.srcHeight)/2,b},drop:function(){return this.element.classList.remove("dz-drag-hover")},dragstart:g,dragend:function(){return this.element.classList.remove("dz-drag-hover")},dragenter:function(){return this.element.classList.add("dz-drag-hover")},dragover:function(){return this.element.classList.add("dz-drag-hover")},dragleave:function(){return this.element.classList.remove("dz-drag-hover")},paste:g,reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(a){var b,d,e,f,g,h,i,j,k,l,m,n,o;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){for(a.previewElement=c.createElement(this.options.previewTemplate.trim()),a.previewTemplate=a.previewElement,this.previewsContainer.appendChild(a.previewElement),l=a.previewElement.querySelectorAll("[data-dz-name]"),f=0,i=l.length;i>f;f++)b=l[f],b.textContent=a.name;for(m=a.previewElement.querySelectorAll("[data-dz-size]"),g=0,j=m.length;j>g;g++)b=m[g],b.innerHTML=this.filesize(a.size);for(this.options.addRemoveLinks&&(a._removeLink=c.createElement('<a class="dz-remove" href="javascript:undefined;" data-dz-remove>'+this.options.dictRemoveFile+"</a>"),a.previewElement.appendChild(a._removeLink)),d=function(b){return function(d){return d.preventDefault(),d.stopPropagation(),a.status===c.UPLOADING?c.confirm(b.options.dictCancelUploadConfirmation,function(){return b.removeFile(a)}):b.options.dictRemoveFileConfirmation?c.confirm(b.options.dictRemoveFileConfirmation,function(){return b.removeFile(a)}):b.removeFile(a)}}(this),n=a.previewElement.querySelectorAll("[data-dz-remove]"),o=[],h=0,k=n.length;k>h;h++)e=n[h],o.push(e.addEventListener("click",d));return o}},removedfile:function(a){var b;return a.previewElement&&null!=(b=a.previewElement)&&b.parentNode.removeChild(a.previewElement),this._updateMaxFilesReachedClass()},thumbnail:function(a,b){var c,d,e,f;if(a.previewElement){for(a.previewElement.classList.remove("dz-file-preview"),f=a.previewElement.querySelectorAll("[data-dz-thumbnail]"),d=0,e=f.length;e>d;d++)c=f[d],c.alt=a.name,c.src=b;return setTimeout(function(){return function(){return a.previewElement.classList.add("dz-image-preview")}}(this),1)}},error:function(a,b){var c,d,e,f,g;if(a.previewElement){for(a.previewElement.classList.add("dz-error"),"String"!=typeof b&&b.error&&(b=b.error),f=a.previewElement.querySelectorAll("[data-dz-errormessage]"),g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push(c.textContent=b);return g}},errormultiple:g,processing:function(a){return a.previewElement&&(a.previewElement.classList.add("dz-processing"),a._removeLink)?a._removeLink.textContent=this.options.dictCancelUpload:void 0},processingmultiple:g,uploadprogress:function(a,b){var c,d,e,f,g;if(a.previewElement){for(f=a.previewElement.querySelectorAll("[data-dz-uploadprogress]"),g=[],d=0,e=f.length;e>d;d++)c=f[d],g.push("PROGRESS"===c.nodeName?c.value=b:c.style.width=""+b+"%");return g}},totaluploadprogress:g,sending:g,sendingmultiple:g,success:function(a){return a.previewElement?a.previewElement.classList.add("dz-success"):void 0},successmultiple:g,canceled:function(a){return this.emit("error",a,"Upload canceled.")},canceledmultiple:g,complete:function(a){return a._removeLink&&(a._removeLink.textContent=this.options.dictRemoveFile),a.previewElement?a.previewElement.classList.add("dz-complete"):void 0},completemultiple:g,maxfilesexceeded:g,maxfilesreached:g,queuecomplete:g,addedfiles:g,previewTemplate:'<div class="dz-preview dz-file-preview">\n <div class="dz-image"><img data-dz-thumbnail /></div>\n <div class="dz-details">\n <div class="dz-size"><span data-dz-size></span></div>\n <div class="dz-filename"><span data-dz-name></span></div>\n </div>\n <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>\n <div class="dz-error-message"><span data-dz-errormessage></span></div>\n <div class="dz-success-mark">\n <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">\n <title>Check</title>\n <defs></defs>\n <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">\n <path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>\n </g>\n </svg>\n </div>\n <div class="dz-error-mark">\n <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">\n <title>Error</title>\n <defs></defs>\n <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">\n <g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">\n <path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>\n </g>\n </g>\n </svg>\n </div>\n</div>'},d=function(){var a,b,c,d,e,f,g;for(d=arguments[0],c=2<=arguments.length?i.call(arguments,1):[],f=0,g=c.length;g>f;f++){b=c[f];for(a in b)e=b[a],d[a]=e}return d},c.prototype.getAcceptedFiles=function(){var a,b,c,d,e;for(d=this.files,e=[],b=0,c=d.length;c>b;b++)a=d[b],a.accepted&&e.push(a);return e},c.prototype.getRejectedFiles=function(){var a,b,c,d,e;for(d=this.files,e=[],b=0,c=d.length;c>b;b++)a=d[b],a.accepted||e.push(a);return e},c.prototype.getFilesWithStatus=function(a){var b,c,d,e,f;for(e=this.files,f=[],c=0,d=e.length;d>c;c++)b=e[c],b.status===a&&f.push(b);return f},c.prototype.getQueuedFiles=function(){return this.getFilesWithStatus(c.QUEUED)},c.prototype.getUploadingFiles=function(){return this.getFilesWithStatus(c.UPLOADING)},c.prototype.getAddedFiles=function(){return this.getFilesWithStatus(c.ADDED)},c.prototype.getActiveFiles=function(){var a,b,d,e,f;for(e=this.files,f=[],b=0,d=e.length;d>b;b++)a=e[b],(a.status===c.UPLOADING||a.status===c.QUEUED)&&f.push(a);return f},c.prototype.init=function(){var a,b,d,e,f,g,h;for("form"===this.element.tagName&&this.element.setAttribute("enctype","multipart/form-data"),this.element.classList.contains("dropzone")&&!this.element.querySelector(".dz-message")&&this.element.appendChild(c.createElement('<div class="dz-default dz-message"><span>'+this.options.dictDefaultMessage+"</span></div>")),this.clickableElements.length&&(d=function(a){return function(){return a.hiddenFileInput&&a.hiddenFileInput.parentNode.removeChild(a.hiddenFileInput),a.hiddenFileInput=document.createElement("input"),a.hiddenFileInput.setAttribute("type","file"),(null==a.options.maxFiles||a.options.maxFiles>1)&&a.hiddenFileInput.setAttribute("multiple","multiple"),a.hiddenFileInput.className="dz-hidden-input",null!=a.options.acceptedFiles&&a.hiddenFileInput.setAttribute("accept",a.options.acceptedFiles),null!=a.options.capture&&a.hiddenFileInput.setAttribute("capture",a.options.capture),a.hiddenFileInput.style.visibility="hidden",a.hiddenFileInput.style.position="absolute",a.hiddenFileInput.style.top="0",a.hiddenFileInput.style.left="0",a.hiddenFileInput.style.height="0",a.hiddenFileInput.style.width="0",document.querySelector(a.options.hiddenInputContainer).appendChild(a.hiddenFileInput),a.hiddenFileInput.addEventListener("change",function(){var b,c,e,f;if(c=a.hiddenFileInput.files,c.length)for(e=0,f=c.length;f>e;e++)b=c[e],a.addFile(b);return a.emit("addedfiles",c),d()})}}(this))(),this.URL=null!=(g=window.URL)?g:window.webkitURL,h=this.events,e=0,f=h.length;f>e;e++)a=h[e],this.on(a,this.options[a]);return this.on("uploadprogress",function(a){return function(){return a.updateTotalUploadProgress()}}(this)),this.on("removedfile",function(a){return function(){return a.updateTotalUploadProgress()}}(this)),this.on("canceled",function(a){return function(b){return a.emit("complete",b)}}(this)),this.on("complete",function(a){return function(){return 0===a.getAddedFiles().length&&0===a.getUploadingFiles().length&&0===a.getQueuedFiles().length?setTimeout(function(){return a.emit("queuecomplete")},0):void 0}}(this)),b=function(a){return a.stopPropagation(),a.preventDefault?a.preventDefault():a.returnValue=!1},this.listeners=[{element:this.element,events:{dragstart:function(a){return function(b){return a.emit("dragstart",b)}}(this),dragenter:function(a){return function(c){return b(c),a.emit("dragenter",c)}}(this),dragover:function(a){return function(c){var d;try{d=c.dataTransfer.effectAllowed}catch(e){}return c.dataTransfer.dropEffect="move"===d||"linkMove"===d?"move":"copy",b(c),a.emit("dragover",c)}}(this),dragleave:function(a){return function(b){return a.emit("dragleave",b)}}(this),drop:function(a){return function(c){return b(c),a.drop(c)}}(this),dragend:function(a){return function(b){return a.emit("dragend",b)}}(this)}}],this.clickableElements.forEach(function(a){return function(b){return a.listeners.push({element:b,events:{click:function(d){return(b!==a.element||d.target===a.element||c.elementInside(d.target,a.element.querySelector(".dz-message")))&&a.hiddenFileInput.click(),!0}}})}}(this)),this.enable(),this.options.init.call(this)},c.prototype.destroy=function(){var a;return this.disable(),this.removeAllFiles(!0),(null!=(a=this.hiddenFileInput)?a.parentNode:void 0)&&(this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput),this.hiddenFileInput=null),delete this.element.dropzone,c.instances.splice(c.instances.indexOf(this),1)},c.prototype.updateTotalUploadProgress=function(){var a,b,c,d,e,f,g,h;if(d=0,c=0,a=this.getActiveFiles(),a.length){for(h=this.getActiveFiles(),f=0,g=h.length;g>f;f++)b=h[f],d+=b.upload.bytesSent,c+=b.upload.total;e=100*d/c}else e=100;return this.emit("totaluploadprogress",e,c,d)},c.prototype._getParamName=function(a){return"function"==typeof this.options.paramName?this.options.paramName(a):""+this.options.paramName+(this.options.uploadMultiple?"["+a+"]":"")},c.prototype.getFallbackForm=function(){var a,b,d,e;return(a=this.getExistingFallback())?a:(d='<div class="dz-fallback">',this.options.dictFallbackText&&(d+="<p>"+this.options.dictFallbackText+"</p>"),d+='<input type="file" name="'+this._getParamName(0)+'" '+(this.options.uploadMultiple?'multiple="multiple"':void 0)+' /><input type="submit" value="Upload!"></div>',b=c.createElement(d),"FORM"!==this.element.tagName?(e=c.createElement('<form action="'+this.options.url+'" enctype="multipart/form-data" method="'+this.options.method+'"></form>'),e.appendChild(b)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=e?e:b)},c.prototype.getExistingFallback=function(){var a,b,c,d,e,f;for(b=function(a){var b,c,d;for(c=0,d=a.length;d>c;c++)if(b=a[c],/(^| )fallback($| )/.test(b.className))return b},f=["div","form"],d=0,e=f.length;e>d;d++)if(c=f[d],a=b(this.element.getElementsByTagName(c)))return a},c.prototype.setupEventListeners=function(){var a,b,c,d,e,f,g;for(f=this.listeners,g=[],d=0,e=f.length;e>d;d++)a=f[d],g.push(function(){var d,e;d=a.events,e=[];for(b in d)c=d[b],e.push(a.element.addEventListener(b,c,!1));return e}());return g},c.prototype.removeEventListeners=function(){var a,b,c,d,e,f,g;for(f=this.listeners,g=[],d=0,e=f.length;e>d;d++)a=f[d],g.push(function(){var d,e;d=a.events,e=[];for(b in d)c=d[b],e.push(a.element.removeEventListener(b,c,!1));return e}());return g},c.prototype.disable=function(){var a,b,c,d,e;for(this.clickableElements.forEach(function(a){return a.classList.remove("dz-clickable")}),this.removeEventListeners(),d=this.files,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(this.cancelUpload(a));return e},c.prototype.enable=function(){return this.clickableElements.forEach(function(a){return a.classList.add("dz-clickable")}),this.setupEventListeners()},c.prototype.filesize=function(a){var b,c,d,e,f,g,h,i;if(d=0,e="b",a>0){for(g=["TB","GB","MB","KB","b"],c=h=0,i=g.length;i>h;c=++h)if(f=g[c],b=Math.pow(this.options.filesizeBase,4-c)/10,a>=b){d=a/Math.pow(this.options.filesizeBase,4-c),e=f;break}d=Math.round(10*d)/10}return"<strong>"+d+"</strong> "+e},c.prototype._updateMaxFilesReachedClass=function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")},c.prototype.drop=function(a){var b,c;a.dataTransfer&&(this.emit("drop",a),b=a.dataTransfer.files,this.emit("addedfiles",b),b.length&&(c=a.dataTransfer.items,c&&c.length&&null!=c[0].webkitGetAsEntry?this._addFilesFromItems(c):this.handleFiles(b)))},c.prototype.paste=function(a){var b,c;if(null!=(null!=a&&null!=(c=a.clipboardData)?c.items:void 0))return this.emit("paste",a),b=a.clipboardData.items,b.length?this._addFilesFromItems(b):void 0},c.prototype.handleFiles=function(a){var b,c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(this.addFile(b));return e},c.prototype._addFilesFromItems=function(a){var b,c,d,e,f;for(f=[],d=0,e=a.length;e>d;d++)c=a[d],f.push(null!=c.webkitGetAsEntry&&(b=c.webkitGetAsEntry())?b.isFile?this.addFile(c.getAsFile()):b.isDirectory?this._addFilesFromDirectory(b,b.name):void 0:null!=c.getAsFile?null==c.kind||"file"===c.kind?this.addFile(c.getAsFile()):void 0:void 0);return f},c.prototype._addFilesFromDirectory=function(a,b){var c,d;return c=a.createReader(),d=function(a){return function(c){var d,e,f;for(e=0,f=c.length;f>e;e++)d=c[e],d.isFile?d.file(function(c){return a.options.ignoreHiddenFiles&&"."===c.name.substring(0,1)?void 0:(c.fullPath=""+b+"/"+c.name,a.addFile(c))}):d.isDirectory&&a._addFilesFromDirectory(d,""+b+"/"+d.name)}}(this),c.readEntries(d,function(a){return"undefined"!=typeof console&&null!==console&&"function"==typeof console.log?console.log(a):void 0})},c.prototype.accept=function(a,b){return a.size>1024*this.options.maxFilesize*1024?b(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(a.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):c.isValidFile(a,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(b(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",a)):this.options.accept.call(this,a,b):b(this.options.dictInvalidFileType)},c.prototype.addFile=function(a){return a.upload={progress:0,total:a.size,bytesSent:0},this.files.push(a),a.status=c.ADDED,this.emit("addedfile",a),this._enqueueThumbnail(a),this.accept(a,function(b){return function(c){return c?(a.accepted=!1,b._errorProcessing([a],c)):(a.accepted=!0,b.options.autoQueue&&b.enqueueFile(a)),b._updateMaxFilesReachedClass()}}(this))},c.prototype.enqueueFiles=function(a){var b,c,d;for(c=0,d=a.length;d>c;c++)b=a[c],this.enqueueFile(b);return null},c.prototype.enqueueFile=function(a){if(a.status!==c.ADDED||a.accepted!==!0)throw new Error("This file can't be queued because it has already been processed or was rejected.");return a.status=c.QUEUED,this.options.autoProcessQueue?setTimeout(function(a){return function(){return a.processQueue()}}(this),0):void 0},c.prototype._thumbnailQueue=[],c.prototype._processingThumbnail=!1,c.prototype._enqueueThumbnail=function(a){return this.options.createImageThumbnails&&a.type.match(/image.*/)&&a.size<=1024*this.options.maxThumbnailFilesize*1024?(this._thumbnailQueue.push(a),setTimeout(function(a){return function(){return a._processThumbnailQueue()}}(this),0)):void 0},c.prototype._processThumbnailQueue=function(){return this._processingThumbnail||0===this._thumbnailQueue.length?void 0:(this._processingThumbnail=!0,this.createThumbnail(this._thumbnailQueue.shift(),function(a){return function(){return a._processingThumbnail=!1,a._processThumbnailQueue()}}(this)))},c.prototype.removeFile=function(a){return a.status===c.UPLOADING&&this.cancelUpload(a),this.files=h(this.files,a),this.emit("removedfile",a),0===this.files.length?this.emit("reset"):void 0},c.prototype.removeAllFiles=function(a){var b,d,e,f;for(null==a&&(a=!1),f=this.files.slice(),d=0,e=f.length;e>d;d++)b=f[d],(b.status!==c.UPLOADING||a)&&this.removeFile(b);return null},c.prototype.createThumbnail=function(a,b){var c;return c=new FileReader,c.onload=function(d){return function(){return"image/svg+xml"===a.type?(d.emit("thumbnail",a,c.result),void(null!=b&&b())):d.createThumbnailFromUrl(a,c.result,b)}}(this),c.readAsDataURL(a)},c.prototype.createThumbnailFromUrl=function(a,b,c,d){var e;return e=document.createElement("img"),d&&(e.crossOrigin=d),e.onload=function(b){return function(){var d,g,h,i,j,k,l,m;return a.width=e.width,a.height=e.height,h=b.options.resize.call(b,a),null==h.trgWidth&&(h.trgWidth=h.optWidth),null==h.trgHeight&&(h.trgHeight=h.optHeight),d=document.createElement("canvas"),g=d.getContext("2d"),d.width=h.trgWidth,d.height=h.trgHeight,f(g,e,null!=(j=h.srcX)?j:0,null!=(k=h.srcY)?k:0,h.srcWidth,h.srcHeight,null!=(l=h.trgX)?l:0,null!=(m=h.trgY)?m:0,h.trgWidth,h.trgHeight),i=d.toDataURL("image/png"),b.emit("thumbnail",a,i),null!=c?c():void 0}}(this),null!=c&&(e.onerror=c),e.src=b},c.prototype.processQueue=function(){var a,b,c,d;if(b=this.options.parallelUploads,c=this.getUploadingFiles().length,a=c,!(c>=b)&&(d=this.getQueuedFiles(),d.length>0)){if(this.options.uploadMultiple)return this.processFiles(d.slice(0,b-c));for(;b>a;){if(!d.length)return;this.processFile(d.shift()),a++}}},c.prototype.processFile=function(a){return this.processFiles([a])},c.prototype.processFiles=function(a){var b,d,e;for(d=0,e=a.length;e>d;d++)b=a[d],b.processing=!0,b.status=c.UPLOADING,this.emit("processing",b);return this.options.uploadMultiple&&this.emit("processingmultiple",a),this.uploadFiles(a)},c.prototype._getFilesWithXhr=function(a){var b,c;return c=function(){var c,d,e,f;for(e=this.files,f=[],c=0,d=e.length;d>c;c++)b=e[c],b.xhr===a&&f.push(b);return f}.call(this)},c.prototype.cancelUpload=function(a){var b,d,e,f,g,h,i;if(a.status===c.UPLOADING){for(d=this._getFilesWithXhr(a.xhr),e=0,g=d.length;g>e;e++)b=d[e],b.status=c.CANCELED;for(a.xhr.abort(),f=0,h=d.length;h>f;f++)b=d[f],this.emit("canceled",b);this.options.uploadMultiple&&this.emit("canceledmultiple",d)}else((i=a.status)===c.ADDED||i===c.QUEUED)&&(a.status=c.CANCELED,this.emit("canceled",a),this.options.uploadMultiple&&this.emit("canceledmultiple",[a]));return this.options.autoProcessQueue?this.processQueue():void 0},e=function(){var a,b;return b=arguments[0],a=2<=arguments.length?i.call(arguments,1):[],"function"==typeof b?b.apply(this,a):b},c.prototype.uploadFile=function(a){return this.uploadFiles([a])},c.prototype.uploadFiles=function(a){var b,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L;for(w=new XMLHttpRequest,x=0,B=a.length;B>x;x++)b=a[x],b.xhr=w;p=e(this.options.method,a),u=e(this.options.url,a),w.open(p,u,!0),w.withCredentials=!!this.options.withCredentials,s=null,g=function(c){return function(){var d,e,f;for(f=[],d=0,e=a.length;e>d;d++)b=a[d],f.push(c._errorProcessing(a,s||c.options.dictResponseError.replace("{{statusCode}}",w.status),w));return f}}(this),t=function(c){return function(d){var e,f,g,h,i,j,k,l,m;if(null!=d)for(f=100*d.loaded/d.total,g=0,j=a.length;j>g;g++)b=a[g],b.upload={progress:f,total:d.total,bytesSent:d.loaded};else{for(e=!0,f=100,h=0,k=a.length;k>h;h++)b=a[h],(100!==b.upload.progress||b.upload.bytesSent!==b.upload.total)&&(e=!1),b.upload.progress=f,b.upload.bytesSent=b.upload.total;if(e)return}for(m=[],i=0,l=a.length;l>i;i++)b=a[i],m.push(c.emit("uploadprogress",b,f,b.upload.bytesSent));return m}}(this),w.onload=function(b){return function(d){var e;if(a[0].status!==c.CANCELED&&4===w.readyState){if(s=w.responseText,w.getResponseHeader("content-type")&&~w.getResponseHeader("content-type").indexOf("application/json"))try{s=JSON.parse(s)}catch(f){d=f,s="Invalid JSON response from server."}return t(),200<=(e=w.status)&&300>e?b._finished(a,s,d):g()}}}(this),w.onerror=function(){return function(){return a[0].status!==c.CANCELED?g():void 0}}(this),r=null!=(G=w.upload)?G:w,r.onprogress=t,j={Accept:"application/json","Cache-Control":"no-cache","X-Requested-With":"XMLHttpRequest"},this.options.headers&&d(j,this.options.headers);for(h in j)i=j[h],i&&w.setRequestHeader(h,i);if(f=new FormData,this.options.params){H=this.options.params;for(o in H)v=H[o],f.append(o,v)}for(y=0,C=a.length;C>y;y++)b=a[y],this.emit("sending",b,w,f);if(this.options.uploadMultiple&&this.emit("sendingmultiple",a,w,f),"FORM"===this.element.tagName)for(I=this.element.querySelectorAll("input, textarea, select, button"),z=0,D=I.length;D>z;z++)if(l=I[z],m=l.getAttribute("name"),n=l.getAttribute("type"),"SELECT"===l.tagName&&l.hasAttribute("multiple"))for(J=l.options,A=0,E=J.length;E>A;A++)q=J[A],q.selected&&f.append(m,q.value);else(!n||"checkbox"!==(K=n.toLowerCase())&&"radio"!==K||l.checked)&&f.append(m,l.value);for(k=F=0,L=a.length-1;L>=0?L>=F:F>=L;k=L>=0?++F:--F)f.append(this._getParamName(k),a[k],a[k].name);return this.submitRequest(w,f,a)},c.prototype.submitRequest=function(a,b){return a.send(b)},c.prototype._finished=function(a,b,d){var e,f,g;for(f=0,g=a.length;g>f;f++)e=a[f],e.status=c.SUCCESS,this.emit("success",e,b,d),this.emit("complete",e);return this.options.uploadMultiple&&(this.emit("successmultiple",a,b,d),this.emit("completemultiple",a)),this.options.autoProcessQueue?this.processQueue():void 0},c.prototype._errorProcessing=function(a,b,d){var e,f,g;for(f=0,g=a.length;g>f;f++)e=a[f],e.status=c.ERROR,this.emit("error",e,b,d),this.emit("complete",e);return this.options.uploadMultiple&&(this.emit("errormultiple",a,b,d),this.emit("completemultiple",a)),this.options.autoProcessQueue?this.processQueue():void 0},c}(b),a.version="4.2.0",a.options={},a.optionsForElement=function(b){return b.getAttribute("id")?a.options[c(b.getAttribute("id"))]:void 0},a.instances=[],a.forElement=function(a){if("string"==typeof a&&(a=document.querySelector(a)),null==(null!=a?a.dropzone:void 0))throw new Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");return a.dropzone},a.autoDiscover=!0,a.discover=function(){var b,c,d,e,f,g;for(document.querySelectorAll?d=document.querySelectorAll(".dropzone"):(d=[],b=function(a){var b,c,e,f;for(f=[],c=0,e=a.length;e>c;c++)b=a[c],f.push(/(^| )dropzone($| )/.test(b.className)?d.push(b):void 0);return f},b(document.getElementsByTagName("div")),b(document.getElementsByTagName("form"))),g=[],e=0,f=d.length;f>e;e++)c=d[e],g.push(a.optionsForElement(c)!==!1?new a(c):void 0);return g},a.blacklistedBrowsers=[/opera.*(Windows Phone|Macintosh).*version\/12/i],a.isBrowserSupported=function(){var b,c,d,e,f;if(b=!0,window.File&&window.FileReader&&window.FileList&&window.Blob&&window.FormData&&document.querySelector)if("classList"in document.createElement("a"))for(f=a.blacklistedBrowsers,d=0,e=f.length;e>d;d++)c=f[d],c.test(navigator.userAgent)&&(b=!1);else b=!1;else b=!1;return b},h=function(a,b){var c,d,e,f;for(f=[],d=0,e=a.length;e>d;d++)c=a[d],c!==b&&f.push(c);return f},c=function(a){return a.replace(/[\-_](\w)/g,function(a){return a.charAt(1).toUpperCase()})},a.createElement=function(a){var b;return b=document.createElement("div"),b.innerHTML=a,b.childNodes[0]},a.elementInside=function(a,b){if(a===b)return!0;for(;a=a.parentNode;)if(a===b)return!0;return!1},a.getElement=function(a,b){var c;if("string"==typeof a?c=document.querySelector(a):null!=a.nodeType&&(c=a),null==c)throw new Error("Invalid `"+b+"` option provided. Please provide a CSS selector or a plain HTML element.");return c},a.getElements=function(a,b){var c,d,e,f,g,h,i,j;if(a instanceof Array){e=[];try{for(f=0,h=a.length;h>f;f++)d=a[f],e.push(this.getElement(d,b))}catch(k){c=k,e=null}}else if("string"==typeof a)for(e=[],j=document.querySelectorAll(a),g=0,i=j.length;i>g;g++)d=j[g],e.push(d);else null!=a.nodeType&&(e=[a]);if(null==e||!e.length)throw new Error("Invalid `"+b+"` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");return e},a.confirm=function(a,b,c){return window.confirm(a)?b():null!=c?c():void 0},a.isValidFile=function(a,b){var c,d,e,f,g;if(!b)return!0;for(b=b.split(","),d=a.type,c=d.replace(/\/.*$/,""),f=0,g=b.length;g>f;f++)if(e=b[f],e=e.trim(),"."===e.charAt(0)){if(-1!==a.name.toLowerCase().indexOf(e.toLowerCase(),a.name.length-e.length))return!0}else if(/\/\*$/.test(e)){if(c===e.replace(/\/.*$/,""))return!0}else if(d===e)return!0;return!1},"undefined"!=typeof jQuery&&null!==jQuery&&(jQuery.fn.dropzone=function(b){return this.each(function(){return new a(this,b)})}),"undefined"!=typeof module&&null!==module?module.exports=a:window.Dropzone=a,a.ADDED="added",a.QUEUED="queued",a.ACCEPTED=a.QUEUED,a.UPLOADING="uploading",a.PROCESSING=a.UPLOADING,a.CANCELED="canceled",a.ERROR="error",a.SUCCESS="success",e=function(a){var b,c,d,e,f,g,h,i,j,k;
-for(h=a.naturalWidth,g=a.naturalHeight,c=document.createElement("canvas"),c.width=1,c.height=g,d=c.getContext("2d"),d.drawImage(a,0,0),e=d.getImageData(0,0,1,g).data,k=0,f=g,i=g;i>k;)b=e[4*(i-1)+3],0===b?f=i:k=i,i=f+k>>1;return j=i/g,0===j?1:j},f=function(a,b,c,d,f,g,h,i,j,k){var l;return l=e(b),a.drawImage(b,c,d,f,g,h,i,j,k/l)},d=function(a,b){var c,d,e,f,g,h,i,j,k;if(e=!1,k=!0,d=a.document,j=d.documentElement,c=d.addEventListener?"addEventListener":"attachEvent",i=d.addEventListener?"removeEventListener":"detachEvent",h=d.addEventListener?"":"on",f=function(c){return"readystatechange"!==c.type||"complete"===d.readyState?(("load"===c.type?a:d)[i](h+c.type,f,!1),!e&&(e=!0)?b.call(a,c.type||c):void 0):void 0},g=function(){var a;try{j.doScroll("left")}catch(b){return a=b,void setTimeout(g,50)}return f("poll")},"complete"!==d.readyState){if(d.createEventObject&&j.doScroll){try{k=!a.frameElement}catch(l){}k&&g()}return d[c](h+"DOMContentLoaded",f,!1),d[c](h+"readystatechange",f,!1),a[c](h+"load",f,!1)}},a._autoDiscoverFunction=function(){return a.autoDiscover?a.discover():void 0},d(window,a._autoDiscoverFunction)}).call(this);
+(function(){function k(k,n){for(var p="",l=n;l<n+4;l++)p+=String.fromCharCode(k.getUint8(l));if("Exif"!=p)return!1;l=n+6;if(18761==k.getUint16(l))p=!1;else if(19789==k.getUint16(l))p=!0;else return!1;if(42!=k.getUint16(l+2,!p))return!1;var r=k.getUint32(l+4,!p);if(8>r)return!1;var l=l+r,r=k.getUint16(l,!p),v,w;for(w=0;w<r;w++)if(v=l+12*w+2,274==k.getUint16(v,!p))return parseInt(k.getUint16(v+8,!p),10);return!1}var w={};window.EXIF=w;w.getData=function(q){if((q instanceof Image||q instanceof HTMLImageElement)&&
+!q.complete)return!1;q=q.src.replace(/^data\:([^\;]+)\;base64,/gmi,"");var n=atob(q),p=n.length;q=new ArrayBuffer(p);for(var l=new Uint8Array(q),r=0;r<p;r++)l[r]=n.charCodeAt(r);n=new DataView(q);if(255!=n.getUint8(0)||216!=n.getUint8(1))return!1;p=2;for(q=q.byteLength;p<q;){if(255!=n.getUint8(p))return!1;l=n.getUint8(p+1);if(225==l)return k(n,p+4);p+=2+n.getUint16(p+2)}}})();(function(){var k,w,q,n,p,l,r,v=[].slice,A={}.hasOwnProperty,B=function(e,c){function u(){this.constructor=e}for(var h in c)A.call(c,h)&&(e[h]=c[h]);u.prototype=c.prototype;e.prototype=new u;e.__super__=c.prototype;return e};l=function(){};w=function(){function e(){}e.prototype.addEventListener=e.prototype.on;e.prototype.on=function(c,e){this._callbacks=this._callbacks||{};this._callbacks[c]||(this._callbacks[c]=[]);this._callbacks[c].push(e);return this};e.prototype.emit=function(){var c,e,h,a,b;
+e=arguments[0];c=2<=arguments.length?v.call(arguments,1):[];this._callbacks=this._callbacks||{};if(h=this._callbacks[e])for(a=0,b=h.length;a<b;a++)e=h[a],e.apply(this,c);return this};e.prototype.removeListener=e.prototype.off;e.prototype.removeAllListeners=e.prototype.off;e.prototype.removeEventListener=e.prototype.off;e.prototype.off=function(c,e){var h,a,b,d,f;if(!this._callbacks||0===arguments.length)return this._callbacks={},this;a=this._callbacks[c];if(!a)return this;if(1===arguments.length)return delete this._callbacks[c],
+this;b=d=0;for(f=a.length;d<f;b=++d)if(h=a[b],h===e){a.splice(b,1);break}return this};return e}();k=function(e){function c(a,b){var d,f,g;this.element=a;this.version=c.version;this.defaultOptions.previewTemplate=this.defaultOptions.previewTemplate.replace(/\n*/g,"");this.clickableElements=[];this.listeners=[];this.files=[];"string"===typeof this.element&&(this.element=document.querySelector(this.element));if(!this.element||null==this.element.nodeType)throw Error("Invalid dropzone element.");if(this.element.dropzone)throw Error("Dropzone already attached.");
+c.instances.push(this);this.element.dropzone=this;d=null!=(g=c.optionsForElement(this.element))?g:{};this.options=k({},this.defaultOptions,d,null!=b?b:{});if(this.options.forceFallback||!c.isBrowserSupported())return this.options.fallback.call(this);null==this.options.url&&(this.options.url=this.element.getAttribute("action"));if(!this.options.url)throw Error("No URL provided.");if(this.options.acceptedFiles&&this.options.acceptedMimeTypes)throw Error("You can't provide both 'acceptedFiles' and 'acceptedMimeTypes'. 'acceptedMimeTypes' is deprecated.");
+this.options.acceptedMimeTypes&&(this.options.acceptedFiles=this.options.acceptedMimeTypes,delete this.options.acceptedMimeTypes);this.options.method=this.options.method.toUpperCase();(f=this.getExistingFallback())&&f.parentNode&&f.parentNode.removeChild(f);!1!==this.options.previewsContainer&&(this.previewsContainer=this.options.previewsContainer?c.getElement(this.options.previewsContainer,"previewsContainer"):this.element);this.options.clickable&&(this.clickableElements=!0===this.options.clickable?
+[this.element]:c.getElements(this.options.clickable,"clickable"));this.init()}var k,h;B(c,e);c.prototype.Emitter=w;c.prototype.events="drop dragstart dragend dragenter dragover dragleave addedfile addedfiles removedfile thumbnail error errormultiple processing processingmultiple uploadprogress totaluploadprogress sending sendingmultiple success successmultiple canceled canceledmultiple complete completemultiple reset maxfilesexceeded maxfilesreached queuecomplete".split(" ");c.prototype.defaultOptions=
+{url:null,method:"post",withCredentials:!1,parallelUploads:2,uploadMultiple:!1,maxFilesize:256,paramName:"file",createImageThumbnails:!0,maxThumbnailFilesize:10,thumbnailWidth:120,thumbnailHeight:120,filesizeBase:1E3,maxFiles:null,params:{},clickable:!0,ignoreHiddenFiles:!0,acceptedFiles:null,acceptedMimeTypes:null,autoProcessQueue:!0,autoQueue:!0,addRemoveLinks:!1,previewsContainer:null,hiddenInputContainer:"body",capture:null,dictDefaultMessage:"Drop files here to upload",dictFallbackMessage:"Your browser does not support drag'n'drop file uploads.",
+dictFallbackText:"Please use the fallback form below to upload your files like in the olden days.",dictFileTooBig:"File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.",dictInvalidFileType:"You can't upload files of this type.",dictResponseError:"Server responded with {{statusCode}} code.",dictCancelUpload:"Cancel upload",dictCancelUploadConfirmation:"Are you sure you want to cancel this upload?",dictRemoveFile:"Remove file",dictRemoveFileConfirmation:null,dictMaxFilesExceeded:"You can not upload any more files.",
+accept:function(a,b){return b()},init:function(){return l},forceFallback:!1,fallback:function(){var a,b,d,f,g;this.element.className=""+this.element.className+" dz-browser-not-supported";g=this.element.getElementsByTagName("div");d=0;for(f=g.length;d<f;d++)a=g[d],/(^| )dz-message($| )/.test(a.className)&&(b=a,a.className="dz-message");b||(b=c.createElement('<div class="dz-message"><span></span></div>'),this.element.appendChild(b));if(a=b.getElementsByTagName("span")[0])null!=a.textContent?a.textContent=
+this.options.dictFallbackMessage:null!=a.innerText&&(a.innerText=this.options.dictFallbackMessage);return this.element.appendChild(this.getFallbackForm())},resize:function(a){var b,d,f;b={srcX:0,srcY:0,srcWidth:a.width,srcHeight:a.height};d=a.width/a.height;b.optWidth=this.options.thumbnailWidth;b.optHeight=this.options.thumbnailHeight;null==b.optWidth&&null==b.optHeight?(b.optWidth=b.srcWidth,b.optHeight=b.srcHeight):null==b.optWidth?b.optWidth=d*b.optHeight:null==b.optHeight&&(b.optHeight=1/d*b.optWidth);
+f=b.optWidth/b.optHeight;a.height<b.optHeight||a.width<b.optWidth?(b.trgHeight=b.srcHeight,b.trgWidth=b.srcWidth):d>f?(b.srcHeight=a.height,b.srcWidth=b.srcHeight*f):(b.srcWidth=a.width,b.srcHeight=b.srcWidth/f);b.srcX=(a.width-b.srcWidth)/2;b.srcY=(a.height-b.srcHeight)/2;return b},drop:function(a){return this.element.classList.remove("dz-drag-hover")},dragstart:l,dragend:function(a){return this.element.classList.remove("dz-drag-hover")},dragenter:function(a){return this.element.classList.add("dz-drag-hover")},
+dragover:function(a){return this.element.classList.add("dz-drag-hover")},dragleave:function(a){return this.element.classList.remove("dz-drag-hover")},paste:l,reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(a){var b,d,f,g,m,x;this.element===this.previewsContainer&&this.element.classList.add("dz-started");if(this.previewsContainer){a.previewElement=c.createElement(this.options.previewTemplate.trim());a.previewTemplate=a.previewElement;this.previewsContainer.appendChild(a.previewElement);
+g=a.previewElement.querySelectorAll("[data-dz-name]");d=0;for(f=g.length;d<f;d++)b=g[d],b.textContent=a.name;g=a.previewElement.querySelectorAll("[data-dz-size]");d=0;for(f=g.length;d<f;d++)b=g[d],b.innerHTML=this.filesize(a.size);this.options.addRemoveLinks&&(a._removeLink=c.createElement('<a class="dz-remove" href="javascript:undefined;" data-dz-remove>'+this.options.dictRemoveFile+"</a>"),a.previewElement.appendChild(a._removeLink));b=function(b){return function(d){d.preventDefault();d.stopPropagation();
+return a.status===c.UPLOADING?c.confirm(b.options.dictCancelUploadConfirmation,function(){return b.removeFile(a)}):b.options.dictRemoveFileConfirmation?c.confirm(b.options.dictRemoveFileConfirmation,function(){return b.removeFile(a)}):b.removeFile(a)}}(this);m=a.previewElement.querySelectorAll("[data-dz-remove]");x=[];f=0;for(g=m.length;f<g;f++)d=m[f],x.push(d.addEventListener("click",b));return x}},removedfile:function(a){var b;a.previewElement&&null!=(b=a.previewElement)&&b.parentNode.removeChild(a.previewElement);
+return this._updateMaxFilesReachedClass()},thumbnail:function(a,b){var d,f,c,m;if(a.previewElement){a.previewElement.classList.remove("dz-file-preview");m=a.previewElement.querySelectorAll("[data-dz-thumbnail]");f=0;for(c=m.length;f<c;f++)d=m[f],d.alt=a.name,d.src=b;return setTimeout(function(b){return function(){return a.previewElement.classList.add("dz-image-preview")}}(this),1)}},error:function(a,b){var d,f,c,m,x;if(a.previewElement){a.previewElement.classList.add("dz-error");"String"!==typeof b&&
+b.error&&(b=b.error);m=a.previewElement.querySelectorAll("[data-dz-errormessage]");x=[];f=0;for(c=m.length;f<c;f++)d=m[f],x.push(d.textContent=b);return x}},errormultiple:l,processing:function(a){if(a.previewElement&&(a.previewElement.classList.add("dz-processing"),a._removeLink))return a._removeLink.textContent=this.options.dictCancelUpload},processingmultiple:l,uploadprogress:function(a,b,d){var c,g,m;if(a.previewElement){g=a.previewElement.querySelectorAll("[data-dz-uploadprogress]");m=[];d=0;
+for(c=g.length;d<c;d++)a=g[d],"PROGRESS"===a.nodeName?m.push(a.value=b):m.push(a.style.width=""+b+"%");return m}},totaluploadprogress:l,sending:l,sendingmultiple:l,success:function(a){if(a.previewElement)return a.previewElement.classList.add("dz-success")},successmultiple:l,canceled:function(a){return this.emit("error",a,"Upload canceled.")},canceledmultiple:l,complete:function(a){a._removeLink&&(a._removeLink.textContent=this.options.dictRemoveFile);if(a.previewElement)return a.previewElement.classList.add("dz-complete")},
+completemultiple:l,maxfilesexceeded:l,maxfilesreached:l,queuecomplete:l,addedfiles:l,previewTemplate:'<div class="dz-preview dz-file-preview">\n <div class="dz-image"><img data-dz-thumbnail /></div>\n <div class="dz-details">\n <div class="dz-size"><span data-dz-size></span></div>\n <div class="dz-filename"><span data-dz-name></span></div>\n </div>\n <div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>\n <div class="dz-error-message"><span data-dz-errormessage></span></div>\n <div class="dz-success-mark">\n <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">\n <title>Check</title>\n <defs></defs>\n <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">\n <path d="M23.5,31.8431458 L17.5852419,25.9283877 C16.0248253,24.3679711 13.4910294,24.366835 11.9289322,25.9289322 C10.3700136,27.4878508 10.3665912,30.0234455 11.9283877,31.5852419 L20.4147581,40.0716123 C20.5133999,40.1702541 20.6159315,40.2626649 20.7218615,40.3488435 C22.2835669,41.8725651 24.794234,41.8626202 26.3461564,40.3106978 L43.3106978,23.3461564 C44.8771021,21.7797521 44.8758057,19.2483887 43.3137085,17.6862915 C41.7547899,16.1273729 39.2176035,16.1255422 37.6538436,17.6893022 L23.5,31.8431458 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" stroke-opacity="0.198794158" stroke="#747474" fill-opacity="0.816519475" fill="#FFFFFF" sketch:type="MSShapeGroup"></path>\n </g>\n </svg>\n </div>\n <div class="dz-error-mark">\n <svg width="54px" height="54px" viewBox="0 0 54 54" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">\n <title>Error</title>\n <defs></defs>\n <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">\n <g id="Check-+-Oval-2" sketch:type="MSLayerGroup" stroke="#747474" stroke-opacity="0.198794158" fill="#FFFFFF" fill-opacity="0.816519475">\n <path d="M32.6568542,29 L38.3106978,23.3461564 C39.8771021,21.7797521 39.8758057,19.2483887 38.3137085,17.6862915 C36.7547899,16.1273729 34.2176035,16.1255422 32.6538436,17.6893022 L27,23.3431458 L21.3461564,17.6893022 C19.7823965,16.1255422 17.2452101,16.1273729 15.6862915,17.6862915 C14.1241943,19.2483887 14.1228979,21.7797521 15.6893022,23.3461564 L21.3431458,29 L15.6893022,34.6538436 C14.1228979,36.2202479 14.1241943,38.7516113 15.6862915,40.3137085 C17.2452101,41.8726271 19.7823965,41.8744578 21.3461564,40.3106978 L27,34.6568542 L32.6538436,40.3106978 C34.2176035,41.8744578 36.7547899,41.8726271 38.3137085,40.3137085 C39.8758057,38.7516113 39.8771021,36.2202479 38.3106978,34.6538436 L32.6568542,29 Z M27,53 C41.3594035,53 53,41.3594035 53,27 C53,12.6405965 41.3594035,1 27,1 C12.6405965,1 1,12.6405965 1,27 C1,41.3594035 12.6405965,53 27,53 Z" id="Oval-2" sketch:type="MSShapeGroup"></path>\n </g>\n </g>\n </svg>\n </div>\n</div>'};
+k=function(){var a,b,d,c,g,m,x;c=arguments[0];d=2<=arguments.length?v.call(arguments,1):[];m=0;for(x=d.length;m<x;m++)for(a in b=d[m],b)g=b[a],c[a]=g;return c};c.prototype.getAcceptedFiles=function(){var a,b,d,c,g;c=this.files;g=[];b=0;for(d=c.length;b<d;b++)a=c[b],a.accepted&&g.push(a);return g};c.prototype.getRejectedFiles=function(){var a,b,d,c,g;c=this.files;g=[];b=0;for(d=c.length;b<d;b++)a=c[b],a.accepted||g.push(a);return g};c.prototype.getFilesWithStatus=function(a){var b,d,c,g,m;g=this.files;
+m=[];d=0;for(c=g.length;d<c;d++)b=g[d],b.status===a&&m.push(b);return m};c.prototype.getQueuedFiles=function(){return this.getFilesWithStatus(c.QUEUED)};c.prototype.getUploadingFiles=function(){return this.getFilesWithStatus(c.UPLOADING)};c.prototype.getAddedFiles=function(){return this.getFilesWithStatus(c.ADDED)};c.prototype.getActiveFiles=function(){var a,b,d,f,g;f=this.files;g=[];b=0;for(d=f.length;b<d;b++)a=f[b],a.status!==c.UPLOADING&&a.status!==c.QUEUED||g.push(a);return g};c.prototype.init=
+function(){var a,b,d,f,g,m;"form"===this.element.tagName&&this.element.setAttribute("enctype","multipart/form-data");this.element.classList.contains("dropzone")&&!this.element.querySelector(".dz-message")&&this.element.appendChild(c.createElement('<div class="dz-default dz-message"><span>'+this.options.dictDefaultMessage+"</span></div>"));this.clickableElements.length&&(d=function(a){return function(){a.hiddenFileInput&&a.hiddenFileInput.parentNode.removeChild(a.hiddenFileInput);a.hiddenFileInput=
+document.createElement("input");a.hiddenFileInput.setAttribute("type","file");(null==a.options.maxFiles||1<a.options.maxFiles)&&a.hiddenFileInput.setAttribute("multiple","multiple");a.hiddenFileInput.className="dz-hidden-input";null!=a.options.acceptedFiles&&a.hiddenFileInput.setAttribute("accept",a.options.acceptedFiles);null!=a.options.capture&&a.hiddenFileInput.setAttribute("capture",a.options.capture);a.hiddenFileInput.style.visibility="hidden";a.hiddenFileInput.style.position="absolute";a.hiddenFileInput.style.top=
+"0";a.hiddenFileInput.style.left="0";a.hiddenFileInput.style.height="0";a.hiddenFileInput.style.width="0";document.querySelector(a.options.hiddenInputContainer).appendChild(a.hiddenFileInput);return a.hiddenFileInput.addEventListener("change",function(){var b,c,f,g;c=a.hiddenFileInput.files;if(c.length)for(f=0,g=c.length;f<g;f++)b=c[f],a.addFile(b);a.emit("addedfiles",c);return d()})}}(this),d());this.URL=null!=(a=window.URL)?a:window.webkitURL;m=this.events;f=0;for(g=m.length;f<g;f++)a=m[f],this.on(a,
+this.options[a]);this.on("uploadprogress",function(a){return function(){return a.updateTotalUploadProgress()}}(this));this.on("removedfile",function(a){return function(){return a.updateTotalUploadProgress()}}(this));this.on("canceled",function(a){return function(b){return a.emit("complete",b)}}(this));this.on("complete",function(a){return function(b){if(0===a.getAddedFiles().length&&0===a.getUploadingFiles().length&&0===a.getQueuedFiles().length)return setTimeout(function(){return a.emit("queuecomplete")},
+0)}}(this));b=function(a){a.stopPropagation();return a.preventDefault?a.preventDefault():a.returnValue=!1};this.listeners=[{element:this.element,events:{dragstart:function(a){return function(b){return a.emit("dragstart",b)}}(this),dragenter:function(a){return function(d){b(d);return a.emit("dragenter",d)}}(this),dragover:function(a){return function(d){var c;try{c=d.dataTransfer.effectAllowed}catch(f){}d.dataTransfer.dropEffect="move"===c||"linkMove"===c?"move":"copy";b(d);return a.emit("dragover",
+d)}}(this),dragleave:function(a){return function(b){return a.emit("dragleave",b)}}(this),drop:function(a){return function(d){b(d);return a.drop(d)}}(this),dragend:function(a){return function(b){return a.emit("dragend",b)}}(this)}}];this.clickableElements.forEach(function(a){return function(b){return a.listeners.push({element:b,events:{click:function(d){(b!==a.element||d.target===a.element||c.elementInside(d.target,a.element.querySelector(".dz-message")))&&a.hiddenFileInput.click();return!0}}})}}(this));
+this.enable();return this.options.init.call(this)};c.prototype.destroy=function(){var a;this.disable();this.removeAllFiles(!0);null!=(a=this.hiddenFileInput)&&a.parentNode&&(this.hiddenFileInput.parentNode.removeChild(this.hiddenFileInput),this.hiddenFileInput=null);delete this.element.dropzone;return c.instances.splice(c.instances.indexOf(this),1)};c.prototype.updateTotalUploadProgress=function(){var a,b,d,c,g,m;b=d=0;if(this.getActiveFiles().length){m=this.getActiveFiles();c=0;for(g=m.length;c<
+g;c++)a=m[c],d+=a.upload.bytesSent,b+=a.upload.total;a=100*d/b}else a=100;return this.emit("totaluploadprogress",a,b,d)};c.prototype._getParamName=function(a){return"function"===typeof this.options.paramName?this.options.paramName(a):""+this.options.paramName+(this.options.uploadMultiple?"["+a+"]":"")};c.prototype.getFallbackForm=function(){var a,b;if(a=this.getExistingFallback())return a;a='<div class="dz-fallback">';this.options.dictFallbackText&&(a+="<p>"+this.options.dictFallbackText+"</p>");
+a+='<input type="file" name="'+this._getParamName(0)+'" '+(this.options.uploadMultiple?'multiple="multiple"':void 0)+' /><input type="submit" value="Upload!"></div>';a=c.createElement(a);"FORM"!==this.element.tagName?(b=c.createElement('<form action="'+this.options.url+'" enctype="multipart/form-data" method="'+this.options.method+'"></form>'),b.appendChild(a)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method));return null!=b?b:a};
+c.prototype.getExistingFallback=function(){var a,b,d,c,g;b=function(a){var b,d,c;d=0;for(c=a.length;d<c;d++)if(b=a[d],/(^| )fallback($| )/.test(b.className))return b};g=["div","form"];d=0;for(c=g.length;d<c;d++)if(a=g[d],a=b(this.element.getElementsByTagName(a)))return a};c.prototype.setupEventListeners=function(){var a,b,d,c,g,m,e;m=this.listeners;e=[];c=0;for(g=m.length;c<g;c++)a=m[c],e.push(function(){var c,f;c=a.events;f=[];for(b in c)d=c[b],f.push(a.element.addEventListener(b,d,!1));return f}());
+return e};c.prototype.removeEventListeners=function(){var a,b,c,f,g,e,h;e=this.listeners;h=[];f=0;for(g=e.length;f<g;f++)a=e[f],h.push(function(){var f,g;f=a.events;g=[];for(b in f)c=f[b],g.push(a.element.removeEventListener(b,c,!1));return g}());return h};c.prototype.disable=function(){var a,b,c,f,g;this.clickableElements.forEach(function(a){return a.classList.remove("dz-clickable")});this.removeEventListeners();f=this.files;g=[];b=0;for(c=f.length;b<c;b++)a=f[b],g.push(this.cancelUpload(a));return g};
+c.prototype.enable=function(){this.clickableElements.forEach(function(a){return a.classList.add("dz-clickable")});return this.setupEventListeners()};c.prototype.filesize=function(a){var b,c,f,g,e,h,k,u;f=0;g="b";if(0<a){h=["TB","GB","MB","KB","b"];c=k=0;for(u=h.length;k<u;c=++k)if(e=h[c],b=Math.pow(this.options.filesizeBase,4-c)/10,a>=b){f=a/Math.pow(this.options.filesizeBase,4-c);g=e;break}f=Math.round(10*f)/10}return"<strong>"+f+"</strong> "+g};c.prototype._updateMaxFilesReachedClass=function(){return null!=
+this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")};c.prototype.drop=function(a){var b;a.dataTransfer&&(this.emit("drop",a),b=a.dataTransfer.files,this.emit("addedfiles",b),b.length&&((a=a.dataTransfer.items)&&a.length&&null!=a[0].webkitGetAsEntry?this._addFilesFromItems(a):
+this.handleFiles(b)))};c.prototype.paste=function(a){var b;if(null!=(null!=a?null!=(b=a.clipboardData)?b.items:void 0:void 0)&&(this.emit("paste",a),a=a.clipboardData.items,a.length))return this._addFilesFromItems(a)};c.prototype.handleFiles=function(a){var b,c,f,g;g=[];c=0;for(f=a.length;c<f;c++)b=a[c],g.push(this.addFile(b));return g};c.prototype._addFilesFromItems=function(a){var b,c,f,g,e;e=[];f=0;for(g=a.length;f<g;f++)c=a[f],null!=c.webkitGetAsEntry&&(b=c.webkitGetAsEntry())?b.isFile?e.push(this.addFile(c.getAsFile())):
+b.isDirectory?e.push(this._addFilesFromDirectory(b,b.name)):e.push(void 0):null!=c.getAsFile?null==c.kind||"file"===c.kind?e.push(this.addFile(c.getAsFile())):e.push(void 0):e.push(void 0);return e};c.prototype._addFilesFromDirectory=function(a,b){var c,f;c=a.createReader();f=function(a){return function(c){var d,f,e;f=0;for(e=c.length;f<e;f++)d=c[f],d.isFile?d.file(function(c){if(!a.options.ignoreHiddenFiles||"."!==c.name.substring(0,1))return c.fullPath=""+b+"/"+c.name,a.addFile(c)}):d.isDirectory&&
+a._addFilesFromDirectory(d,""+b+"/"+d.name)}}(this);return c.readEntries(f,function(a){return"undefined"!==typeof console&&null!==console?"function"===typeof console.log?console.log(a):void 0:void 0})};c.prototype.accept=function(a,b){return a.size>1048576*this.options.maxFilesize?b(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(a.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):c.isValidFile(a,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=
+this.options.maxFiles?(b(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",a)):this.options.accept.call(this,a,b):b(this.options.dictInvalidFileType)};c.prototype.addFile=function(a){a.upload={progress:0,total:a.size,bytesSent:0};this.files.push(a);a.status=c.ADDED;this.emit("addedfile",a);this._enqueueThumbnail(a);return this.accept(a,function(b){return function(c){c?(a.accepted=!1,b._errorProcessing([a],c)):(a.accepted=!0,b.options.autoQueue&&
+b.enqueueFile(a));return b._updateMaxFilesReachedClass()}}(this))};c.prototype.enqueueFiles=function(a){var b,c,f;c=0;for(f=a.length;c<f;c++)b=a[c],this.enqueueFile(b);return null};c.prototype.enqueueFile=function(a){if(a.status===c.ADDED&&!0===a.accepted){if(a.status=c.QUEUED,this.options.autoProcessQueue)return setTimeout(function(a){return function(){return a.processQueue()}}(this),0)}else throw Error("This file can't be queued because it has already been processed or was rejected.");};c.prototype._thumbnailQueue=
+[];c.prototype._processingThumbnail=!1;c.prototype._enqueueThumbnail=function(a){if(this.options.createImageThumbnails&&a.type.match(/image.*/)&&a.size<=1048576*this.options.maxThumbnailFilesize)return this._thumbnailQueue.push(a),setTimeout(function(a){return function(){return a._processThumbnailQueue()}}(this),0)};c.prototype._processThumbnailQueue=function(){if(!this._processingThumbnail&&0!==this._thumbnailQueue.length)return this._processingThumbnail=!0,this.createThumbnail(this._thumbnailQueue.shift(),
+function(a){return function(){a._processingThumbnail=!1;return a._processThumbnailQueue()}}(this))};c.prototype.removeFile=function(a){a.status===c.UPLOADING&&this.cancelUpload(a);this.files=r(this.files,a);this.emit("removedfile",a);if(0===this.files.length)return this.emit("reset")};c.prototype.removeAllFiles=function(a){var b,d,f,g;null==a&&(a=!1);g=this.files.slice();d=0;for(f=g.length;d<f;d++)b=g[d],(b.status!==c.UPLOADING||a)&&this.removeFile(b);return null};c.prototype.createThumbnail=function(a,
+b){var c;c=new FileReader;c.onload=function(f){return function(){if("image/svg+xml"===a.type)f.emit("thumbnail",a,c.result),null!=b&&b();else return f.createThumbnailFromUrl(a,c.result,b)}}(this);return c.readAsDataURL(a)};c.prototype.createThumbnailFromUrl=function(a,b,c,f){var g;g=document.createElement("img");f&&(g.crossOrigin=f);g.onload=function(b){return function(){var f,e,h,k,u,l,n;a.width=g.width;a.height=g.height;h=b.options.resize.call(b,a);null==h.trgWidth&&(h.trgWidth=h.optWidth);null==
+h.trgHeight&&(h.trgHeight=h.optHeight);f=document.createElement("canvas");e=f.getContext("2d");f.width=h.trgWidth;f.height=h.trgHeight;p(e,g,null!=(k=h.srcX)?k:0,null!=(u=h.srcY)?u:0,h.srcWidth,h.srcHeight,null!=(l=h.trgX)?l:0,null!=(n=h.trgY)?n:0,h.trgWidth,h.trgHeight);f=f.toDataURL("image/png");b.emit("thumbnail",a,f);if(null!=c)return c()}}(this);null!=c&&(g.onerror=c);return g.src=b};c.prototype.processQueue=function(){var a,b,c,f;b=this.options.parallelUploads;a=c=this.getUploadingFiles().length;
+if(!(c>=b)&&(f=this.getQueuedFiles(),0<f.length)){if(this.options.uploadMultiple)return this.processFiles(f.slice(0,b-c));for(;a<b&&f.length;)this.processFile(f.shift()),a++}};c.prototype.processFile=function(a){return this.processFiles([a])};c.prototype.processFiles=function(a){var b,d,f;d=0;for(f=a.length;d<f;d++)b=a[d],b.processing=!0,b.status=c.UPLOADING,this.emit("processing",b);this.options.uploadMultiple&&this.emit("processingmultiple",a);return this.uploadFiles(a)};c.prototype._getFilesWithXhr=
+function(a){var b,c,f,g,e;g=this.files;e=[];c=0;for(f=g.length;c<f;c++)b=g[c],b.xhr===a&&e.push(b);return e};c.prototype.cancelUpload=function(a){var b,d,f,g;if(a.status===c.UPLOADING){d=this._getFilesWithXhr(a.xhr);f=0;for(g=d.length;f<g;f++)b=d[f],b.status=c.CANCELED;a.xhr.abort();a=0;for(f=d.length;a<f;a++)b=d[a],this.emit("canceled",b);this.options.uploadMultiple&&this.emit("canceledmultiple",d)}else if((b=a.status)===c.ADDED||b===c.QUEUED)a.status=c.CANCELED,this.emit("canceled",a),this.options.uploadMultiple&&
+this.emit("canceledmultiple",[a]);if(this.options.autoProcessQueue)return this.processQueue()};h=function(){var a,b;b=arguments[0];a=2<=arguments.length?v.call(arguments,1):[];return"function"===typeof b?b.apply(this,a):b};c.prototype.uploadFile=function(a){return this.uploadFiles([a])};c.prototype.uploadFiles=function(a){var b,d,f,g,e,l,p,n,q,r,w,v,t,z,y;t=new XMLHttpRequest;e=0;for(v=a.length;e<v;e++)b=a[e],b.xhr=t;e=h(this.options.method,a);v=h(this.options.url,a);t.open(e,v,!0);t.withCredentials=
+!!this.options.withCredentials;r=null;f=function(c){return function(){var d,f,e;e=[];d=0;for(f=a.length;d<f;d++)b=a[d],e.push(c._errorProcessing(a,r||c.options.dictResponseError.replace("{{statusCode}}",t.status),t));return e}}(this);w=function(c){return function(d){var f,e,g;if(null!=d)for(f=100*d.loaded/d.total,e=0,g=a.length;e<g;e++)b=a[e],b.upload={progress:f,total:d.total,bytesSent:d.loaded};else{d=!0;f=100;e=0;for(g=a.length;e<g;e++){b=a[e];if(100!==b.upload.progress||b.upload.bytesSent!==b.upload.total)d=
+!1;b.upload.progress=f;b.upload.bytesSent=b.upload.total}if(d)return}g=[];d=0;for(e=a.length;d<e;d++)b=a[d],g.push(c.emit("uploadprogress",b,f,b.upload.bytesSent));return g}}(this);t.onload=function(b){return function(d){var e;if(a[0].status!==c.CANCELED&&4===t.readyState){r=t.responseText;if(t.getResponseHeader("content-type")&&~t.getResponseHeader("content-type").indexOf("application/json"))try{r=JSON.parse(r)}catch(g){d=g,r="Invalid JSON response from server."}w();return 200<=(e=t.status)&&300>
+e?b._finished(a,r,d):f()}}}(this);t.onerror=function(b){return function(){if(a[0].status!==c.CANCELED)return f()}}(this);(null!=(g=t.upload)?g:t).onprogress=w;e={Accept:"application/json","Cache-Control":"no-cache","X-Requested-With":"XMLHttpRequest"};this.options.headers&&k(e,this.options.headers);for(d in e)(g=e[d])&&t.setRequestHeader(d,g);d=new FormData;if(this.options.params)for(n in e=this.options.params,e)g=e[n],d.append(n,g);n=0;for(g=a.length;n<g;n++)b=a[n],this.emit("sending",b,t,d);this.options.uploadMultiple&&
+this.emit("sendingmultiple",a,t,d);if("FORM"===this.element.tagName)for(v=this.element.querySelectorAll("input, textarea, select, button"),g=0,e=v.length;g<e;g++)if(p=v[g],n=p.getAttribute("name"),q=p.getAttribute("type"),"SELECT"===p.tagName&&p.hasAttribute("multiple"))for(y=p.options,q=0,z=y.length;q<z;q++)p=y[q],p.selected&&d.append(n,p.value);else(!q||"checkbox"!==(l=q.toLowerCase())&&"radio"!==l||p.checked)&&d.append(n,p.value);l=n=0;for(g=a.length-1;0<=g?n<=g:n>=g;l=0<=g?++n:--n)d.append(this._getParamName(l),
+a[l],a[l].name);return this.submitRequest(t,d,a)};c.prototype.submitRequest=function(a,b,c){return a.send(b)};c.prototype._finished=function(a,b,d){var f,e,h;e=0;for(h=a.length;e<h;e++)f=a[e],f.status=c.SUCCESS,this.emit("success",f,b,d),this.emit("complete",f);this.options.uploadMultiple&&(this.emit("successmultiple",a,b,d),this.emit("completemultiple",a));if(this.options.autoProcessQueue)return this.processQueue()};c.prototype._errorProcessing=function(a,b,d){var e,g,h;g=0;for(h=a.length;g<h;g++)e=
+a[g],e.status=c.ERROR,this.emit("error",e,b,d),this.emit("complete",e);this.options.uploadMultiple&&(this.emit("errormultiple",a,b,d),this.emit("completemultiple",a));if(this.options.autoProcessQueue)return this.processQueue()};return c}(w);k.version="4.2.0";k.options={};k.optionsForElement=function(e){if(e.getAttribute("id"))return k.options[q(e.getAttribute("id"))]};k.instances=[];k.forElement=function(e){"string"===typeof e&&(e=document.querySelector(e));if(null==(null!=e?e.dropzone:void 0))throw Error("No Dropzone found for given element. This is probably because you're trying to access it before Dropzone had the time to initialize. Use the `init` option to setup any additional observers on your Dropzone.");
+return e.dropzone};k.autoDiscover=!0;k.discover=function(){var e,c,u,h,a;document.querySelectorAll?c=document.querySelectorAll(".dropzone"):(c=[],e=function(a){var d,e,g,h;h=[];e=0;for(g=a.length;e<g;e++)d=a[e],/(^| )dropzone($| )/.test(d.className)?h.push(c.push(d)):h.push(void 0);return h},e(document.getElementsByTagName("div")),e(document.getElementsByTagName("form")));a=[];u=0;for(h=c.length;u<h;u++)e=c[u],!1!==k.optionsForElement(e)?a.push(new k(e)):a.push(void 0);return a};k.blacklistedBrowsers=
+[/opera.*(Windows Phone|Macintosh).*version\/12/i];k.isBrowserSupported=function(){var e,c,u,h,a;e=!0;if(window.File&&window.FileReader&&window.FileList&&window.Blob&&window.FormData&&document.querySelector)if("classList"in document.createElement("a"))for(a=k.blacklistedBrowsers,u=0,h=a.length;u<h;u++)c=a[u],c.test(navigator.userAgent)&&(e=!1);else e=!1;else e=!1;return e};r=function(e,c){var k,h,a,b;b=[];h=0;for(a=e.length;h<a;h++)k=e[h],k!==c&&b.push(k);return b};q=function(e){return e.replace(/[\-_](\w)/g,
+function(c){return c.charAt(1).toUpperCase()})};k.createElement=function(e){var c;c=document.createElement("div");c.innerHTML=e;return c.childNodes[0]};k.elementInside=function(e,c){if(e===c)return!0;for(;e=e.parentNode;)if(e===c)return!0;return!1};k.getElement=function(e,c){var k;"string"===typeof e?k=document.querySelector(e):null!=e.nodeType&&(k=e);if(null==k)throw Error("Invalid `"+c+"` option provided. Please provide a CSS selector or a plain HTML element.");return k};k.getElements=function(e,
+c){var k,h,a,b,d;if(e instanceof Array){h=[];try{for(a=0,b=e.length;a<b;a++)k=e[a],h.push(this.getElement(k,c))}catch(f){h=null}}else if("string"===typeof e)for(h=[],d=document.querySelectorAll(e),a=0,b=d.length;a<b;a++)k=d[a],h.push(k);else null!=e.nodeType&&(h=[e]);if(null==h||!h.length)throw Error("Invalid `"+c+"` option provided. Please provide a CSS selector, a plain HTML element or a list of those.");return h};k.confirm=function(e,c,k){if(window.confirm(e))return c();if(null!=k)return k()};
+k.isValidFile=function(e,c){var k,h,a,b,d;if(!c)return!0;c=c.split(",");h=e.type;k=h.replace(/\/.*$/,"");b=0;for(d=c.length;b<d;b++)if(a=c[b],a=a.trim(),"."===a.charAt(0)){if(-1!==e.name.toLowerCase().indexOf(a.toLowerCase(),e.name.length-a.length))return!0}else if(/\/\*$/.test(a)){if(k===a.replace(/\/.*$/,""))return!0}else if(h===a)return!0;return!1};"undefined"!==typeof jQuery&&null!==jQuery&&(jQuery.fn.dropzone=function(e){return this.each(function(){return new k(this,e)})});"undefined"!==typeof module&&
+null!==module?module.exports=k:window.Dropzone=k;k.ADDED="added";k.QUEUED="queued";k.ACCEPTED=k.QUEUED;k.UPLOADING="uploading";k.PROCESSING=k.UPLOADING;k.CANCELED="canceled";k.ERROR="error";k.SUCCESS="success";n=function(e){var c,k,h,a,b;h=e.naturalHeight;c=document.createElement("canvas");c.width=1;c.height=h;c=c.getContext("2d");c.drawImage(e,0,0);c=c.getImageData(0,0,1,h).data;b=0;for(a=k=h;a>b;)e=c[4*(a-1)+3],0===e?k=a:b=a,a=k+b>>1;h=a/h;return 0===h?1:h};p=function(e,c,k,h,a,b,d,f,g,m){var l;
+l=n(c);m/=l;l=0;switch(EXIF.getData(c)){case 3:l=2;break;case 6:l=1;break;case 8:l=-1}l&&(e.translate(d+g/2,f+m/2),e.rotate(l*Math.PI/2),d=-g/2,f=-m/2);return e.drawImage(c,k,h,a,b,d,f,g,m)};k._autoDiscoverFunction=function(){if(k.autoDiscover)return k.discover()};(function(e,c){var k,h,a,b,d,f,g,l,n;a=!1;n=!0;h=e.document;l=h.documentElement;k=h.addEventListener?"addEventListener":"attachEvent";g=h.addEventListener?"removeEventListener":"detachEvent";f=h.addEventListener?"":"on";b=function(d){if("readystatechange"!==
+d.type||"complete"===h.readyState)if(("load"===d.type?e:h)[g](f+d.type,b,!1),!a&&(a=!0))return c.call(e,d.type||d)};d=function(){try{l.doScroll("left")}catch(a){setTimeout(d,50);return}return b("poll")};if("complete"!==h.readyState){if(h.createEventObject&&l.doScroll){try{n=!e.frameElement}catch(p){}n&&d()}h[k](f+"DOMContentLoaded",b,!1);h[k](f+"readystatechange",b,!1);return e[k](f+"load",b,!1)}})(window,k._autoDiscoverFunction)}).call(this);
diff --git a/web/js/src/exif.js b/web/js/src/exif.js
new file mode 100644
index 000000000..3a2c7e1c3
--- /dev/null
+++ b/web/js/src/exif.js
@@ -0,0 +1,94 @@
+/* Cut down version of https://github.com/exif-js/exif-js
+ * only looking for orientation EXIF data. MIT. */
+
+(function() {
+ var EXIF = {};
+ window.EXIF = EXIF;
+
+ function readEXIFData(file, start) {
+ var str = "";
+ for (var n = start; n < start+4; n++) {
+ str += String.fromCharCode(file.getUint8(n));
+ }
+ if (str != "Exif") {
+ return false;
+ }
+
+ var bigEnd,
+ tiffOffset = start + 6;
+
+ // test for TIFF validity and endianness
+ if (file.getUint16(tiffOffset) == 0x4949) {
+ bigEnd = false;
+ } else if (file.getUint16(tiffOffset) == 0x4D4D) {
+ bigEnd = true;
+ } else {
+ return false;
+ }
+
+ if (file.getUint16(tiffOffset+2, !bigEnd) != 0x002A) {
+ return false;
+ }
+
+ var firstIFDOffset = file.getUint32(tiffOffset+4, !bigEnd);
+ if (firstIFDOffset < 0x00000008) {
+ return false;
+ }
+
+ var dirStart = tiffOffset + firstIFDOffset,
+ entries = file.getUint16(dirStart, !bigEnd),
+ tags = {},
+ entryOffset,
+ i;
+
+ for (i=0;i<entries;i++) {
+ entryOffset = dirStart + i*12 + 2;
+ if (file.getUint16(entryOffset, !bigEnd) == 0x0112) {
+ return parseInt(file.getUint16(entryOffset + 8, !bigEnd), 10);
+ }
+ }
+ return false;
+ }
+
+ EXIF.getData = function(img) {
+ if ((img instanceof Image || img instanceof HTMLImageElement) && !img.complete) {
+ return false;
+ }
+
+ var base64 = img.src.replace(/^data\:([^\;]+)\;base64,/gmi, '');
+ var binary = atob(base64);
+ var len = binary.length;
+ var file = new ArrayBuffer(len);
+ var view = new Uint8Array(file);
+ for (var i = 0; i < len; i++) {
+ view[i] = binary.charCodeAt(i);
+ }
+
+ var dataView = new DataView(file);
+ if ((dataView.getUint8(0) != 0xFF) || (dataView.getUint8(1) != 0xD8)) {
+ return false; // not a valid jpeg
+ }
+
+ var offset = 2,
+ length = file.byteLength,
+ marker;
+
+ while (offset < length) {
+ if (dataView.getUint8(offset) != 0xFF) {
+ return false; // not a valid marker, something is wrong
+ }
+
+ marker = dataView.getUint8(offset + 1);
+
+ // we could implement handling for other markers here,
+ // but we're only looking for 0xFFE1 for EXIF data
+ if (marker == 225) {
+ return readEXIFData(dataView, offset + 4);
+ // offset += 2 + file.getShortAt(offset+2, true);
+ } else {
+ offset += 2 + dataView.getUint16(offset+2);
+ }
+ }
+ };
+
+})();