summaryrefslogtreecommitdiff
path: root/lib/Travelynx/Controller/Account.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Travelynx/Controller/Account.pm')
-rw-r--r--lib/Travelynx/Controller/Account.pm378
1 files changed, 330 insertions, 48 deletions
diff --git a/lib/Travelynx/Controller/Account.pm b/lib/Travelynx/Controller/Account.pm
index 2664329..533e6eb 100644
--- a/lib/Travelynx/Controller/Account.pm
+++ b/lib/Travelynx/Controller/Account.pm
@@ -1,11 +1,13 @@
package Travelynx::Controller::Account;
# Copyright (C) 2020-2023 Birte Kristina Friesel
+# Copyright (C) 2025 networkException <git@nwex.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
use Mojo::Base 'Mojolicious::Controller';
use JSON;
+use Math::Polygon;
use Mojo::Util qw(xml_escape);
use Text::Markdown;
use UUID::Tiny qw(:std);
@@ -45,6 +47,7 @@ sub send_registration_mail {
my $ua = $self->req->headers->user_agent;
my $reg_url = $self->url_for('reg')->to_abs->scheme('https');
+ my $tos_url = $self->url_for('tos')->to_abs->scheme('https');
my $imprint_url = $self->url_for('impressum')->to_abs->scheme('https');
my $body = "Hallo, ${user}!\n\n";
@@ -53,7 +56,8 @@ sub send_registration_mail {
$body
.= "Falls die Registrierung von dir ausging, kannst du den Account unter\n";
$body .= "${reg_url}/${user_id}/${token}\n";
- $body .= "freischalten.\n\n";
+ $body .= "freischalten.\n";
+ $body .= "Beachte dabei die Nutzungsbedingungen: ${tos_url}\n\n";
$body
.= "Falls nicht, ignoriere diese Mail bitte. Nach etwa 48 Stunden wird deine\n";
$body
@@ -799,11 +803,8 @@ sub profile {
sub insight {
my ($self) = @_;
- my $user = $self->current_user;
- my ( $use_history, $destinations ) = $self->users->use_history(
- uid => $user->{id},
- with_local_transit => 1
- );
+ my $user = $self->current_user;
+ my $use_history = $self->users->use_history( uid => $user->{id} );
if ( $self->param('action') and $self->param('action') eq 'save' ) {
if ( $self->param('on_departure') ) {
@@ -820,58 +821,20 @@ sub insight {
$use_history &= ~0x02;
}
- if ( $self->param('local_transit') ) {
- $use_history |= 0x04;
- }
- else {
- $use_history &= ~0x04;
- }
-
- if ( $self->param('destinations') ) {
- $destinations
- = [ split( qr{\r?\n\r?}, $self->param('destinations') ) ];
- }
-
$self->users->use_history(
- uid => $user->{id},
- set => $use_history,
- destinations => $destinations
+ uid => $user->{id},
+ set => $use_history
);
$self->flash( success => 'use_history' );
$self->redirect_to('account');
}
- $self->param( on_departure => $use_history & 0x01 ? 1 : 0 );
- $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 );
- $self->param( local_transit => $use_history & 0x04 ? 1 : 0 );
- $self->param( destinations => join( "\n", @{$destinations} ) );
+ $self->param( on_departure => $use_history & 0x01 ? 1 : 0 );
+ $self->param( on_arrival => $use_history & 0x02 ? 1 : 0 );
$self->render('use_history');
}
-sub services {
- my ($self) = @_;
- my $user = $self->current_user;
-
- if ( $self->param('action') and $self->param('action') eq 'save' ) {
- my $sb = $self->param('stationboard');
- my $value = 0;
- if ( $sb =~ m{ ^ \d+ $ }x and $sb >= 0 and $sb <= 4 ) {
- $value = int($sb);
- }
- $self->users->use_external_services(
- uid => $user->{id},
- set => $value
- );
- $self->flash( success => 'external' );
- $self->redirect_to('account');
- }
-
- $self->param( stationboard =>
- $self->users->use_external_services( uid => $user->{id} ) );
- $self->render('use_external_links');
-}
-
sub webhook {
my ($self) = @_;
@@ -911,6 +874,35 @@ sub webhook {
$self->render( 'webhooks', hook => $hook );
}
+sub change_language {
+ my ($self) = @_;
+
+ my $action = $self->req->param('action');
+ my $language = $self->req->param('language');
+
+ if ( $action and $action eq 'save' ) {
+ if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
+ $self->render(
+ 'bad_request',
+ csrf => 1,
+ status => 400
+ );
+ return;
+ }
+ $self->users->set_language(
+ uid => $self->current_user->{id},
+ language => $language eq 'none' ? undef : $language,
+ );
+ $self->flash( success => 'language' );
+ $self->redirect_to('account');
+ }
+ else {
+ my @languages = @{ $self->current_user->{languages} };
+ $self->param( language => $languages[0] // 'none' );
+ $self->render('language');
+ }
+}
+
sub change_mail {
my ($self) = @_;
@@ -1040,6 +1032,287 @@ sub password_form {
$self->render('change_password');
}
+sub lonlat_in_polygon {
+ my ( $self, $polygon, $lonlat ) = @_;
+
+ my $circle = shift( @{$polygon} );
+ my @holes = @{$polygon};
+
+ my $circle_poly = Math::Polygon->new( @{$circle} );
+ if ( $circle_poly->contains($lonlat) ) {
+ for my $hole (@holes) {
+ my $hole_poly = Math::Polygon->new( @{$hole} );
+ if ( $hole_poly->contains($lonlat) ) {
+ return;
+ }
+ }
+ return 1;
+ }
+ return;
+}
+
+sub backend_form {
+ my ($self) = @_;
+ my $user = $self->current_user;
+
+ my %backend_by_id;
+ my @backends = $self->stations->get_backends;
+ my @suggested_backends;
+
+ my %place_map = (
+ AT => 'Österreich',
+ CH => 'Schweiz',
+ 'CH-BE' => 'Kanton Bern',
+ 'CH-GE' => 'Kanton Genf',
+ 'CH-LU' => 'Kanton Luzern',
+ 'CH-ZH' => 'Kanton Zürich',
+ DE => 'Deutschland',
+ 'DE-BB' => 'Brandenburg',
+ 'DE-BW' => 'Baden-Württemberg',
+ 'DE-BE' => 'Berlin',
+ 'DE-BY' => 'Bayern',
+ 'DE-HB' => 'Bremen',
+ 'DE-HE' => 'Hessen',
+ 'DE-MV' => 'Mecklenburg-Vorpommern',
+ 'DE-NI' => 'Niedersachsen',
+ 'DE-NW' => 'Nordrhein-Westfalen',
+ 'DE-RP' => 'Rheinland-Pfalz',
+ 'DE-SH' => 'Schleswig-Holstein',
+ 'DE-ST' => 'Sachsen-Anhalt',
+ 'DE-TH' => 'Thüringen',
+ DK => 'Dänemark',
+ 'GB-NIR' => 'Nordirland',
+ LI => 'Liechtenstein',
+ LU => 'Luxembourg',
+ IE => 'Irland',
+ 'US-CA' => 'California',
+ 'US-TX' => 'Texas',
+ );
+
+ my ( $user_lat, $user_lon )
+ = $self->journeys->get_latest_checkout_latlon( uid => $user->{id} );
+
+ for my $backend (@backends) {
+ my $type = 'UNKNOWN';
+ if ( $backend->{iris} ) {
+ $type = 'IRIS-TTS';
+ $backend->{name} = 'IRIS';
+ $backend->{longname} = 'Deutsche Bahn: IRIS-TTS';
+ $backend->{homepage} = 'https://www.bahn.de';
+ $backend->{legacy} = 1;
+ }
+ elsif ( $backend->{dbris} ) {
+ $type = 'DBRIS';
+ $backend->{longname} = 'Deutsche Bahn: bahn.de';
+ $backend->{homepage} = 'https://www.bahn.de';
+ $backend->{recommended} = 1;
+ }
+ elsif ( $backend->{efa} ) {
+ if ( my $s = $self->efa->get_service( $backend->{name} ) ) {
+ $type = 'EFA';
+ $backend->{longname} = $s->{name};
+ $backend->{homepage} = $s->{homepage};
+ $backend->{regions} = [ map { $place_map{$_} // $_ }
+ @{ $s->{coverage}{regions} // [] } ];
+ $backend->{has_area} = $s->{coverage}{area} ? 1 : 0;
+ $backend->{association} = 1;
+
+ if (
+ $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'Polygon'
+ and defined $user_lon
+ and $self->lonlat_in_polygon(
+ $s->{coverage}{area}{coordinates},
+ [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ }
+ elsif ( $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'MultiPolygon'
+ and defined $user_lon )
+ {
+ for my $s_poly (
+ @{ $s->{coverage}{area}{coordinates} // [] } )
+ {
+ if (
+ $self->lonlat_in_polygon(
+ $s_poly, [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ last;
+ }
+ }
+ }
+ }
+ else {
+ $type = undef;
+ }
+ }
+ elsif ( $backend->{hafas} ) {
+
+ # These backends lack a journey endpoint or are no longer
+ # operational and are thus useless for travelynx
+ if ( $backend->{name} eq 'Resrobot'
+ or $backend->{name} eq 'TPG'
+ or $backend->{name} eq 'VRN'
+ or $backend->{name} eq 'DB' )
+ {
+ $type = undef;
+ }
+
+ # PKP is behind a GeoIP filter. Only list it if travelynx.conf
+ # indicates that our IP is allowed or provides a proxy.
+ elsif (
+ $backend->{name} eq 'PKP'
+ and not( $self->app->config->{hafas}{PKP}{geoip_ok}
+ or $self->app->config->{hafas}{PKP}{proxy} )
+ )
+ {
+ $type = undef;
+ }
+ elsif ( my $s = $self->hafas->get_service( $backend->{name} ) ) {
+ $type = 'HAFAS';
+ $backend->{longname} = $s->{name};
+ $backend->{homepage} = $s->{homepage};
+ $backend->{regions} = [ map { $place_map{$_} // $_ }
+ @{ $s->{coverage}{regions} // [] } ];
+ $backend->{has_area} = $s->{coverage}{area} ? 1 : 0;
+
+ if ( $backend->{name} eq 'ÖBB' ) {
+ $backend->{recommended} = 1;
+ }
+ else {
+ $backend->{association} = 1;
+ }
+
+ if (
+ $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'Polygon'
+ and defined $user_lon
+ and $self->lonlat_in_polygon(
+ $s->{coverage}{area}{coordinates},
+ [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ }
+ elsif ( $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'MultiPolygon'
+ and defined $user_lon )
+ {
+ for my $s_poly (
+ @{ $s->{coverage}{area}{coordinates} // [] } )
+ {
+ if (
+ $self->lonlat_in_polygon(
+ $s_poly, [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ last;
+ }
+ }
+ }
+ }
+ else {
+ $type = undef;
+ }
+ }
+ elsif ( $backend->{motis} ) {
+ my $s = $self->motis->get_service( $backend->{name} );
+
+ $type = 'MOTIS';
+ $backend->{longname} = $s->{name};
+ $backend->{homepage} = $s->{homepage};
+ $backend->{regions} = [ map { $place_map{$_} // $_ }
+ @{ $s->{coverage}{regions} // [] } ];
+ $backend->{has_area} = $s->{coverage}{area} ? 1 : 0;
+ $backend->{experimental} = 1;
+
+ if ( $backend->{name} eq 'transitous' ) {
+ $backend->{regions} = ['Weltweit'];
+ }
+ if ( $backend->{name} eq 'RNV' ) {
+ $backend->{homepage} = 'https://rnv-online.de/';
+ }
+
+ if (
+ $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'Polygon'
+ and defined $user_lon
+ and $self->lonlat_in_polygon(
+ $s->{coverage}{area}{coordinates},
+ [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ }
+ elsif ( $s->{coverage}{area}
+ and $s->{coverage}{area}{type} eq 'MultiPolygon'
+ and defined $user_lon )
+ {
+ for my $s_poly ( @{ $s->{coverage}{area}{coordinates} // [] } )
+ {
+ if (
+ $self->lonlat_in_polygon(
+ $s_poly, [ $user_lon, $user_lat ]
+ )
+ )
+ {
+ push( @suggested_backends, $backend );
+ last;
+ }
+ }
+ }
+ }
+ $backend->{type} = $type;
+
+ $backend_by_id{ $backend->{id} } = $backend;
+ }
+
+ my @frequent_backends = grep { $_->{type} }
+ map { $backend_by_id{$_} }
+ $self->journeys->get_frequent_backend_ids( uid => $user->{id} );
+
+ @backends = map { $_->[1] }
+ sort { $a->[0] cmp $b->[0] }
+ map { [ lc( $_->{name} ), $_ ] } grep { $_->{type} } @backends;
+
+ $self->render(
+ 'select_backend',
+ suggestions => \@suggested_backends,
+ frequent => \@frequent_backends,
+ backends => \@backends,
+ user => $user,
+ redirect_to => $self->req->param('redirect_to') // '/',
+ );
+}
+
+sub change_backend {
+ my ($self) = @_;
+
+ my $backend_id = $self->req->param('backend');
+ my $redir = $self->req->param('redirect_to') // '/';
+
+ if ( $backend_id !~ m{ ^ \d+ $ }x ) {
+ $self->redirect_to($redir);
+ }
+
+ $self->users->set_backend(
+ uid => $self->current_user->{id},
+ backend_id => $backend_id,
+ );
+
+ $self->redirect_to($redir);
+}
+
sub change_password {
my ($self) = @_;
my $old_password = $self->req->param('oldpw');
@@ -1236,6 +1509,15 @@ sub confirm_mail {
my $id = $self->current_user->{id};
my $token = $self->stash('token');
+ # Some mail clients include the trailing ">" from the confirmation mail
+ # when opening/copying the confirmation link. A token will never contain
+ # this symbol, so remove it just in case.
+ $token =~ s{>}{};
+
+ # I did not yet find a mail client that also includes the trailing ",",
+ # but you never now...
+ $token =~ s{,}{};
+
if (
$self->users->change_mail_with_token(
uid => $id,