Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
open-source
MetaGer
Commits
978f83ff
Commit
978f83ff
authored
Mar 04, 2019
by
Dominik Hebeler
Browse files
Merge branch 'development' into '864-optimize-request-times'
Development See merge request
!1393
parents
ff7cc64c
a8bd7ade
Changes
9
Hide whitespace changes
Inline
Side-by-side
app/Console/Kernel.php
View file @
978f83ff
...
...
@@ -31,13 +31,6 @@ class Kernel extends ConsoleKernel
DB
::
table
(
'monthlyrequests'
)
->
truncate
();
DB
::
disconnect
(
'mysql'
);
})
->
monthlyOn
(
1
,
'00:00'
);
// Delete all of the old humanverification entries
$schedule
->
call
(
function
()
{
DB
::
delete
(
'DELETE FROM humanverification WHERE updated_at < (now() - interval 72 hour) AND whitelist = 0 ORDER BY updated_at DESC'
);
DB
::
delete
(
'DELETE FROM humanverification WHERE updated_at < (now() - interval 2 week) AND whitelist = 1 ORDER BY updated_at DESC'
);
DB
::
disconnect
(
'mysql'
);
})
->
everyThirtyMinutes
();
}
/**
...
...
app/Http/Controllers/HumanVerification.php
View file @
978f83ff
...
...
@@ -4,15 +4,21 @@ namespace App\Http\Controllers;
use
Captcha
;
use
Carbon
;
use
DB
;
use
Illuminate\Hashing\BcryptHasher
as
Hasher
;
use
Illuminate\Http\Request
;
use
Illuminate\Support\Facades\Redis
;
use
Input
;
class
HumanVerification
extends
Controller
{
const
PREFIX
=
"humanverification"
;
const
EXPIRELONG
=
60
*
60
*
24
*
14
;
const
EXPIRESHORT
=
60
*
60
*
72
;
public
static
function
captcha
(
Request
$request
,
Hasher
$hasher
,
$id
,
$url
=
null
)
{
$redis
=
Redis
::
connection
(
'redisCache'
);
if
(
$url
!=
null
)
{
$url
=
base64_decode
(
str_replace
(
"<<SLASH>>"
,
"/"
,
$url
));
}
else
{
...
...
@@ -20,15 +26,26 @@ class HumanVerification extends Controller
}
if
(
$request
->
getMethod
()
==
'POST'
)
{
$user
=
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$id
)
->
first
();
$lockedKey
=
$user
->
lockedKey
;
$user
=
$redis
->
hgetall
(
HumanVerification
::
PREFIX
.
"."
.
$id
);
$user
=
[
'uid'
=>
$user
[
"uid"
],
'id'
=>
$user
[
"id"
],
'unusedResultPages'
=>
intval
(
$user
[
"unusedResultPages"
]),
'whitelist'
=>
filter_var
(
$user
[
"whitelist"
],
FILTER_VALIDATE_BOOLEAN
),
'locked'
=>
filter_var
(
$user
[
"locked"
],
FILTER_VALIDATE_BOOLEAN
),
"lockedKey"
=>
$user
[
"lockedKey"
],
];
$lockedKey
=
$user
[
"lockedKey"
];
$key
=
$request
->
input
(
'captcha'
);
$key
=
strtolower
(
$key
);
if
(
!
$hasher
->
check
(
$key
,
$lockedKey
))
{
$captcha
=
Captcha
::
create
(
"default"
,
true
);
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$id
)
->
update
([
'lockedKey'
=>
$captcha
[
"key"
]]);
$pipeline
=
$redis
->
pipeline
();
$pipeline
->
hset
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
'lockedKey'
,
$captcha
[
"key"
]);
$pipeline
->
expire
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
$user
[
"whitelist"
]
?
HumanVerification
::
EXPIRELONG
:
HumanVerification
::
EXPIRESHORT
);
$pipeline
->
execute
();
return
view
(
'humanverification.captcha'
)
->
with
(
'title'
,
'Bestätigung notwendig'
)
->
with
(
'id'
,
$id
)
->
with
(
'url'
,
$url
)
...
...
@@ -36,9 +53,15 @@ class HumanVerification extends Controller
->
with
(
'errorMessage'
,
'Fehler: Falsches Captcha eingegeben!'
);
}
else
{
# If we can unlock the Account of this user we will redirect him to the result page
if
(
$user
!==
null
&&
$user
->
locked
===
1
)
{
if
(
$user
!==
null
&&
$user
[
"
locked
"
]
)
{
# The Captcha was correct. We can remove the key from the user
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$id
)
->
update
([
'locked'
=>
false
,
'lockedKey'
=>
""
,
'whitelist'
=>
1
]);
# If the sum of all users with that ip is too high we need to whitelist the user or they will receive a captcha again on the next request
$sum
=
0
;
$users
=
[];
$pipeline
=
$redis
->
pipeline
();
$pipeline
->
hmset
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
[
'locked'
=>
"0"
,
'lockedKey'
=>
""
,
'whitelist'
=>
'1'
]);
$pipeline
->
expire
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
$user
[
"whitelist"
]
?
HumanVerification
::
EXPIRELONG
:
HumanVerification
::
EXPIRESHORT
);
$pipeline
->
execute
();
return
redirect
(
$url
);
}
else
{
return
redirect
(
'/'
);
...
...
@@ -46,7 +69,10 @@ class HumanVerification extends Controller
}
}
$captcha
=
Captcha
::
create
(
"default"
,
true
);
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$id
)
->
update
([
'lockedKey'
=>
$captcha
[
"key"
]]);
$pipeline
=
$redis
->
pipeline
();
$pipeline
->
hset
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
'lockedKey'
,
$captcha
[
"key"
]);
$pipeline
->
expire
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
$user
[
"whitelist"
]
?
HumanVerification
::
EXPIRELONG
:
HumanVerification
::
EXPIRESHORT
);
$pipeline
->
execute
();
return
view
(
'humanverification.captcha'
)
->
with
(
'title'
,
'Bestätigung notwendig'
)
->
with
(
'id'
,
$id
)
->
with
(
'url'
,
$url
)
...
...
@@ -81,30 +107,63 @@ class HumanVerification extends Controller
private
static
function
removeUser
(
$request
,
$uid
)
{
$redis
=
Redis
::
connection
(
'redisCache'
);
$id
=
hash
(
"sha512"
,
$request
->
ip
());
$sum
=
DB
::
table
(
'humanverification'
)
->
where
(
'id'
,
$id
)
->
where
(
'whitelist'
,
false
)
->
sum
(
'unusedResultPages'
);
$user
=
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$uid
)
->
first
();
$userList
=
$redis
->
smembers
(
HumanVerification
::
PREFIX
.
"."
.
$id
);
$pipe
=
$redis
->
pipeline
();
foreach
(
$userList
as
$userid
)
{
$pipe
->
hgetall
(
HumanVerification
::
PREFIX
.
"."
.
$userid
);
}
$usersData
=
$pipe
->
execute
();
$user
=
[];
$users
=
[];
$sum
=
0
;
foreach
(
$usersData
as
$userTmp
)
{
if
(
empty
(
$userTmp
))
{
continue
;
}
$userNew
=
[
'uid'
=>
$userTmp
[
"uid"
],
'id'
=>
$userTmp
[
"id"
],
'unusedResultPages'
=>
intval
(
$userTmp
[
"unusedResultPages"
]),
'whitelist'
=>
filter_var
(
$userTmp
[
"whitelist"
],
FILTER_VALIDATE_BOOLEAN
),
'locked'
=>
filter_var
(
$userTmp
[
"locked"
],
FILTER_VALIDATE_BOOLEAN
),
"lockedKey"
=>
$userTmp
[
"lockedKey"
],
];
if
(
$uid
===
$userTmp
[
"uid"
])
{
$user
=
$userNew
;
}
else
{
$users
[]
=
$userNew
;
}
if
(
$userNew
[
"whitelist"
])
{
$sum
+=
intval
(
$userTmp
[
"unusedResultPages"
]);
}
}
if
(
$user
===
null
)
{
if
(
empty
(
$user
)
)
{
return
;
}
$pipeline
=
$redis
->
pipeline
();
# Check if we have to whitelist the user or if we can simply delete the data
if
(
$user
->
unusedResultPages
<
$sum
&&
$user
->
whitelist
===
0
)
{
if
(
$user
[
"
unusedResultPages
"
]
<
$sum
&&
!
$user
[
"
whitelist
"
]
)
{
# Whitelist
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$uid
)
->
update
([
'whitelist'
=>
true
,
'whitelistCounter'
=>
0
]);
$user
->
whitelist
=
1
;
$user
->
whitelistCounter
=
0
;
$pipeline
->
hset
(
HumanVerification
::
PREFIX
.
"."
.
$uid
,
'whitelist'
,
"1"
);
$user
[
"whitelist"
]
=
true
;
}
if
(
$user
->
whitelist
===
1
)
{
DB
::
table
(
'h
uman
v
erification
'
)
->
where
(
'uid'
,
$uid
)
->
update
([
'unusedResultPages'
=>
0
]
);
if
(
$user
[
"
whitelist
"
]
)
{
$pipeline
->
hset
(
H
uman
V
erification
::
PREFIX
.
"."
.
$uid
,
'unusedResultPages'
,
"0"
);
}
else
{
DB
::
table
(
'h
uman
v
erification
'
)
->
where
(
'uid'
,
$uid
)
->
where
(
'updated_at'
,
'<'
,
Carbon
::
NOW
()
->
subSeconds
(
2
))
->
delete
(
);
$pipeline
->
del
(
H
uman
V
erification
::
PREFIX
.
"."
.
$uid
);
$pipeline
->
srem
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
$uid
);
}
$pipeline
->
expire
(
HumanVerification
::
PREFIX
.
"."
.
$uid
,
$user
[
"whitelist"
]
?
HumanVerification
::
EXPIRELONG
:
HumanVerification
::
EXPIRESHORT
);
$pipeline
->
expire
(
HumanVerification
::
PREFIX
.
"."
.
$id
,
HumanVerification
::
EXPIRELONG
);
$pipeline
->
execute
();
}
private
static
function
checkId
(
$request
,
$id
)
...
...
app/Http/Controllers/MetaGerSearch.php
View file @
978f83ff
...
...
@@ -12,7 +12,6 @@ class MetaGerSearch extends Controller
{
public
function
search
(
Request
$request
,
MetaGer
$metager
)
{
$focus
=
$request
->
input
(
"focus"
,
"web"
);
if
(
$focus
===
"maps"
)
{
...
...
app/Http/Middleware/HumanVerification.php
View file @
978f83ff
...
...
@@ -3,10 +3,10 @@
namespace
App\Http\Middleware
;
use
Captcha
;
use
Carbon
;
use
Closure
;
use
DB
;
use
Cookie
;
use
Illuminate\Http\Response
;
use
Illuminate\Support\Facades\Redis
;
use
URL
;
class
HumanVerification
...
...
@@ -22,8 +22,9 @@ class HumanVerification
{
// The specific user
$user
=
null
;
$newUser
=
true
;
$update
=
true
;
$prefix
=
"humanverification"
;
$redis
=
Redis
::
connection
(
'redisCache'
);
try
{
$id
=
hash
(
"sha512"
,
$request
->
ip
());
$uid
=
hash
(
"sha512"
,
$request
->
ip
()
.
$_SERVER
[
"AGENT"
]);
...
...
@@ -35,47 +36,61 @@ class HumanVerification
* If someone that uses a bot finds this out we
* might have to change it at some point.
*/
if
(
$request
->
filled
(
'password'
)
||
$request
->
filled
(
'key'
)
||
$request
->
filled
(
'appversion'
)
||
!
env
(
'BOT_PROTECTION'
,
false
))
{
if
(
$request
->
filled
(
'password'
)
||
$request
->
filled
(
'key'
)
||
Cookie
::
get
(
'key'
)
!==
null
||
$request
->
filled
(
'appversion'
)
||
!
env
(
'BOT_PROTECTION'
,
false
))
{
$update
=
false
;
return
$next
(
$request
);
}
$users
=
DB
::
select
(
'select * from humanverification where id = ?'
,
[
$id
]);
# Get all Users of this IP
$userList
=
$redis
->
smembers
(
$prefix
.
"."
.
$id
);
$pipe
=
$redis
->
pipeline
();
foreach
(
$userList
as
$userid
)
{
$pipe
->
hgetall
(
$prefix
.
"."
.
$userid
);
}
$usersData
=
$pipe
->
execute
();
$user
=
[];
$users
=
[];
# Lock out everyone in a Bot network
# Find out how many requests this IP has made
$sum
=
0
;
foreach
(
$users
as
$userTmp
)
{
if
(
$uid
==
$userTmp
->
uid
)
{
$user
=
[
'uid'
=>
$userTmp
->
uid
,
'id'
=>
$userTmp
->
id
,
'unusedResultPages'
=>
intval
(
$userTmp
->
unusedResultPages
),
'whitelist'
=>
filter_var
(
$userTmp
->
whitelist
,
FILTER_VALIDATE_BOOLEAN
),
'whitelistCounter'
=>
$userTmp
->
whitelistCounter
,
'locked'
=>
filter_var
(
$userTmp
->
locked
,
FILTER_VALIDATE_BOOLEAN
),
"lockedKey"
=>
$userTmp
->
lockedKey
,
'updated_at'
=>
Carbon
::
now
(),
];
$newUser
=
false
;
foreach
(
$usersData
as
$index
=>
$userTmp
)
{
if
(
empty
(
$userTmp
))
{
// This is a key that has been expired and should be deleted
$redis
->
srem
(
$prefix
.
"."
.
$id
,
$userList
[
$index
]);
continue
;
}
if
(
$userTmp
->
whitelist
===
0
)
{
$sum
+=
$userTmp
->
unusedResultPages
;
$userNew
=
[
'uid'
=>
$userTmp
[
"uid"
],
'id'
=>
$userTmp
[
"id"
],
'unusedResultPages'
=>
intval
(
$userTmp
[
"unusedResultPages"
]),
'whitelist'
=>
filter_var
(
$userTmp
[
"whitelist"
],
FILTER_VALIDATE_BOOLEAN
),
'locked'
=>
filter_var
(
$userTmp
[
"locked"
],
FILTER_VALIDATE_BOOLEAN
),
"lockedKey"
=>
$userTmp
[
"lockedKey"
],
];
if
(
$uid
===
$userTmp
[
"uid"
])
{
$user
=
$userNew
;
}
else
{
$users
[]
=
$userNew
;
}
if
(
!
$userNew
[
"whitelist"
])
{
$sum
+=
intval
(
$userTmp
[
"unusedResultPages"
]);
}
}
# If this user doesn't have an entry we will create one
if
(
$user
===
null
)
{
# If this user doesn't have an entry we will create one
if
(
empty
(
$user
))
{
$user
=
[
'uid'
=>
$uid
,
'id'
=>
$id
,
'unusedResultPages'
=>
0
,
'whitelist'
=>
false
,
'whitelistCounter'
=>
0
,
'locked'
=>
false
,
"lockedKey"
=>
""
,
'updated_at'
=>
Carbon
::
now
(),
];
}
...
...
@@ -96,7 +111,7 @@ class HumanVerification
// Defines if this is the only user using that IP Adress
$alone
=
true
;
foreach
(
$users
as
$userTmp
)
{
if
(
$userTmp
->
uid
!=
$uid
&&
!
$userTmp
->
whitelist
)
{
if
(
$userTmp
[
"
uid
"
]
!=
$uid
&&
!
$userTmp
[
"
whitelist
"
]
)
{
$alone
=
false
;
}
...
...
@@ -133,41 +148,34 @@ class HumanVerification
}
}
}
catch
(
\
Illuminate\Database\Query
Exception
$e
)
{
// Failure in contacting metager3.de
}
catch
(
\
Predis\Connection\Connection
Exception
$e
)
{
$update
=
false
;
}
finally
{
if
(
$update
)
{
// Update the user in the database
if
(
$newUser
)
{
DB
::
table
(
'humanverification'
)
->
insert
(
[
'uid'
=>
$user
[
"uid"
],
'id'
=>
$user
[
"id"
],
'unusedResultPages'
=>
$user
[
'unusedResultPages'
],
'whitelist'
=>
$user
[
"whitelist"
],
'whitelistCounter'
=>
$user
[
"whitelistCounter"
],
'locked'
=>
$user
[
"locked"
],
"lockedKey"
=>
$user
[
"lockedKey"
],
'updated_at'
=>
$user
[
"updated_at"
],
]
);
$pipeline
=
$redis
->
pipeline
();
$pipeline
->
hmset
(
$prefix
.
"."
.
$user
[
'uid'
],
$user
);
$pipeline
->
sadd
(
$prefix
.
"."
.
$user
[
"id"
],
$user
[
"uid"
]);
// Expire in two weeks
$expireLong
=
60
*
60
*
24
*
14
;
// Expire in 72h
$expireShort
=
60
*
60
*
72
;
if
(
$user
[
"whitelist"
])
{
$pipeline
->
expire
(
$prefix
.
"."
.
$user
[
'uid'
],
$expireLong
);
}
else
{
DB
::
table
(
'humanverification'
)
->
where
(
'uid'
,
$uid
)
->
update
(
[
'uid'
=>
$user
[
"uid"
],
'id'
=>
$user
[
"id"
],
'unusedResultPages'
=>
$user
[
'unusedResultPages'
],
'whitelist'
=>
$user
[
"whitelist"
],
'whitelistCounter'
=>
$user
[
"whitelistCounter"
],
'locked'
=>
$user
[
"locked"
],
"lockedKey"
=>
$user
[
"lockedKey"
],
'updated_at'
=>
$user
[
"updated_at"
],
]
);
$pipeline
->
expire
(
$prefix
.
"."
.
$user
[
'uid'
],
$expireShort
);
}
$pipeline
->
expire
(
$prefix
.
"."
.
$user
[
"id"
],
$expireLong
);
$pipeline
->
execute
();
}
DB
::
disconnect
(
'mysql'
);
}
$request
->
request
->
add
([
'verification_id'
=>
$user
[
"uid"
],
'verification_count'
=>
$user
[
"unusedResultPages"
]]);
return
$next
(
$request
);
...
...
resources/lang/de/index.php
View file @
978f83ff
...
...
@@ -32,7 +32,7 @@ return [
'slogan.2'
=>
'Mit MetaGer bewahren Sie einen neutralen Blick auf’s Web!'
,
'preredesign'
=>
'<a href="https://klassik.metager.org">Rückblick auf die vorige MetaGer Version</a>'
,
'sponsors.head'
=>
'
Sponsoren
'
,
'sponsors.head'
=>
'
Partner
'
,
'sponsors.woxikon'
=>
'SEO Agentur'
,
'sponsors.gutscheine'
=>
'STERN.de: Günstige Kredite im Kreditvergleich'
,
'sponsors.seo'
=>
'Weihnachtsfeier'
,
...
...
resources/lang/en/index.php
View file @
978f83ff
...
...
@@ -32,7 +32,7 @@ return [
'slogan.2'
=>
'You keep a neutral view on the web by using MetaGer'
,
'preredesign'
=>
'<a href="https://klassik.metager.org/en">Flashback to the previous MetaGer version</a>'
,
"sponsors.head"
=>
"
Sponso
rs"
,
"sponsors.head"
=>
"
Partne
rs"
,
'sponsors.woxikon'
=>
'Tagesgeld jetzt!'
,
'sponsors.gutscheine'
=>
'Aktuelle Gutscheine auf Gutschein-Magazin.de'
,
'sponsors.seo'
=>
'Suchmaschinenoptimierung'
,
...
...
resources/lang/fr/index.php
View file @
978f83ff
<?php
return
[
"foki.web"
=>
"Web"
,
"foki.bilder"
=>
"Images"
,
"foki.nachrichten"
=>
"News/Politique"
,
"foki.web"
=>
"Web"
,
"foki.bilder"
=>
"Images"
,
"foki.nachrichten"
=>
"News/Politique"
,
"foki.wissenschaft"
=>
"Sciènces"
,
"foki.produkte"
=>
"Produits"
,
"foki.angepasst"
=>
"ajusté(e)"
,
"foki.maps"
=>
"Maps.metager.de"
,
"design"
=>
"Choisir un design personnel"
,
"partnertitle"
=>
"Soutenir MetaGer, sans frais supplémentaires pour vous"
,
"plugin"
=>
"Ajouter un plug-in MetaGer"
,
"plugin-title"
=>
"Ajouter MetaGer à votre browser"
,
"sponsors.head"
=>
"Sponso
rs"
,
"foki.produkte"
=>
"Produits"
,
"foki.angepasst"
=>
"ajusté(e)"
,
"foki.maps"
=>
"Maps.metager.de"
,
"design"
=>
"Choisir un design personnel"
,
"partnertitle"
=>
"Soutenir MetaGer, sans frais supplémentaires pour vous"
,
"plugin"
=>
"Ajouter un plug-in MetaGer"
,
"plugin-title"
=>
"Ajouter MetaGer à votre browser"
,
"sponsors.head"
=>
"Partne
rs"
,
];
resources/less/metager/pages/start-page.less
View file @
978f83ff
...
...
@@ -69,6 +69,7 @@
margin: 0px;
max-width: 100%;
.card-medium;
order: 2;
p {
text-align: justify;
}
...
...
@@ -92,6 +93,7 @@
list-style-type: none;
padding: 0px;
text-align: left;
margin-bottom: 0;
li.sr {
a {
color: inherit;
...
...
resources/views/ad-info.blade.php
View file @
978f83ff
...
...
@@ -6,7 +6,7 @@
<
h1
class
=
"page-title"
>
Werbung
bei
MetaGer
</
h1
>
<
div
class
=
"card-heavy"
>
<
h2
>
Warum
wir
Werbung
zeigen
</
h2
>
<
p
>
MetaGer
ist
nicht
einfach
nur
eine
Webseite
,
sondern
ein
ganzer
Service
der
gepflegt
,
gewartet
und
ständig
verbessert
werden
muss
.
Dabei
entstehen
laufende
Kosten
,
die
sich
derzeit
nicht
alleine
durch
Mitgliedsbeiträge
und
Spenden
decken
lassen
.
Deshalb
zeigen
wir
zusätzlich
zu
unseren
Suchergebnissen
auch
Werbeergebnisse
an
,
die
möglichst
gut
zur
Suche
passen
.
Zusätzlich
finden
sich
auf
unserer
Startseite
Sponsoren
links
von
ausgewählten
Firmen
.
</
p
>
<
p
>
MetaGer
ist
nicht
einfach
nur
eine
Webseite
,
sondern
ein
ganzer
Service
der
gepflegt
,
gewartet
und
ständig
verbessert
werden
muss
.
Dabei
entstehen
laufende
Kosten
,
die
sich
derzeit
nicht
alleine
durch
Mitgliedsbeiträge
und
Spenden
decken
lassen
.
Deshalb
zeigen
wir
zusätzlich
zu
unseren
Suchergebnissen
auch
Werbeergebnisse
an
,
die
möglichst
gut
zur
Suche
passen
.
Zusätzlich
finden
sich
auf
unserer
Startseite
Partner
links
von
ausgewählten
Firmen
.
</
p
>
<
p
>
MetaGer
wird
betrieben
und
stetig
weiterentwickelt
vom
SUMA
-
EV
-
Verein
für
freien
Wissenszugang
.
Der
SUMA
-
EV
ist
ein
als
gemeinnützig
anerkannter
Verein
und
erhält
keine
öffentlichen
Fördergelder
.
Deshalb
sind
wir
auf
ihre
Mithilfe
angewiesen
.
</
p
>
{{
--
<
span
>~
Hier
grafik
mit
benötigten
Mitgliedern
/
Spenden
für
Werbefreiheit
einfügen
~</
span
>
--
}}
{{
--
<
p
>
Helfen
Sie
uns
jetzt
dieses
Ziel
zu
erreichen
:<
p
>
--
}}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment