Compare commits
131
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e645a1313 | ||
|
|
9c9edcbbd1
|
||
|
|
de009637de | ||
|
|
6fce002458
|
||
|
|
1287fa0bc9
|
||
|
|
bf8e5b5f4d
|
||
|
|
c8a49396c2
|
||
|
|
c2323c4c89
|
||
|
|
60cbe64718
|
||
|
|
c48bbdc3c2
|
||
|
|
ee6aa59253 | ||
|
|
e1df28123f
|
||
|
|
b4769b62dd | ||
|
|
bee991e300 | ||
|
|
ddc56426c2 | ||
|
|
c06dd1e1d2 | ||
|
|
fd657b4244 | ||
|
|
532f099ec9 | ||
|
|
ec969167e4 | ||
|
|
e4a51c139f | ||
|
|
27115e44b6 | ||
|
|
45904a98c2 | ||
|
|
b1ae7391e7 | ||
|
|
8987b563dd | ||
|
|
70d8807f38 | ||
|
|
910fdddf34 | ||
|
|
87c4c06aa6 | ||
|
|
ad24f8ac28 | ||
|
|
e2067627c4 | ||
|
|
a8419535e1 | ||
|
|
725d14400b | ||
|
|
b75eb16b3d | ||
|
|
f8a570d0d5 | ||
|
|
e7ee7bbe8e | ||
|
|
2f4f66cc94 | ||
|
|
4afbc046ff | ||
|
|
79bd5b98d5 | ||
|
|
c1cdeba1d3 | ||
|
|
255168ffb6 | ||
|
|
ca0b8da733 | ||
|
|
34206cac12 | ||
|
|
47a2f15fc5 | ||
|
|
17658f70ba | ||
|
|
500ec958cb | ||
|
|
d2fa2050ac | ||
|
|
e8be433ee6 | ||
|
|
506072017c | ||
|
|
4561c825a4 | ||
|
|
693bddbcf0 | ||
|
|
eae7cefae1 | ||
|
|
212728e138 | ||
|
|
0d627f297e | ||
|
|
f07ae979a2 | ||
|
|
8bdbbafcff | ||
|
|
3856ccb576 | ||
|
|
9f839d3f5d | ||
|
|
721b611cdb | ||
|
|
70fb30f673 | ||
|
|
e200dab2a4 | ||
|
|
e8c246f002 | ||
|
|
251bf1be3d | ||
|
|
0be20a6097 | ||
|
|
d5d3563ddc | ||
|
|
19aa4098e5 | ||
|
|
394c8b8830 | ||
|
|
dc4ba4b5a4 | ||
|
|
eda4e00378 | ||
|
|
dfc74375d7 | ||
|
|
dc1aedd28f | ||
|
|
787427b211 | ||
|
|
05b1a84168 | ||
|
|
e2a0da3dd2 | ||
|
|
e751fdaa3e | ||
|
|
675d7e60d0 | ||
|
|
0d097aacb5 | ||
|
|
98cd15e91f | ||
|
|
4f680db8f3 | ||
|
|
ea98af0681 | ||
|
|
05934d6245 | ||
|
|
3ab8ac5fe6 | ||
|
|
94e60e4ac5 | ||
|
|
9df5028c20 | ||
|
|
49bd03f303 | ||
|
|
7210e12656 | ||
|
|
8320b94210 | ||
|
|
28aa762153 | ||
|
|
33eb154112 | ||
|
|
a2ca7888f4 | ||
|
|
e0395132d6 | ||
|
|
aeaf0fe8d3 | ||
|
|
d96ac938f8 | ||
|
|
cb5229a5e8 | ||
|
|
5a4fe48a9e | ||
|
|
02d6298d95 | ||
|
|
a56ce849c0 | ||
|
|
87d476065d | ||
|
|
df60efbb8c | ||
|
|
6338e35cfb | ||
|
|
1cfbef418e | ||
|
|
a8777b897b | ||
|
|
285f2dd0ac | ||
|
|
10b7766458 | ||
|
|
9697163457 | ||
|
|
d1c9e221f7 | ||
|
|
2d2e218002 | ||
|
|
7ba11f34cc | ||
|
|
99c72d99be | ||
|
|
295570a28d | ||
|
|
e612d0d93f | ||
|
|
a2d2b94cc6 | ||
|
|
80b850fdd7 | ||
|
|
0bf6e900fe | ||
|
|
bd46809d3a | ||
|
|
821a9d80c0 | ||
|
|
eafa98571e | ||
|
|
316e39f2f2 | ||
|
|
5ff720e3c6 | ||
|
|
a9eec05288 | ||
|
|
d6750777c2 | ||
|
|
1a4d928143 | ||
|
|
b35668c2fb | ||
|
|
5814620b4f | ||
|
|
64b6197bd4 | ||
|
|
1be51b3e07 | ||
|
|
43775c8f4f | ||
|
|
95eb956403 | ||
|
|
ce96e15911 | ||
|
|
3726573f4c | ||
|
|
a8913b0d44 | ||
|
|
e014bf9dc1 | ||
|
|
50c7e3972c |
No files matched your search
@@ -1,3 +1,4 @@
|
|||||||
|
APP_NAME=MapQuiz
|
||||||
DEV=1
|
DEV=1
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
@@ -11,3 +12,6 @@ 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
|
||||||
|
GOOGLE_ANALITICS_ID=your_google_analytics_id
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
installed
|
installed
|
||||||
|
unit_test_results.xml
|
||||||
vendor
|
vendor
|
||||||
@@ -1,5 +1,63 @@
|
|||||||
# MapGuesser
|
# MapGuesser
|
||||||
|
|
||||||
This is the MapGuesser Application project.
|
This is the MapGuesser Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications.
|
||||||
|
|
||||||
License: GNU AGPL 3.0
|
## Installation
|
||||||
|
|
||||||
|
### Clone the Git repository
|
||||||
|
|
||||||
|
The first step is obviously cloning the repository to your machine:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://bitbucket.org/esoko/mapguesser.git
|
||||||
|
```
|
||||||
|
|
||||||
|
All the commands listed here should be executed from the repository root.
|
||||||
|
|
||||||
|
### (Optional) Setup Docker stack
|
||||||
|
|
||||||
|
The easiest way to build up a fully working application with web server and database is to use Docker Compose with the included `docker-compose.yml`.
|
||||||
|
|
||||||
|
All you have to do is executing the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Attach shell to the container of `mapguesser_app`. All of the following commands should be executed there.
|
||||||
|
|
||||||
|
**If you don't use the Docker stack you need to install your environment manually. Check `docker-compose.yml` and `docker/Dockerfile` to see the system requirements.**
|
||||||
|
|
||||||
|
### Initialize project
|
||||||
|
|
||||||
|
This command installes all of the Composer requirements and creates a copy of the example `.env` file.
|
||||||
|
|
||||||
|
```
|
||||||
|
composer create-project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Set environment variables
|
||||||
|
|
||||||
|
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment.
|
||||||
|
|
||||||
|
**You should set here the API keys that enable playing the game. Without these API keys the application cannot work well. To get Google API keys visit this page: https://console.developers.google.com/**
|
||||||
|
|
||||||
|
One very important variable is `DEV`. This indicates that the application operates in development (staging) and not in produciton mode.
|
||||||
|
|
||||||
|
If you install the application in the Docker stack for development (staging) environment, only the variables for external dependencies (API keys, map attribution, etc.) should be adapted. All other variables (for DB connection, static root, mailing, etc.) are fine with the default value.
|
||||||
|
|
||||||
|
### Finalize installation
|
||||||
|
|
||||||
|
After you set the environment variables in the `.env` file, execute the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
scripts/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**And you are done!** The application is ready to use and develop.
|
||||||
|
|
||||||
|
If you installed it in the Docker stack, you can reach it on http://localhost. The mails that are sent by the application can be found on http://localhost:8080/. If needed, the database server can be directly reached on localhost:3306.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*License: **GNU AGPL 3.0**. Full license text can be found in file `LICENSE`.*
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
image: php:7.4.7-cli-buster
|
||||||
|
|
||||||
|
pipelines:
|
||||||
|
default:
|
||||||
|
- step:
|
||||||
|
name: Unit Testing
|
||||||
|
caches:
|
||||||
|
- composer
|
||||||
|
artifacts:
|
||||||
|
- unit_test_results.xml
|
||||||
|
script:
|
||||||
|
- apt-get update && apt-get install -y unzip
|
||||||
|
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
|
- composer install
|
||||||
|
- vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
+3
-1
@@ -8,7 +8,9 @@
|
|||||||
"symfony/console": "^5.1",
|
"symfony/console": "^5.1",
|
||||||
"phpmailer/phpmailer": "^6.1"
|
"phpmailer/phpmailer": "^6.1"
|
||||||
},
|
},
|
||||||
"require-dev": {},
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9"
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"MapGuesser\\": "src"
|
"MapGuesser\\": "src"
|
||||||
|
|||||||
Generated
+1701
-2
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,8 @@
|
|||||||
|
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;
|
||||||
+3
-1
@@ -10,7 +10,9 @@ services:
|
|||||||
- 'mariadb'
|
- 'mariadb'
|
||||||
- 'mail'
|
- 'mail'
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.1
|
image: mariadb:10.3
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
volumes:
|
volumes:
|
||||||
- mysql:/var/lib/mysql
|
- mysql:/var/lib/mysql
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
+3
-3
@@ -4,8 +4,8 @@ ENV DEBIAN_FRONTEND noninteractive
|
|||||||
|
|
||||||
# Install Nginx, PHP and further necessary packages
|
# Install Nginx, PHP and further necessary packages
|
||||||
RUN apt update --fix-missing
|
RUN apt update --fix-missing
|
||||||
RUN apt install -y curl git mariadb-client nginx \
|
RUN apt install -y curl git unzip mariadb-client nginx \
|
||||||
php-apcu php-xdebug php7.4-cli php7.4-curl php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip
|
php-apcu php-xdebug php7.4-cli php7.4-curl php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip php7.4-xml
|
||||||
|
|
||||||
# Configure Nginx with PHP
|
# Configure Nginx with PHP
|
||||||
RUN mkdir -p /run/php
|
RUN mkdir -p /run/php
|
||||||
@@ -21,7 +21,7 @@ RUN ./install-composer.sh
|
|||||||
# Install Node.js and required packages
|
# Install Node.js and required packages
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||||
RUN apt install -y nodejs
|
RUN apt install -y nodejs
|
||||||
RUN npm install -g uglify-js clean-css-cli svgo
|
RUN npm install -g uglify-js clean-css-cli svgo yarn
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
VOLUME /var/www/mapguesser
|
VOLUME /var/www/mapguesser
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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>
|
||||||
+5
-4
@@ -1,13 +1,14 @@
|
|||||||
Hi,
|
Hi,
|
||||||
<br><br>
|
<br><br>
|
||||||
You recently signed up on MapGuesser with this email address ({{EMAIL}}). To activate your account, please click on the following link:<br>
|
You recently signed up on {{APP_NAME}} 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 MapGuesser or changed your mind, no further action is required, your email address will be deleted soon.<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>
|
||||||
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 MapGuesser!
|
Have fun on {{APP_NAME}}!
|
||||||
<br><br>
|
<br><br>
|
||||||
Regards,<br>
|
Regards,<br>
|
||||||
MapGuesser
|
{{APP_NAME}}<br>
|
||||||
|
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
define('SCRIPT_STARTED', hrtime(true));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
const ROOT = __DIR__;
|
const ROOT = __DIR__;
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ $app = new Symfony\Component\Console\Application('MapGuesser Console', '');
|
|||||||
|
|
||||||
$app->add(new MapGuesser\Cli\DatabaseMigration());
|
$app->add(new MapGuesser\Cli\DatabaseMigration());
|
||||||
$app->add(new MapGuesser\Cli\AddUserCommand());
|
$app->add(new MapGuesser\Cli\AddUserCommand());
|
||||||
|
$app->add(new MapGuesser\Cli\LinkViewCommand());
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
+4
-3
@@ -9,7 +9,7 @@ if (($pos = strpos($url, '?')) !== false) {
|
|||||||
}
|
}
|
||||||
$url = rawurldecode($url);
|
$url = rawurldecode($url);
|
||||||
|
|
||||||
$match = Container::$routeCollection->match($method, explode('/', $url));
|
$match = Container::$routeCollection->match($method, $url == '' ? [] : explode('/', $url));
|
||||||
|
|
||||||
if ($match !== null) {
|
if ($match !== null) {
|
||||||
list($route, $params) = $match;
|
list($route, $params) = $match;
|
||||||
@@ -40,12 +40,13 @@ if ($match !== null) {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||||
header('Location: ' . Container::$request->getBase() . '/' . $response->getUrl(), true, $response->getHttpCode());
|
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$content = new MapGuesser\Response\HtmlContent('error/404');
|
||||||
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
||||||
require ROOT . '/views/error/404.php';
|
echo $content->render();
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
#panorama {
|
#panorama {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
height: calc(100% - 40px);
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guessCover {
|
#panoCover {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 40px;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -111,7 +114,7 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
#guess {
|
#guess {
|
||||||
top: 50px;
|
top: 10px;
|
||||||
left: 20px;
|
left: 20px;
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -157,7 +160,7 @@
|
|||||||
#guess.result {
|
#guess.result {
|
||||||
width: initial;
|
width: initial;
|
||||||
height: initial;
|
height: initial;
|
||||||
top: 50px;
|
top: 10px;
|
||||||
left: 50px;
|
left: 50px;
|
||||||
right: 50px;
|
right: 50px;
|
||||||
bottom: 50px;
|
bottom: 50px;
|
||||||
@@ -167,11 +170,11 @@
|
|||||||
}
|
}
|
||||||
@media screen and (max-height: 424px) {
|
@media screen and (max-height: 424px) {
|
||||||
#guess {
|
#guess {
|
||||||
top: 50px;
|
top: 10px;
|
||||||
height: initial;
|
height: initial;
|
||||||
}
|
}
|
||||||
#guess.adapt:hover {
|
#guess.adapt:hover {
|
||||||
top: 50px;
|
top: 10px;
|
||||||
height: initial;
|
height: initial;
|
||||||
}
|
}
|
||||||
#guess.result {
|
#guess.result {
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
#map {
|
#map {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
height: calc(100% - 40px);
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* modify the cursor for the Leaflet map */
|
||||||
.leaflet-container {
|
.leaflet-container {
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
}
|
}
|
||||||
@@ -16,6 +20,8 @@
|
|||||||
|
|
||||||
#noPano {
|
#noPano {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -23,12 +29,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#noPano>p {
|
#noPano>p {
|
||||||
margin: auto;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#control {
|
#control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
width: 125px;
|
width: 125px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
@@ -48,31 +54,34 @@
|
|||||||
|
|
||||||
@media screen and (max-width: 999px) and (min-height: 600px) {
|
@media screen and (max-width: 999px) and (min-height: 600px) {
|
||||||
#map.selected {
|
#map.selected {
|
||||||
height: calc(50% - 20px);
|
height: 50%;
|
||||||
}
|
}
|
||||||
#panorama, #noPano {
|
#panorama, #noPano {
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: calc(50% - 20px);
|
height: 50%;
|
||||||
}
|
}
|
||||||
#placeControl {
|
#placeControl {
|
||||||
top: calc(50% + 30px);
|
top: calc(50% + 10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1000px), (max-height: 599px) {
|
@media screen and (min-width: 1000px), (max-height: 599px) {
|
||||||
#map.selected {
|
#map.selected {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
#panorama, #noPano {
|
#panorama, #noPano {
|
||||||
top: 40px;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
#placeControl {
|
#placeControl {
|
||||||
top: 50px;
|
top: 10px;
|
||||||
}
|
}
|
||||||
#modified.selected {
|
#modified.selected {
|
||||||
right: calc(50% + 10px);
|
right: calc(50% + 10px);
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ button::-moz-focus-inner, input::-moz-focus-inner {
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* to be compatible with browsers that don't know <main> */
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background-color: #28a745;
|
background-color: #28a745;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@@ -42,7 +47,7 @@ h1>a:hover, h1>a:focus {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, div.header.small h1 {
|
h2, header.small h1 {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,8 +88,8 @@ hr {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
p.small, span.small {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justify {
|
.justify {
|
||||||
@@ -107,6 +112,10 @@ hr {
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -115,6 +124,7 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +153,13 @@ 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;
|
||||||
@@ -246,12 +263,11 @@ 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;
|
||||||
z-index: 3;
|
overflow-y: auto;
|
||||||
|
z-index: 6;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +279,7 @@ div.modal {
|
|||||||
right: 0;
|
right: 0;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
z-index: 2;
|
z-index: 5;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +292,7 @@ p.formError {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header {
|
header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
@@ -286,33 +302,51 @@ div.header {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header.small {
|
header.small {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>p.header {
|
header>p {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>p.header>span {
|
header>p>span {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>p.header>span>a:link, div.header>p.header>span>a:visited {
|
header>p>span>a:link, header>p>span>a:visited {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>p.header>span:not(:last-child) {
|
header>p>span:not(:last-child) {
|
||||||
border-right: solid white 1px;
|
border-right: solid white 1px;
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.main {
|
main {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main.full {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -321,6 +355,12 @@ 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;
|
||||||
@@ -329,7 +369,7 @@ div.buttonContainer>button {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -32px;
|
margin-top: -32px;
|
||||||
margin-left: -32px;
|
margin-left: -32px;
|
||||||
z-index: 5;
|
z-index: 7;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,16 +383,27 @@ div.box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
div.header h1 span {
|
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;
|
||||||
@@ -360,9 +411,48 @@ 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,17 +29,16 @@ 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: #cccccc;
|
background-color: #cccccc;
|
||||||
}
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
div.mapItem>div.imgContainer>img {
|
background-size: cover;
|
||||||
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 {
|
||||||
@@ -47,6 +46,12 @@ div.mapItem>div.inner>div.info {
|
|||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.inner>div.description {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
div.mapItem>div.inner>div.info>p:nth-child(1) {
|
div.mapItem>div.inner>div.info>p:nth-child(1) {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
initialize: function () {
|
initialize: function () {
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
document.getElementById('guessCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS);
|
document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
document.getElementById('currentScoreSum').innerHTML = '0/0';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
MapGuesser.httpRequest('GET', '/game/' + mapId + '/newPlace.json', function () {
|
MapGuesser.httpRequest('GET', '/game/' + mapId + '/newPlace.json', function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
document.getElementById('guessCover').style.visibility = 'hidden';
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
|
|
||||||
if (this.response.error) {
|
if (this.response.error) {
|
||||||
//TODO: handle this error
|
//TODO: handle this error
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
lastRound.line.setVisible(false);
|
lastRound.line.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('guessCover').style.visibility = 'hidden';
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
document.getElementById('showGuessButton').style.visibility = null;
|
document.getElementById('showGuessButton').style.visibility = null;
|
||||||
document.getElementById('guess').style.visibility = null;
|
document.getElementById('guess').style.visibility = null;
|
||||||
document.getElementById('guess').classList.remove('result')
|
document.getElementById('guess').classList.remove('result')
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
document.getElementById('guess').classList.remove('adapt');
|
document.getElementById('guess').classList.remove('adapt');
|
||||||
}
|
}
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
document.getElementById('guessCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
|
|
||||||
var data = new FormData();
|
var data = new FormData();
|
||||||
data.append('lat', String(guessPosition.lat));
|
data.append('lat', String(guessPosition.lat));
|
||||||
@@ -370,6 +370,14 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MapGuesser.sessionAvailableHooks.reinitializeGame = function () {
|
||||||
|
MapGuesser.httpRequest('GET', '/game/' + mapId + '/json', function () {
|
||||||
|
mapBounds = this.response.bounds;
|
||||||
|
|
||||||
|
Game.initialize();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (!('ontouchstart' in document.documentElement)) {
|
if (!('ontouchstart' in document.documentElement)) {
|
||||||
Game.adaptGuess = true;
|
Game.adaptGuess = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var form = document.getElementById('loginForm');
|
|
||||||
|
|
||||||
form.onsubmit = function (e) {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var formData = new FormData(form);
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', form.action, function () {
|
|
||||||
if (this.response.error) {
|
|
||||||
var errorText;
|
|
||||||
switch (this.response.error) {
|
|
||||||
case 'user_not_found':
|
|
||||||
errorText = 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up here</a>!';
|
|
||||||
break;
|
|
||||||
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!';
|
|
||||||
break;
|
|
||||||
case 'password_not_match':
|
|
||||||
errorText = 'The given password is wrong.'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
var loginFormError = document.getElementById('loginFormError');
|
|
||||||
loginFormError.style.display = 'block';
|
|
||||||
loginFormError.innerHTML = errorText;
|
|
||||||
|
|
||||||
form.elements.email.select();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.replace('/');
|
|
||||||
}, formData);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
(function () {
|
||||||
|
document.getElementById('cancelGoogleSignupButton').onclick = function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', '/signup/google/reset', function () {
|
||||||
|
window.location.replace('/signup');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
(function () {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -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 };
|
return { ppi: 320, tileSize: 128, zoomOffset: 1, minZoom: 0, maxZoom: 18 };
|
||||||
} else if (window.devicePixelRatio >= 2) {
|
} else if (window.devicePixelRatio >= 2) {
|
||||||
return { ppi: 250, tileSize: 256, zoomOffset: 0 };
|
return { ppi: 250, tileSize: 256, zoomOffset: 0, minZoom: 1, maxZoom: 19 };
|
||||||
} else {
|
} else {
|
||||||
return { ppi: 72, tileSize: 512, zoomOffset: -1 };
|
return { ppi: 72, tileSize: 512, zoomOffset: -1, minZoom: 2, maxZoom: 20 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -368,8 +368,8 @@
|
|||||||
ppi: highResData.ppi,
|
ppi: highResData.ppi,
|
||||||
tileSize: highResData.tileSize,
|
tileSize: highResData.tileSize,
|
||||||
zoomOffset: highResData.zoomOffset,
|
zoomOffset: highResData.zoomOffset,
|
||||||
minZoom: 2,
|
minZoom: highResData.minZoom,
|
||||||
maxZoom: 20
|
maxZoom: highResData.maxZoom
|
||||||
}).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 }));
|
||||||
|
|||||||
@@ -1,12 +1,58 @@
|
|||||||
var MapGuesser = {
|
var MapGuesser = {
|
||||||
|
cookiesAgreed: false,
|
||||||
|
sessionAvailableHooks: {},
|
||||||
|
|
||||||
|
initGoogleAnalitics: function () {
|
||||||
|
if (typeof GOOGLE_ANALITICS_ID === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global site tag (gtag.js) - Google Analytics
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + GOOGLE_ANALITICS_ID;
|
||||||
|
script.async = true;
|
||||||
|
|
||||||
|
document.head.appendChild(script);
|
||||||
|
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag() { dataLayer.push(arguments); }
|
||||||
|
gtag('js', new Date());
|
||||||
|
gtag('config', GOOGLE_ANALITICS_ID);
|
||||||
|
},
|
||||||
|
|
||||||
|
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.initGoogleAnalitics();
|
||||||
|
MapGuesser.httpRequest('GET', '/startSession.json', function () {
|
||||||
|
ANTI_CSRF_TOKEN = this.response.antiCsrfToken;
|
||||||
|
|
||||||
|
for (var hookId in MapGuesser.sessionAvailableHooks) {
|
||||||
|
if (!MapGuesser.sessionAvailableHooks.hasOwnProperty(hookId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapGuesser.sessionAvailableHooks[hookId]();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MapGuesser.cookiesAgreed = true;
|
||||||
|
},
|
||||||
|
|
||||||
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();
|
||||||
@@ -20,6 +66,52 @@ var MapGuesser = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setOnsubmitForForm: function (form) {
|
||||||
|
form.onsubmit = function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
var formData = new FormData(form);
|
||||||
|
var formError = form.getElementsByClassName('formError')[0];
|
||||||
|
var pageLeaveOnSuccess = form.dataset.redirectOnSuccess || form.dataset.reloadOnSuccess;
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', form.action, function () {
|
||||||
|
if (!pageLeaveOnSuccess) {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.response.error) {
|
||||||
|
if (pageLeaveOnSuccess) {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
formError.style.display = 'block';
|
||||||
|
formError.innerHTML = this.response.error.errorText;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.response.redirect) {
|
||||||
|
window.location.replace(this.response.redirect.target);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pageLeaveOnSuccess) {
|
||||||
|
formError.style.display = 'none';
|
||||||
|
form.reset();
|
||||||
|
} else {
|
||||||
|
if (form.dataset.redirectOnSuccess) {
|
||||||
|
window.location.replace(form.dataset.redirectOnSuccess);
|
||||||
|
} else if (form.dataset.reloadOnSuccess) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, formData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
showModal: function (id) {
|
showModal: function (id) {
|
||||||
document.getElementById(id).style.visibility = 'visible';
|
document.getElementById(id).style.visibility = 'visible';
|
||||||
document.getElementById('cover').style.visibility = 'visible';
|
document.getElementById('cover').style.visibility = 'visible';
|
||||||
@@ -90,11 +182,35 @@ var MapGuesser = {
|
|||||||
document.getElementById('cover').style.visibility = 'hidden';
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleDisableOnChange: function (input, button) {
|
observeInput: function (input, buttonToToggle) {
|
||||||
if (input.defaultValue !== input.value) {
|
if (input.defaultValue !== input.value) {
|
||||||
button.disabled = false;
|
buttonToToggle.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
button.disabled = true;
|
buttonToToggle.disabled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
observeInputsInForm: function (form, observedInputs) {
|
||||||
|
for (var i = 0; i < observedInputs.length; i++) {
|
||||||
|
var input = form.elements[observedInputs[i]];
|
||||||
|
|
||||||
|
switch (input.tagName) {
|
||||||
|
case 'INPUT':
|
||||||
|
case 'TEXTAREA':
|
||||||
|
input.oninput = function () {
|
||||||
|
MapGuesser.observeInput(this, form.elements.submit);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'SELECT':
|
||||||
|
input.onchange = function () {
|
||||||
|
MapGuesser.observeInput(this, form.elements.submit);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form.onreset = function () {
|
||||||
|
form.elements.submit.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -110,6 +226,21 @@ var MapGuesser = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var forms = document.getElementsByTagName('form');
|
||||||
|
for (var i = 0; i < forms.length; i++) {
|
||||||
|
var form = forms[i];
|
||||||
|
|
||||||
|
if (form.dataset.noSubmit) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapGuesser.setOnsubmitForForm(form);
|
||||||
|
|
||||||
|
if (form.dataset.observeInputs) {
|
||||||
|
MapGuesser.observeInputsInForm(form, form.dataset.observeInputs.split(','));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('cover').onclick = function () {
|
document.getElementById('cover').onclick = function () {
|
||||||
MapGuesser.hideModal();
|
MapGuesser.hideModal();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
(function () {
|
||||||
|
var Maps = {
|
||||||
|
descriptionDivs: null,
|
||||||
|
|
||||||
|
addStaticMaps: function () {
|
||||||
|
var imgContainers = document.getElementById('mapContainer').getElementsByClassName('imgContainer');
|
||||||
|
for (var i = 0; i < imgContainers.length; i++) {
|
||||||
|
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 + '")';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
initializeDescriptionDivs: function () {
|
||||||
|
Maps.descriptionDivs = document.getElementById('mapContainer').getElementsByClassName('description');
|
||||||
|
|
||||||
|
for (var i = 0; i < Maps.descriptionDivs.length; i++) {
|
||||||
|
var description = Maps.descriptionDivs[i];
|
||||||
|
var boundingClientRect = description.getBoundingClientRect();
|
||||||
|
|
||||||
|
description.defaultHeight = boundingClientRect.height;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateDescriptionDivHeights: function () {
|
||||||
|
var currentY;
|
||||||
|
var rows = [];
|
||||||
|
for (var i = 0; i < Maps.descriptionDivs.length; i++) {
|
||||||
|
var description = Maps.descriptionDivs[i];
|
||||||
|
var boundingClientRect = description.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (currentY !== boundingClientRect.y) {
|
||||||
|
rows.push([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rows[rows.length - 1].push(description);
|
||||||
|
|
||||||
|
currentY = boundingClientRect.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var row = rows[i];
|
||||||
|
|
||||||
|
var maxHeight = 0;
|
||||||
|
for (var j = 0; j < row.length; j++) {
|
||||||
|
var description = row[j];
|
||||||
|
|
||||||
|
if (description.defaultHeight > maxHeight) {
|
||||||
|
maxHeight = description.defaultHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < row.length; j++) {
|
||||||
|
var description = row[j];
|
||||||
|
|
||||||
|
description.style.height = maxHeight + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Maps.addStaticMaps();
|
||||||
|
|
||||||
|
Maps.initializeDescriptionDivs();
|
||||||
|
Maps.calculateDescriptionDivHeights();
|
||||||
|
|
||||||
|
window.onresize = function () {
|
||||||
|
Maps.calculateDescriptionDivHeights();
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
(function () {
|
||||||
|
Maps = {
|
||||||
|
deleteMap: function (mapId, mapName) {
|
||||||
|
MapGuesser.showModalWithContent('Delete map', 'Are you sure you want to delete map \'' + mapName + '\'?', [{
|
||||||
|
type: 'button',
|
||||||
|
classNames: ['red'],
|
||||||
|
text: 'Delete',
|
||||||
|
onclick: function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', '/admin/deleteMap/' + mapId, function () {
|
||||||
|
if (this.response.error) {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
//TODO: handle this error
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
|
||||||
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
|
var button = buttons[i];
|
||||||
|
|
||||||
|
button.onclick = function () {
|
||||||
|
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var form = document.getElementById('profileForm');
|
|
||||||
|
|
||||||
form.elements.password_new.onkeyup = function () {
|
|
||||||
MapGuesser.toggleDisableOnChange(this, form.elements.save);
|
|
||||||
};
|
|
||||||
|
|
||||||
form.elements.password_new_confirm.onkeyup = function () {
|
|
||||||
MapGuesser.toggleDisableOnChange(this, form.elements.save);
|
|
||||||
};
|
|
||||||
|
|
||||||
form.onsubmit = function (e) {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var formData = new FormData(form);
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', form.action, function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
if (this.response.error) {
|
|
||||||
var errorText;
|
|
||||||
switch (this.response.error) {
|
|
||||||
case 'password_not_match':
|
|
||||||
errorText = 'The given current password is wrong.'
|
|
||||||
break;
|
|
||||||
case 'passwords_too_short':
|
|
||||||
errorText = 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
|
||||||
break;
|
|
||||||
case 'passwords_not_match':
|
|
||||||
errorText = 'The given new passwords do not match.'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var profileFormError = document.getElementById('profileFormError');
|
|
||||||
profileFormError.style.display = 'block';
|
|
||||||
profileFormError.innerHTML = errorText;
|
|
||||||
|
|
||||||
form.elements.password_new.select();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('profileFormError').style.display = 'none';
|
|
||||||
form.reset();
|
|
||||||
form.elements.save.disabled = true;
|
|
||||||
form.elements.password_new.focus();
|
|
||||||
}, formData);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
(function () {
|
|
||||||
var form = document.getElementById('signupForm');
|
|
||||||
|
|
||||||
form.onsubmit = function (e) {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var formData = new FormData(form);
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', form.action, function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
if (this.response.error) {
|
|
||||||
var errorText;
|
|
||||||
switch (this.response.error) {
|
|
||||||
case 'passwords_too_short':
|
|
||||||
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
|
||||||
break;
|
|
||||||
case 'passwords_not_match':
|
|
||||||
errorText = 'The given passwords do not match.'
|
|
||||||
break;
|
|
||||||
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!';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var signupFormError = document.getElementById('signupFormError');
|
|
||||||
signupFormError.style.display = 'block';
|
|
||||||
signupFormError.innerHTML = errorText;
|
|
||||||
|
|
||||||
form.elements.email.select();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -21,6 +21,9 @@ echo "Migrating DB..."
|
|||||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
echo "Minifying JS, CSS and SVG files..."
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
${ROOT_DIR}/scripts/minify.sh
|
${ROOT_DIR}/scripts/minify.sh
|
||||||
|
|
||||||
|
echo "Linking view files..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg view:link)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
touch ${ROOT_DIR}/installed
|
touch ${ROOT_DIR}/installed
|
||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/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}
|
||||||
+8
-1
@@ -5,7 +5,11 @@ ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
|||||||
. ${ROOT_DIR}/.env
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
echo "Installing Composer packages..."
|
echo "Installing Composer packages..."
|
||||||
(cd ${ROOT_DIR} && composer install)
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
(cd ${ROOT_DIR} && composer install --no-dev)
|
||||||
|
else
|
||||||
|
(cd ${ROOT_DIR} && composer install --dev)
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
echo "Installing Yarn packages..."
|
||||||
(cd ${ROOT_DIR}/public/static && yarn install)
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
@@ -16,4 +20,7 @@ echo "Migrating DB..."
|
|||||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
echo "Minifying JS, CSS and SVG files..."
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
${ROOT_DIR}/scripts/minify.sh
|
${ROOT_DIR}/scripts/minify.sh
|
||||||
|
|
||||||
|
echo "Linking view files..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg view:link)
|
||||||
fi
|
fi
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Modify;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Model\User;
|
use MapGuesser\PersistentData\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,10 +20,8 @@ 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();
|
||||||
'email' => $input->getArgument('email'),
|
$user->setEmail($input->getArgument('email'));
|
||||||
]);
|
|
||||||
|
|
||||||
$user->setPlainPassword($input->getArgument('password'));
|
$user->setPlainPassword($input->getArgument('password'));
|
||||||
|
|
||||||
if ($input->hasArgument('type')) {
|
if ($input->hasArgument('type')) {
|
||||||
@@ -31,9 +29,8 @@ class AddUserCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$modify = new Modify(\Container::$dbConnection, 'users');
|
$pdm = new PersistentDataManager();
|
||||||
$modify->fill($user->toArray());
|
$pdm->saveToDb($user);
|
||||||
$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('');
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
|
use FilesystemIterator;
|
||||||
|
use MapGuesser\View\Linker;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class LinkViewCommand extends Command
|
||||||
|
{
|
||||||
|
public function configure()
|
||||||
|
{
|
||||||
|
$this->setName('view:link')
|
||||||
|
->setDescription('Linking of views.')
|
||||||
|
->addArgument('view', InputArgument::OPTIONAL, 'View file to be linked.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$views = [];
|
||||||
|
|
||||||
|
$view = $input->getArgument('view');
|
||||||
|
|
||||||
|
if ($view !== null) {
|
||||||
|
$views[] = $view;
|
||||||
|
} else {
|
||||||
|
$folder = ROOT . '/views';
|
||||||
|
$folderLength = strlen($folder) + 1;
|
||||||
|
|
||||||
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
|
||||||
|
foreach ($iterator as $file) {
|
||||||
|
if ($file->isDir() || $file->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view = substr($file->getPath(), $folderLength) . '/' . $file->getBasename('.php');
|
||||||
|
|
||||||
|
if (strpos($view, 'templates') === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$views[] = $view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($views as $view) {
|
||||||
|
$generator = new Linker($view);
|
||||||
|
$generator->generate();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln('<error>Linking view(s) failed!</error>');
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
$output->writeln((string) $e);
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('<info>View(s) successfully linked!</info>');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<?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;
|
||||||
@@ -37,18 +36,16 @@ 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['area'],
|
'area' => $map->getArea(),
|
||||||
'rounds' => []
|
'rounds' => []
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['mapId' => $mapId, 'mapName' => $map['name'], 'bounds' => $bounds->toArray()];
|
return ['mapId' => $mapId, 'mapName' => $map->getName(), 'bounds' => $map->getBounds()->toArray()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ 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
|
||||||
@@ -33,11 +34,14 @@ class GameFlowController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (count($state['rounds']) === 0) {
|
if (count($state['rounds']) === 0) {
|
||||||
$place = $this->placeRepository->getForMapWithValidPano($mapId);
|
$placesWithoutPano = [];
|
||||||
$state['rounds'][] = $place;
|
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, [], $placesWithoutPano);
|
||||||
|
|
||||||
|
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
$data = ['panoId' => $place['panoId']];
|
$data = ['panoId' => $place->getPanoIdCached()];
|
||||||
} else {
|
} else {
|
||||||
$rounds = count($state['rounds']);
|
$rounds = count($state['rounds']);
|
||||||
$last = $state['rounds'][$rounds - 1];
|
$last = $state['rounds'][$rounds - 1];
|
||||||
@@ -93,18 +97,20 @@ class GameFlowController
|
|||||||
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$place = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
|
$placesWithoutPano = [];
|
||||||
$state['rounds'][] = $place;
|
$place = $this->placeRepository->getRandomForMapWithValidPano($mapId, $exclude, $placesWithoutPano);
|
||||||
$session->set('state', $state);
|
|
||||||
|
|
||||||
$panoId = $place['panoId'];
|
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
||||||
|
|
||||||
|
$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(),
|
||||||
@@ -116,6 +122,16 @@ 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);
|
||||||
|
|||||||
@@ -1,12 +1,30 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Response\Redirect;
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
class HomeController
|
class HomeController
|
||||||
{
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
public function getIndex(): IRedirect
|
public function getIndex(): IRedirect
|
||||||
{
|
{
|
||||||
return new Redirect([\Container::$routeCollection->getRoute('maps'), []], IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('maps')->generateLink(), IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startSession(): IContent
|
||||||
|
{
|
||||||
|
// session starts with the request, this method just sends valid data to the client
|
||||||
|
|
||||||
|
$data = ['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')];
|
||||||
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,78 +1,396 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Http\Request;
|
||||||
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\Model\User;
|
use MapGuesser\Mailing\Mail;
|
||||||
|
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()
|
||||||
{
|
{
|
||||||
$session = $this->request->session();
|
if ($this->request->user() !== null) {
|
||||||
|
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', $data);
|
return new HtmlContent('login/login', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGoogleLoginRedirect(): IRedirect
|
||||||
|
{
|
||||||
|
$state = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$this->request->session()->set('oauth_state', $state);
|
||||||
|
|
||||||
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
|
$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
|
||||||
{
|
{
|
||||||
$session = $this->request->session();
|
if ($this->request->user() !== null) {
|
||||||
|
|
||||||
if ($session->get('user')) {
|
|
||||||
$data = ['success' => true];
|
$data = ['success' => true];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection, 'users');
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
$select->columns(User::getFields());
|
|
||||||
$select->where('email', '=', $this->request->post('email'));
|
|
||||||
|
|
||||||
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
if ($user === null) {
|
||||||
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
|
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
if ($userData === null) {
|
$tmpUser = new User();
|
||||||
$data = ['error' => 'user_not_found'];
|
$tmpUser->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
|
$this->request->session()->set('tmp_user_data', ['email' => $this->request->post('email'), 'password_hashed' => $tmpUser->getPassword()]);
|
||||||
|
|
||||||
|
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()]];
|
||||||
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' => ['errorText' => 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!']];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
$data = ['error' => 'password_not_match'];
|
$data = ['error' => ['errorText' => 'The given password is wrong.']];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$session->set('user', $user);
|
$this->request->setUser($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(new Request());
|
||||||
|
$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->session()->delete('user');
|
$this->request->setUser(null);
|
||||||
|
|
||||||
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function signup(): IContent
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]];
|
||||||
|
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' => ['errorText' => 'There is a user already registered with the given email address, but the given password is wrong.']];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->setUser($user);
|
||||||
|
|
||||||
|
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]];
|
||||||
|
} else {
|
||||||
|
$data = ['error' => ['errorText' => 'There is a user already registered with the given email address. Please check your email and click on the activation link!']];
|
||||||
|
}
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
||||||
|
$data = ['error' => ['errorText' => 'The given email address is 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' => ['errorText' => 'The given passwords do not match.']];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
|
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
|
$data = ['error' => ['errorText' => 'The given passwords do 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,13 +1,15 @@
|
|||||||
<?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;
|
||||||
@@ -21,6 +23,8 @@ 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;
|
||||||
@@ -28,6 +32,7 @@ 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();
|
||||||
}
|
}
|
||||||
@@ -45,18 +50,14 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
if ($mapId) {
|
if ($mapId) {
|
||||||
$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']);
|
$places = $this->getPlaces($map);
|
||||||
$places = $this->getPlaces($mapId);
|
|
||||||
} else {
|
} else {
|
||||||
$map = [
|
$map = new Map();
|
||||||
'name' => self::$unnamedMapName,
|
$map->setName(self::$unnamedMapName);
|
||||||
'description' => ''
|
|
||||||
];
|
|
||||||
$bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0);
|
|
||||||
$places = [];
|
$places = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = ['mapId' => $mapId, 'mapName' => $map['name'], 'mapDescription' => str_replace('<br>', "\n", $map['description']), 'bounds' => $bounds->toArray(), 'places' => &$places];
|
$data = ['mapId' => $mapId, 'mapName' => $map->getName(), 'mapDescription' => str_replace('<br>', "\n", $map->getDescription()), 'bounds' => $map->getBounds()->toArray(), 'places' => &$places];
|
||||||
return new HtmlContent('admin/map_editor', $data);
|
return new HtmlContent('admin/map_editor', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,9 +65,9 @@ class MapAdminController implements ISecured
|
|||||||
{
|
{
|
||||||
$placeId = (int) $this->request->query('placeId');
|
$placeId = (int) $this->request->query('placeId');
|
||||||
|
|
||||||
$placeData = $this->placeRepository->getById($placeId);
|
$place = $this->placeRepository->getById($placeId);
|
||||||
|
|
||||||
$data = ['panoId' => $placeData['panoId']];
|
$data = ['panoId' => $place->getFreshPanoId()];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,8 +77,12 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
if (!$mapId) {
|
if ($mapId) {
|
||||||
$mapId = $this->addNewMap();
|
$map = $this->mapRepository->getById($mapId);
|
||||||
|
} else {
|
||||||
|
$map = new Map();
|
||||||
|
$map->setName(self::$unnamedMapName);
|
||||||
|
$this->pdm->saveToDb($map);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_POST['added'])) {
|
if (isset($_POST['added'])) {
|
||||||
@@ -85,11 +90,18 @@ class MapAdminController implements ISecured
|
|||||||
foreach ($_POST['added'] as $placeRaw) {
|
foreach ($_POST['added'] as $placeRaw) {
|
||||||
$placeRaw = json_decode($placeRaw, true);
|
$placeRaw = json_decode($placeRaw, true);
|
||||||
|
|
||||||
$addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $this->placeRepository->addToMap($mapId, [
|
$place = new Place();
|
||||||
'lat' => (float) $placeRaw['lat'],
|
$place->setMap($map);
|
||||||
'lng' => (float) $placeRaw['lng'],
|
$place->setLat((float) $placeRaw['lat']);
|
||||||
'pano_id_cached_timestamp' => $placeRaw['panoId'] === -1 ? (new DateTime('-1 day'))->format('Y-m-d H:i:s') : null
|
$place->setLng((float) $placeRaw['lng']);
|
||||||
])];
|
|
||||||
|
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 = [];
|
||||||
@@ -99,10 +111,12 @@ class MapAdminController implements ISecured
|
|||||||
foreach ($_POST['edited'] as $placeRaw) {
|
foreach ($_POST['edited'] as $placeRaw) {
|
||||||
$placeRaw = json_decode($placeRaw, true);
|
$placeRaw = json_decode($placeRaw, true);
|
||||||
|
|
||||||
$this->placeRepository->modify((int) $placeRaw['id'], [
|
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
||||||
'lat' => (float) $placeRaw['lat'],
|
$place->setLat((float) $placeRaw['lat']);
|
||||||
'lng' => (float) $placeRaw['lng']
|
$place->setLng((float) $placeRaw['lng']);
|
||||||
]);
|
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,45 +124,42 @@ class MapAdminController implements ISecured
|
|||||||
foreach ($_POST['deleted'] as $placeRaw) {
|
foreach ($_POST['deleted'] as $placeRaw) {
|
||||||
$placeRaw = json_decode($placeRaw, true);
|
$placeRaw = json_decode($placeRaw, true);
|
||||||
|
|
||||||
$this->placeRepository->delete($placeRaw['id']);
|
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapBounds = $this->calculateMapBounds($mapId);
|
$mapBounds = $this->calculateMapBounds($map);
|
||||||
|
|
||||||
$map = [
|
$map->setBounds($mapBounds);
|
||||||
'bound_south_lat' => $mapBounds->getSouthLat(),
|
$map->setArea($mapBounds->calculateApproximateArea());
|
||||||
'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['name'] = $_POST['name'] ? $_POST['name'] : self::$unnamedMapName;
|
$map->setName($_POST['name'] ? $_POST['name'] : self::$unnamedMapName);
|
||||||
}
|
}
|
||||||
if (isset($_POST['description'])) {
|
if (isset($_POST['description'])) {
|
||||||
$map['description'] = str_replace(["\n", "\r\n"], '<br>', $_POST['description']);
|
$map->setDescription(str_replace(["\n", "\r\n"], '<br>', $_POST['description']));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->saveMapData($mapId, $map);
|
$this->pdm->saveToDb($map);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['mapId' => $mapId, 'added' => $addedIds];
|
$data = ['mapId' => $map->getId(), '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($mapId);
|
$this->deletePlaces($map);
|
||||||
|
|
||||||
$modify = new Modify(\Container::$dbConnection, 'maps');
|
$this->pdm->deleteFromDb($map);
|
||||||
$modify->setId($mapId);
|
|
||||||
$modify->delete();
|
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
@@ -156,26 +167,30 @@ class MapAdminController implements ISecured
|
|||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deletePlaces(int $mapId): void
|
private function deletePlaces(Map $map): void
|
||||||
{
|
{
|
||||||
|
//TODO: relations?
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
$select->columns(['id']);
|
$select->columns(Place::getFields());
|
||||||
$select->where('map_id', '=', $mapId);
|
$select->where('map_id', '=', $map->getId());
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($placeData = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$modify = new Modify(\Container::$dbConnection, 'places');
|
$place = new Place();
|
||||||
$modify->setId($place['id']);
|
|
||||||
$modify->delete();
|
$this->pdm->fillWithData($placeData, $place);
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateMapBounds(int $mapId): Bounds
|
private function calculateMapBounds(Map $map): 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', '=', $mapId);
|
$select->where('map_id', '=', $map->getId());
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
@@ -187,35 +202,12 @@ class MapAdminController implements ISecured
|
|||||||
return $bounds;
|
return $bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addNewMap(): int
|
private function &getPlaces(Map $map): array
|
||||||
{
|
|
||||||
$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', '=', $mapId);
|
$select->where('map_id', '=', $map->getId());
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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'],
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
<?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,9 +1,14 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Modify;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
|
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\PersistentDataManager;
|
||||||
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||||
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
|
|
||||||
@@ -11,9 +16,15 @@ class UserController implements ISecured
|
|||||||
{
|
{
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private UserConfirmationRepository $userConfirmationRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@@ -23,40 +34,81 @@ class UserController implements ISecured
|
|||||||
return $user !== null;
|
return $user !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProfile(): IContent
|
public function getAccount(): IContent
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
$data = ['user' => $user->toArray()];
|
$data = ['user' => $user->toArray()];
|
||||||
return new HtmlContent('profile', $data);
|
return new HtmlContent('account/account', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveProfile(): IContent
|
public function getDeleteAccount(): IContent
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
$data = ['user' => $user->toArray()];
|
||||||
|
return new HtmlContent('account/delete', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAccount(): 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'))) {
|
||||||
$data = ['error' => 'password_not_match'];
|
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
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' => 'passwords_too_short'];
|
$data = ['error' => ['errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!']];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
||||||
$data = ['error' => 'passwords_not_match'];
|
$data = ['error' => ['errorText' => 'The given new passwords do not match.']];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->setPlainPassword($this->request->post('password_new'));
|
$user->setPlainPassword($this->request->post('password_new'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$modify = new Modify(\Container::$dbConnection, 'users');
|
$this->pdm->saveToDb($user);
|
||||||
$modify->fill($user->toArray());
|
|
||||||
$modify->save();
|
$data = ['success' => true];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAccount(): IContent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
|
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
|
||||||
|
return new JsonContent($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
foreach ($this->userConfirmationRepository->getByUser($user) as $userConfirmation) {
|
||||||
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['success' => true];
|
$data = ['success' => true];
|
||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?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
|
||||||
@@ -14,8 +13,6 @@ 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;
|
||||||
@@ -116,48 +113,15 @@ class Modify
|
|||||||
|
|
||||||
private function update(): void
|
private function update(): void
|
||||||
{
|
{
|
||||||
/*$diff = $this->generateDiff();
|
$attributes = $this->attributes;
|
||||||
|
unset($attributes[$this->idName]);
|
||||||
|
|
||||||
if (count($diff) === 0) {
|
$set = $this->generateColumnsWithBinding(array_keys($attributes));
|
||||||
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($diff, [$this->idName => $this->attributes[$this->idName]]));
|
$stmt->execute(array_merge($attributes, [$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,10 +32,13 @@ class Select
|
|||||||
|
|
||||||
private array $limit;
|
private array $limit;
|
||||||
|
|
||||||
public function __construct(IConnection $connection, string $table)
|
public function __construct(IConnection $connection, ?string $table = null)
|
||||||
{
|
{
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->table = $table;
|
|
||||||
|
if ($table !== null) {
|
||||||
|
$this->table = $table;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setIdName(string $idName): Select
|
public function setIdName(string $idName): Select
|
||||||
@@ -52,6 +55,13 @@ 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);
|
||||||
|
|||||||
+16
-7
@@ -1,11 +1,10 @@
|
|||||||
<?php namespace MapGuesser\Http;
|
<?php namespace MapGuesser\Http;
|
||||||
|
|
||||||
class Request
|
use MapGuesser\Interfaces\Http\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Http\IResponse;
|
||||||
|
|
||||||
|
class Request implements IRequest
|
||||||
{
|
{
|
||||||
const HTTP_GET = 0;
|
|
||||||
|
|
||||||
const HTTP_POST = 1;
|
|
||||||
|
|
||||||
private string $url;
|
private string $url;
|
||||||
|
|
||||||
private int $method;
|
private int $method;
|
||||||
@@ -14,12 +13,22 @@ class Request
|
|||||||
|
|
||||||
private array $headers = [];
|
private array $headers = [];
|
||||||
|
|
||||||
public function __construct(string $url, int $method = self::HTTP_GET)
|
public function __construct(string $url = '', int $method = self::HTTP_GET)
|
||||||
{
|
{
|
||||||
$this->url = $url;
|
$this->url = $url;
|
||||||
$this->method = $method;
|
$this->method = $method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setUrl(string $url): void
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMethod(int $method): void
|
||||||
|
{
|
||||||
|
$this->method = $method;
|
||||||
|
}
|
||||||
|
|
||||||
public function setQuery($query)
|
public function setQuery($query)
|
||||||
{
|
{
|
||||||
if (is_string($query)) {
|
if (is_string($query)) {
|
||||||
@@ -34,7 +43,7 @@ class Request
|
|||||||
$this->headers = array_merge($this->headers, $headers);
|
$this->headers = array_merge($this->headers, $headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function send(): Response
|
public function send(): IResponse
|
||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<?php namespace MapGuesser\Http;
|
<?php namespace MapGuesser\Http;
|
||||||
|
|
||||||
class Response
|
use MapGuesser\Interfaces\Http\IResponse;
|
||||||
|
|
||||||
|
class Response implements IResponse
|
||||||
{
|
{
|
||||||
private string $body;
|
private string $body;
|
||||||
|
|
||||||
|
|||||||
@@ -8,5 +8,9 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Http;
|
||||||
|
|
||||||
|
interface IRequest
|
||||||
|
{
|
||||||
|
const HTTP_GET = 0;
|
||||||
|
|
||||||
|
const HTTP_POST = 1;
|
||||||
|
|
||||||
|
public function setUrl(string $url): void;
|
||||||
|
|
||||||
|
public function setMethod(int $method): void;
|
||||||
|
|
||||||
|
public function setQuery($query);
|
||||||
|
|
||||||
|
public function setHeaders(array $headers);
|
||||||
|
|
||||||
|
public function send(): IResponse;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Http;
|
||||||
|
|
||||||
|
interface IResponse
|
||||||
|
{
|
||||||
|
public function getBody();
|
||||||
|
|
||||||
|
public function getHeaders();
|
||||||
|
}
|
||||||
@@ -14,5 +14,7 @@ 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,6 +29,13 @@ 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);
|
||||||
}
|
}
|
||||||
@@ -58,8 +65,8 @@ class Mail
|
|||||||
$mailer->Mailer = 'mail';
|
$mailer->Mailer = 'mail';
|
||||||
}
|
}
|
||||||
|
|
||||||
$mailer->setFrom($_ENV['MAIL_FROM'], 'MapGuesser');
|
$mailer->setFrom($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
||||||
$mailer->addReplyTo($_ENV['MAIL_FROM'], 'MapGuesser');
|
$mailer->addReplyTo($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
||||||
|
|
||||||
$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;
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<?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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php namespace MapGuesser\OAuth;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Http\IRequest;
|
||||||
|
|
||||||
|
class GoogleOAuth
|
||||||
|
{
|
||||||
|
private static $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||||
|
|
||||||
|
private static $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
||||||
|
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->request->setUrl(self::$tokenUrlBase);
|
||||||
|
$this->request->setMethod(IRequest::HTTP_POST);
|
||||||
|
$this->request->setQuery($tokenParams);
|
||||||
|
$response = $this->request->send();
|
||||||
|
|
||||||
|
return json_decode($response->getBody(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<?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,27 +1,31 @@
|
|||||||
<?php namespace MapGuesser\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
class User extends BaseModel implements IUser
|
class User extends Model 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'];
|
||||||
|
|
||||||
protected static array $fields = ['email', 'password', 'type', 'active'];
|
private string $email = '';
|
||||||
|
|
||||||
private string $email;
|
private ?string $password = null;
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -43,12 +47,17 @@ class User extends BaseModel 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;
|
||||||
}
|
}
|
||||||
@@ -63,6 +72,11 @@ class User extends BaseModel 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) {
|
||||||
@@ -75,6 +89,11 @@ class User extends BaseModel implements IUser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUniqueId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
public function getDisplayName(): string
|
public function getDisplayName(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
$select = $this->createSelect($select, $type, $withRelations);
|
||||||
|
|
||||||
|
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($data === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = new $type();
|
||||||
|
$this->fillWithData($data, $model);
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
|
||||||
|
{
|
||||||
|
$select = $this->createSelect($select, $type, $withRelations);
|
||||||
|
|
||||||
|
while ($data = $select->execute()->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$model = new $type();
|
||||||
|
$this->fillWithData($data, $model);
|
||||||
|
|
||||||
|
yield $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 createSelect(Select $select, string $type, bool $withRelations = false): Select
|
||||||
|
{
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $select;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,16 +1,19 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class MapRepository
|
class MapRepository
|
||||||
{
|
{
|
||||||
public function getById(int $mapId)
|
private PersistentDataManager $pdm;
|
||||||
{
|
|
||||||
$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);
|
|
||||||
|
|
||||||
return $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById(int $mapId): ?Map
|
||||||
|
{
|
||||||
|
return $this->pdm->selectFromDbById($mapId, Map::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,95 +1,43 @@
|
|||||||
<?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\Database\Query\Modify;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\Http\Request;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
|
||||||
use DateTime;
|
|
||||||
use DateInterval;
|
|
||||||
|
|
||||||
class PlaceRepository
|
class PlaceRepository
|
||||||
{
|
{
|
||||||
public function getById(int $placeId)
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
{
|
{
|
||||||
$place = $this->selectFromDbById($placeId);
|
$this->pdm = new PersistentDataManager();
|
||||||
|
|
||||||
$panoId = $this->requestPanoId($place);
|
|
||||||
|
|
||||||
$position = new Position($place['lat'], $place['lng']);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'position' => $position,
|
|
||||||
'panoId' => $panoId
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getForMapWithValidPano(int $mapId, array $exclude = []): array
|
public function getById(int $placeId): ?Place
|
||||||
|
{
|
||||||
|
return $this->pdm->selectFromDbById($placeId, Place::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place
|
||||||
{
|
{
|
||||||
$placesWithoutPano = [];
|
$placesWithoutPano = [];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$place = $this->selectFromDbForMap($mapId, $exclude);
|
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
|
||||||
|
|
||||||
$panoId = $this->requestPanoId($place);
|
$panoId = $place->getFreshPanoId();
|
||||||
|
|
||||||
if ($panoId === null) {
|
if ($panoId === null) {
|
||||||
$placesWithoutPano[] = $exclude[] = $place['id'];
|
$placesWithoutPano[] = $exclude[] = $place->getId();
|
||||||
}
|
}
|
||||||
} 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 selectFromDbForMap(int $mapId, array $exclude): array
|
private function selectRandomFromDbForMap(int $mapId, array $exclude): ?Place
|
||||||
{
|
{
|
||||||
$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);
|
||||||
|
|
||||||
@@ -99,49 +47,6 @@ class PlaceRepository
|
|||||||
$select->orderBy('id');
|
$select->orderBy('id');
|
||||||
$select->limit(1, $randomOffset);
|
$select->limit(1, $randomOffset);
|
||||||
|
|
||||||
$place = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
return $this->pdm->selectFromDb($select, Place::class);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByUser(User $user): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, UserConfirmation::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
-1
@@ -3,6 +3,9 @@
|
|||||||
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
|
||||||
{
|
{
|
||||||
@@ -16,12 +19,24 @@ 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)
|
||||||
@@ -61,8 +76,18 @@ 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->session->get('user');
|
return $this->user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,33 @@
|
|||||||
<?php namespace MapGuesser\Response;
|
<?php namespace MapGuesser\Response;
|
||||||
|
|
||||||
|
use MapGuesser\View\Linker;
|
||||||
|
|
||||||
class HtmlContent extends ContentBase
|
class HtmlContent extends ContentBase
|
||||||
{
|
{
|
||||||
private string $template;
|
private string $view;
|
||||||
|
|
||||||
public function __construct(string $template, array &$data = [])
|
public function __construct(string $view, array &$data = [])
|
||||||
{
|
{
|
||||||
$this->template = $template;
|
$this->view = $view;
|
||||||
$this->data = &$data;
|
$this->data = &$data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function &render(): string
|
public function &render(): string
|
||||||
{
|
{
|
||||||
|
if (!empty($_ENV['DEV'])) {
|
||||||
|
$generator = new Linker($this->view);
|
||||||
|
$generator->generate();
|
||||||
|
}
|
||||||
|
|
||||||
extract($this->data);
|
extract($this->data);
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
require ROOT . '/views/' . $this->template . '.php';
|
require ROOT . '/cache/views/' . $this->view . '.php';
|
||||||
$content = ob_get_contents();
|
$content = ob_get_contents();
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
|
|
||||||
|
$content .= '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->';
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ class JsonContent extends ContentBase
|
|||||||
|
|
||||||
public function &render(): string
|
public function &render(): string
|
||||||
{
|
{
|
||||||
|
$this->data['__debug__runtime'] = round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1);
|
||||||
|
|
||||||
$content = json_encode($this->data);
|
$content = json_encode($this->data);
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ use MapGuesser\Interfaces\Response\IRedirect;
|
|||||||
|
|
||||||
class Redirect implements IRedirect
|
class Redirect implements IRedirect
|
||||||
{
|
{
|
||||||
private $target;
|
private string $target;
|
||||||
|
|
||||||
private int $type;
|
private int $type;
|
||||||
|
|
||||||
public function __construct($target, int $type = IRedirect::TEMPORARY)
|
public function __construct(string $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 (is_array($this->target)) {
|
if (preg_match('/^http(s)?/', $this->target)) {
|
||||||
$link = $this->target[0]->generateLink($this->target[1]);
|
|
||||||
} else {
|
|
||||||
$link = $this->target;
|
$link = $this->target;
|
||||||
|
} else {
|
||||||
|
$link = \Container::$request->getBase() . '/' . $this->target;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $link;
|
return $link;
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -64,7 +69,7 @@ class RouteCollection
|
|||||||
throw new \Exception('Route already exists: ' . $id);
|
throw new \Exception('Route already exists: ' . $id);
|
||||||
}
|
}
|
||||||
|
|
||||||
$pattern = array_merge($this->groupStack, explode('/', $pattern));
|
$pattern = array_merge($this->groupStack, $pattern === '' ? [] : explode('/', $pattern));
|
||||||
$route = new Route($id, $pattern, $handler);
|
$route = new Route($id, $pattern, $handler);
|
||||||
|
|
||||||
$groupNumber = count($pattern);
|
$groupNumber = count($pattern);
|
||||||
|
|||||||
@@ -59,6 +59,26 @@ 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,6 +13,16 @@ 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;
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php namespace MapGuesser\View;
|
||||||
|
|
||||||
|
class Linker
|
||||||
|
{
|
||||||
|
private string $view;
|
||||||
|
|
||||||
|
public function __construct(string $view)
|
||||||
|
{
|
||||||
|
$this->view = $view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate(): void
|
||||||
|
{
|
||||||
|
$input = ROOT . '/views/' . $this->view . '.php';
|
||||||
|
|
||||||
|
$temporaryFiles = [];
|
||||||
|
$sections = [];
|
||||||
|
$extra = ['', ''];
|
||||||
|
|
||||||
|
do {
|
||||||
|
$parser = new Parser($input);
|
||||||
|
$fragment = $parser->parse();
|
||||||
|
|
||||||
|
$extends = $fragment->getExtends();
|
||||||
|
|
||||||
|
$sections = array_merge($sections, $fragment->getSections()); //TODO: detect if section defined multiple times
|
||||||
|
$extra[0] = $fragment->getExtra()[0] . $extra[0];
|
||||||
|
$extra[1] = $extra[1] . $fragment->getExtra()[1];
|
||||||
|
|
||||||
|
if ($extends === null) {
|
||||||
|
$this->writeFinal($extra, $input, ROOT . '/cache/views/' . $this->view . '.php');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmpFile = tempnam(sys_get_temp_dir(), 'mapg-view-');
|
||||||
|
$temporaryFiles[] = $tmpFile;
|
||||||
|
|
||||||
|
$this->extendTemplate($sections, ROOT . '/views/' . $extends . '.php', $tmpFile);
|
||||||
|
|
||||||
|
$input = $tmpFile;
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
foreach ($temporaryFiles as $tmpFile) {
|
||||||
|
unlink($tmpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extendTemplate(array $sections, string $file, string $output): void
|
||||||
|
{
|
||||||
|
$inputFileHandle = fopen($file, 'r');
|
||||||
|
if (!$inputFileHandle) {
|
||||||
|
throw new \Exception('Cannot open file ' . $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputFileHandle = fopen($output, 'w');
|
||||||
|
if (!$outputFileHandle) {
|
||||||
|
throw new \Exception('Cannot open file ' . $output . 'for writing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineNumber = 0;
|
||||||
|
while (($line = fgets($inputFileHandle)) !== false) {
|
||||||
|
++$lineNumber;
|
||||||
|
|
||||||
|
if (preg_match('/^\s*@yields\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
|
||||||
|
if (isset($sections[$matches[1]])) {
|
||||||
|
fwrite($outputFileHandle, $sections[$matches[1]]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fwrite($outputFileHandle, $line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($inputFileHandle);
|
||||||
|
fclose($outputFileHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeFinal(array $extra, string $file, string $output): void
|
||||||
|
{
|
||||||
|
$dirname = pathinfo($output, PATHINFO_DIRNAME);
|
||||||
|
if (!is_dir($dirname)) {
|
||||||
|
mkdir($dirname, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$inputFileHandle = fopen($file, 'r');
|
||||||
|
if (!$inputFileHandle) {
|
||||||
|
throw new \Exception('Cannot open file ' . $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$outputFileHandle = fopen($output, 'w');
|
||||||
|
if (!$outputFileHandle) {
|
||||||
|
throw new \Exception('Cannot open file ' . $output . 'for writing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite($outputFileHandle, $extra[0]);
|
||||||
|
while (($line = fgets($inputFileHandle)) !== false) {
|
||||||
|
fwrite($outputFileHandle, $line);
|
||||||
|
}
|
||||||
|
fwrite($outputFileHandle, $extra[1]);
|
||||||
|
|
||||||
|
fclose($inputFileHandle);
|
||||||
|
fclose($outputFileHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php namespace MapGuesser\View;
|
||||||
|
|
||||||
|
class ParsedFragment
|
||||||
|
{
|
||||||
|
private ?string $extends;
|
||||||
|
|
||||||
|
private array $sections;
|
||||||
|
|
||||||
|
private array $extra;
|
||||||
|
|
||||||
|
public function __construct(?string $extends, array $sections, array $extra)
|
||||||
|
{
|
||||||
|
$this->extends = $extends;
|
||||||
|
$this->sections = $sections;
|
||||||
|
$this->extra = $extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExtends(): ?string
|
||||||
|
{
|
||||||
|
return $this->extends;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSections(): array
|
||||||
|
{
|
||||||
|
return $this->sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExtra(): array
|
||||||
|
{
|
||||||
|
return $this->extra;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<?php namespace MapGuesser\View;
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
{
|
||||||
|
private string $file;
|
||||||
|
|
||||||
|
public function __construct(string $file)
|
||||||
|
{
|
||||||
|
$this->file = $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse(): ParsedFragment
|
||||||
|
{
|
||||||
|
|
||||||
|
$sectionOpen = null;
|
||||||
|
$extraOpen = false;
|
||||||
|
|
||||||
|
$extends = null;
|
||||||
|
$sections = [];
|
||||||
|
$extra = ['', ''];
|
||||||
|
|
||||||
|
$fileHandle = fopen($this->file, 'r');
|
||||||
|
if (!$fileHandle) {
|
||||||
|
throw new \Exception('Cannot open file ' . $this->file);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lineNumber = 0;
|
||||||
|
while (($line = fgets($fileHandle)) !== false) {
|
||||||
|
++$lineNumber;
|
||||||
|
|
||||||
|
if (($extendsMatched = $this->matchExtends($line)) !== null) {
|
||||||
|
if ($extends !== null) {
|
||||||
|
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - There is already an \'@extends\' declared.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$extends = $extendsMatched;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($sectionMatched = $this->matchSection($line)) !== null) {
|
||||||
|
if ($extends === null) {
|
||||||
|
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - \'@section\' has no meaning if view extends nothing.');
|
||||||
|
}
|
||||||
|
if ($sectionOpen !== null) {
|
||||||
|
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - A \'@section\' is already open (no \'@endsection\' found).');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sectionOpen = $sectionMatched;
|
||||||
|
$sections[$sectionOpen] = '';
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->matchEndSection($line)) {
|
||||||
|
if ($sectionOpen === null) {
|
||||||
|
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - Cannot end section until no \'@section\' is open.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sectionOpen = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->matchExtra($line)) {
|
||||||
|
if ($extraOpen) {
|
||||||
|
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - An \'@extra\' is already open (no \'@endextra\' found).');
|
||||||
|
}
|
||||||
|
|
||||||
|
$extraOpen = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->matchEndExtra($line)) {
|
||||||
|
if (!$extraOpen) {
|
||||||
|
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - Cannot end extra until no \'@extra\' is open.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$extraOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sectionOpen !== null) {
|
||||||
|
$sections[$sectionOpen] .= $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($extraOpen) {
|
||||||
|
$offset = $extends === null ? 0 : 1;
|
||||||
|
$extra[$offset] .= $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($fileHandle);
|
||||||
|
|
||||||
|
return new ParsedFragment($extends, $sections, $extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchExtends(string $line): ?string
|
||||||
|
{
|
||||||
|
if (preg_match('/^\s*@extends\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchSection(string $line): ?string
|
||||||
|
{
|
||||||
|
if (preg_match('/^\s*@section\(\'(\w+)\'\)\s*$/', $line, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchEndSection(string $line): bool
|
||||||
|
{
|
||||||
|
return preg_match('/^\s*@endsection(?:\(\))?\s*$/', $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchExtra(string $line): bool
|
||||||
|
{
|
||||||
|
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchEndExtra(string $line): bool
|
||||||
|
{
|
||||||
|
return preg_match('/^\s*@endextra(?:\(\))?\s*$/', $line);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace MapGuesser\Tests\OAuth;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Http\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Http\IResponse;
|
||||||
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class GoogleOAuthTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCanCreateDialogUrl(): void
|
||||||
|
{
|
||||||
|
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz';
|
||||||
|
$state = 'random_state_string';
|
||||||
|
$redirectUrl = 'http://example.com/oauth';
|
||||||
|
|
||||||
|
$requestMock = $this->getMockBuilder(IRequest::class)
|
||||||
|
->setMethods(['setUrl', 'setMethod', 'setQuery', 'setHeaders', 'send'])
|
||||||
|
->getMock();
|
||||||
|
$googleOAuth = new GoogleOAuth($requestMock);
|
||||||
|
|
||||||
|
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl);
|
||||||
|
$dialogUrlParsed = explode('?', $dialogUrl);
|
||||||
|
|
||||||
|
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
|
||||||
|
|
||||||
|
parse_str($dialogUrlParsed[1], $dialogUrlQueryParams);
|
||||||
|
|
||||||
|
$expectedQueryParams = [
|
||||||
|
'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()),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->assertEquals($expectedQueryParams['response_type'], $dialogUrlQueryParams['response_type']);
|
||||||
|
$this->assertEquals($expectedQueryParams['client_id'], $dialogUrlQueryParams['client_id']);
|
||||||
|
$this->assertEquals($expectedQueryParams['scope'], $dialogUrlQueryParams['scope']);
|
||||||
|
$this->assertEquals($expectedQueryParams['redirect_uri'], $dialogUrlQueryParams['redirect_uri']);
|
||||||
|
$this->assertEquals($expectedQueryParams['state'], $dialogUrlQueryParams['state']);
|
||||||
|
$this->assertMatchesRegularExpression('/^[a-f0-9]{64}$/', $dialogUrlQueryParams['nonce']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanRequestToken(): void
|
||||||
|
{
|
||||||
|
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'abc';
|
||||||
|
$_ENV['GOOGLE_OAUTH_CLIENT_SECRET'] = 'xxx';
|
||||||
|
$code = 'code_from_google';
|
||||||
|
$redirectUrl = 'http://example.com/oauth';
|
||||||
|
|
||||||
|
$requestMock = $this->getMockBuilder(IRequest::class)
|
||||||
|
->setMethods(['setUrl', 'setMethod', 'setQuery', 'setHeaders', 'send'])
|
||||||
|
->getMock();
|
||||||
|
$responseMock = $this->getMockBuilder(IResponse::class)
|
||||||
|
->setMethods(['getBody', 'getHeaders'])
|
||||||
|
->getMock();
|
||||||
|
$googleOAuth = new GoogleOAuth($requestMock);
|
||||||
|
|
||||||
|
$expectedQueryParams = [
|
||||||
|
'code' => $code,
|
||||||
|
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
||||||
|
'client_secret' => $_ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
|
||||||
|
'redirect_uri' => $redirectUrl,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
];
|
||||||
|
|
||||||
|
$requestMock->expects($this->once())
|
||||||
|
->method('setUrl')
|
||||||
|
->with($this->equalTo('https://oauth2.googleapis.com/token'));
|
||||||
|
$requestMock->expects($this->once())
|
||||||
|
->method('setMethod')
|
||||||
|
->with($this->equalTo(IRequest::HTTP_POST));
|
||||||
|
$requestMock->expects($this->once())
|
||||||
|
->method('setQuery')
|
||||||
|
->with($this->equalTo($expectedQueryParams));
|
||||||
|
$requestMock->expects($this->once())
|
||||||
|
->method('send')
|
||||||
|
->will($this->returnValue($responseMock));
|
||||||
|
$responseMock->expects($this->once())
|
||||||
|
->method('getBody')
|
||||||
|
->will($this->returnValue('{"test":"json"}'));
|
||||||
|
|
||||||
|
$token = $googleOAuth->getToken($code, $redirectUrl);
|
||||||
|
|
||||||
|
$this->assertEquals(['test' => 'json'], $token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php namespace MapGuesser\Tests\Util;
|
||||||
|
|
||||||
|
use MapGuesser\PersistentData\Model\Model;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class DummyModel extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'test_table';
|
||||||
|
|
||||||
|
protected static array $fields = ['name', 'valid'];
|
||||||
|
|
||||||
|
protected static array $relations = ['other_model' => OtherModel::class];
|
||||||
|
|
||||||
|
private string $name;
|
||||||
|
|
||||||
|
private bool $valid;
|
||||||
|
|
||||||
|
public function setName(string $name): void
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValid(bool $valid): void
|
||||||
|
{
|
||||||
|
$this->valid = $valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValid(): bool
|
||||||
|
{
|
||||||
|
return $this->valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ModelTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCanReturnTable(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals('test_table', DummyModel::getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanReturnFields(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals(['id', 'name', 'valid'], DummyModel::getFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanReturnRelations(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals(['other_model' => OtherModel::class], DummyModel::getRelations());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanBeConvertedToArray(): void
|
||||||
|
{
|
||||||
|
$model = new DummyModel();
|
||||||
|
$model->setId(123);
|
||||||
|
$model->setName('John');
|
||||||
|
$model->setValid(true);
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'id' => 123,
|
||||||
|
'name' => 'John',
|
||||||
|
'valid' => true
|
||||||
|
], $model->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanSaveAndResetSnapshot(): void
|
||||||
|
{
|
||||||
|
$model = new DummyModel();
|
||||||
|
$model->setId(123);
|
||||||
|
$model->setName('John');
|
||||||
|
$model->setValid(true);
|
||||||
|
|
||||||
|
$model->saveSnapshot();
|
||||||
|
|
||||||
|
$this->assertEquals([
|
||||||
|
'id' => 123,
|
||||||
|
'name' => 'John',
|
||||||
|
'valid' => true
|
||||||
|
], $model->getSnapshot());
|
||||||
|
|
||||||
|
$model->resetSnapshot();
|
||||||
|
|
||||||
|
$this->assertEquals([], $model->getSnapshot());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<?php namespace MapGuesser\Tests\Util\Geo;
|
||||||
|
|
||||||
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
|
use MapGuesser\Util\Geo\Position;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class BoundsTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCanBeCreatedEmpty(): void
|
||||||
|
{
|
||||||
|
$bounds = new Bounds();
|
||||||
|
|
||||||
|
$this->assertEquals(90.0, $bounds->getSouthLat());
|
||||||
|
$this->assertEquals(180.0, $bounds->getWestLng());
|
||||||
|
$this->assertEquals(-90.0, $bounds->getNorthLat());
|
||||||
|
$this->assertEquals(-180.0, $bounds->getEastLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanBeCreatedWithPosition(): void
|
||||||
|
{
|
||||||
|
$position = new Position(45.25, 18.12);
|
||||||
|
|
||||||
|
$bounds = new Bounds($position);
|
||||||
|
|
||||||
|
$this->assertEquals($position->getLat(), $bounds->getSouthLat());
|
||||||
|
$this->assertEquals($position->getLng(), $bounds->getWestLng());
|
||||||
|
$this->assertEquals($position->getLat(), $bounds->getNorthLat());
|
||||||
|
$this->assertEquals($position->getLng(), $bounds->getEastLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanBeCreatedDirectly(): void
|
||||||
|
{
|
||||||
|
$bounds = Bounds::createDirectly(44.12, 7.33, 50.12, 15.11);
|
||||||
|
|
||||||
|
$this->assertEquals(44.12, $bounds->getSouthLat());
|
||||||
|
$this->assertEquals(7.33, $bounds->getWestLng());
|
||||||
|
$this->assertEquals(50.12, $bounds->getNorthLat());
|
||||||
|
$this->assertEquals(15.11, $bounds->getEastLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExtendingWithOnePositionIsTheSameAsCreatingWithPosition(): void
|
||||||
|
{
|
||||||
|
$position = new Position(45.25, 18.12);
|
||||||
|
|
||||||
|
$bounds1 = new Bounds();
|
||||||
|
$bounds1->extend($position);
|
||||||
|
|
||||||
|
$bounds2 = new Bounds($position);
|
||||||
|
|
||||||
|
$this->assertEquals($bounds1, $bounds2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanBeExtendedWithTwoPositions(): void
|
||||||
|
{
|
||||||
|
$bounds = new Bounds();
|
||||||
|
|
||||||
|
$position1 = new Position(45.25, 18.12);
|
||||||
|
$position2 = new Position(43.15, 19.28);
|
||||||
|
|
||||||
|
$bounds->extend($position1);
|
||||||
|
$bounds->extend($position2);
|
||||||
|
|
||||||
|
$this->assertEquals($position2->getLat(), $bounds->getSouthLat());
|
||||||
|
$this->assertEquals($position1->getLng(), $bounds->getWestLng());
|
||||||
|
$this->assertEquals($position1->getLat(), $bounds->getNorthLat());
|
||||||
|
$this->assertEquals($position2->getLng(), $bounds->getEastLng());
|
||||||
|
|
||||||
|
$bounds = new Bounds();
|
||||||
|
|
||||||
|
$position1 = new Position(43.15, 18.12);
|
||||||
|
$position2 = new Position(45.25, 19.28);
|
||||||
|
|
||||||
|
$bounds->extend($position1);
|
||||||
|
$bounds->extend($position2);
|
||||||
|
|
||||||
|
$this->assertEquals($position1->getLat(), $bounds->getSouthLat());
|
||||||
|
$this->assertEquals($position1->getLng(), $bounds->getWestLng());
|
||||||
|
$this->assertEquals($position2->getLat(), $bounds->getNorthLat());
|
||||||
|
$this->assertEquals($position2->getLng(), $bounds->getEastLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanCalculateApproximateArea(): void
|
||||||
|
{
|
||||||
|
$bounds = Bounds::createDirectly(44.12, 7.33, 50.12, 15.11);
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(391766.09, $bounds->calculateApproximateArea(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanBeConvertedToArray(): void
|
||||||
|
{
|
||||||
|
$bounds = Bounds::createDirectly(44.12, 7.33, 50.12, 15.11);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
['south' => 44.12, 'west' => 7.33, 'north' => 50.12, 'east' => 15.11],
|
||||||
|
$bounds->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php namespace MapGuesser\Tests\Util\Geo;
|
||||||
|
|
||||||
|
use MapGuesser\Util\Geo\Position;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class PositionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testCanBeCreatedWithLatAndLng(): void
|
||||||
|
{
|
||||||
|
$position = new Position(45.25, 18.12);
|
||||||
|
|
||||||
|
$this->assertEquals(45.25, $position->getLat());
|
||||||
|
$this->assertEquals(18.12, $position->getLng());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanCalculateDistanceToAnotherPosition(): void
|
||||||
|
{
|
||||||
|
$position1 = new Position(44.12, 7.33);
|
||||||
|
$position2 = new Position(50.12, 15.11);
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta(888785.73, $position1->calculateDistanceTo($position2), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanBeConvertedToArray(): void
|
||||||
|
{
|
||||||
|
$position = new Position(45.25, 18.12);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
['lat' => 45.25, 'lng' => 18.12],
|
||||||
|
$position->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php namespace MapGuesser\Tests\Util;
|
||||||
|
|
||||||
|
use MapGuesser\Util\JwtParser;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class JwtParserTest extends TestCase
|
||||||
|
{
|
||||||
|
private JwtParser $jwtParser;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->jwtParser = new JwtParser(
|
||||||
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSettingTokenIsTheSameAsCreatingWithToken(): void
|
||||||
|
{
|
||||||
|
$jwtParser2 = new JwtParser();
|
||||||
|
$jwtParser2->setToken(
|
||||||
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($this->jwtParser, $jwtParser2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanParseTokenHeader(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals([
|
||||||
|
'alg' => 'HS256',
|
||||||
|
'typ' => 'JWT'
|
||||||
|
], $this->jwtParser->getHeader());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanParseTokenPayload(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals([
|
||||||
|
'sub' => '1234567890',
|
||||||
|
'name' => 'John Doe',
|
||||||
|
'iat' => 1516239022
|
||||||
|
], $this->jwtParser->getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanParseTokenSignature(): void
|
||||||
|
{
|
||||||
|
$this->assertEquals(
|
||||||
|
'49f94ac7044948c78a285d904f87f0a4c7897f7e8f3a4eb2255fda750b2cc397',
|
||||||
|
bin2hex($this->jwtParser->getSignature())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<h2>Account</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="accountForm" action="/account" method="post" data-observe-inputs="password_new,password_new_confirm">
|
||||||
|
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
|
||||||
|
<hr>
|
||||||
|
<?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 marginTop" type="password" name="password_new" placeholder="New password" minlength="6">
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password_new_confirm" placeholder="New password confirmation" minlength="6">
|
||||||
|
<p id="accountFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button type="submit" name="submit" disabled>Save</button>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="center">
|
||||||
|
<a class="button red" href="/account/delete" title="Delete account">Delete account</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<h2>Delete account</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="deleteAccountForm" action="/account/delete" method="post" data-redirect-on-success="/">
|
||||||
|
<p class="justify">Are you sure you want to delete your account? This cannot be undone!</p>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
|
||||||
|
<p id="deleteAccountFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button class="red" type="submit" name="submit">Delete account</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
+37
-33
@@ -1,3 +1,4 @@
|
|||||||
|
@extra
|
||||||
<?php
|
<?php
|
||||||
$cssFiles = [
|
$cssFiles = [
|
||||||
'node_modules/leaflet/dist/leaflet.css',
|
'node_modules/leaflet/dist/leaflet.css',
|
||||||
@@ -12,45 +13,42 @@ $jsFiles = [
|
|||||||
'js/map_editor.js',
|
'js/map_editor.js',
|
||||||
];
|
];
|
||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
@endextra
|
||||||
<div class="header small">
|
|
||||||
<h1>
|
@extends('templates/layout_full')
|
||||||
<a href="/maps" title="Back to playable maps">
|
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
@section('subheader')
|
||||||
<span>MapGuesser</span>
|
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
<p class="header">
|
|
||||||
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></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="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>
|
@endsection
|
||||||
</div>
|
|
||||||
|
@section('pagemodal')
|
||||||
<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" data-no-submit="true">
|
||||||
<input class="fullWidth" type="text" name="name" value="<?= $mapName ?>" placeholder="Name of the map">
|
<input class="fullWidth" type="text" name="name" value="<?= $mapName ?>" placeholder="Name of the map">
|
||||||
<textarea class="fullWidth marginTop" name="description" rows="4" placeholder="Description of the map"><?= $mapDescription ?></textarea>
|
<textarea class="fullWidth marginTop" name="description" rows="4" placeholder="Description of the map"><?= $mapDescription ?></textarea>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
@@ -59,19 +57,25 @@ $jsFiles = [
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="map"></div>
|
@endsection
|
||||||
<div id="control">
|
|
||||||
<button id="saveButton" class="fullWidth" disabled>Save</button>
|
@section('main')
|
||||||
</div>
|
<div id="map"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="noPano">
|
<div id="noPano">
|
||||||
<p class="bold">No panorama is available for this location.</p>
|
<p class="bold">No panorama is available for this location.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="placeControl">
|
<div id="control">
|
||||||
<button id="applyButton" class="fullWidth">Apply</button>
|
<button id="saveButton" class="fullWidth" disabled>Save</button>
|
||||||
<button id="closeButton" class="gray fullWidth marginTop">Close</button>
|
</div>
|
||||||
<button id="deleteButton" class="red fullWidth marginTop">Delete</button>
|
<div id="placeControl">
|
||||||
</div>
|
<button id="applyButton" class="fullWidth">Apply</button>
|
||||||
|
<button id="closeButton" class="gray fullWidth marginTop">Close</button>
|
||||||
|
<button id="deleteButton" class="red fullWidth marginTop">Delete</button>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('pagescript')
|
||||||
<script>
|
<script>
|
||||||
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
||||||
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
||||||
@@ -79,4 +83,4 @@ $jsFiles = [
|
|||||||
var mapBounds = <?= json_encode($bounds) ?>;
|
var mapBounds = <?= json_encode($bounds) ?>;
|
||||||
var places = <?= json_encode($places, JSON_FORCE_OBJECT) ?>;
|
var places = <?= json_encode($places, JSON_FORCE_OBJECT) ?>;
|
||||||
</script>
|
</script>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
@endsection
|
||||||
+5
-6
@@ -1,7 +1,6 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
@extends('templates/layout_normal')
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<div class="main">
|
@section('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="MapGuesser">Back to start.</a></p>
|
<p>The requested URL was not found on this server. <a href="/" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
|
||||||
</div>
|
@endsection
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
+41
-40
@@ -1,3 +1,4 @@
|
|||||||
|
@extra
|
||||||
<?php
|
<?php
|
||||||
$cssFiles = [
|
$cssFiles = [
|
||||||
'css/game.css'
|
'css/game.css'
|
||||||
@@ -7,56 +8,56 @@ $jsFiles = [
|
|||||||
'js/game.js',
|
'js/game.js',
|
||||||
];
|
];
|
||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
@endextra
|
||||||
<div class="header small">
|
|
||||||
<h1>
|
@extends('templates/layout_full')
|
||||||
<a href="/maps" title="Back to playable maps">
|
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
|
@section('subheader')
|
||||||
<span>MapGuesser</span>
|
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
<p class="header">
|
|
||||||
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
||||||
--><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>
|
@endsection
|
||||||
</div>
|
|
||||||
<div id="guessCover"></div>
|
@section('main')
|
||||||
<div id="panorama"></div>
|
<div id="panoCover"></div>
|
||||||
<div id="showGuessButtonContainer">
|
<div id="panorama"></div>
|
||||||
<button id="showGuessButton" class="fullWidth">Show guess map</button>
|
<div id="showGuessButtonContainer">
|
||||||
</div>
|
<button id="showGuessButton" class="fullWidth">Show guess map</button>
|
||||||
<div id="guess">
|
|
||||||
<div id="closeGuessButtonContainer" class="buttonContainer marginBottom">
|
|
||||||
<button id="closeGuessButton" class="fullWidth gray">Close</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="map"></div>
|
<div id="guess">
|
||||||
<div id="guessButtonContainer" class="buttonContainer marginTop">
|
<div id="closeGuessButtonContainer" class="buttonContainer marginBottom">
|
||||||
<button id="guessButton" class="fullWidth" disabled>Guess</button>
|
<button id="closeGuessButton" class="fullWidth gray">Close</button>
|
||||||
</div>
|
|
||||||
<div id="resultInfo">
|
|
||||||
<div id="distanceInfo">
|
|
||||||
<p>You were <span id="distance" class="bold"></span> close.</p>
|
|
||||||
<p class="bold">Game finished.</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="scoreInfo">
|
<div id="map"></div>
|
||||||
<p>You earned <span id="score" class="bold"></span> points.</p>
|
<div id="guessButtonContainer" class="buttonContainer marginTop">
|
||||||
<p>You got <span id="scoreSum" class="bold"></span> points in total.</p>
|
<button id="guessButton" class="fullWidth" disabled>Guess</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div id="resultInfo">
|
||||||
<div id="scoreBarBase">
|
<div id="distanceInfo">
|
||||||
<div id="scoreBar"></div>
|
<p>You were <span id="distance" class="bold"></span> close.</p>
|
||||||
|
<p class="bold">Game finished.</p>
|
||||||
|
</div>
|
||||||
|
<div id="scoreInfo">
|
||||||
|
<p>You earned <span id="score" class="bold"></span> points.</p>
|
||||||
|
<p>You got <span id="scoreSum" class="bold"></span> points in total.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div id="scoreBarBase">
|
||||||
|
<div id="scoreBar"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="continueButtonContainer" class="buttonContainer marginTop">
|
||||||
|
<button id="continueButton" class="fullWidth">Continue</button>
|
||||||
|
<button id="showSummaryButton" class="fullWidth">Show summary</button>
|
||||||
|
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="continueButtonContainer" class="buttonContainer marginTop">
|
@endsection
|
||||||
<button id="continueButton" class="fullWidth">Continue</button>
|
|
||||||
<button id="showSummaryButton" class="fullWidth">Show summary</button>
|
@section('pagescript')
|
||||||
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script>
|
<script>
|
||||||
var mapId = <?= $mapId ?>;
|
var mapId = <?= $mapId ?>;
|
||||||
var mapBounds = <?= json_encode($bounds) ?>;
|
var mapBounds = <?= json_encode($bounds) ?>;
|
||||||
</script>
|
</script>
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
@endsection
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
$jsFiles = [
|
|
||||||
'js/login.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<div class="main">
|
|
||||||
<h2>Login</h2>
|
|
||||||
<div class="box">
|
|
||||||
<form id="loginForm" action="/login" 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">
|
|
||||||
<p id="loginFormError" class="formError justify marginTop"></p>
|
|
||||||
<div class="right marginTop">
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
@extends('templates/layout_normal')
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<div class="main">
|
@section('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>
|
||||||
</div>
|
@endsection
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
@extends('templates/layout_normal')
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<div class="main">
|
@section('main')
|
||||||
<h2>Account cancellation</h2>
|
<h2>Account cancellation</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<?php if ($success) : ?>
|
<?php if ($success) : ?>
|
||||||
@@ -9,5 +9,4 @@
|
|||||||
<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>
|
||||||
</div>
|
@endsection
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<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>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
@extra
|
||||||
|
<?php
|
||||||
|
$jsFiles = [
|
||||||
|
'js/login/google_signup.js',
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
@endextra
|
||||||
|
|
||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<h2>Sign up</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/">
|
||||||
|
<?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>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<h2>Login</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
|
||||||
|
<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">
|
||||||
|
<p id="loginFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="center">
|
||||||
|
<a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
@extra
|
||||||
|
<?php
|
||||||
|
$jsFiles = [
|
||||||
|
'js/login/signup.js',
|
||||||
|
];
|
||||||
|
?>
|
||||||
|
@endextra
|
||||||
|
|
||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<h2>Sign up</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success">
|
||||||
|
<?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>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@extends('templates/layout_normal')
|
||||||
|
|
||||||
|
@section('main')
|
||||||
|
<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>
|
||||||
|
@endsection
|
||||||
+21
-107
@@ -1,40 +1,48 @@
|
|||||||
|
@extra
|
||||||
<?php
|
<?php
|
||||||
$cssFiles = [
|
$cssFiles = [
|
||||||
'css/maps.css'
|
'css/maps.css'
|
||||||
];
|
];
|
||||||
|
$jsFiles = [
|
||||||
|
'js/maps.js',
|
||||||
|
];
|
||||||
|
if ($isAdmin) {
|
||||||
|
$jsFiles[] = 'js/maps_admin.js';
|
||||||
|
}
|
||||||
?>
|
?>
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
@endextra
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<div class="main">
|
@extends('templates/layout_normal')
|
||||||
<h2>Playable maps</h2>
|
|
||||||
|
@section('main')
|
||||||
<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">
|
<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>
|
||||||
<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 justify marginTop"><?= $map['description'] ?></p>
|
<div class="description marginTop">
|
||||||
|
<p class="small center"><?= $map['description'] ?></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($isAdmin): ?>
|
<?php if ($isAdmin): ?>
|
||||||
<div class="buttonContainer">
|
<div class="buttonContainer">
|
||||||
@@ -62,98 +70,4 @@ $cssFiles = [
|
|||||||
<?php endfor; ?>
|
<?php endfor; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endsection
|
||||||
|
|
||||||
<style>
|
|
||||||
div.popup {
|
|
||||||
position: absolute;
|
|
||||||
top: 50px;
|
|
||||||
left: 50px;
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
div.popup>div.content, div.popup>div.tipContainer>div.tip {
|
|
||||||
background: white;
|
|
||||||
color: #333;
|
|
||||||
border: solid #555 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.popup>div.content {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px 10px 8px 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.popup>div.tipContainer {
|
|
||||||
width: 40px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
top: 1px;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -20px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.popup>div.tipContainer>div.tip {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<?php if ($isAdmin): ?>
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
Maps = {
|
|
||||||
deleteMap: function(mapId, mapName) {
|
|
||||||
MapGuesser.showModalWithContent('Delete map', 'Are you sure you want to delete map \'' + mapName + '\'?', [{
|
|
||||||
type: 'button',
|
|
||||||
classNames: ['red'],
|
|
||||||
text: 'Delete',
|
|
||||||
onclick: function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
|
|
||||||
MapGuesser.httpRequest('POST', '/admin/deleteMap/' + mapId, function () {
|
|
||||||
if (this.response.error) {
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
//TODO: handle this error
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
|
|
||||||
for (var button of buttons) {
|
|
||||||
button.onclick = function() {
|
|
||||||
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
$jsFiles = [
|
|
||||||
'js/profile.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
|
|
||||||
<?php require ROOT . '/views/templates/header.php'; ?>
|
|
||||||
<div class="main">
|
|
||||||
<h2>Profile</h2>
|
|
||||||
<div class="box">
|
|
||||||
<form id="profileForm" action="/profile" method="post">
|
|
||||||
<?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 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">
|
|
||||||
<hr>
|
|
||||||
<input class="big fullWidth" type="password" name="password" placeholder="Current password">
|
|
||||||
<p id="profileFormError" class="formError justify marginTop"></p>
|
|
||||||
<div class="right marginTop">
|
|
||||||
<button type="submit" name="save" disabled>Save</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?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'; ?>
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
@extends('templates/mapguesser')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<header class="small">
|
||||||
|
<h1>
|
||||||
|
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
||||||
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
|
||||||
|
--><span><?= $_ENV['APP_NAME'] ?></span>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<p class="header">
|
||||||
|
@yields('subheader')
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<main class="full">
|
||||||
|
@yields('main')
|
||||||
|
</main>
|
||||||
|
@endsection
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
<div class="header">
|
@extends('templates/mapguesser')
|
||||||
|
@section('content')
|
||||||
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="MapGuesser">
|
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
||||||
<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>MapGuesser</span>
|
--><span><?= $_ENV['APP_NAME'] ?></span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="header">
|
<p>
|
||||||
<?php if (Container::$request->user()) : ?>
|
<?php if (Container::$request->user()) : ?>
|
||||||
<span><a href="/profile" title="Profile">
|
<span><a href="/account" title="Account">
|
||||||
<?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,4 +22,12 @@
|
|||||||
--><span><a href="/login" title="Login">Login</a></span>
|
--><span><a href="/login" title="Login">Login</a></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</header>
|
||||||
|
<main>
|
||||||
|
@yields('main')
|
||||||
|
</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>
|
||||||
|
@endsection
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script>
|
|
||||||
const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
|
|
||||||
const REVISION = '<?= REVISION ?>';
|
|
||||||
const ANTI_CSRF_TOKEN = '<?= $_SESSION['anti_csrf_token'] ?>';
|
|
||||||
</script>
|
|
||||||
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
|
|
||||||
<?php if (isset($jsFiles)) : ?>
|
|
||||||
<?php foreach ($jsFiles as $jsFile) : ?>
|
|
||||||
<?php
|
|
||||||
if (!preg_match('/^http(s)?/', $jsFile)) {
|
|
||||||
$jsFile = $_ENV['STATIC_ROOT'] . '/' . $jsFile . '?rev=' . REVISION;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<script src="<?= $jsFile ?>"></script>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>MapGuesser</title>
|
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
|
|
||||||
<?php if (isset($cssFiles)) : ?>
|
|
||||||
<?php foreach ($cssFiles as $cssFile) : ?>
|
|
||||||
<?php
|
|
||||||
if (!preg_match('/^http(s)?/', $cssFile)) {
|
|
||||||
$cssFile = $_ENV['STATIC_ROOT'] . '/' . $cssFile . '?rev=' . REVISION;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<link href="<?= $cssFile ?>" rel="stylesheet">
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
|
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
|
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/96x96.png?rev=<?= REVISION ?>">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="loading">
|
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
|
|
||||||
</div>
|
|
||||||
<div id="cover"></div>
|
|
||||||
<div id="modal" class="modal">
|
|
||||||
<h2 id="modalTitle"></h2>
|
|
||||||
<p id="modalText" class="justify marginTop"></p>
|
|
||||||
<div id="modalButtons" class="right"></div>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title><?= $_ENV['APP_NAME'] ?></title>
|
||||||
|
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
|
||||||
|
<?php if (isset($cssFiles)) : ?>
|
||||||
|
<?php foreach ($cssFiles as $cssFile) : ?>
|
||||||
|
<?php
|
||||||
|
if (!preg_match('/^http(s)?/', $cssFile)) {
|
||||||
|
$cssFile = $_ENV['STATIC_ROOT'] . '/' . $cssFile . '?rev=' . REVISION;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<link href="<?= $cssFile ?>" rel="stylesheet">
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/96x96.png?rev=<?= REVISION ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
|
||||||
|
</head>
|
||||||
|
<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 'Agree', you consent to our use of cookies.
|
||||||
|
</p>
|
||||||
|
<button id="agreeCookiesButton" class="small marginTop">Agree</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div id="loading">
|
||||||
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
|
||||||
|
</div>
|
||||||
|
<div id="cover"></div>
|
||||||
|
<div id="modal" class="modal">
|
||||||
|
<h2 id="modalTitle"></h2>
|
||||||
|
<p id="modalText" class="justify marginTop"></p>
|
||||||
|
<div id="modalButtons" class="right"></div>
|
||||||
|
</div>
|
||||||
|
@yields('pagemodal')
|
||||||
|
@yields('content')
|
||||||
|
<script>
|
||||||
|
const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
|
||||||
|
const REVISION = '<?= REVISION ?>';
|
||||||
|
var ANTI_CSRF_TOKEN = '<?= \Container::$request->session()->get('anti_csrf_token') ?>';
|
||||||
|
const GOOGLE_MAPS_JS_API_KEY = '<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>';
|
||||||
|
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
|
||||||
|
const GOOGLE_ANALITICS_ID = '<?= $_ENV['GOOGLE_ANALITICS_ID'] ?>';
|
||||||
|
<?php endif; ?>
|
||||||
|
</script>
|
||||||
|
@yields('pagescript')
|
||||||
|
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
|
||||||
|
<?php if (isset($jsFiles)) : ?>
|
||||||
|
<?php foreach ($jsFiles as $jsFile) : ?>
|
||||||
|
<?php
|
||||||
|
if (!preg_match('/^http(s)?/', $jsFile)) {
|
||||||
|
$jsFile = $_ENV['STATIC_ROOT'] . '/' . $jsFile . '?rev=' . REVISION;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<script src="<?= $jsFile ?>"></script>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
// we don't want user to agree cookies when clicking on the notice itself
|
||||||
|
document.getElementById('cookiesNotice').onclick = function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('agreeCookiesButton').onclick = function () {
|
||||||
|
MapGuesser.agreeCookies();
|
||||||
|
|
||||||
|
document.getElementById('cookiesNotice').style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.onclick = function () {
|
||||||
|
MapGuesser.agreeCookies();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
MapGuesser.initGoogleAnalitics();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user