Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a821e07aad |
No files matched your search
@@ -1,4 +1,3 @@
|
|||||||
APP_NAME=MapQuiz
|
|
||||||
DEV=1
|
DEV=1
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
@@ -12,5 +11,3 @@ STATIC_ROOT=/static
|
|||||||
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
||||||
MAIL_HOST=mail
|
MAIL_HOST=mail
|
||||||
MAIL_PORT=2500
|
MAIL_PORT=2500
|
||||||
GOOGLE_OAUTH_CLIENT_ID=your_google_oauth_client_id
|
|
||||||
GOOGLE_OAUTH_CLIENT_SECRET=your_google_oauth_client_secret
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
ALTER TABLE
|
|
||||||
`users`
|
|
||||||
ADD
|
|
||||||
`google_sub` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NULL DEFAULT NULL,
|
|
||||||
ADD
|
|
||||||
UNIQUE `google_sub` (`google_sub`),
|
|
||||||
MODIFY
|
|
||||||
`password` varchar(60) NULL DEFAULT NULL;
|
|
||||||
+1
-1
@@ -10,7 +10,7 @@ services:
|
|||||||
- 'mariadb'
|
- 'mariadb'
|
||||||
- 'mail'
|
- 'mail'
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.3
|
image: mariadb:10.1
|
||||||
volumes:
|
volumes:
|
||||||
- mysql:/var/lib/mysql
|
- mysql:/var/lib/mysql
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
Hi,
|
|
||||||
<br><br>
|
|
||||||
You recently signed up on {{APP_NAME}} with this Google account ({{EMAIL}}).
|
|
||||||
<br><br>
|
|
||||||
Have fun on {{APP_NAME}}!
|
|
||||||
<br><br>
|
|
||||||
Regards,<br>
|
|
||||||
{{APP_NAME}}<br>
|
|
||||||
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
|
||||||
+4
-5
@@ -1,14 +1,13 @@
|
|||||||
Hi,
|
Hi,
|
||||||
<br><br>
|
<br><br>
|
||||||
You recently signed up on {{APP_NAME}} with this email address ({{EMAIL}}). To activate your account, please click on the following link:<br>
|
You recently signed up on MapGuesser with this email address ({{EMAIL}}). To activate your account, please click on the following link:<br>
|
||||||
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
If you did not sign up on {{APP_NAME}} or changed your mind, no further action is required, your email address will be deleted soon.<br>
|
If you did not sign up on MapGuesser or changed your mind, no further action is required, your email address will be deleted soon.<br>
|
||||||
However if you want to immediately delete it, please click on the following link:<br>
|
However if you want to immediately delete it, please click on the following link:<br>
|
||||||
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
Have fun on {{APP_NAME}}!
|
Have fun on MapGuesser!
|
||||||
<br><br>
|
<br><br>
|
||||||
Regards,<br>
|
Regards,<br>
|
||||||
{{APP_NAME}}<br>
|
MapGuesser
|
||||||
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
|
||||||
+2
-2
@@ -9,7 +9,7 @@ if (($pos = strpos($url, '?')) !== false) {
|
|||||||
}
|
}
|
||||||
$url = rawurldecode($url);
|
$url = rawurldecode($url);
|
||||||
|
|
||||||
$match = Container::$routeCollection->match($method, $url == '' ? [] : explode('/', $url));
|
$match = Container::$routeCollection->match($method, explode('/', $url));
|
||||||
|
|
||||||
if ($match !== null) {
|
if ($match !== null) {
|
||||||
list($route, $params) = $match;
|
list($route, $params) = $match;
|
||||||
@@ -40,7 +40,7 @@ if ($match !== null) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||||
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
header('Location: ' . Container::$request->getBase() . '/' . $response->getUrl(), true, $response->getHttpCode());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ h1>a:hover, h1>a:focus {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, header.small h1 {
|
h2, div.header.small h1 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +83,8 @@ hr {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.small, span.small {
|
.small {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justify {
|
.justify {
|
||||||
@@ -107,10 +107,6 @@ p.small, span.small {
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -119,7 +115,6 @@ svg.inline, img.inline {
|
|||||||
display: inline;
|
display: inline;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
margin-right: 0.3em;
|
|
||||||
vertical-align: -0.15em;
|
vertical-align: -0.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,13 +143,6 @@ button, a.button {
|
|||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.small {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0 12px;
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus {
|
button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus {
|
||||||
background-color: #29457f;
|
background-color: #29457f;
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -258,11 +246,12 @@ input.big:focus, select.big:focus, textarea.big:focus {
|
|||||||
|
|
||||||
div.modal {
|
div.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
top: 100px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
padding: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
z-index: 3;
|
||||||
z-index: 6;
|
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +263,7 @@ div.modal {
|
|||||||
right: 0;
|
right: 0;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
z-index: 5;
|
z-index: 2;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +276,7 @@ p.formError {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
div.header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
@@ -297,44 +286,33 @@ header {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
header.small {
|
div.header.small {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p {
|
div.header>p.header {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p>span {
|
div.header>p.header>span {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p>span>a:link, header>p>span>a:visited {
|
div.header>p.header>span>a:link, div.header>p.header>span>a:visited {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p>span:not(:last-child) {
|
div.header>p.header>span:not(:last-child) {
|
||||||
border-right: solid white 1px;
|
border-right: solid white 1px;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
div.main {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
|
||||||
background-color: #444444;
|
|
||||||
padding: 6px 12px;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer>p {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.buttonContainer {
|
div.buttonContainer {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
@@ -343,12 +321,6 @@ div.buttonContainer>button {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cookiesNotice {
|
|
||||||
background-color: #eeeeee;
|
|
||||||
padding: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 64px;
|
width: 64px;
|
||||||
@@ -357,7 +329,7 @@ div.buttonContainer>button {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -32px;
|
margin-top: -32px;
|
||||||
margin-left: -32px;
|
margin-left: -32px;
|
||||||
z-index: 7;
|
z-index: 5;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,27 +343,16 @@ div.box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
header h1 span {
|
div.header h1 span {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
footer>p:not(:first-child) {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
button, a.button {
|
button, a.button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
button.marginLeft, a.button.marginLeft {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
button.marginRight, a.button.marginRight {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
div.modal {
|
div.modal {
|
||||||
left: 20px;
|
left: 20px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
padding-left: 15px;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
}
|
||||||
div.box {
|
div.box {
|
||||||
width: initial;
|
width: initial;
|
||||||
@@ -399,48 +360,9 @@ div.box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 600px) {
|
@media screen and (min-width: 600px) {
|
||||||
footer>p {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
footer>p:not(:first-child) {
|
|
||||||
padding-left: 6px;
|
|
||||||
}
|
|
||||||
footer>p:not(:last-child) {
|
|
||||||
border-right: solid white 1px;
|
|
||||||
padding-right: 6px;
|
|
||||||
}
|
|
||||||
div.modal {
|
div.modal {
|
||||||
width: 540px;
|
width: 540px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-left: -270px;
|
margin-left: -270px;
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-height: 399px) {
|
|
||||||
div.modal {
|
|
||||||
top: 20px;
|
|
||||||
max-height: calc(100% - 40px);
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-height: 400px) and (max-height: 499px) {
|
|
||||||
div.modal {
|
|
||||||
top: 50px;
|
|
||||||
max-height: calc(100% - 100px);
|
|
||||||
padding-top: 15px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-height: 500px) {
|
|
||||||
div.modal {
|
|
||||||
top: 75px;
|
|
||||||
max-height: calc(100% - 150px);
|
|
||||||
padding-top: 15px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,16 +29,17 @@ div.mapItem>div.title>p.title {
|
|||||||
div.mapItem>div.imgContainer {
|
div.mapItem>div.imgContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: 50%;
|
padding-top: 50%;
|
||||||
background-color: #cccccc;
|
background: #cccccc;
|
||||||
background-position: center;
|
}
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
div.mapItem>div.imgContainer>img {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: -50%
|
||||||
}
|
}
|
||||||
|
|
||||||
div.mapItem>div.inner {
|
div.mapItem>div.inner {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
padding: 10px 8px;
|
padding: 10px 8px;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.mapItem>div.inner>div.info {
|
div.mapItem>div.inner>div.info {
|
||||||
|
|||||||
@@ -10,15 +10,10 @@
|
|||||||
|
|
||||||
MapGuesser.httpRequest('POST', form.action, function () {
|
MapGuesser.httpRequest('POST', form.action, function () {
|
||||||
if (this.response.error) {
|
if (this.response.error) {
|
||||||
if (this.response.error === 'user_not_found') {
|
|
||||||
window.location.replace('/signup');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorText;
|
var errorText;
|
||||||
switch (this.response.error) {
|
switch (this.response.error) {
|
||||||
case 'password_too_short':
|
case 'user_not_found':
|
||||||
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
errorText = 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up here</a>!';
|
||||||
break;
|
break;
|
||||||
case 'user_not_active':
|
case 'user_not_active':
|
||||||
errorText = 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!';
|
errorText = 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!';
|
||||||
@@ -34,6 +29,8 @@
|
|||||||
loginFormError.style.display = 'block';
|
loginFormError.style.display = 'block';
|
||||||
loginFormError.innerHTML = errorText;
|
loginFormError.innerHTML = errorText;
|
||||||
|
|
||||||
|
form.elements.email.select();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -334,11 +334,11 @@
|
|||||||
var Util = {
|
var Util = {
|
||||||
getHighResData: function () {
|
getHighResData: function () {
|
||||||
if (window.devicePixelRatio >= 4) {
|
if (window.devicePixelRatio >= 4) {
|
||||||
return { ppi: 320, tileSize: 128, zoomOffset: 1, minZoom: 0, maxZoom: 18 };
|
return { ppi: 320, tileSize: 128, zoomOffset: 1 };
|
||||||
} else if (window.devicePixelRatio >= 2) {
|
} else if (window.devicePixelRatio >= 2) {
|
||||||
return { ppi: 250, tileSize: 256, zoomOffset: 0, minZoom: 1, maxZoom: 19 };
|
return { ppi: 250, tileSize: 256, zoomOffset: 0 };
|
||||||
} else {
|
} else {
|
||||||
return { ppi: 72, tileSize: 512, zoomOffset: -1, minZoom: 2, maxZoom: 20 };
|
return { ppi: 72, tileSize: 512, zoomOffset: -1 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -368,8 +368,8 @@
|
|||||||
ppi: highResData.ppi,
|
ppi: highResData.ppi,
|
||||||
tileSize: highResData.tileSize,
|
tileSize: highResData.tileSize,
|
||||||
zoomOffset: highResData.zoomOffset,
|
zoomOffset: highResData.zoomOffset,
|
||||||
minZoom: highResData.minZoom,
|
minZoom: 2,
|
||||||
maxZoom: highResData.maxZoom
|
maxZoom: 20
|
||||||
}).addTo(MapEditor.map);
|
}).addTo(MapEditor.map);
|
||||||
|
|
||||||
MapEditor.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east }));
|
MapEditor.map.fitBounds(L.latLngBounds({ lat: mapBounds.south, lng: mapBounds.west }, { lat: mapBounds.north, lng: mapBounds.east }));
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ var MapGuesser = {
|
|||||||
httpRequest: function (method, url, callback, data) {
|
httpRequest: function (method, url, callback, data) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.responseType = 'json';
|
||||||
xhr.onload = callback;
|
xhr.onload = callback;
|
||||||
|
|
||||||
xhr.open(method, url, true);
|
xhr.open(method, url, true);
|
||||||
|
|
||||||
xhr.responseType = 'json';
|
|
||||||
|
|
||||||
if (method === 'POST') {
|
if (method === 'POST') {
|
||||||
if (typeof data === 'undefined') {
|
if (typeof data === 'undefined') {
|
||||||
data = new FormData();
|
data = new FormData();
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
case 'password_not_match':
|
case 'password_not_match':
|
||||||
errorText = 'The given current password is wrong.'
|
errorText = 'The given current password is wrong.'
|
||||||
break;
|
break;
|
||||||
case 'password_too_short':
|
case 'passwords_too_short':
|
||||||
errorText = 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
errorText = 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
break;
|
break;
|
||||||
case 'passwords_not_match':
|
case 'passwords_not_match':
|
||||||
@@ -37,12 +37,15 @@
|
|||||||
profileFormError.style.display = 'block';
|
profileFormError.style.display = 'block';
|
||||||
profileFormError.innerHTML = errorText;
|
profileFormError.innerHTML = errorText;
|
||||||
|
|
||||||
|
form.elements.password_new.select();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('profileFormError').style.display = 'none';
|
document.getElementById('profileFormError').style.display = 'none';
|
||||||
form.reset();
|
form.reset();
|
||||||
form.elements.save.disabled = true;
|
form.elements.save.disabled = true;
|
||||||
|
form.elements.password_new.focus();
|
||||||
}, formData);
|
}, formData);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
+14
-27
@@ -9,52 +9,39 @@
|
|||||||
var formData = new FormData(form);
|
var formData = new FormData(form);
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', form.action, function () {
|
MapGuesser.httpRequest('POST', form.action, function () {
|
||||||
if (this.response.error) {
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
if (this.response.error === 'user_found') {
|
|
||||||
window.location.replace('/');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
var errorText;
|
var errorText;
|
||||||
switch (this.response.error) {
|
switch (this.response.error) {
|
||||||
case 'email_not_valid':
|
case 'passwords_too_short':
|
||||||
errorText = 'The given email address is not valid.'
|
|
||||||
break;
|
|
||||||
case 'password_too_short':
|
|
||||||
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
break;
|
break;
|
||||||
case 'passwords_not_match':
|
case 'passwords_not_match':
|
||||||
errorText = 'The given passwords do not match.'
|
errorText = 'The given passwords do not match.'
|
||||||
break;
|
break;
|
||||||
case 'user_found_user_not_active':
|
case 'user_found':
|
||||||
|
errorText = 'There is a user already registered with the given email address. Please <a href="/login" title="Login">login here</a>!';
|
||||||
|
break;
|
||||||
|
case 'not_active_user_found':
|
||||||
errorText = 'There is a user already registered with the given email address. Please check your email and click on the activation link!';
|
errorText = 'There is a user already registered with the given email address. Please check your email and click on the activation link!';
|
||||||
break;
|
break;
|
||||||
case 'user_found_password_not_match':
|
|
||||||
errorText = 'There is a user already registered with the given email address, but the given password is wrong.'
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
var signupFormError = document.getElementById('signupFormError');
|
var signupFormError = document.getElementById('signupFormError');
|
||||||
signupFormError.style.display = 'block';
|
signupFormError.style.display = 'block';
|
||||||
signupFormError.innerHTML = errorText;
|
signupFormError.innerHTML = errorText;
|
||||||
|
|
||||||
|
form.elements.email.select();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.replace('/signup/success');
|
document.getElementById('signupFormError').style.display = 'none';
|
||||||
|
form.reset();
|
||||||
|
form.elements.email.focus();
|
||||||
|
|
||||||
|
MapGuesser.showModalWithContent('Sign up successful', 'Sign up was successful. Please check your email and click on the activation link to activate your account!');
|
||||||
}, formData);
|
}, formData);
|
||||||
};
|
};
|
||||||
|
|
||||||
var resetSignupButton = document.getElementById('resetSignupButton');
|
|
||||||
if (resetSignupButton) {
|
|
||||||
resetSignupButton.onclick = function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', '/signup/reset', function () {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ "$#" -lt 1 ]; then
|
|
||||||
echo "Usage: $0 <sql_file>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SQL_FILE=$1
|
|
||||||
|
|
||||||
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
|
||||||
|
|
||||||
. ${ROOT_DIR}/.env
|
|
||||||
|
|
||||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
|
||||||
echo "This script can only be used in DEV mode!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Running SQL on DB..."
|
|
||||||
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${SQL_FILE}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\Model\User;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
@@ -20,8 +20,10 @@ class AddUserCommand extends Command
|
|||||||
|
|
||||||
public function execute(InputInterface $input, OutputInterface $output): int
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
{
|
{
|
||||||
$user = new User();
|
$user = new User([
|
||||||
$user->setEmail($input->getArgument('email'));
|
'email' => $input->getArgument('email'),
|
||||||
|
]);
|
||||||
|
|
||||||
$user->setPlainPassword($input->getArgument('password'));
|
$user->setPlainPassword($input->getArgument('password'));
|
||||||
|
|
||||||
if ($input->hasArgument('type')) {
|
if ($input->hasArgument('type')) {
|
||||||
@@ -29,8 +31,9 @@ class AddUserCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$pdm = new PersistentDataManager();
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
$pdm->saveToDb($user);
|
$modify->fill($user->toArray());
|
||||||
|
$modify->save();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$output->writeln('<error>Adding user failed!</error>');
|
$output->writeln('<error>Adding user failed!</error>');
|
||||||
$output->writeln('');
|
$output->writeln('');
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
@@ -36,16 +37,18 @@ class GameController
|
|||||||
{
|
{
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
|
|
||||||
|
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
||||||
|
|
||||||
$session = $this->request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
$session->set('state', [
|
$session->set('state', [
|
||||||
'mapId' => $mapId,
|
'mapId' => $mapId,
|
||||||
'area' => $map->getArea(),
|
'area' => $map['area'],
|
||||||
'rounds' => []
|
'rounds' => []
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['mapId' => $mapId, 'mapName' => $map->getName(), 'bounds' => $map->getBounds()->toArray()];
|
return ['mapId' => $mapId, 'mapName' => $map['name'], 'bounds' => $bounds->toArray()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ use MapGuesser\Interfaces\Request\IRequest;
|
|||||||
use MapGuesser\Util\Geo\Position;
|
use MapGuesser\Util\Geo\Position;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
|
||||||
class GameFlowController
|
class GameFlowController
|
||||||
@@ -34,14 +33,11 @@ class GameFlowController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count($state['rounds']) === 0) {
|
if (count($state['rounds']) === 0) {
|
||||||
$placesWithoutPano = [];
|
$place = $this->placeRepository->getForMapWithValidPano($mapId);
|
||||||
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, [], $placesWithoutPano);
|
$state['rounds'][] = $place;
|
||||||
|
|
||||||
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
|
||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
$data = ['panoId' => $place->getPanoIdCached()];
|
$data = ['panoId' => $place['panoId']];
|
||||||
} else {
|
} else {
|
||||||
$rounds = count($state['rounds']);
|
$rounds = count($state['rounds']);
|
||||||
$last = $state['rounds'][$rounds - 1];
|
$last = $state['rounds'][$rounds - 1];
|
||||||
@@ -97,20 +93,18 @@ class GameFlowController
|
|||||||
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$placesWithoutPano = [];
|
$place = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
|
||||||
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, $exclude, $placesWithoutPano);
|
$state['rounds'][] = $place;
|
||||||
|
$session->set('state', $state);
|
||||||
|
|
||||||
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
$panoId = $place['panoId'];
|
||||||
|
|
||||||
$panoId = $place->getPanoIdCached();
|
|
||||||
} else {
|
} else {
|
||||||
$state['rounds'] = [];
|
$state['rounds'] = [];
|
||||||
|
$session->set('state', $state);
|
||||||
|
|
||||||
$panoId = null;
|
$panoId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$session->set('state', $state);
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'result' => [
|
'result' => [
|
||||||
'position' => $position->toArray(),
|
'position' => $position->toArray(),
|
||||||
@@ -122,16 +116,6 @@ class GameFlowController
|
|||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addNewRoundToState(&$state, Place $place, array $placesWithoutPano): void
|
|
||||||
{
|
|
||||||
$state['rounds'][] = [
|
|
||||||
'placesWithoutPano' => $placesWithoutPano,
|
|
||||||
'placeId' => $place->getId(),
|
|
||||||
'position' => $place->getPosition(),
|
|
||||||
'panoId' => $place->getPanoIdCached()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function calculateDistance(Position $realPosition, Position $guessPosition): float
|
private function calculateDistance(Position $realPosition, Position $guessPosition): float
|
||||||
{
|
{
|
||||||
return $realPosition->calculateDistanceTo($guessPosition);
|
return $realPosition->calculateDistanceTo($guessPosition);
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ class HomeController
|
|||||||
{
|
{
|
||||||
public function getIndex(): IRedirect
|
public function getIndex(): IRedirect
|
||||||
{
|
{
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('maps')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect([\Container::$routeCollection->getRoute('maps'), []], IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,125 +1,58 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
use MapGuesser\Mailing\Mail;
|
use MapGuesser\Model\User;
|
||||||
use MapGuesser\OAuth\GoogleOAuth;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
|
||||||
use MapGuesser\Repository\UserRepository;
|
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Response\Redirect;
|
use MapGuesser\Response\Redirect;
|
||||||
use MapGuesser\Util\JwtParser;
|
|
||||||
|
|
||||||
class LoginController
|
class LoginController
|
||||||
{
|
{
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
private PersistentDataManager $pdm;
|
|
||||||
|
|
||||||
private UserRepository $userRepository;
|
|
||||||
|
|
||||||
private UserConfirmationRepository $userConfirmationRepository;
|
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
|
||||||
$this->userRepository = new UserRepository();
|
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLoginForm()
|
public function getLoginForm()
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
$session = $this->request->session();
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [];
|
$data = [];
|
||||||
return new HtmlContent('login/login', $data);
|
return new HtmlContent('login', $data);
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleLoginRedirect(): IRedirect
|
|
||||||
{
|
|
||||||
$state = bin2hex(random_bytes(16));
|
|
||||||
|
|
||||||
$this->request->session()->set('oauth_state', $state);
|
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth();
|
|
||||||
$url = $oAuth->getDialogUrl($state, $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
|
|
||||||
|
|
||||||
return new Redirect($url, IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSignupForm()
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->request->session()->has('tmp_user_data')) {
|
|
||||||
$tmpUserData = $this->request->session()->get('tmp_user_data');
|
|
||||||
|
|
||||||
$data = ['email' => $tmpUserData['email']];
|
|
||||||
} else {
|
|
||||||
$data = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HtmlContent('login/signup', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSignupSuccess(): IContent
|
|
||||||
{
|
|
||||||
$data = [];
|
|
||||||
return new HtmlContent('login/signup_success', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSignupWithGoogleForm()
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->request->session()->has('google_user_data')) {
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('login-google')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
$userData = $this->request->session()->get('google_user_data');
|
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($userData['email']);
|
|
||||||
|
|
||||||
$data = ['found' => $user !== null, 'email' => $userData['email']];
|
|
||||||
return new HtmlContent('login/google_signup', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function login(): IContent
|
public function login(): IContent
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
$data = ['success' => true];
|
$data = ['success' => true];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(User::getFields());
|
||||||
|
$select->where('email', '=', $this->request->post('email'));
|
||||||
|
|
||||||
if ($user === null) {
|
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
if (strlen($this->request->post('password')) < 6) {
|
|
||||||
$data = ['error' => 'password_too_short'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$tmpUser = new User();
|
|
||||||
$tmpUser->setPlainPassword($this->request->post('password'));
|
|
||||||
|
|
||||||
$this->request->session()->set('tmp_user_data', ['email' => $this->request->post('email'), 'password_hashed' => $tmpUser->getPassword()]);
|
|
||||||
|
|
||||||
|
if ($userData === null) {
|
||||||
$data = ['error' => 'user_not_found'];
|
$data = ['error' => 'user_not_found'];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user = new User($userData);
|
||||||
|
|
||||||
if (!$user->getActive()) {
|
if (!$user->getActive()) {
|
||||||
$data = ['error' => 'user_not_active'];
|
$data = ['error' => 'user_not_active'];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
@@ -130,266 +63,16 @@ class LoginController
|
|||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->request->setUser($user);
|
$session->set('user', $user);
|
||||||
|
|
||||||
$data = ['success' => true];
|
$data = ['success' => true];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginWithGoogle()
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
|
||||||
$data = [];
|
|
||||||
return new HtmlContent('login/google_login', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth();
|
|
||||||
$tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
|
|
||||||
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
|
||||||
$data = [];
|
|
||||||
return new HtmlContent('login/google_login', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
|
||||||
$userData = $jwtParser->getPayload();
|
|
||||||
|
|
||||||
if (!$userData['email_verified']) {
|
|
||||||
$data = [];
|
|
||||||
return new HtmlContent('login/google_login', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->userRepository->getByGoogleSub($userData['sub']);
|
|
||||||
|
|
||||||
if ($user === null) {
|
|
||||||
$this->request->session()->set('google_user_data', $userData);
|
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->request->setUser($user);
|
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logout(): IRedirect
|
public function logout(): IRedirect
|
||||||
{
|
{
|
||||||
$this->request->setUser(null);
|
$this->request->session()->delete('user');
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
}
|
|
||||||
|
|
||||||
public function signup(): IContent
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
$data = ['error' => 'logged_in'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
|
||||||
|
|
||||||
if ($user !== null) {
|
|
||||||
if ($user->getActive()) {
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
|
||||||
$data = ['error' => 'user_found_password_not_match'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->request->setUser($user);
|
|
||||||
|
|
||||||
$data = ['error' => 'user_found'];
|
|
||||||
} else {
|
|
||||||
$data = ['error' => 'user_found_user_not_active'];
|
|
||||||
}
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
|
||||||
$data = ['error' => 'email_not_valid'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->request->session()->has('tmp_user_data')) {
|
|
||||||
$tmpUserData = $this->request->session()->get('tmp_user_data');
|
|
||||||
|
|
||||||
$tmpUser = new User();
|
|
||||||
$tmpUser->setPassword($tmpUserData['password_hashed']);
|
|
||||||
|
|
||||||
if (!$tmpUser->checkPassword($this->request->post('password'))) {
|
|
||||||
$data = ['error' => 'passwords_not_match'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (strlen($this->request->post('password')) < 6) {
|
|
||||||
$data = ['error' => 'password_too_short'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
|
||||||
$data = ['error' => 'passwords_not_match'];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = new User();
|
|
||||||
$user->setEmail($this->request->post('email'));
|
|
||||||
$user->setPlainPassword($this->request->post('password'));
|
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
|
||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
|
||||||
|
|
||||||
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
|
|
||||||
|
|
||||||
$confirmation = new UserConfirmation();
|
|
||||||
$confirmation->setUser($user);
|
|
||||||
$confirmation->setToken($token);
|
|
||||||
|
|
||||||
$this->pdm->saveToDb($confirmation);
|
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
|
||||||
|
|
||||||
$this->sendConfirmationEmail($user->getEmail(), $token);
|
|
||||||
|
|
||||||
$this->request->session()->delete('tmp_user_data');
|
|
||||||
|
|
||||||
$data = ['success' => true];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function signupWithGoogle(): IContent
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
$data = ['success' => true];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
$userData = $this->request->session()->get('google_user_data');
|
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($userData['email']);
|
|
||||||
|
|
||||||
if ($user === null) {
|
|
||||||
$sendWelcomeEmail = true;
|
|
||||||
|
|
||||||
$user = new User();
|
|
||||||
$user->setEmail($userData['email']);
|
|
||||||
} else {
|
|
||||||
$sendWelcomeEmail = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->setActive(true);
|
|
||||||
$user->setGoogleSub($userData['sub']);
|
|
||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
|
||||||
|
|
||||||
if ($sendWelcomeEmail) {
|
|
||||||
$this->sendWelcomeEmail($user->getEmail());
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->request->session()->delete('google_user_data');
|
|
||||||
$this->request->setUser($user);
|
|
||||||
|
|
||||||
$data = ['success' => true];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetSignup(): IContent
|
|
||||||
{
|
|
||||||
$this->request->session()->delete('tmp_user_data');
|
|
||||||
|
|
||||||
$data = ['success' => true];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetGoogleSignup(): IContent
|
|
||||||
{
|
|
||||||
$this->request->session()->delete('google_user_data');
|
|
||||||
|
|
||||||
$data = ['success' => true];
|
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function activate()
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
|
|
||||||
|
|
||||||
if ($confirmation === null) {
|
|
||||||
$data = [];
|
|
||||||
return new HtmlContent('login/activate', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($confirmation);
|
|
||||||
|
|
||||||
$user = $this->userRepository->getById($confirmation->getUserId());
|
|
||||||
$user->setActive(true);
|
|
||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
|
||||||
|
|
||||||
$this->request->setUser($user);
|
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cancel()
|
|
||||||
{
|
|
||||||
if ($this->request->user() !== null) {
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
|
|
||||||
|
|
||||||
if ($confirmation === null) {
|
|
||||||
$data = ['success' => false];
|
|
||||||
return new HtmlContent('login/cancel', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($confirmation);
|
|
||||||
|
|
||||||
$user = $this->userRepository->getById($confirmation->getUserId());
|
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($user);
|
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
|
||||||
|
|
||||||
$data = ['success' => true];
|
|
||||||
return new HtmlContent('login/cancel', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sendConfirmationEmail(string $email, string $token): void
|
|
||||||
{
|
|
||||||
$mail = new Mail();
|
|
||||||
$mail->addRecipient($email);
|
|
||||||
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
|
|
||||||
$mail->setBodyFromTemplate('signup', [
|
|
||||||
'EMAIL' => $email,
|
|
||||||
'ACTIVATE_LINK' => $this->request->getBase() . '/'. \Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
|
|
||||||
'CANCEL_LINK' => $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
|
|
||||||
]);
|
|
||||||
$mail->send();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function sendWelcomeEmail(string $email): void
|
|
||||||
{
|
|
||||||
$mail = new Mail();
|
|
||||||
$mail->addRecipient($email);
|
|
||||||
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME']);
|
|
||||||
$mail->setBodyFromTemplate('signup-noconfirm', [
|
|
||||||
'EMAIL' => $email,
|
|
||||||
]);
|
|
||||||
$mail->send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
@@ -23,8 +21,6 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
private PersistentDataManager $pdm;
|
|
||||||
|
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
@@ -32,7 +28,6 @@ class MapAdminController implements ISecured
|
|||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
|
||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
}
|
}
|
||||||
@@ -50,14 +45,18 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
if ($mapId) {
|
if ($mapId) {
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
$places = $this->getPlaces($map);
|
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
||||||
|
$places = $this->getPlaces($mapId);
|
||||||
} else {
|
} else {
|
||||||
$map = new Map();
|
$map = [
|
||||||
$map->setName(self::$unnamedMapName);
|
'name' => self::$unnamedMapName,
|
||||||
|
'description' => ''
|
||||||
|
];
|
||||||
|
$bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0);
|
||||||
$places = [];
|
$places = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = ['mapId' => $mapId, 'mapName' => $map->getName(), 'mapDescription' => str_replace('<br>', "\n", $map->getDescription()), 'bounds' => $map->getBounds()->toArray(), 'places' => &$places];
|
$data = ['mapId' => $mapId, 'mapName' => $map['name'], 'mapDescription' => str_replace('<br>', "\n", $map['description']), 'bounds' => $bounds->toArray(), 'places' => &$places];
|
||||||
return new HtmlContent('admin/map_editor', $data);
|
return new HtmlContent('admin/map_editor', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +64,9 @@ class MapAdminController implements ISecured
|
|||||||
{
|
{
|
||||||
$placeId = (int) $this->request->query('placeId');
|
$placeId = (int) $this->request->query('placeId');
|
||||||
|
|
||||||
$place = $this->placeRepository->getById($placeId);
|
$placeData = $this->placeRepository->getById($placeId);
|
||||||
|
|
||||||
$data = ['panoId' => $place->getFreshPanoId()];
|
$data = ['panoId' => $placeData['panoId']];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,12 +76,8 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
if ($mapId) {
|
if (!$mapId) {
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$mapId = $this->addNewMap();
|
||||||
} else {
|
|
||||||
$map = new Map();
|
|
||||||
$map->setName(self::$unnamedMapName);
|
|
||||||
$this->pdm->saveToDb($map);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_POST['added'])) {
|
if (isset($_POST['added'])) {
|
||||||
@@ -90,18 +85,11 @@ class MapAdminController implements ISecured
|
|||||||
foreach ($_POST['added'] as $placeRaw) {
|
foreach ($_POST['added'] as $placeRaw) {
|
||||||
$placeRaw = json_decode($placeRaw, true);
|
$placeRaw = json_decode($placeRaw, true);
|
||||||
|
|
||||||
$place = new Place();
|
$addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $this->placeRepository->addToMap($mapId, [
|
||||||
$place->setMap($map);
|
'lat' => (float) $placeRaw['lat'],
|
||||||
$place->setLat((float) $placeRaw['lat']);
|
'lng' => (float) $placeRaw['lng'],
|
||||||
$place->setLng((float) $placeRaw['lng']);
|
'pano_id_cached_timestamp' => $placeRaw['panoId'] === -1 ? (new DateTime('-1 day'))->format('Y-m-d H:i:s') : null
|
||||||
|
])];
|
||||||
if ($placeRaw['panoId'] === -1) {
|
|
||||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->pdm->saveToDb($place);
|
|
||||||
|
|
||||||
$addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $place->getId()];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$addedIds = [];
|
$addedIds = [];
|
||||||
@@ -111,12 +99,10 @@ class MapAdminController implements ISecured
|
|||||||
foreach ($_POST['edited'] as $placeRaw) {
|
foreach ($_POST['edited'] as $placeRaw) {
|
||||||
$placeRaw = json_decode($placeRaw, true);
|
$placeRaw = json_decode($placeRaw, true);
|
||||||
|
|
||||||
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
$this->placeRepository->modify((int) $placeRaw['id'], [
|
||||||
$place->setLat((float) $placeRaw['lat']);
|
'lat' => (float) $placeRaw['lat'],
|
||||||
$place->setLng((float) $placeRaw['lng']);
|
'lng' => (float) $placeRaw['lng']
|
||||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
]);
|
||||||
|
|
||||||
$this->pdm->saveToDb($place);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,42 +110,45 @@ class MapAdminController implements ISecured
|
|||||||
foreach ($_POST['deleted'] as $placeRaw) {
|
foreach ($_POST['deleted'] as $placeRaw) {
|
||||||
$placeRaw = json_decode($placeRaw, true);
|
$placeRaw = json_decode($placeRaw, true);
|
||||||
|
|
||||||
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
$this->placeRepository->delete($placeRaw['id']);
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($place);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapBounds = $this->calculateMapBounds($map);
|
$mapBounds = $this->calculateMapBounds($mapId);
|
||||||
|
|
||||||
$map->setBounds($mapBounds);
|
$map = [
|
||||||
$map->setArea($mapBounds->calculateApproximateArea());
|
'bound_south_lat' => $mapBounds->getSouthLat(),
|
||||||
|
'bound_west_lng' => $mapBounds->getWestLng(),
|
||||||
|
'bound_north_lat' => $mapBounds->getNorthLat(),
|
||||||
|
'bound_east_lng' => $mapBounds->getEastLng(),
|
||||||
|
'area' => $mapBounds->calculateApproximateArea(),
|
||||||
|
];
|
||||||
|
|
||||||
if (isset($_POST['name'])) {
|
if (isset($_POST['name'])) {
|
||||||
$map->setName($_POST['name'] ? $_POST['name'] : self::$unnamedMapName);
|
$map['name'] = $_POST['name'] ? $_POST['name'] : self::$unnamedMapName;
|
||||||
}
|
}
|
||||||
if (isset($_POST['description'])) {
|
if (isset($_POST['description'])) {
|
||||||
$map->setDescription(str_replace(["\n", "\r\n"], '<br>', $_POST['description']));
|
$map['description'] = str_replace(["\n", "\r\n"], '<br>', $_POST['description']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->pdm->saveToDb($map);
|
$this->saveMapData($mapId, $map);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['mapId' => $map->getId(), 'added' => $addedIds];
|
$data = ['mapId' => $mapId, 'added' => $addedIds];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteMap() {
|
public function deleteMap() {
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
$map = $this->mapRepository->getById($mapId);
|
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
$this->deletePlaces($map);
|
$this->deletePlaces($mapId);
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($map);
|
$modify = new Modify(\Container::$dbConnection, 'maps');
|
||||||
|
$modify->setId($mapId);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
@@ -167,30 +156,26 @@ class MapAdminController implements ISecured
|
|||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deletePlaces(Map $map): void
|
private function deletePlaces(int $mapId): void
|
||||||
{
|
{
|
||||||
//TODO: relations?
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
$select->columns(Place::getFields());
|
$select->columns(['id']);
|
||||||
$select->where('map_id', '=', $map->getId());
|
$select->where('map_id', '=', $mapId);
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
while ($placeData = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$place = new Place();
|
$modify = new Modify(\Container::$dbConnection, 'places');
|
||||||
|
$modify->setId($place['id']);
|
||||||
$this->pdm->fillWithData($placeData, $place);
|
$modify->delete();
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($place);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateMapBounds(Map $map): Bounds
|
private function calculateMapBounds(int $mapId): Bounds
|
||||||
{
|
{
|
||||||
//TODO: from repository or relations
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
$select->columns(['lat', 'lng']);
|
$select->columns(['lat', 'lng']);
|
||||||
$select->where('map_id', '=', $map->getId());
|
$select->where('map_id', '=', $mapId);
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
@@ -202,12 +187,35 @@ class MapAdminController implements ISecured
|
|||||||
return $bounds;
|
return $bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function &getPlaces(Map $map): array
|
private function addNewMap(): int
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'maps');
|
||||||
|
$modify->fill([
|
||||||
|
'name' => self::$unnamedMapName,
|
||||||
|
'description' => '',
|
||||||
|
'bound_south_lat' => 0.0,
|
||||||
|
'bound_west_lng' => 0.0,
|
||||||
|
'bound_north_lat' => 0.0,
|
||||||
|
'bound_east_lng' => 0.0
|
||||||
|
]);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
return $modify->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveMapData(int $mapId, array $map): void
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'maps');
|
||||||
|
$modify->setId($mapId);
|
||||||
|
$modify->fill($map);
|
||||||
|
$modify->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function &getPlaces(int $mapId): array
|
||||||
{
|
{
|
||||||
//TODO: from repository or relations
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
|
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
|
||||||
$select->where('map_id', '=', $map->getId());
|
$select->where('map_id', '=', $mapId);
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class MapsController
|
|||||||
|
|
||||||
public function getMaps(): IContent
|
public function getMaps(): IContent
|
||||||
{
|
{
|
||||||
//TODO: from repository
|
|
||||||
$select = new Select(\Container::$dbConnection, 'maps');
|
$select = new Select(\Container::$dbConnection, 'maps');
|
||||||
$select->columns([
|
$select->columns([
|
||||||
['maps', 'id'],
|
['maps', 'id'],
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\Mailing\Mail;
|
||||||
|
use MapGuesser\Model\User;
|
||||||
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
use MapGuesser\Response\JsonContent;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
|
class SignupController
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignupForm()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
return new HtmlContent('signup/signup', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function signup(): IContent
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
//TODO: return with some error
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(User::getFields());
|
||||||
|
$select->where('email', '=', $this->request->post('email'));
|
||||||
|
|
||||||
|
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($userData !== null) {
|
||||||
|
$user = new User($userData);
|
||||||
|
|
||||||
|
if ($user->getActive()) {
|
||||||
|
$data = ['error' => 'user_found'];
|
||||||
|
} else {
|
||||||
|
$data = ['error' => 'not_active_user_found'];
|
||||||
|
}
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
|
$data = ['error' => 'passwords_too_short'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
|
$data = ['error' => 'passwords_not_match'];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = new User([
|
||||||
|
'email' => $this->request->post('email'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->fill($user->toArray());
|
||||||
|
$modify->save();
|
||||||
|
$userId = $modify->getId();
|
||||||
|
|
||||||
|
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$modify->set('user_id', $userId);
|
||||||
|
$modify->set('token', $token);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->sendConfirmationEmail($user->getEmail(), $token);
|
||||||
|
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activate()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$select->columns(['id', 'user_id']);
|
||||||
|
$select->where('token', '=', $this->request->query('token'));
|
||||||
|
|
||||||
|
$confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($confirmation === null) {
|
||||||
|
$data = [];
|
||||||
|
return new HtmlContent('signup/activate', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$modify->setId($confirmation['id']);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->setId($confirmation['user_id']);
|
||||||
|
$modify->set('active', true);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(User::getFields());
|
||||||
|
$select->whereId($confirmation['user_id']);
|
||||||
|
|
||||||
|
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
$user = new User($userData);
|
||||||
|
|
||||||
|
$session->set('user', $user);
|
||||||
|
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel()
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if ($session->get('user')) {
|
||||||
|
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$select->columns(['id', 'user_id']);
|
||||||
|
$select->where('token', '=', $this->request->query('token'));
|
||||||
|
|
||||||
|
$confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($confirmation === null) {
|
||||||
|
$data = ['success' => false];
|
||||||
|
return new HtmlContent('signup/cancel', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
|
||||||
|
$modify->setId($confirmation['id']);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->setId($confirmation['user_id']);
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$data = ['success' => true];
|
||||||
|
return new HtmlContent('signup/cancel', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendConfirmationEmail($email, $token): void
|
||||||
|
{
|
||||||
|
$mail = new Mail();
|
||||||
|
$mail->addRecipient($email);
|
||||||
|
$mail->setSubject('Welcome to MapGuesser - Activate your account');
|
||||||
|
$mail->setBodyFromTemplate('signup', [
|
||||||
|
'EMAIL' => $email,
|
||||||
|
'ACTIVATE_LINK' => $this->request->getBase() . '/signup/activate/' . $token,
|
||||||
|
'CANCEL_LINK' => $this->request->getBase() . '/signup/cancel/' . $token,
|
||||||
|
]);
|
||||||
|
$mail->send();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
|
|
||||||
@@ -12,12 +11,9 @@ class UserController implements ISecured
|
|||||||
{
|
{
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
private PersistentDataManager $pdm;
|
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$this->pdm = new PersistentDataManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@@ -29,9 +25,6 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
public function getProfile(): IContent
|
public function getProfile(): IContent
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
$data = ['user' => $user->toArray()];
|
$data = ['user' => $user->toArray()];
|
||||||
@@ -40,9 +33,6 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
public function saveProfile(): IContent
|
public function saveProfile(): IContent
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
@@ -52,7 +42,7 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
if (strlen($this->request->post('password_new')) > 0) {
|
if (strlen($this->request->post('password_new')) > 0) {
|
||||||
if (strlen($this->request->post('password_new')) < 6) {
|
if (strlen($this->request->post('password_new')) < 6) {
|
||||||
$data = ['error' => 'password_too_short'];
|
$data = ['error' => 'passwords_too_short'];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +54,9 @@ class UserController implements ISecured
|
|||||||
$user->setPlainPassword($this->request->post('password_new'));
|
$user->setPlainPassword($this->request->post('password_new'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->fill($user->toArray());
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
$data = ['success' => true];
|
$data = ['success' => true];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Database\Query;
|
<?php namespace MapGuesser\Database\Query;
|
||||||
|
|
||||||
use MapGuesser\Interfaces\Database\IConnection;
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use MapGuesser\Database\Utils;
|
use MapGuesser\Database\Utils;
|
||||||
|
|
||||||
class Modify
|
class Modify
|
||||||
@@ -13,6 +14,8 @@ class Modify
|
|||||||
|
|
||||||
private array $attributes = [];
|
private array $attributes = [];
|
||||||
|
|
||||||
|
private array $original = [];
|
||||||
|
|
||||||
private ?string $externalId = null;
|
private ?string $externalId = null;
|
||||||
|
|
||||||
private bool $autoIncrement = true;
|
private bool $autoIncrement = true;
|
||||||
@@ -113,15 +116,48 @@ class Modify
|
|||||||
|
|
||||||
private function update(): void
|
private function update(): void
|
||||||
{
|
{
|
||||||
$attributes = $this->attributes;
|
/*$diff = $this->generateDiff();
|
||||||
unset($attributes[$this->idName]);
|
|
||||||
|
|
||||||
$set = $this->generateColumnsWithBinding(array_keys($attributes));
|
if (count($diff) === 0) {
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
$diff = $this->attributes;
|
||||||
|
unset($diff[$this->idName]);
|
||||||
|
|
||||||
|
$set = $this->generateColumnsWithBinding(array_keys($diff));
|
||||||
|
|
||||||
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||||
|
|
||||||
$stmt = $this->connection->prepare($query);
|
$stmt = $this->connection->prepare($query);
|
||||||
$stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]]));
|
$stmt->execute(array_merge($diff, [$this->idName => $this->attributes[$this->idName]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readFromDB(array $columns): void
|
||||||
|
{
|
||||||
|
$select = (new Select($this->connection, $this->table))
|
||||||
|
->setIdName($this->idName)
|
||||||
|
->whereId($this->attributes[$this->idName])
|
||||||
|
->columns($columns);
|
||||||
|
|
||||||
|
$this->original = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateDiff(): array
|
||||||
|
{
|
||||||
|
$this->readFromDB(array_keys($this->attributes));
|
||||||
|
|
||||||
|
$diff = [];
|
||||||
|
|
||||||
|
foreach ($this->attributes as $name => $value) {
|
||||||
|
$original = $this->original[$name];
|
||||||
|
|
||||||
|
if ($original != $value) {
|
||||||
|
$diff[$name] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function generateColumnsWithBinding(array $columns): string
|
public static function generateColumnsWithBinding(array $columns): string
|
||||||
|
|||||||
@@ -32,14 +32,11 @@ class Select
|
|||||||
|
|
||||||
private array $limit;
|
private array $limit;
|
||||||
|
|
||||||
public function __construct(IConnection $connection, ?string $table = null)
|
public function __construct(IConnection $connection, string $table)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
|
|
||||||
if ($table !== null) {
|
|
||||||
$this->table = $table;
|
$this->table = $table;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function setIdName(string $idName): Select
|
public function setIdName(string $idName): Select
|
||||||
{
|
{
|
||||||
@@ -55,13 +52,6 @@ class Select
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function from(string $table): Select
|
|
||||||
{
|
|
||||||
$this->table = $table;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function columns(array $columns): Select
|
public function columns(array $columns): Select
|
||||||
{
|
{
|
||||||
$this->columns = array_merge($this->columns, $columns);
|
$this->columns = array_merge($this->columns, $columns);
|
||||||
|
|||||||
@@ -8,9 +8,5 @@ interface IUser
|
|||||||
|
|
||||||
public function hasPermission(int $permission): bool;
|
public function hasPermission(int $permission): bool;
|
||||||
|
|
||||||
public function getUniqueId();
|
|
||||||
|
|
||||||
public function getDisplayName(): string;
|
public function getDisplayName(): string;
|
||||||
|
|
||||||
public function checkPassword(string $password): bool;
|
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,5 @@ interface IRequest
|
|||||||
|
|
||||||
public function session(): ISession;
|
public function session(): ISession;
|
||||||
|
|
||||||
public function setUser(?IUser $user): void;
|
|
||||||
|
|
||||||
public function user(): ?IUser;
|
public function user(): ?IUser;
|
||||||
}
|
}
|
||||||
@@ -29,13 +29,6 @@ class Mail
|
|||||||
{
|
{
|
||||||
$this->body = file_get_contents(ROOT . '/mail/' . $template . '.html');
|
$this->body = file_get_contents(ROOT . '/mail/' . $template . '.html');
|
||||||
|
|
||||||
$baseParameters = [
|
|
||||||
'APP_NAME' => $_ENV['APP_NAME'],
|
|
||||||
'BASE_URL' => \Container::$request->getBase(),
|
|
||||||
];
|
|
||||||
|
|
||||||
$params = array_merge($baseParameters, $params);
|
|
||||||
|
|
||||||
foreach ($params as $key => $param) {
|
foreach ($params as $key => $param) {
|
||||||
$this->body = str_replace('{{' . $key . '}}', $param, $this->body);
|
$this->body = str_replace('{{' . $key . '}}', $param, $this->body);
|
||||||
}
|
}
|
||||||
@@ -65,8 +58,8 @@ class Mail
|
|||||||
$mailer->Mailer = 'mail';
|
$mailer->Mailer = 'mail';
|
||||||
}
|
}
|
||||||
|
|
||||||
$mailer->setFrom($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
$mailer->setFrom($_ENV['MAIL_FROM'], 'MapGuesser');
|
||||||
$mailer->addReplyTo($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
$mailer->addReplyTo($_ENV['MAIL_FROM'], 'MapGuesser');
|
||||||
|
|
||||||
$mailer->Sender = !empty($_ENV['MAIL_BOUNCE']) ? $_ENV['MAIL_BOUNCE'] : $_ENV['MAIL_FROM'];
|
$mailer->Sender = !empty($_ENV['MAIL_BOUNCE']) ? $_ENV['MAIL_BOUNCE'] : $_ENV['MAIL_FROM'];
|
||||||
$mailer->Subject = $this->subject;
|
$mailer->Subject = $this->subject;
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php namespace MapGuesser\Model;
|
||||||
|
|
||||||
|
abstract class BaseModel
|
||||||
|
{
|
||||||
|
protected static array $fields;
|
||||||
|
|
||||||
|
protected $id = null;
|
||||||
|
|
||||||
|
public static function getFields(): array
|
||||||
|
{
|
||||||
|
return array_merge(['id'], static::$fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($this, $method)) {
|
||||||
|
$this->$method($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id): void
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toArray(): array
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
foreach (self::getFields() as $key) {
|
||||||
|
$method = 'get' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($this, $method)) {
|
||||||
|
$array[$key] = $this->$method();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +1,27 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\Model;
|
||||||
|
|
||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
class User extends Model implements IUser
|
class User extends BaseModel implements IUser
|
||||||
{
|
{
|
||||||
protected static string $table = 'users';
|
|
||||||
|
|
||||||
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub'];
|
|
||||||
|
|
||||||
private static array $types = ['user', 'admin'];
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
private string $email = '';
|
protected static array $fields = ['email', 'password', 'type', 'active'];
|
||||||
|
|
||||||
private ?string $password = null;
|
private string $email;
|
||||||
|
|
||||||
|
private string $password;
|
||||||
|
|
||||||
private string $type = 'user';
|
private string $type = 'user';
|
||||||
|
|
||||||
private bool $active = false;
|
private bool $active = false;
|
||||||
|
|
||||||
private ?string $googleSub = null;
|
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
public function setEmail(string $email): void
|
||||||
{
|
{
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPassword(?string $hashedPassword): void
|
public function setPassword(string $hashedPassword): void
|
||||||
{
|
{
|
||||||
$this->password = $hashedPassword;
|
$this->password = $hashedPassword;
|
||||||
}
|
}
|
||||||
@@ -47,17 +43,12 @@ class User extends Model implements IUser
|
|||||||
$this->active = (bool) $active;
|
$this->active = (bool) $active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGoogleSub(?string $googleSub): void
|
|
||||||
{
|
|
||||||
$this->googleSub = $googleSub;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEmail(): string
|
public function getEmail(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPassword(): ?string
|
public function getPassword(): string
|
||||||
{
|
{
|
||||||
return $this->password;
|
return $this->password;
|
||||||
}
|
}
|
||||||
@@ -72,11 +63,6 @@ class User extends Model implements IUser
|
|||||||
return $this->active;
|
return $this->active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGoogleSub(): ?string
|
|
||||||
{
|
|
||||||
return $this->googleSub;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasPermission(int $permission): bool
|
public function hasPermission(int $permission): bool
|
||||||
{
|
{
|
||||||
switch ($permission) {
|
switch ($permission) {
|
||||||
@@ -89,11 +75,6 @@ class User extends Model implements IUser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUniqueId()
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDisplayName(): string
|
public function getDisplayName(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php namespace MapGuesser\OAuth;
|
|
||||||
|
|
||||||
use MapGuesser\Http\Request;
|
|
||||||
|
|
||||||
class GoogleOAuth
|
|
||||||
{
|
|
||||||
private static $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
||||||
|
|
||||||
private static $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
|
||||||
|
|
||||||
public function getDialogUrl(string $state, string $redirectUrl): string
|
|
||||||
{
|
|
||||||
$oauthParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
|
||||||
'scope' => 'openid email',
|
|
||||||
'redirect_uri' => $redirectUrl,
|
|
||||||
'state' => $state,
|
|
||||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
|
||||||
];
|
|
||||||
|
|
||||||
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getToken(string $code, string $redirectUrl)
|
|
||||||
{
|
|
||||||
$tokenParams = [
|
|
||||||
'code' => $code,
|
|
||||||
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
|
||||||
'client_secret' => $_ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
|
|
||||||
'redirect_uri' => $redirectUrl,
|
|
||||||
'grant_type' => 'authorization_code',
|
|
||||||
];
|
|
||||||
|
|
||||||
$request = new Request(self::$tokenUrlBase, Request::HTTP_POST);
|
|
||||||
$request->setQuery($tokenParams);
|
|
||||||
$response = $request->send();
|
|
||||||
|
|
||||||
return json_decode($response->getBody(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
|
||||||
|
|
||||||
class Map extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'maps';
|
|
||||||
|
|
||||||
protected static array $fields = ['name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng', 'area'];
|
|
||||||
|
|
||||||
private string $name = '';
|
|
||||||
|
|
||||||
private string $description = '';
|
|
||||||
|
|
||||||
private Bounds $bounds;
|
|
||||||
|
|
||||||
private float $area = 0.0;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setName(string $name): void
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDescription(string $description): void
|
|
||||||
{
|
|
||||||
$this->description = $description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBounds(Bounds $bounds): void
|
|
||||||
{
|
|
||||||
$this->bounds = $bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBoundSouthLat(float $bound_south_lat): void
|
|
||||||
{
|
|
||||||
$this->bounds->setSouthLat($bound_south_lat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBoundWestLng(float $bound_west_lng): void
|
|
||||||
{
|
|
||||||
$this->bounds->setWestLng($bound_west_lng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBoundNorthLat(float $bound_north_lat): void
|
|
||||||
{
|
|
||||||
$this->bounds->setNorthLat($bound_north_lat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setBoundEastLng(float $bound_east_lng): void
|
|
||||||
{
|
|
||||||
$this->bounds->setEastLng($bound_east_lng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setArea(float $area): void
|
|
||||||
{
|
|
||||||
$this->area = $area;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return $this->description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBounds(): Bounds
|
|
||||||
{
|
|
||||||
return $this->bounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBoundSouthLat(): float
|
|
||||||
{
|
|
||||||
return $this->bounds->getSouthLat();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBoundWestLng(): float
|
|
||||||
{
|
|
||||||
return $this->bounds->getWestLng();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBoundNorthLat(): float
|
|
||||||
{
|
|
||||||
return $this->bounds->getNorthLat();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getBoundEastLng(): float
|
|
||||||
{
|
|
||||||
return $this->bounds->getEastLng();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getArea(): float
|
|
||||||
{
|
|
||||||
return $this->area;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
abstract class Model
|
|
||||||
{
|
|
||||||
protected static string $table;
|
|
||||||
|
|
||||||
protected static array $fields;
|
|
||||||
|
|
||||||
protected static array $relations = [];
|
|
||||||
|
|
||||||
protected $id = null;
|
|
||||||
|
|
||||||
private array $snapshot = [];
|
|
||||||
|
|
||||||
public static function getTable(): string
|
|
||||||
{
|
|
||||||
return static::$table;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getFields(): array
|
|
||||||
{
|
|
||||||
return array_merge(['id'], static::$fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getRelations(): array
|
|
||||||
{
|
|
||||||
return static::$relations;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setId($id): void
|
|
||||||
{
|
|
||||||
$this->id = $id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId()
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toArray(): array
|
|
||||||
{
|
|
||||||
$array = [];
|
|
||||||
|
|
||||||
foreach (self::getFields() as $key) {
|
|
||||||
$method = 'get' . str_replace('_', '', ucwords($key, '_'));
|
|
||||||
|
|
||||||
if (method_exists($this, $method)) {
|
|
||||||
$array[$key] = $this->$method();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveSnapshot(): void
|
|
||||||
{
|
|
||||||
$this->snapshot = $this->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function resetSnapshot(): void
|
|
||||||
{
|
|
||||||
$this->snapshot = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSnapshot(): array
|
|
||||||
{
|
|
||||||
return $this->snapshot;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use DateInterval;
|
|
||||||
use DateTime;
|
|
||||||
use MapGuesser\Http\Request;
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
use MapGuesser\Util\Geo\Position;
|
|
||||||
|
|
||||||
class Place extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'places';
|
|
||||||
|
|
||||||
protected static array $fields = ['map_id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp'];
|
|
||||||
|
|
||||||
protected static array $relations = ['map' => Map::class];
|
|
||||||
|
|
||||||
private ?Map $map = null;
|
|
||||||
|
|
||||||
private ?int $mapId = null;
|
|
||||||
|
|
||||||
private Position $position;
|
|
||||||
|
|
||||||
private ?string $panoIdCached = null;
|
|
||||||
|
|
||||||
private ?DateTime $panoIdCachedTimestamp = null;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->position = new Position(0.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMap(Map $map): void
|
|
||||||
{
|
|
||||||
$this->map = $map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMapId(int $mapId): void
|
|
||||||
{
|
|
||||||
$this->mapId = $mapId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPosition(Position $position): void
|
|
||||||
{
|
|
||||||
$this->position = $position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLat(float $lat): void
|
|
||||||
{
|
|
||||||
$this->position->setLat($lat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLng(float $lng): void
|
|
||||||
{
|
|
||||||
$this->position->setLng($lng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPanoIdCached(?string $panoIdCached): void
|
|
||||||
{
|
|
||||||
$this->panoIdCached = $panoIdCached;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPanoIdCachedTimestampDate(?DateTime $panoIdCachedTimestamp): void
|
|
||||||
{
|
|
||||||
$this->panoIdCachedTimestamp = $panoIdCachedTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPanoIdCachedTimestamp(?string $panoIdCachedTimestamp): void
|
|
||||||
{
|
|
||||||
if ($panoIdCachedTimestamp !== null) {
|
|
||||||
$this->panoIdCachedTimestamp = new DateTime($panoIdCachedTimestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMap(): ?Map
|
|
||||||
{
|
|
||||||
return $this->map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getMapId(): ?int
|
|
||||||
{
|
|
||||||
return $this->mapId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPosition(): Position
|
|
||||||
{
|
|
||||||
return $this->position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLat(): float
|
|
||||||
{
|
|
||||||
return $this->position->getLat();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLng(): float
|
|
||||||
{
|
|
||||||
return $this->position->getLng();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFreshPanoId(bool $canBeIndoor = false): ?string
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
$this->panoIdCachedTimestamp !== null &&
|
|
||||||
(clone $this->panoIdCachedTimestamp)->add(new DateInterval('P1D')) > new DateTime()
|
|
||||||
) {
|
|
||||||
return $this->panoIdCached;
|
|
||||||
}
|
|
||||||
|
|
||||||
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
|
|
||||||
$request->setQuery([
|
|
||||||
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
|
||||||
'location' => $this->position->getLat() . ',' . $this->position->getLng(),
|
|
||||||
'source' => $canBeIndoor ? 'default' : 'outdoor'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = $request->send();
|
|
||||||
$panoData = json_decode($response->getBody(), true);
|
|
||||||
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
|
|
||||||
|
|
||||||
// enable indoor panos if no outdoor found
|
|
||||||
if ($panoId === null && !$canBeIndoor) {
|
|
||||||
return $this->getFreshPanoId(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->panoIdCached = $panoId;
|
|
||||||
$this->panoIdCachedTimestamp = new DateTime('now');
|
|
||||||
|
|
||||||
(new PersistentDataManager())->saveToDb($this);
|
|
||||||
|
|
||||||
return $panoId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPanoIdCached(): ?string
|
|
||||||
{
|
|
||||||
return $this->panoIdCached;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPanoIdCachedTimestampDate(): ?DateTime
|
|
||||||
{
|
|
||||||
return $this->panoIdCachedTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPanoIdCachedTimestamp(): ?string
|
|
||||||
{
|
|
||||||
if ($this->panoIdCachedTimestamp === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->panoIdCachedTimestamp->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
class UserConfirmation extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'user_confirmations';
|
|
||||||
|
|
||||||
protected static array $fields = ['user_id', 'token'];
|
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class];
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
private string $token = '';
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setToken(string $token): void
|
|
||||||
{
|
|
||||||
$this->token = $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getToken(): string
|
|
||||||
{
|
|
||||||
return $this->token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData;
|
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Modify;
|
|
||||||
use MapGuesser\Database\Query\Select;
|
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
|
||||||
use MapGuesser\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class PersistentDataManager
|
|
||||||
{
|
|
||||||
public function selectFromDb(Select $select, string $type, bool $withRelations = false): ?Model
|
|
||||||
{
|
|
||||||
$table = call_user_func([$type, 'getTable']);
|
|
||||||
$fields = call_user_func([$type, 'getFields']);
|
|
||||||
|
|
||||||
$select->from($table);
|
|
||||||
|
|
||||||
//TODO: only with some relations?
|
|
||||||
if ($withRelations) {
|
|
||||||
$relations = call_user_func([$type, 'getRelations']);
|
|
||||||
|
|
||||||
$columns = [];
|
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
|
||||||
$columns[] = [$table, $field];
|
|
||||||
}
|
|
||||||
|
|
||||||
$columns = array_merge($columns, $this->getRelationColumns($relations));
|
|
||||||
|
|
||||||
$this->leftJoinRelations($select, $table, $relations);
|
|
||||||
$select->columns($columns);
|
|
||||||
} else {
|
|
||||||
$select->columns($fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: return with array?
|
|
||||||
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($data === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$model = new $type();
|
|
||||||
$this->fillWithData($data, $model);
|
|
||||||
|
|
||||||
return $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function selectFromDbById($id, string $type, bool $withRelations = false): ?Model
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->whereId($id);
|
|
||||||
|
|
||||||
return $this->selectFromDb($select, $type, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function fillWithData(array $data, Model $model): void
|
|
||||||
{
|
|
||||||
$relations = $model::getRelations();
|
|
||||||
$relationData = [];
|
|
||||||
|
|
||||||
foreach ($data as $key => $value) {
|
|
||||||
if ($this->extractRelationData($key, $value, $relationData, $relations)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
|
|
||||||
|
|
||||||
if (method_exists($model, $method)) {
|
|
||||||
$model->$method($value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setRelations($model, $relationData);
|
|
||||||
|
|
||||||
$model->saveSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadRelationsFromDb(Model $model, bool $recursive): void
|
|
||||||
{
|
|
||||||
foreach ($model::getRelations() as $relation => $relationType) {
|
|
||||||
$camel = str_replace('_', '', ucwords($relation, '_'));
|
|
||||||
|
|
||||||
$methodGet = 'get' . $camel . 'Id';
|
|
||||||
$methodSet = 'set' . $camel;
|
|
||||||
|
|
||||||
$relationId = $model->$methodGet();
|
|
||||||
|
|
||||||
if ($relationId !== null) {
|
|
||||||
$relationModel = $this->selectFromDbById($relationId, $relationType, $recursive);
|
|
||||||
|
|
||||||
$model->$methodSet($relationModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveToDb(Model $model): void
|
|
||||||
{
|
|
||||||
$this->syncRelations($model);
|
|
||||||
|
|
||||||
$modified = $model->toArray();
|
|
||||||
$id = $model->getId();
|
|
||||||
|
|
||||||
$modify = new Modify(\Container::$dbConnection, $model::getTable());
|
|
||||||
|
|
||||||
if ($id !== null) {
|
|
||||||
$original = $model->getSnapshot();
|
|
||||||
|
|
||||||
foreach ($original as $key => $value) {
|
|
||||||
if ($value === $modified[$key]) {
|
|
||||||
unset($modified[$key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count($modified) > 0) {
|
|
||||||
$modify->setId($id);
|
|
||||||
$modify->fill($modified);
|
|
||||||
$modify->save();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$modify->fill($modified);
|
|
||||||
$modify->save();
|
|
||||||
|
|
||||||
$model->setId($modify->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
$model->saveSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteFromDb(Model $model): void
|
|
||||||
{
|
|
||||||
$modify = new Modify(\Container::$dbConnection, $model::getTable());
|
|
||||||
$modify->setId($model->getId());
|
|
||||||
$modify->delete();
|
|
||||||
|
|
||||||
$model->setId(null);
|
|
||||||
$model->resetSnapshot();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getRelationColumns(array $relations): array
|
|
||||||
{
|
|
||||||
$columns = [];
|
|
||||||
|
|
||||||
foreach ($relations as $relation => $relationType) {
|
|
||||||
$relationTable = call_user_func([$relationType, 'getTable']);
|
|
||||||
foreach (call_user_func([$relationType, 'getFields']) as $relationField) {
|
|
||||||
$columns[] = [$relationTable, $relationField, $relation . '__' . $relationField];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function leftJoinRelations(Select $select, string $table, array $relations): void
|
|
||||||
{
|
|
||||||
foreach ($relations as $relation => $relationType) {
|
|
||||||
$relationTable = call_user_func([$relationType, 'getTable']);
|
|
||||||
$select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function extractRelationData(string $key, $value, array &$relationData, array $relations): bool
|
|
||||||
{
|
|
||||||
$found = false;
|
|
||||||
|
|
||||||
foreach ($relations as $relation => $relationType) {
|
|
||||||
if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
|
|
||||||
$found = true;
|
|
||||||
$relationData[$relation][substr($key, strlen($relation . '__'))] = $value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $found;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function setRelations(Model $model, array &$relations): void
|
|
||||||
{
|
|
||||||
foreach ($model::getRelations() as $relation => $relationType) {
|
|
||||||
if (isset($relations[$relation])) {
|
|
||||||
$object = new $relationType();
|
|
||||||
|
|
||||||
$this->fillWithData($relations[$relation], $object);
|
|
||||||
|
|
||||||
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
|
|
||||||
|
|
||||||
$model->$method($object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function syncRelations(Model $model): void
|
|
||||||
{
|
|
||||||
foreach ($model::getRelations() as $relation => $relationType) {
|
|
||||||
$camel = str_replace('_', '', ucwords($relation, '_'));
|
|
||||||
|
|
||||||
$methodGet = 'get' . $camel;
|
|
||||||
$methodSet = 'set' . $camel . 'Id';
|
|
||||||
|
|
||||||
$relationModel = $model->$methodGet();
|
|
||||||
|
|
||||||
if ($relationModel !== null) {
|
|
||||||
$model->$methodSet($relationModel->getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
|
||||||
class MapRepository
|
class MapRepository
|
||||||
{
|
{
|
||||||
private PersistentDataManager $pdm;
|
public function getById(int $mapId)
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
$this->pdm = new PersistentDataManager();
|
$select = new Select(\Container::$dbConnection, 'maps');
|
||||||
}
|
$select->columns(['id', 'name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng', 'area']);
|
||||||
|
$select->whereId($mapId);
|
||||||
|
|
||||||
public function getById(int $mapId): ?Map
|
return $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
{
|
|
||||||
return $this->pdm->selectFromDbById($mapId, Map::class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,95 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use MapGuesser\Util\Geo\Position;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\Http\Request;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use DateTime;
|
||||||
|
use DateInterval;
|
||||||
|
|
||||||
class PlaceRepository
|
class PlaceRepository
|
||||||
{
|
{
|
||||||
private PersistentDataManager $pdm;
|
public function getById(int $placeId)
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
$this->pdm = new PersistentDataManager();
|
$place = $this->selectFromDbById($placeId);
|
||||||
|
|
||||||
|
$panoId = $this->requestPanoId($place);
|
||||||
|
|
||||||
|
$position = new Position($place['lat'], $place['lng']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'position' => $position,
|
||||||
|
'panoId' => $panoId
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getById(int $placeId): ?Place
|
public function getForMapWithValidPano(int $mapId, array $exclude = []): array
|
||||||
{
|
|
||||||
return $this->pdm->selectFromDbById($placeId, Place::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place
|
|
||||||
{
|
{
|
||||||
$placesWithoutPano = [];
|
$placesWithoutPano = [];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
|
$place = $this->selectFromDbForMap($mapId, $exclude);
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
$panoId = $this->requestPanoId($place);
|
||||||
|
|
||||||
if ($panoId === null) {
|
if ($panoId === null) {
|
||||||
$placesWithoutPano[] = $exclude[] = $place->getId();
|
$placesWithoutPano[] = $exclude[] = $place['id'];
|
||||||
}
|
}
|
||||||
} while ($panoId === null);
|
} while ($panoId === null);
|
||||||
|
|
||||||
|
$position = new Position($place['lat'], $place['lng']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'placesWithoutPano' => $placesWithoutPano,
|
||||||
|
'placeId' => $place['id'],
|
||||||
|
'position' => $position,
|
||||||
|
'panoId' => $panoId
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addToMap(int $mapId, array $place): int
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'places');
|
||||||
|
$modify->set('map_id', $mapId);
|
||||||
|
$modify->fill($place);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
return $modify->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function modify(int $id, array $place): void
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'places');
|
||||||
|
$modify->setId($id);
|
||||||
|
$modify->set('pano_id_cached', null);
|
||||||
|
$modify->set('pano_id_cached_timestamp', null);
|
||||||
|
$modify->fill($place);
|
||||||
|
$modify->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(int $id): void
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'places');
|
||||||
|
$modify->setId($id);
|
||||||
|
$modify->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function selectFromDbById(int $placeId): array
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
|
||||||
|
$select->whereId($placeId);
|
||||||
|
|
||||||
|
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
return $place;
|
return $place;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectRandomFromDbForMap(int $mapId, array $exclude): ?Place
|
private function selectFromDbForMap(int $mapId, array $exclude): array
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
|
||||||
$select->where('id', 'NOT IN', $exclude);
|
$select->where('id', 'NOT IN', $exclude);
|
||||||
$select->where('map_id', '=', $mapId);
|
$select->where('map_id', '=', $mapId);
|
||||||
|
|
||||||
@@ -47,6 +99,49 @@ class PlaceRepository
|
|||||||
$select->orderBy('id');
|
$select->orderBy('id');
|
||||||
$select->limit(1, $randomOffset);
|
$select->limit(1, $randomOffset);
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select, Place::class);
|
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return $place;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function requestPanoId(array $place, bool $canBeIndoor = false): ?string
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
$place['pano_id_cached_timestamp'] &&
|
||||||
|
(new DateTime($place['pano_id_cached_timestamp']))->add(new DateInterval('P1D')) > new DateTime()
|
||||||
|
) {
|
||||||
|
return $place['pano_id_cached'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
|
||||||
|
$request->setQuery([
|
||||||
|
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
||||||
|
'location' => $place['lat'] . ',' . $place['lng'],
|
||||||
|
'source' => $canBeIndoor ? 'default' : 'outdoor'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $request->send();
|
||||||
|
|
||||||
|
$panoData = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
|
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
|
||||||
|
|
||||||
|
// enable indoor panos if no outdoor found
|
||||||
|
if ($panoId === null && !$canBeIndoor) {
|
||||||
|
return $this->requestPanoId($place, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->saveCachedPanoId($place['id'], $panoId);
|
||||||
|
|
||||||
|
return $panoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function saveCachedPanoId(int $placeId, ?string $panoId): void
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'places');
|
||||||
|
$modify->setId($placeId);
|
||||||
|
$modify->set('pano_id_cached', $panoId);
|
||||||
|
$modify->set('pano_id_cached_timestamp', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
|
$modify->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
|
|
||||||
class UserConfirmationRepository
|
|
||||||
{
|
|
||||||
private PersistentDataManager $pdm;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->pdm = new PersistentDataManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getById(int $userConfirmationId): ?UserConfirmation
|
|
||||||
{
|
|
||||||
return $this->pdm->selectFromDbById($userConfirmationId, UserConfirmation::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByToken(string $token): ?UserConfirmation
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('token', '=', $token);
|
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
|
|
||||||
class UserRepository
|
|
||||||
{
|
|
||||||
private PersistentDataManager $pdm;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->pdm = new PersistentDataManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getById(int $userId): ?User
|
|
||||||
{
|
|
||||||
return $this->pdm->selectFromDbById($userId, User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByEmail(string $email): ?User
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('email', '=', $email);
|
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select, User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByGoogleSub(string $sub): ?User
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('google_sub', '=', $sub);
|
|
||||||
|
|
||||||
return $this->pdm->selectFromDb($select, User::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-26
@@ -3,9 +3,6 @@
|
|||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Request\ISession;
|
use MapGuesser\Interfaces\Request\ISession;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
|
||||||
use MapGuesser\Repository\UserRepository;
|
|
||||||
|
|
||||||
class Request implements IRequest
|
class Request implements IRequest
|
||||||
{
|
{
|
||||||
@@ -19,24 +16,12 @@ class Request implements IRequest
|
|||||||
|
|
||||||
private Session $session;
|
private Session $session;
|
||||||
|
|
||||||
private UserRepository $userRepository;
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
public function __construct(string $base, array &$get, array &$post, array &$session)
|
public function __construct(string $base, array &$get, array &$post, array &$session)
|
||||||
{
|
{
|
||||||
$this->base = $base;
|
$this->base = $base;
|
||||||
$this->get = &$get;
|
$this->get = &$get;
|
||||||
$this->post = &$post;
|
$this->post = &$post;
|
||||||
$this->session = new Session($session);
|
$this->session = new Session($session);
|
||||||
|
|
||||||
$this->userRepository = new UserRepository();
|
|
||||||
|
|
||||||
$userId = $this->session->get('userId');
|
|
||||||
|
|
||||||
if ($userId !== null) {
|
|
||||||
$this->user = $this->userRepository->getById($userId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setParsedRouteParams(array &$routeParams)
|
public function setParsedRouteParams(array &$routeParams)
|
||||||
@@ -76,18 +61,8 @@ class Request implements IRequest
|
|||||||
return $this->session;
|
return $this->session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUser(?IUser $user): void
|
|
||||||
{
|
|
||||||
if ($user === null) {
|
|
||||||
$this->session->delete('userId');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->session->set('userId', $user->getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function user(): ?IUser
|
public function user(): ?IUser
|
||||||
{
|
{
|
||||||
return $this->user;
|
return $this->session->get('user');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,11 @@ use MapGuesser\Interfaces\Response\IRedirect;
|
|||||||
|
|
||||||
class Redirect implements IRedirect
|
class Redirect implements IRedirect
|
||||||
{
|
{
|
||||||
private string $target;
|
private $target;
|
||||||
|
|
||||||
private int $type;
|
private int $type;
|
||||||
|
|
||||||
public function __construct(string $target, int $type = IRedirect::TEMPORARY)
|
public function __construct($target, int $type = IRedirect::TEMPORARY)
|
||||||
{
|
{
|
||||||
$this->target = $target;
|
$this->target = $target;
|
||||||
$this->type = $type;
|
$this->type = $type;
|
||||||
@@ -16,10 +16,10 @@ class Redirect implements IRedirect
|
|||||||
|
|
||||||
public function getUrl(): string
|
public function getUrl(): string
|
||||||
{
|
{
|
||||||
if (preg_match('/^http(s)?/', $this->target)) {
|
if (is_array($this->target)) {
|
||||||
$link = $this->target;
|
$link = $this->target[0]->generateLink($this->target[1]);
|
||||||
} else {
|
} else {
|
||||||
$link = \Container::$request->getBase() . '/' . $this->target;
|
$link = $this->target;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $link;
|
return $link;
|
||||||
|
|||||||
@@ -45,11 +45,6 @@ class RouteCollection
|
|||||||
{
|
{
|
||||||
$groupNumber = count($uri);
|
$groupNumber = count($uri);
|
||||||
|
|
||||||
// response to HEAD request with the GET content
|
|
||||||
if ($method === 'head') {
|
|
||||||
$method = 'get';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($this->searchTable[$method][$groupNumber])) {
|
if (!isset($this->searchTable[$method][$groupNumber])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -69,7 +64,7 @@ class RouteCollection
|
|||||||
throw new \Exception('Route already exists: ' . $id);
|
throw new \Exception('Route already exists: ' . $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pattern = array_merge($this->groupStack, $pattern === '' ? [] : explode('/', $pattern));
|
$pattern = array_merge($this->groupStack, explode('/', $pattern));
|
||||||
$route = new Route($id, $pattern, $handler);
|
$route = new Route($id, $pattern, $handler);
|
||||||
|
|
||||||
$groupNumber = count($pattern);
|
$groupNumber = count($pattern);
|
||||||
|
|||||||
@@ -59,26 +59,6 @@ class Bounds
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setSouthLat(float $southLat): void
|
|
||||||
{
|
|
||||||
$this->southLat = $southLat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setWestLng(float $westLng): void
|
|
||||||
{
|
|
||||||
$this->westLng = $westLng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNorthLat(float $northLat): void
|
|
||||||
{
|
|
||||||
$this->northLat = $northLat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setEastLng(float $eastLng): void
|
|
||||||
{
|
|
||||||
$this->eastLng = $eastLng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSouthLat(): float
|
public function getSouthLat(): float
|
||||||
{
|
{
|
||||||
return $this->southLat;
|
return $this->southLat;
|
||||||
|
|||||||
@@ -13,16 +13,6 @@ class Position
|
|||||||
$this->lng = $lng;
|
$this->lng = $lng;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setLat(float $lat): void
|
|
||||||
{
|
|
||||||
$this->lat = $lat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLng(float $lng): void
|
|
||||||
{
|
|
||||||
$this->lng = $lng;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLat(): float
|
public function getLat(): float
|
||||||
{
|
{
|
||||||
return $this->lat;
|
return $this->lat;
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Util;
|
|
||||||
|
|
||||||
class JwtParser
|
|
||||||
{
|
|
||||||
private array $token;
|
|
||||||
|
|
||||||
public function __construct(?string $token = null)
|
|
||||||
{
|
|
||||||
if ($token !== null) {
|
|
||||||
$this->setToken($token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setToken(string $token)
|
|
||||||
{
|
|
||||||
$this->token = explode('.', str_replace(['_', '-'], ['/', '+'], $token));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getHeader(): array
|
|
||||||
{
|
|
||||||
return json_decode(base64_decode($this->token[0]), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPayload(): array
|
|
||||||
{
|
|
||||||
return json_decode(base64_decode($this->token[1]), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSignature(): string
|
|
||||||
{
|
|
||||||
return base64_decode($this->token[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+11
-11
@@ -13,11 +13,11 @@ $jsFiles = [
|
|||||||
];
|
];
|
||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<header class="small">
|
<div class="header small">
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
<a href="/maps" title="Back to playable maps">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
||||||
--><span><?= $_ENV['APP_NAME'] ?></span>
|
<span>MapGuesser</span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="header">
|
<p class="header">
|
||||||
@@ -28,26 +28,26 @@ $jsFiles = [
|
|||||||
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/>
|
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/>
|
||||||
<path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/>
|
<path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/>
|
||||||
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
||||||
</svg><!--
|
</svg>
|
||||||
--><span id="added" class="bold">0</span><!--
|
<span id="added" class="bold">0</span><!--
|
||||||
--></span><!--
|
--></span><!--
|
||||||
--><span><!--
|
--><span><!--
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
<path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
||||||
</svg><!--
|
</svg>
|
||||||
--><span id="edited" class="bold">0</span><!--
|
<span id="edited" class="bold">0</span><!--
|
||||||
--></span><!--
|
--></span><!--
|
||||||
--><span><!--
|
--><span><!--
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
--><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/>
|
<path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/>
|
||||||
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
|
||||||
</svg><!--
|
</svg>
|
||||||
--><span id="deleted" class="bold">0</span><!--
|
<span id="deleted" class="bold">0</span><!--
|
||||||
--></span>
|
--></span>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</div>
|
||||||
<div id="metadata" class="modal">
|
<div id="metadata" class="modal">
|
||||||
<h2>Edit map data</h2>
|
<h2>Edit map data</h2>
|
||||||
<form id="metadataForm" class="marginTop">
|
<form id="metadataForm" class="marginTop">
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,7 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
<h2>404 | Page not found</h2>
|
<h2>404 | Page not found</h2>
|
||||||
<p>The requested URL was not found on this server. <a href="/" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
|
<p>The requested URL was not found on this server. <a href="/" title="MapGuesser">Back to start.</a></p>
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
||||||
+5
-5
@@ -8,11 +8,11 @@ $jsFiles = [
|
|||||||
];
|
];
|
||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<header class="small">
|
<div class="header small">
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
<a href="/maps" title="Back to playable maps">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
||||||
--><span><?= $_ENV['APP_NAME'] ?></span>
|
<span>MapGuesser</span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="header">
|
<p class="header">
|
||||||
@@ -20,7 +20,7 @@ $jsFiles = [
|
|||||||
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
||||||
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</div>
|
||||||
<div id="guessCover"></div>
|
<div id="guessCover"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="showGuessButtonContainer">
|
<div id="showGuessButtonContainer">
|
||||||
|
|||||||
@@ -5,20 +5,17 @@ $jsFiles = [
|
|||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="loginForm" action="/login" method="post">
|
<form id="loginForm" action="/login" method="post">
|
||||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" required autofocus>
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
|
||||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6">
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
|
||||||
<p id="loginFormError" class="formError justify marginTop"></p>
|
<p id="loginFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
|
||||||
<div class="center">
|
|
||||||
<a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<h2>Login up with Google</h2>
|
|
||||||
<div class="box">
|
|
||||||
<p class="error justify">Authenticating with Google failed. Please <a href="/login/google" title="Login with Google">retry</a>!</p>
|
|
||||||
</div>
|
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<h2>Sign up</h2>
|
|
||||||
<div class="box">
|
|
||||||
<form id="googleSignupForm" action="/signup/google" method="post">
|
|
||||||
<?php if ($found): ?>
|
|
||||||
<p class="justify">Please confirm that you link your account to your Google account.</p>
|
|
||||||
<?php else: ?>
|
|
||||||
<p class="justify">Please confirm your sign up request. Your account will be linked to your Google account.</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
<input class="big fullWidth marginTop" type="email" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
|
|
||||||
<div class="right">
|
|
||||||
<button class="marginTop marginRight" type="submit">
|
|
||||||
<?php if ($found): ?>
|
|
||||||
Link
|
|
||||||
<?php else: ?>
|
|
||||||
Sign up
|
|
||||||
<?php endif; ?>
|
|
||||||
</button><!--
|
|
||||||
--><button id="cancelGoogleSignupButton" class="gray marginTop" type="button">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
var form = document.getElementById('googleSignupForm');
|
|
||||||
|
|
||||||
form.onsubmit = function (e) {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', form.action, function () {
|
|
||||||
window.location.replace('/');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('cancelGoogleSignupButton').onclick = function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', '/signup/google/reset', function () {
|
|
||||||
window.location.replace('/signup');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
$jsFiles = [
|
|
||||||
'js/signup.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<h2>Sign up</h2>
|
|
||||||
<div class="box">
|
|
||||||
<form id="signupForm" action="/signup" method="post">
|
|
||||||
<?php if (isset($email)): ?>
|
|
||||||
<p class="justify">No user found with the given email address. Sign up with one click!</p>
|
|
||||||
<input class="big fullWidth marginTop" type="email" name="email" placeholder="Email address" value="<?= $email ?>" required>
|
|
||||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password confirmation" required minlength="6" autofocus>
|
|
||||||
<?php else: ?>
|
|
||||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" required autofocus>
|
|
||||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6">
|
|
||||||
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation" minlength="6">
|
|
||||||
<?php endif; ?>
|
|
||||||
<p id="signupFormError" class="formError justify marginTop"></p>
|
|
||||||
<div class="right">
|
|
||||||
<button class="marginTop" type="submit">Sign up</button><!--
|
|
||||||
--><?php if (isset($email)): ?><!--
|
|
||||||
--><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="center">
|
|
||||||
<a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<h2>Sign up</h2>
|
|
||||||
<div class="box">
|
|
||||||
<p class="justify">Sign up was successful. Please check your email and click on the activation link to activate your account!</p>
|
|
||||||
</div>
|
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
+57
-78
@@ -5,32 +5,36 @@ $cssFiles = [
|
|||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
|
<h2>Playable maps</h2>
|
||||||
<div id="mapContainer">
|
<div id="mapContainer">
|
||||||
<?php foreach ($maps as $map): ?>
|
<?php foreach ($maps as $map): ?>
|
||||||
<div class="mapItem">
|
<div class="mapItem">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<p class="title"><?= $map['name'] ?></p>
|
<p class="title"><?= $map['name'] ?></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="imgContainer" data-bound-south-lat="<?= $map['bound_south_lat'] ?>" data-bound-west-lng="<?= $map['bound_west_lng'] ?>" data-bound-north-lat="<?= $map['bound_north_lat'] ?>" data-bound-east-lng="<?= $map['bound_east_lng'] ?>"></div>
|
<div class="imgContainer">
|
||||||
|
<img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>" alt="Map area">
|
||||||
|
</div>
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<p>
|
<p>
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10zm0-7a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
|
<path fill-rule="evenodd" d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10zm0-7a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
|
||||||
</svg><!--
|
</svg>
|
||||||
--><?= $map['num_places'] ?> places
|
<?= $map['num_places'] ?> places
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M12.5 2h-9V1h9v1zm-10 1.5v9h-1v-9h1zm11 9v-9h1v9h-1zM3.5 14h9v1h-9v-1z" />
|
<path fill-rule="evenodd" d="M12.5 2h-9V1h9v1zm-10 1.5v9h-1v-9h1zm11 9v-9h1v9h-1zM3.5 14h9v1h-9v-1z" />
|
||||||
<path fill-rule="evenodd" d="M14 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
|
<path fill-rule="evenodd" d="M14 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM2 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 11a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 1a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
|
||||||
</svg><!--
|
</svg>
|
||||||
-->~ <?= $map['area'][0] ?> <?= $map['area'][1] ?><sup>2</sup>
|
~ <?= $map['area'][0] ?> <?= $map['area'][1] ?><sup>2</sup>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="small center marginTop"><?= $map['description'] ?></p>
|
<p class="small justify marginTop"><?= $map['description'] ?></p>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($isAdmin): ?>
|
<?php if ($isAdmin): ?>
|
||||||
<div class="buttonContainer">
|
<div class="buttonContainer">
|
||||||
@@ -58,87 +62,64 @@ $cssFiles = [
|
|||||||
<?php endfor; ?>
|
<?php endfor; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
</div>
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
const GOOGLE_MAPS_JS_API_KEY = '<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>';
|
|
||||||
|
|
||||||
var Maps = {
|
<style>
|
||||||
innerDivs: null,
|
div.popup {
|
||||||
|
position: absolute;
|
||||||
addStaticMaps: function () {
|
top: 50px;
|
||||||
var imgContainers = document.getElementById('mapContainer').getElementsByClassName('imgContainer');
|
left: 50px;
|
||||||
for (var i = 0; i < imgContainers.length; i++) {
|
width: 250px;
|
||||||
var imgContainer = imgContainers[i];
|
|
||||||
|
|
||||||
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
|
|
||||||
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&' +
|
|
||||||
'visible=' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
|
|
||||||
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng +
|
|
||||||
'&key=' + GOOGLE_MAPS_JS_API_KEY;
|
|
||||||
|
|
||||||
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
|
|
||||||
}
|
}
|
||||||
},
|
div.popup>div.content, div.popup>div.tipContainer>div.tip {
|
||||||
|
background: white;
|
||||||
initializeInnerDivs: function () {
|
color: #333;
|
||||||
Maps.innerDivs = document.getElementById('mapContainer').getElementsByClassName('inner');
|
border: solid #555 1px;
|
||||||
|
|
||||||
for (var i = 0; i < Maps.innerDivs.length; i++) {
|
|
||||||
var inner = Maps.innerDivs[i];
|
|
||||||
var boundingClientRect = inner.getBoundingClientRect();
|
|
||||||
|
|
||||||
inner.defaultHeight = boundingClientRect.height;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
calculateInnerDivHeights: function () {
|
|
||||||
var currentY;
|
|
||||||
var rows = [];
|
|
||||||
for (var i = 0; i < Maps.innerDivs.length; i++) {
|
|
||||||
var inner = Maps.innerDivs[i];
|
|
||||||
var boundingClientRect = inner.getBoundingClientRect();
|
|
||||||
|
|
||||||
if (currentY !== boundingClientRect.y) {
|
|
||||||
rows.push([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows[rows.length - 1].push(inner);
|
div.popup>div.content {
|
||||||
|
margin-top: 20px;
|
||||||
currentY = boundingClientRect.y;
|
padding: 20px 10px 8px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < rows.length; i++) {
|
div.popup>div.tipContainer {
|
||||||
var row = rows[i];
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
var maxHeight = 0;
|
position: absolute;
|
||||||
for (var j = 0; j < row.length; j++) {
|
top: 1px;
|
||||||
var inner = row[j];
|
left: 50%;
|
||||||
|
margin-left: -20px;
|
||||||
if (inner.defaultHeight > maxHeight) {
|
overflow: hidden;
|
||||||
maxHeight = inner.defaultHeight;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var j = 0; j < row.length; j++) {
|
div.popup>div.tipContainer>div.tip {
|
||||||
var inner = row[j];
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
inner.style.height = maxHeight + 'px';
|
padding: 1px;
|
||||||
|
margin: 10px auto 0;
|
||||||
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.popup>a.closeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
color: #555;
|
||||||
}
|
}
|
||||||
}
|
</style>
|
||||||
};
|
|
||||||
|
|
||||||
Maps.addStaticMaps();
|
<div class="popup">
|
||||||
|
<div class="tipContainer">
|
||||||
|
<div class="tip"></div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>A pretty CSS3 popup.<br>Easily customizable.</p>
|
||||||
|
</div>
|
||||||
|
<a class="closeButton" href="#close">×</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
Maps.initializeInnerDivs();
|
|
||||||
Maps.calculateInnerDivHeights();
|
|
||||||
|
|
||||||
window.onresize = function () {
|
|
||||||
Maps.calculateInnerDivHeights();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php if ($isAdmin): ?>
|
<?php if ($isAdmin): ?>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
@@ -167,9 +148,7 @@ $cssFiles = [
|
|||||||
};
|
};
|
||||||
|
|
||||||
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
|
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
|
||||||
for (var i = 0; i < buttons.length; i++) {
|
for (var button of buttons) {
|
||||||
var button = buttons[i];
|
|
||||||
|
|
||||||
button.onclick = function() {
|
button.onclick = function() {
|
||||||
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
|
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-5
@@ -5,20 +5,21 @@ $jsFiles = [
|
|||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
<h2>Profile</h2>
|
<h2>Profile</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="profileForm" action="/profile" method="post">
|
<form id="profileForm" action="/profile" method="post">
|
||||||
<input class="big fullWidth" type="password" name="password" placeholder="Current password" autofocus>
|
|
||||||
<hr>
|
|
||||||
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
|
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
|
||||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
|
||||||
<input class="big fullWidth marginTop" type="password" name="password_new" placeholder="New password" minlength="6">
|
<input class="big fullWidth marginTop" type="password" name="password_new" placeholder="New password" autofocus>
|
||||||
<input class="big fullWidth marginTop" type="password" name="password_new_confirm" placeholder="New password confirmation" minlength="6">
|
<input class="big fullWidth marginTop" type="password" name="password_new_confirm" placeholder="New password confirmation">
|
||||||
|
<hr>
|
||||||
|
<input class="big fullWidth" type="password" name="password" placeholder="Current password">
|
||||||
<p id="profileFormError" class="formError justify marginTop"></p>
|
<p id="profileFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit" name="save" disabled>Save</button>
|
<button type="submit" name="save" disabled>Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
<h2>Account activation</h2>
|
<h2>Account activation</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
|
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
|
||||||
</div>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
<h2>Account cancellation</h2>
|
<h2>Account cancellation</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<?php if ($success) : ?>
|
<?php if ($success) : ?>
|
||||||
@@ -8,5 +9,5 @@
|
|||||||
<p class="error justify">Cancellation failed. Please check the link you entered! Maybe the account was already deleted, in this case no further action is required.</p>
|
<p class="error justify">Cancellation failed. Please check the link you entered! Maybe the account was already deleted, in this case no further action is required.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/footer.php'; ?>
|
</div>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
$jsFiles = [
|
||||||
|
'js/signup.js',
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
||||||
|
<?php require ROOT . '/views/templates/header.php'; ?>
|
||||||
|
<div class="main">
|
||||||
|
<h2>Sign up</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="signupForm" action="/signup" method="post">
|
||||||
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation">
|
||||||
|
<p id="signupFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button type="submit">Sign up</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
</main>
|
|
||||||
<footer>
|
|
||||||
<p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!--
|
|
||||||
--><p>© Pőcze Bence <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
|
|
||||||
</footer>
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
<header>
|
<div class="header">
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
<a href="/" title="MapGuesser">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
||||||
--><span><?= $_ENV['APP_NAME'] ?></span>
|
<span>MapGuesser</span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p class="header">
|
||||||
<?php if (Container::$request->user()) : ?>
|
<?php if (Container::$request->user()) : ?>
|
||||||
<span><a href="/profile" title="Profile">
|
<span><a href="/profile" title="Profile">
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||||
</svg><!--
|
</svg>
|
||||||
--><?= Container::$request->user()->getDisplayName() ?><!--
|
<?= Container::$request->user()->getDisplayName() ?><!--
|
||||||
--></a></span><!--
|
--></a></span><!--
|
||||||
--><span><a href="/logout" title="Logout">Logout</a></span>
|
--><span><a href="/logout" title="Logout">Logout</a></span>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
@@ -20,5 +20,4 @@
|
|||||||
--><span><a href="/login" title="Login">Login</a></span>
|
--><span><a href="/login" title="Login">Login</a></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</div>
|
||||||
<main>
|
|
||||||
@@ -14,30 +14,5 @@
|
|||||||
<script src="<?= $jsFile ?>"></script>
|
<script src="<?= $jsFile ?>"></script>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
MapGuesser = {
|
|
||||||
cookiesAgreed: false,
|
|
||||||
|
|
||||||
agreeCookies: function () {
|
|
||||||
if (MapGuesser.cookiesAgreed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expirationDate = new Date(new Date().getTime() + 20 * 365 * 24 * 60 * 60 * 1000).toUTCString();
|
|
||||||
document.cookie = 'COOKIES_CONSENT=1; expires=' + expirationDate + '; path=/';
|
|
||||||
|
|
||||||
MapGuesser.cookiesAgreed = true;
|
|
||||||
document.getElementById('cookiesNotice').style.display = 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onclick = function () {
|
|
||||||
MapGuesser.agreeCookies();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php endif; ?>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><?= $_ENV['APP_NAME'] ?></title>
|
<title>MapGuesser</title>
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
|
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
|
||||||
<?php if (isset($cssFiles)) : ?>
|
<?php if (isset($cssFiles)) : ?>
|
||||||
<?php foreach ($cssFiles as $cssFile) : ?>
|
<?php foreach ($cssFiles as $cssFile) : ?>
|
||||||
@@ -22,14 +22,6 @@
|
|||||||
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
|
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
|
|
||||||
<div id="cookiesNotice">
|
|
||||||
<p class="small">
|
|
||||||
<?= $_ENV['APP_NAME'] ?> uses cookies to improve user experience. By using the app or clicking I agree, you consent to our use of cookies.
|
|
||||||
</p>
|
|
||||||
<button id="agreeCookies" class="small marginTop">I agree</button>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,30 +12,17 @@ if (!empty($_ENV['DEV'])) {
|
|||||||
|
|
||||||
Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
|
Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
|
||||||
|
|
||||||
Container::$routeCollection->get('index', '', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
||||||
Container::$routeCollection->group('login', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
||||||
$routeCollection->get('login', '', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
|
||||||
$routeCollection->post('login-action', '', [MapGuesser\Controller\LoginController::class, 'login']);
|
Container::$routeCollection->get('signup', 'signup', [MapGuesser\Controller\SignupController::class, 'getSignupForm']);
|
||||||
$routeCollection->get('login-google', 'google', [MapGuesser\Controller\LoginController::class, 'getGoogleLoginRedirect']);
|
Container::$routeCollection->post('signup-action', 'signup', [MapGuesser\Controller\SignupController::class, 'signup']);
|
||||||
$routeCollection->get('login-google-action', 'google/code', [MapGuesser\Controller\LoginController::class, 'loginWithGoogle']);
|
Container::$routeCollection->get('signup.activate', 'signup/activate/{token}', [MapGuesser\Controller\SignupController::class, 'activate']);
|
||||||
});
|
Container::$routeCollection->get('signup.cancel', 'signup/cancel/{token}', [MapGuesser\Controller\SignupController::class, 'cancel']);
|
||||||
Container::$routeCollection->group('signup', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
|
||||||
$routeCollection->get('signup', '', [MapGuesser\Controller\LoginController::class, 'getSignupForm']);
|
|
||||||
$routeCollection->post('signup-action', '', [MapGuesser\Controller\LoginController::class, 'signup']);
|
|
||||||
$routeCollection->get('signup-google', 'google', [MapGuesser\Controller\LoginController::class, 'getSignupWithGoogleForm']);
|
|
||||||
$routeCollection->post('signup-google-action', 'google', [MapGuesser\Controller\LoginController::class, 'signupWithGoogle']);
|
|
||||||
$routeCollection->post('signup.reset', 'reset', [MapGuesser\Controller\LoginController::class, 'resetSignup']);
|
|
||||||
$routeCollection->post('signup-google.reset', 'google/reset', [MapGuesser\Controller\LoginController::class, 'resetGoogleSignup']);
|
|
||||||
$routeCollection->get('signup.success', 'success', [MapGuesser\Controller\LoginController::class, 'getSignupSuccess']);
|
|
||||||
$routeCollection->get('signup.activate', 'activate/{token}', [MapGuesser\Controller\LoginController::class, 'activate']);
|
|
||||||
$routeCollection->get('signup.cancel', 'cancel/{token}', [MapGuesser\Controller\LoginController::class, 'cancel']);
|
|
||||||
});
|
|
||||||
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
||||||
Container::$routeCollection->group('profile', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
Container::$routeCollection->get('profile', 'profile', [MapGuesser\Controller\UserController::class, 'getProfile']);
|
||||||
$routeCollection->get('profile', '', [MapGuesser\Controller\UserController::class, 'getProfile']);
|
Container::$routeCollection->post('profile-action', 'profile', [MapGuesser\Controller\UserController::class, 'saveProfile']);
|
||||||
$routeCollection->post('profile-action', '', [MapGuesser\Controller\UserController::class, 'saveProfile']);
|
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
||||||
});
|
|
||||||
//Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
|
||||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||||
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
||||||
$routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
|
$routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
|
||||||
@@ -49,7 +36,6 @@ Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCo
|
|||||||
$routeCollection->post('admin.deleteMap', 'deleteMap/{mapId}', [MapGuesser\Controller\MapAdminController::class, 'deleteMap']);
|
$routeCollection->post('admin.deleteMap', 'deleteMap/{mapId}', [MapGuesser\Controller\MapAdminController::class, 'deleteMap']);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isset($_COOKIE['COOKIES_CONSENT'])) {
|
|
||||||
Container::$sessionHandler = new MapGuesser\Session\DatabaseSessionHandler();
|
Container::$sessionHandler = new MapGuesser\Session\DatabaseSessionHandler();
|
||||||
|
|
||||||
session_set_save_handler(Container::$sessionHandler, true);
|
session_set_save_handler(Container::$sessionHandler, true);
|
||||||
@@ -59,9 +45,6 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) {
|
|||||||
'cookie_httponly' => true,
|
'cookie_httponly' => true,
|
||||||
'cookie_samesite' => 'Lax'
|
'cookie_samesite' => 'Lax'
|
||||||
]);
|
]);
|
||||||
} else {
|
|
||||||
$_SESSION = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
|
Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user