Compare commits
310
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71681f1e55 | ||
|
|
1cf0ab44fd
|
||
|
|
fc9eb183f1 | ||
|
|
d431a693fc
|
||
|
|
ace490b7e9 | ||
|
|
e3771d36ce
|
||
|
|
6e03476173 | ||
|
|
f3ec71d6dd
|
||
|
|
c2f0a1dd51
|
||
|
|
a417fbd760 | ||
|
|
a9cda56586
|
||
|
|
02fcbd2f9c
|
||
|
|
2f665381c3
|
||
|
|
573440868e
|
||
|
|
79490a0616
|
||
|
|
ed343f2359
|
||
|
|
563f900423
|
||
|
|
24a10c534e
|
||
|
|
0cbb7ba145
|
||
|
|
5dd2ce0d5a
|
||
|
|
e5fb725c69
|
||
|
|
fc40c18679
|
||
|
|
4fabc39d44
|
||
|
|
2120fbebbc
|
||
|
|
b9f0529dce
|
||
|
|
a3ca7638c7
|
||
|
|
8538d0a119
|
||
|
|
0cc63ef936 | ||
|
|
10d239bfb4
|
||
|
|
e2493e1b7e | ||
|
|
edd8c0f8a4
|
||
|
|
01f0f7d4bc | ||
|
|
c317a0abf4
|
||
|
|
9c4b7448a9
|
||
|
|
60e20bd92b
|
||
|
|
2c4d809d49
|
||
|
|
bf27f15709 | ||
|
|
db3094eb62
|
||
|
|
77c71ab2da | ||
|
|
8327cd975a
|
||
|
|
cc8148fcf9 | ||
|
|
2a7b5fb992
|
||
|
|
a4c77c3d32 | ||
|
|
317329ae84
|
||
|
|
234cb7f086 | ||
|
|
66c709f00d
|
||
|
|
2737c7d6d6
|
||
|
|
2ccee26424 | ||
|
|
8cbf611189
|
||
|
|
2ac9794e3f | ||
|
|
9e5f7405d1
|
||
|
|
af98d71924
|
||
|
|
39d691fd7f
|
||
|
|
405488cf4e
|
||
|
|
f2736d4d6e | ||
|
|
88b9821c61
|
||
|
|
da2c8d96a4 | ||
|
|
cd581c6fcd
|
||
|
|
f7b268e10f | ||
|
|
a8b1ab3057
|
||
|
|
8403c21ec0
|
||
|
|
0ba1cda884
|
||
|
|
b2e09c394b
|
||
|
|
0bcb0187c1
|
||
|
|
f3d1608164
|
||
|
|
b254603fb1
|
||
|
|
776561a41c | ||
|
|
27fc687043
|
||
|
|
45869321ac
|
||
|
|
b58137acc9 | ||
|
|
dd6bb5ef9a
|
||
|
|
37094e552b
|
||
|
|
32392590bd
|
||
|
|
38885849de
|
||
|
|
ef013b8b9e
|
||
|
|
ab23b37b97
|
||
|
|
7e3315fc88
|
||
|
|
6cafac1b65
|
||
|
|
631582a912 | ||
|
|
6db6208896
|
||
|
|
e17cf68007
|
||
|
|
091afb0aab | ||
|
|
954f111254
|
||
|
|
de1d7338a4
|
||
|
|
7539f637b0
|
||
|
|
67534a22f5
|
||
|
|
43aef22c4e | ||
|
|
3123643bb7
|
||
|
|
b736ce8970
|
||
|
|
4e48e6ae73
|
||
|
|
028d8a22a2
|
||
|
|
a73e60ae5a | ||
|
|
5383da81b3
|
||
|
|
e8adc9f730 | ||
|
|
149003250c
|
||
|
|
171b4278a6 | ||
|
|
091018d22f
|
||
|
|
34f0c92eb5 | ||
|
|
8ac5d5c6e2
|
||
|
|
e883d184b7
|
||
|
|
71b1e28aa9 | ||
|
|
3555be9b63
|
||
|
|
d03a3934c6 | ||
|
|
bd5b49b09b
|
||
|
|
4333240acc
|
||
|
|
4099f1d962
|
||
|
|
2fac828098
|
||
|
|
f3225ae275
|
||
|
|
48eba75cf4
|
||
|
|
a97a6f8977 | ||
|
|
2a3f891aa0
|
||
|
|
38a427416c | ||
|
|
2f14e1f4c3
|
||
|
|
6098b7bb8d | ||
|
|
596b29f841
|
||
|
|
34a4026ddc | ||
|
|
a55ece7dac
|
||
|
|
efdd5c54be
|
||
|
|
825e0744ec
|
||
|
|
18bf9aaade
|
||
|
|
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 | ||
|
|
efccb2dcc5 | ||
|
|
a38268c751 | ||
|
|
15b5869d5a | ||
|
|
bd2b28ef17 | ||
|
|
6d431717f8 | ||
|
|
4ac18dfc84 | ||
|
|
afbd0b4e20 | ||
|
|
062718dd75 | ||
|
|
20102f0577 | ||
|
|
a69ba3a99b | ||
|
|
66b21ec710 | ||
|
|
28ed02091a | ||
|
|
334df3462b | ||
|
|
01db72796b | ||
|
|
c50c5ed422 | ||
|
|
142c91f36b | ||
|
|
1269c876e9 | ||
|
|
44e441fac0 | ||
|
|
f85340cd00 | ||
|
|
ba83f73c31 | ||
|
|
86f05f7738 | ||
|
|
61ce915ec0 | ||
|
|
2aa3cdc7f3 | ||
|
|
5760e31e29 | ||
|
|
14fd723f26 | ||
|
|
a6d5214166 | ||
|
|
d1d9dede9c | ||
|
|
171064816c | ||
|
|
f8bb7bd092 | ||
|
|
466f065718 | ||
|
|
6d68f247e6 | ||
|
|
e4c7aadc64 | ||
|
|
25262fb5fe | ||
|
|
8e0c1ce08b | ||
|
|
9e196ebf33 | ||
|
|
013668e3d6 | ||
|
|
dbd16d6cbe | ||
|
|
6a191d5c28 | ||
|
|
39e49724da | ||
|
|
6c8a3c6a90 | ||
|
|
7159867db0 | ||
|
|
4d7c86fd1b | ||
|
|
9ca97dccb9 | ||
|
|
2ef67d53f4 | ||
|
|
06a0722899 | ||
|
|
4a9cf8a217 | ||
|
|
b3caf1fb38 | ||
|
|
08d4aec48e | ||
|
|
9c380076fa | ||
|
|
95508ad284 | ||
|
|
7690714b26 | ||
|
|
f732dad1b1 | ||
|
|
49b6f28a9d | ||
|
|
33ec9d3a9c | ||
|
|
6fb122e842 | ||
|
|
90ccdf6de7 | ||
|
|
a869a55b7e | ||
|
|
420ed400a6 | ||
|
|
d6f34b916e |
No files matched your search
@@ -1,3 +1,4 @@
|
|||||||
|
APP_NAME=MapGuesser
|
||||||
DEV=1
|
DEV=1
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
@@ -7,3 +8,14 @@ GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
|
|||||||
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
||||||
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
||||||
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
||||||
|
STATIC_ROOT=/static
|
||||||
|
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
||||||
|
MAIL_HOST=mail
|
||||||
|
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
|
||||||
|
MULTI_INTERNAL_HOST=multi
|
||||||
|
MULTI_INTERNAL_PORT=5000
|
||||||
|
MULTI_WS_URL=mapguesser-dev.ch:8090
|
||||||
|
MULTI_WS_PORT=8090
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
installed
|
installed
|
||||||
vendor
|
vendor
|
||||||
|
node_modules
|
||||||
Vendored
+8
@@ -9,6 +9,14 @@
|
|||||||
"pathMappings": {
|
"pathMappings": {
|
||||||
"/var/www/mapguesser": "${workspaceRoot}",
|
"/var/www/mapguesser": "${workspaceRoot}",
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Listen for NodeJS Inspector in Docker",
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"port": 9229,
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/var/www/mapguesser"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,71 @@
|
|||||||
# 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://gitea.e5tv.hu/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.
|
||||||
|
|
||||||
|
### (Production only) Create cron job
|
||||||
|
|
||||||
|
To maintain database (delete inactive users, old sessions etc.), the command `db:maintain` should be regularly executed. It is recommened to create a cron job that runs every hour:
|
||||||
|
|
||||||
|
```
|
||||||
|
0 * * * * /path/to/your/installation/mapg db:maintain >>/var/log/cron-mapguesser.log 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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,26 @@
|
|||||||
|
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
|
||||||
|
- step:
|
||||||
|
name: Static Code Analysis
|
||||||
|
caches:
|
||||||
|
- composer
|
||||||
|
artifacts:
|
||||||
|
- static_code_analysis_results.json
|
||||||
|
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
|
||||||
|
- php -d memory_limit=1G vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
+7
-2
@@ -5,9 +5,14 @@
|
|||||||
"license": "GNU GPL 3.0",
|
"license": "GNU GPL 3.0",
|
||||||
"require": {
|
"require": {
|
||||||
"vlucas/phpdotenv": "^4.1",
|
"vlucas/phpdotenv": "^4.1",
|
||||||
"symfony/console": "^5.1"
|
"symfony/console": "^5.1",
|
||||||
|
"phpmailer/phpmailer": "^6.1",
|
||||||
|
"fzaninotto/faker": "^1.9"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9",
|
||||||
|
"phpstan/phpstan": "^0.12.32"
|
||||||
},
|
},
|
||||||
"require-dev": {},
|
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"MapGuesser\\": "src"
|
"MapGuesser\\": "src"
|
||||||
|
|||||||
Generated
+1881
-3
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'maps');
|
||||||
|
$select->columns(['id', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']);
|
||||||
|
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'maps');
|
||||||
|
$modify->setId($map['id']);
|
||||||
|
$modify->set('area', $bounds->calculateApproximateArea());
|
||||||
|
$modify->save();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
|
||||||
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
|
$select->columns(['id']);
|
||||||
|
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'users');
|
||||||
|
$modify->setId($map['id']);
|
||||||
|
$modify->set('active', true);
|
||||||
|
$modify->save();
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
`maps`
|
||||||
|
ADD
|
||||||
|
`area` decimal(13, 4) NOT NULL DEFAULT 0.0;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE `sessions` (
|
||||||
|
`id` varchar(64) NOT NULL,
|
||||||
|
`data` text NOT NULL,
|
||||||
|
`updated` timestamp NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE `user_confirmations` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`token` varchar(64) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `token` (`token`),
|
||||||
|
CONSTRAINT `user_confirmations_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
`users`
|
||||||
|
ADD
|
||||||
|
`active` tinyint(1) NOT NULL DEFAULT 0;
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
`places`
|
||||||
|
ADD
|
||||||
|
`pov_heading` decimal(6, 3) NOT NULL DEFAULT 0.0,
|
||||||
|
ADD
|
||||||
|
`pov_pitch` decimal(5, 3) NOT NULL DEFAULT 0.0,
|
||||||
|
ADD
|
||||||
|
`pov_zoom` decimal(5, 4) NOT NULL DEFAULT 0.0;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE `user_password_resetters` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`user_id` int(10) unsigned NOT NULL,
|
||||||
|
`token` varchar(32) CHARACTER SET ascii NOT NULL,
|
||||||
|
`expires` timestamp NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `user_id` (`user_id`),
|
||||||
|
KEY `token` (`token`),
|
||||||
|
CONSTRAINT `user_password_resetters_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
UPDATE `user_confirmations` SET token=SUBSTRING(token, 1, 32);
|
||||||
|
|
||||||
|
ALTER TABLE `user_confirmations`
|
||||||
|
ADD `last_sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MODIFY `token` varchar(32) CHARACTER SET ascii NOT NULL;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `sessions`
|
||||||
|
MODIFY `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
UPDATE `sessions` SET id=SUBSTRING(id, 1, 32);
|
||||||
|
|
||||||
|
ALTER TABLE `sessions`
|
||||||
|
MODIFY `id` varchar(32) CHARACTER SET ascii NOT NULL;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `users`
|
||||||
|
ADD `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE `multi_rooms` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`room_id` varchar(6) NOT NULL,
|
||||||
|
`state` text NOT NULL,
|
||||||
|
`members` text NOT NULL,
|
||||||
|
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `room_id` (`room_id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
||||||
+21
-4
@@ -1,15 +1,27 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build: ./docker
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Dockerfile-app
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/mapguesser
|
- .:/var/www/mapguesser
|
||||||
links:
|
multi:
|
||||||
- 'mariadb'
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Dockerfile-multi
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
- 8090:8090
|
||||||
|
- 9229:9229
|
||||||
|
volumes:
|
||||||
|
- .:/var/www/mapguesser
|
||||||
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:
|
||||||
@@ -17,5 +29,10 @@ services:
|
|||||||
MYSQL_DATABASE: 'mapguesser'
|
MYSQL_DATABASE: 'mapguesser'
|
||||||
MYSQL_USER: 'mapguesser'
|
MYSQL_USER: 'mapguesser'
|
||||||
MYSQL_PASSWORD: 'mapguesser'
|
MYSQL_PASSWORD: 'mapguesser'
|
||||||
|
mail:
|
||||||
|
image: marcopas/docker-mailslurper:latest
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
- 8085:8085
|
||||||
volumes:
|
volumes:
|
||||||
mysql:
|
mysql:
|
||||||
@@ -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,16 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Install necessary packages
|
||||||
|
RUN apt update --fix-missing
|
||||||
|
RUN apt install -y curl build-essential
|
||||||
|
|
||||||
|
# Install Node.js and required packages
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||||
|
RUN apt install -y nodejs
|
||||||
|
|
||||||
|
VOLUME /var/www/mapguesser
|
||||||
|
WORKDIR /var/www/mapguesser
|
||||||
|
|
||||||
|
ENTRYPOINT /usr/bin/node --inspect=0.0.0.0:9229 multi
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
Hi,
|
||||||
|
<br><br>
|
||||||
|
You recently requested password reset on {{APP_NAME}} with this email address ({{EMAIL}}).
|
||||||
|
To reset the password to your account, please click on the following link:<br>
|
||||||
|
<a href="{{RESET_LINK}}" title="Reset password">{{RESET_LINK}}</a>
|
||||||
|
<br><br>
|
||||||
|
You can reset your password with this link util {{EXPIRES}}.
|
||||||
|
<br><br>
|
||||||
|
If you did not requested password reset, no further action is required, your account is not touched.
|
||||||
|
<br><br>
|
||||||
|
Regards,<br>
|
||||||
|
{{APP_NAME}}<br>
|
||||||
|
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Hi,
|
||||||
|
<br><br>
|
||||||
|
You recently signed up on {{APP_NAME}} with this email address ({{EMAIL}}).
|
||||||
|
To activate your account, please click on the following link:<br>
|
||||||
|
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
||||||
|
<br><br>
|
||||||
|
You can activate your account until {{ACTIVATABLE_UNTIL}}.
|
||||||
|
If you don't activate your account, your email address will be permanently deleted after this point of time.
|
||||||
|
<br><br>
|
||||||
|
If you did not sign up on {{APP_NAME}} or changed your mind, no further action is required.
|
||||||
|
However if you want to immediately delete your email address, please click on the following link:<br>
|
||||||
|
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
||||||
|
<br><br>
|
||||||
|
Have fun on {{APP_NAME}}!
|
||||||
|
<br><br>
|
||||||
|
Regards,<br>
|
||||||
|
{{APP_NAME}}<br>
|
||||||
|
<a href="{{BASE_URL}}" title="{{APP_NAME}}">{{BASE_URL}}</a>
|
||||||
@@ -1,27 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
define('SCRIPT_STARTED', hrtime(true));
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
require 'vendor/autoload.php';
|
||||||
|
|
||||||
const ROOT = __DIR__;
|
const ROOT = __DIR__;
|
||||||
|
const VERSION = '';
|
||||||
|
const REVISION = '';
|
||||||
|
const REVISION_DATE = '';
|
||||||
|
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(ROOT);
|
$dotenv = Dotenv\Dotenv::createImmutable(ROOT);
|
||||||
$dotenv->load();
|
$dotenv->load();
|
||||||
|
|
||||||
if (!empty($_ENV['DEV'])) {
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
ini_set('display_errors', '1');
|
|
||||||
} else {
|
|
||||||
ini_set('display_errors', '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
class Container
|
class Container
|
||||||
{
|
{
|
||||||
static MapGuesser\Interfaces\Database\IConnection $dbConnection;
|
static MapGuesser\Interfaces\Database\IConnection $dbConnection;
|
||||||
static MapGuesser\Routing\RouteCollection $routeCollection;
|
static MapGuesser\Routing\RouteCollection $routeCollection;
|
||||||
|
static MapGuesser\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||||
|
static MapGuesser\Interfaces\Request\IRequest $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
||||||
Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
|
|
||||||
|
|
||||||
session_start();
|
|
||||||
@@ -5,7 +5,9 @@ require 'main.php';
|
|||||||
|
|
||||||
$app = new Symfony\Component\Console\Application('MapGuesser Console', '');
|
$app = new Symfony\Component\Console\Application('MapGuesser Console', '');
|
||||||
|
|
||||||
$app->add(new MapGuesser\Cli\DatabaseMigration());
|
$app->add(new MapGuesser\Cli\MigrateDatabaseCommand());
|
||||||
$app->add(new MapGuesser\Cli\AddUserCommand());
|
$app->add(new MapGuesser\Cli\AddUserCommand());
|
||||||
|
$app->add(new MapGuesser\Cli\LinkViewCommand());
|
||||||
|
$app->add(new \MapGuesser\Cli\MaintainDatabaseCommand());
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
+292
@@ -0,0 +1,292 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
process.title = 'mapguesser-multi';
|
||||||
|
|
||||||
|
class State {
|
||||||
|
static OPEN = 1;
|
||||||
|
static PLACE_RECEIVED = 2;
|
||||||
|
static GUESS_SENT = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MultiGame {
|
||||||
|
constructor() {
|
||||||
|
this.rooms = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupRooms() {
|
||||||
|
this.rooms.forEach(function (room, roomId) {
|
||||||
|
var lastValidDate = new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
if (room.updated < lastValidDate) {
|
||||||
|
this.rooms.delete(roomId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
connectToRoom(roomId, token, connection) {
|
||||||
|
if (!this.rooms.has(roomId) || !this.rooms.get(roomId).members.has(token)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var room = this.rooms.get(roomId)
|
||||||
|
var member = room.members.get(token);
|
||||||
|
member.connection = connection;
|
||||||
|
|
||||||
|
this._sendInitialData(room, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
createRoom(roomId) {
|
||||||
|
this.rooms.set(roomId, { members: new Map(), rounds: [], currentRound: -1, updated: new Date() });
|
||||||
|
}
|
||||||
|
|
||||||
|
joinRoom(roomId, token, userName) {
|
||||||
|
if (!this.rooms.has(roomId)) {
|
||||||
|
console.error('Room does not exist!')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var room = this.rooms.get(roomId);
|
||||||
|
room.updated = new Date();
|
||||||
|
|
||||||
|
if (room.members.has(token)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = { userName: userName };
|
||||||
|
var self = this;
|
||||||
|
room.members.forEach(function (member) {
|
||||||
|
self._sendToMember(member, 'member_joined', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
room.members.set(token, { userName: userName, state: State.OPEN, connection: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
startGame(roomId, places) {
|
||||||
|
if (!this.rooms.has(roomId)) {
|
||||||
|
//TODO: send something back
|
||||||
|
console.log('Room does not exist!')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var room = this.rooms.get(roomId);
|
||||||
|
room.updated = new Date();
|
||||||
|
|
||||||
|
var rounds = [];
|
||||||
|
places.forEach(function (place) {
|
||||||
|
rounds.push({ place: place, results: new Map() })
|
||||||
|
});
|
||||||
|
|
||||||
|
room.rounds = rounds;
|
||||||
|
|
||||||
|
this.nextRound(roomId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
guess(roomId, token, guessPosition, distance, score) {
|
||||||
|
if (!this.rooms.has(roomId)) {
|
||||||
|
//TODO: send something back
|
||||||
|
console.log('Room does not exist!')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var room = this.rooms.get(roomId);
|
||||||
|
room.updated = new Date();
|
||||||
|
|
||||||
|
var round = room.rounds[room.currentRound];
|
||||||
|
var member = this.rooms.get(roomId).members.get(token);
|
||||||
|
|
||||||
|
this._sendResultsUntilNow(room, member);
|
||||||
|
|
||||||
|
round.results.set(member.userName, { guessPosition: guessPosition, distance: distance, score: score });
|
||||||
|
member.state = State.GUESS_SENT;
|
||||||
|
|
||||||
|
this._broadcastGuess(room, member.userName, guessPosition, distance, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextRound(roomId, currentRound) {
|
||||||
|
if (!this.rooms.has(roomId)) {
|
||||||
|
//TODO: send something back
|
||||||
|
console.log('Room does not exist!')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var room = this.rooms.get(roomId);
|
||||||
|
room.updated = new Date();
|
||||||
|
|
||||||
|
room.currentRound = currentRound;
|
||||||
|
|
||||||
|
var round = room.rounds[room.currentRound];
|
||||||
|
|
||||||
|
var data = {};
|
||||||
|
data.place = { panoId: round.place.panoId, pov: round.place.pov };
|
||||||
|
|
||||||
|
if (room.currentRound > 0) {
|
||||||
|
data.result = { position: room.rounds[room.currentRound - 1].place.position };
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
room.members.forEach(function (member) {
|
||||||
|
self._sendToMember(member, 'new_round', data);
|
||||||
|
|
||||||
|
member.state = State.PLACE_RECEIVED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendInitialData(room, member) {
|
||||||
|
var data = {};
|
||||||
|
|
||||||
|
if (room.currentRound >= 0) {
|
||||||
|
data.place = room.rounds[room.currentRound].place;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.history = [];
|
||||||
|
for (var i = 0; i < room.currentRound; ++i) {
|
||||||
|
var round = room.rounds[i];
|
||||||
|
var result;
|
||||||
|
if (round.results.has(member.userName)) {
|
||||||
|
result = round.results.get(member.userName);
|
||||||
|
} else {
|
||||||
|
result = { guessPosition: null, distance: null, score: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
data.history.push({
|
||||||
|
position: round.place.position,
|
||||||
|
guessPosition: result.guessPosition,
|
||||||
|
distance: result.distance,
|
||||||
|
score: result.score
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
data.members = [];
|
||||||
|
room.members.forEach(function (currentMember) {
|
||||||
|
data.members.push({ userName: currentMember.userName, me: member === currentMember });
|
||||||
|
});
|
||||||
|
|
||||||
|
this._sendToMember(member, 'initialize', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendResultsUntilNow(room, member) {
|
||||||
|
if (member.state !== State.GUESS_SENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var round = room.rounds[room.currentRound];
|
||||||
|
|
||||||
|
var results = [];
|
||||||
|
round.results.forEach(function (result, userName) {
|
||||||
|
results.push({ userName: userName, guessPosition: result.guessPosition, distance: result.distance, score: result.score });
|
||||||
|
});
|
||||||
|
|
||||||
|
this._sendToMember(member, 'results', results);
|
||||||
|
}
|
||||||
|
|
||||||
|
_broadcastGuess(room, userName, guessPosition, distance, score) {
|
||||||
|
var data = { userName: userName, guessPosition: guessPosition, distance: distance, score: score };
|
||||||
|
|
||||||
|
room.members.forEach(function (member) {
|
||||||
|
if (!member.state !== State.GUESS_SENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sendToMember(member, 'guess', data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendToMember(member, type, data) {
|
||||||
|
if (!member.connection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.connection.readyState !== ws.OPEN) {
|
||||||
|
member.connection = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
member.connection.send(JSON.stringify({ type: type, data: data }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
var
|
||||||
|
net = require('net'),
|
||||||
|
ws = require('ws');
|
||||||
|
|
||||||
|
var multiGame = new MultiGame();
|
||||||
|
|
||||||
|
//TODO: following should be in a separate class/function
|
||||||
|
|
||||||
|
var tcpServer = net.createServer(function (socket) {
|
||||||
|
socket.on('data', function (data) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cannot parse data: ' + data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.func) {
|
||||||
|
case 'create_room':
|
||||||
|
multiGame.createRoom(data.args.roomId);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'join_room':
|
||||||
|
multiGame.joinRoom(data.args.roomId, data.args.token, data.args.userName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'start_game':
|
||||||
|
multiGame.startGame(data.args.roomId, data.args.places);
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'guess':
|
||||||
|
multiGame.guess(data.args.roomId, data.args.token, data.args.guessPosition, data.args.distance, data.args.score);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'next_round':
|
||||||
|
multiGame.nextRound(data.args.roomId, data.args.currentRound);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.write('OK');
|
||||||
|
socket.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
tcpServer.on('listening', function () {
|
||||||
|
console.log('[INFO] TCP server started');
|
||||||
|
});
|
||||||
|
tcpServer.listen(process.env.MULTI_INTERNAL_PORT);
|
||||||
|
|
||||||
|
var wsServer = new ws.Server({ port: process.env.MULTI_WS_PORT });
|
||||||
|
wsServer.on('connection', function (connection, request) {
|
||||||
|
console.log('[INFO] New WS connection: ' + request.connection.remoteAddress);
|
||||||
|
|
||||||
|
connection.on('message', function (data) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cannot parse data: ' + data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (data.func) {
|
||||||
|
case 'connect_to_room':
|
||||||
|
multiGame.connectToRoom(data.args.roomId, data.args.token, connection);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connection.on('close', function () {
|
||||||
|
console.log('[INFO] WS connection ended: ' + request.connection.remoteAddress);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
wsServer.on('listening', function () {
|
||||||
|
console.log('[INFO] WS server started');
|
||||||
|
});
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
multiGame.cleanupRooms();
|
||||||
|
}, 24 * 60 * 60 * 1000);
|
||||||
Generated
+17
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "mapguesser-multi",
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
||||||
|
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "mapguesser-multi",
|
||||||
|
"version": "",
|
||||||
|
"description": "MapGuesser Application - Multiplayer",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"ws": "^7.4.4"
|
||||||
|
},
|
||||||
|
"scripts": {},
|
||||||
|
"author": "Pőcze Bence <bence@pocze.ch>",
|
||||||
|
"license": "GNU AGPL 3.0"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
checkMissingIterableValueType: false
|
||||||
|
paths:
|
||||||
|
- main.php
|
||||||
|
- mapg
|
||||||
|
- web.php
|
||||||
|
- src
|
||||||
|
- tests
|
||||||
+15
-26
@@ -1,9 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require '../main.php';
|
require '../web.php';
|
||||||
|
|
||||||
// very basic routing
|
|
||||||
$host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
|
|
||||||
$method = strtolower($_SERVER['REQUEST_METHOD']);
|
$method = strtolower($_SERVER['REQUEST_METHOD']);
|
||||||
$url = substr($_SERVER['REQUEST_URI'], strlen('/'));
|
$url = substr($_SERVER['REQUEST_URI'], strlen('/'));
|
||||||
if (($pos = strpos($url, '?')) !== false) {
|
if (($pos = strpos($url, '?')) !== false) {
|
||||||
@@ -11,32 +9,15 @@ if (($pos = strpos($url, '?')) !== false) {
|
|||||||
}
|
}
|
||||||
$url = rawurldecode($url);
|
$url = rawurldecode($url);
|
||||||
|
|
||||||
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
|
$match = Container::$routeCollection->match($method, $url == '' ? [] : explode('/', $url));
|
||||||
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
|
|
||||||
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
|
|
||||||
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
|
||||||
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
|
|
||||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
|
||||||
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
|
|
||||||
$routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
|
|
||||||
$routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
|
|
||||||
$routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
|
|
||||||
});
|
|
||||||
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
|
||||||
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
|
|
||||||
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);
|
|
||||||
$routeCollection->post('admin.saveMap', 'saveMap/{mapId}/json', [MapGuesser\Controller\MapAdminController::class, 'saveMap']);
|
|
||||||
});
|
|
||||||
|
|
||||||
$match = Container::$routeCollection->match($method, explode('/', $url));
|
|
||||||
|
|
||||||
if ($match !== null) {
|
if ($match !== null) {
|
||||||
list($route, $params) = $match;
|
list($route, $params) = $match;
|
||||||
|
|
||||||
$request = new MapGuesser\Request\Request($_GET, $params, $_POST, $_SESSION);
|
Container::$request->setParsedRouteParams($params);
|
||||||
|
|
||||||
$handler = $route->getHandler();
|
$handler = $route->getHandler();
|
||||||
$controller = new $handler[0]($request);
|
$controller = new $handler[0](Container::$request);
|
||||||
|
|
||||||
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
|
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
|
||||||
$authorized = $controller->authorize();
|
$authorized = $controller->authorize();
|
||||||
@@ -44,21 +25,29 @@ if ($match !== null) {
|
|||||||
$authorized = true;
|
$authorized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
|
||||||
|
$content = new MapGuesser\Response\JsonContent(['error' => 'no_valid_anti_csrf_token']);
|
||||||
|
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||||||
|
$content->render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($authorized) {
|
if ($authorized) {
|
||||||
$response = call_user_func([$controller, $handler[1]]);
|
$response = call_user_func([$controller, $handler[1]]);
|
||||||
|
|
||||||
if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
|
if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
|
||||||
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
||||||
echo $response->render();
|
$response->render();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||||
header('Location: ' . $host . '/' . $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';
|
$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;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cover {
|
#panoCover {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 40px;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@@ -99,6 +102,15 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#startMultiGameButton {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#players > p {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
#mapName {
|
#mapName {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -111,7 +123,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 +169,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 +179,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,23 +1,13 @@
|
|||||||
#metadata {
|
|
||||||
position: absolute;
|
|
||||||
top: 50px;
|
|
||||||
left: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #eeeeee;
|
|
||||||
border: solid 1px #555555;
|
|
||||||
border-radius: 3px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
opacity: 0.95;
|
|
||||||
z-index: 2;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
||||||
}
|
}
|
||||||
@@ -30,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;
|
||||||
@@ -37,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;
|
||||||
@@ -61,41 +53,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 999px) and (min-height: 600px) {
|
@media screen and (max-width: 999px) and (min-height: 600px) {
|
||||||
#metadata {
|
|
||||||
width: calc(100% - 155px);
|
|
||||||
}
|
|
||||||
#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) {
|
||||||
#metadata {
|
|
||||||
width: calc(50% - 20px);
|
|
||||||
}
|
|
||||||
#metadata.selected {
|
|
||||||
top: 95px;
|
|
||||||
}
|
|
||||||
#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,16 @@ 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 {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
p, h1, h2, input, textarea, select, button, a {
|
p, h1, h2, input, textarea, select, button, a {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
@@ -37,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,16 +79,17 @@ sub {
|
|||||||
bottom: -0.4em;
|
bottom: -0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mono {
|
hr {
|
||||||
font-family: 'Roboto Mono', monospace;
|
border: solid #bbbbbb 1px;
|
||||||
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
p.small, span.small {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justify {
|
.justify {
|
||||||
@@ -89,10 +100,22 @@ sub {
|
|||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.marginLeft {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.marginBottom {
|
.marginBottom {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.marginRight {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@@ -101,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +153,27 @@ button, a.button {
|
|||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.small, div.inputWithButton>button {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 12px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.small {
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.inputWithButton>button {
|
||||||
|
border-radius: 2px;
|
||||||
|
height: 27px;
|
||||||
|
line-height: 27px;
|
||||||
|
width: 75px;
|
||||||
|
margin-left: -79px;
|
||||||
|
vertical-align: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -160,7 +205,7 @@ button.gray, a.button.gray {
|
|||||||
background-color: #808080;
|
background-color: #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.gray:hover, button.gray:focus, a.button.gray:hover, a.button.gray:focus {
|
button.gray:enabled:hover, button.gray:enabled:focus, a.button.gray:hover, a.button.gray:focus {
|
||||||
background-color: #555555;
|
background-color: #555555;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +213,7 @@ button.red, a.button.red {
|
|||||||
background-color: #aa5e5e;
|
background-color: #aa5e5e;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.red:hover, button.red:focus, a.button.red:hover, a.button.red:focus {
|
button.red:enabled:hover, button.red:enabled:focus, a.button.red:hover, a.button.red:focus {
|
||||||
background-color: #7f2929;
|
background-color: #7f2929;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +221,7 @@ button.yellow, a.button.yellow {
|
|||||||
background-color: #e8a349;
|
background-color: #e8a349;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.yellow:hover, button.yellow:focus, a.button.yellow:hover, a.button.yellow:focus {
|
button.yellow:enabled:hover, button.yellow:enabled:focus, a.button.yellow:hover, a.button.yellow:focus {
|
||||||
background-color: #c37713;
|
background-color: #c37713;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +229,7 @@ button.green, a.button.green {
|
|||||||
background-color: #28a745;
|
background-color: #28a745;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.green:hover, button.green:focus, a.button.green:hover, a.button.green:focus {
|
button.green:enabled:hover, button.green:enabled:focus, a.button.green:hover, a.button.green:focus {
|
||||||
background-color: #1b7d31;
|
background-color: #1b7d31;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,22 +237,36 @@ input, select, textarea {
|
|||||||
background-color: #f9fafb;
|
background-color: #f9fafb;
|
||||||
border: solid #c8d2e1 1px;
|
border: solid #c8d2e1 1px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 4px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
font-size: 13px;
|
padding: 5px;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.big, select.big, textarea.big {
|
input.big, select.big, textarea.big, div.inputWithButton>input {
|
||||||
padding: 5px;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.big, select.big, div.inputWithButton>input {
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.big {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
input.fullWidth, select.fullWidth, textarea.fullWidth {
|
input.fullWidth, select.fullWidth, textarea.fullWidth {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -222,21 +281,68 @@ input:disabled, select:disabled, textarea:disabled {
|
|||||||
input:focus, select:focus, textarea:focus {
|
input:focus, select:focus, textarea:focus {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: solid #29457f 2px;
|
border: solid #29457f 2px;
|
||||||
padding: 3px;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.big:focus, select.big:focus, textarea.big:focus {
|
input:focus, select:focus {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.formError {
|
input.big:focus, select.big:focus {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.inputWithButton>input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 83px 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.inputWithButton>input:focus {
|
||||||
|
padding: 0 82px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.big:focus {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.modal {
|
||||||
|
position: fixed;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 6;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cover {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #000000;
|
||||||
|
opacity: 0.5;
|
||||||
|
z-index: 5;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.error, p.formError {
|
||||||
color: #7f2929;
|
color: #7f2929;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.formError {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header {
|
header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto;
|
||||||
background-color: #333333;
|
background-color: #333333;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
@@ -244,38 +350,51 @@ div.header {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid {
|
header.small {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.header.small {
|
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2) {
|
header>p {
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2)>span {
|
header>p>span {
|
||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2)>span>a:link, div.header>div.grid>:nth-child(2)>span>a:visited {
|
header>p>span>a:link, header>p>span>a:visited {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.header>div.grid>:nth-child(2)>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;
|
||||||
}
|
}
|
||||||
@@ -284,15 +403,29 @@ div.buttonContainer>button {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cookiesNotice {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 20px;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
border: solid #888888 1px;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,14 +439,76 @@ div.box {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
div.header.small 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 {
|
||||||
|
left: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
div.box {
|
div.box {
|
||||||
width: initial;
|
width: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
width: 540px;
|
||||||
|
left: 50%;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,16 @@ div.mapItem>div.title {
|
|||||||
color: white;
|
color: white;
|
||||||
border-top-left-radius: 3px;
|
border-top-left-radius: 3px;
|
||||||
border-top-right-radius: 3px;
|
border-top-right-radius: 3px;
|
||||||
padding: 4px 8px;
|
padding: 0 8px;
|
||||||
|
height: 35px;
|
||||||
|
line-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.mapItem>div.title>p {
|
||||||
|
line-height: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.mapItem>div.title>p.title {
|
div.mapItem>div.title>p.title {
|
||||||
@@ -26,13 +35,19 @@ div.mapItem>div.title>p.title {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.mapItem>img {
|
div.mapItem>div.imgContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-top: 50%;
|
||||||
|
background-color: #cccccc;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -40,6 +55,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;
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,69 @@
|
|||||||
|
var Account = {
|
||||||
|
original: null,
|
||||||
|
countdown: null,
|
||||||
|
|
||||||
|
openGoogleAuthenticate: function () {
|
||||||
|
window.open('/account/googleAuthenticate', 'googleAuthenticate', 'height=600,width=600')
|
||||||
|
},
|
||||||
|
|
||||||
|
authenticatedWithGoogleCallback: function (authenticatedWithGoogleUntil) {
|
||||||
|
var password = document.getElementsByTagName('form')[0].elements.password;
|
||||||
|
var button = document.getElementById('authenticateWithGoogleButton');
|
||||||
|
|
||||||
|
Account.original = {
|
||||||
|
type: password.type,
|
||||||
|
placeholder: password.placeholder,
|
||||||
|
required: password.required,
|
||||||
|
disabled: password.disabled
|
||||||
|
};
|
||||||
|
|
||||||
|
password.type = 'text';
|
||||||
|
password.placeholder = 'Authenticated!'
|
||||||
|
password.value = '';
|
||||||
|
password.required = false;
|
||||||
|
password.disabled = true;
|
||||||
|
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
Account.countdown = setInterval(function () {
|
||||||
|
var timeLeft = Math.ceil((authenticatedWithGoogleUntil.getTime() - new Date().getTime()) / 1000);
|
||||||
|
|
||||||
|
if (timeLeft > 30) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
Account.resetGoogleAuthentication();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
password.placeholder = 'Authenticated! ' + timeLeft + ' seconds left...';
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetGoogleAuthentication: function () {
|
||||||
|
if (Account.countdown !== null) {
|
||||||
|
clearInterval(Account.countdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
var password = document.getElementsByTagName('form')[0].elements.password;
|
||||||
|
var button = document.getElementById('authenticateWithGoogleButton');
|
||||||
|
|
||||||
|
password.type = Account.original.type;
|
||||||
|
password.placeholder = Account.original.placeholder
|
||||||
|
password.required = Account.original.required;
|
||||||
|
password.disabled = Account.original.disabled;
|
||||||
|
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
document.getElementById('authenticateWithGoogleButton').onclick = function () {
|
||||||
|
Account.openGoogleAuthenticate();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementsByTagName('form')[0].onreset = function () {
|
||||||
|
Account.resetGoogleAuthentication();
|
||||||
|
};
|
||||||
|
})();
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
(function () {
|
||||||
|
if (success) {
|
||||||
|
window.opener.Account.authenticatedWithGoogleCallback(authenticatedWithGoogleUntil);
|
||||||
|
window.close();
|
||||||
|
} else {
|
||||||
|
document.getElementById('closeWindowButton').onclick = function () {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
+298
-41
@@ -5,38 +5,229 @@
|
|||||||
NUMBER_OF_ROUNDS: 5,
|
NUMBER_OF_ROUNDS: 5,
|
||||||
MAX_SCORE: 1000,
|
MAX_SCORE: 1000,
|
||||||
|
|
||||||
|
mapBounds: null,
|
||||||
|
multi: { token: null, owner: false },
|
||||||
rounds: [],
|
rounds: [],
|
||||||
scoreSum: 0,
|
scoreSum: 0,
|
||||||
panoId: null,
|
panoId: null,
|
||||||
|
pov: null,
|
||||||
panorama: null,
|
panorama: null,
|
||||||
map: null,
|
map: null,
|
||||||
guessMarker: null,
|
guessMarker: null,
|
||||||
adaptGuess: false,
|
adaptGuess: false,
|
||||||
googleLink: null,
|
googleLink: null,
|
||||||
|
|
||||||
initialize: function () {
|
MultiConnector: {
|
||||||
|
connection: null,
|
||||||
|
reconnectCounter: 0,
|
||||||
|
|
||||||
|
connect: function () {
|
||||||
|
if (Game.MultiConnector.connection && Game.MultiConnector.connection.readyState !== WebSocket.CLOSED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.MultiConnector.connection = new WebSocket((MapGuesser.isSecure ? 'wss' : 'ws') + '://' + multiUrl);
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onopen = function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
Game.MultiConnector.reconnectCounter = 0;
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.send(JSON.stringify({ func: 'connect_to_room', args: { roomId: roomId, token: Game.multi.token } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onclose = Game.MultiConnector.noConnection;
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onerror = function (event) {
|
||||||
|
console.error('WebSocket error in Game.MultiConnector:', event);
|
||||||
|
};
|
||||||
|
|
||||||
|
Game.MultiConnector.connection.onmessage = function (message) {
|
||||||
|
var json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = JSON.parse(message.data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cannot parse message!');
|
||||||
|
console.error(message.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (json.type) {
|
||||||
|
case 'initialize':
|
||||||
|
Game.MultiConnector.initialize(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'member_joined':
|
||||||
|
Game.MultiConnector.memberJoined(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'new_round':
|
||||||
|
Game.MultiConnector.newRound(json.data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'results':
|
||||||
|
//TODO
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'guess':
|
||||||
|
//TODO
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
noConnection: function () {
|
||||||
|
if (Game.MultiConnector.reconnectCounter === 2) {
|
||||||
|
console.error('Could not reconnect WebSocket for Game.MultiConnector...')
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
Game.MultiConnector.reconnectCounter++;
|
||||||
|
|
||||||
|
console.log('Reconnecting WebSocket for Game.MultiConnector... ' + Game.MultiConnector.reconnectCounter);
|
||||||
|
Game.MultiConnector.connect();
|
||||||
|
}, 1000 + Math.min(Game.MultiConnector.reconnectCounter * 500, 9000));
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function (data) {
|
||||||
|
if (data.history) {
|
||||||
|
for (var i = 0; i < data.history.length; ++i) {
|
||||||
|
var round = data.history[i];
|
||||||
|
Game.rounds.push({ position: round.position, guessPosition: round.guessPosition, realMarker: null, guessMarker: null, line: null });
|
||||||
|
Game.addRealGuessPair(round.position, round.guessPosition, true);
|
||||||
|
Game.scoreSum += round.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
|
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.place) {
|
||||||
|
Game.panoId = data.place.panoId;
|
||||||
|
Game.pov = data.place.pov;
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'hidden';
|
||||||
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
Game.startNewRound();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
var div = document.getElementById('players');
|
||||||
|
|
||||||
|
for (var i = 0; i < data.members.length; ++i) {
|
||||||
|
var member = data.members[i];
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.innerHTML = member.userName + (member.me ? ' (me)' : '');
|
||||||
|
div.appendChild(p);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
memberJoined: function (data) {
|
||||||
|
var div = document.getElementById('players');
|
||||||
|
|
||||||
|
var p = document.createElement('p');
|
||||||
|
p.innerHTML = data.userName;
|
||||||
|
div.appendChild(p);
|
||||||
|
},
|
||||||
|
|
||||||
|
newRound: function (data) {
|
||||||
|
//TODO: workaround until results are not sent
|
||||||
|
if (Game.adaptGuess) {
|
||||||
|
document.getElementById('guess').classList.remove('adapt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
||||||
|
Game.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if player didn't guess - TODO: show everything on a map
|
||||||
|
if (data.result && Game.rounds.length > 0 && !Game.rounds[Game.rounds.length - 1].position) {
|
||||||
|
Game.rounds[Game.rounds.length - 1].position = data.result.position;
|
||||||
|
Game.addRealGuessPair(data.result.position, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Game.panoId = data.place.panoId;
|
||||||
|
Game.pov = data.place.pov;
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'hidden';
|
||||||
|
Game.resetRound();
|
||||||
|
Game.startNewRound();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
prepare: function () {
|
||||||
|
var data = new FormData();
|
||||||
|
var userNames;
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
var userNames = localStorage.userNames ? JSON.parse(localStorage.userNames) : {};
|
||||||
|
if (!userNames.hasOwnProperty(roomId)) {
|
||||||
|
userNames[roomId] = prompt('Your name: ');
|
||||||
|
localStorage.userNames = JSON.stringify(userNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.append('userName', userNames[roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
document.getElementById('cover').style.visibility = 'visible';
|
|
||||||
|
var url = roomId ? '/multiGame/' + roomId + '/prepare.json' : '/game/' + mapId + '/prepare.json';
|
||||||
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
|
document.getElementById('mapName').innerHTML = this.response.mapName;
|
||||||
|
Game.mapBounds = this.response.bounds;
|
||||||
|
|
||||||
|
Game.initialize();
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
Game.multi.token = this.response.token;
|
||||||
|
Game.multi.owner = this.response.owner;
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'visible';
|
||||||
|
if (Game.multi.owner) {
|
||||||
|
document.getElementById('startMultiGameButton').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
Game.MultiConnector.connect();
|
||||||
|
}
|
||||||
|
}, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function () {
|
||||||
|
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';
|
||||||
|
|
||||||
Game.map.setOptions({
|
Game.map.setOptions({
|
||||||
draggableCursor: 'crosshair'
|
draggableCursor: 'crosshair'
|
||||||
});
|
});
|
||||||
Game.map.fitBounds(mapBounds);
|
Game.map.fitBounds(Game.mapBounds);
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
if (roomId) {
|
||||||
xhr.responseType = 'json';
|
// if it is multiplayer mode, data is sent via WS
|
||||||
xhr.onload = function () {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', '/game/' + mapId + '/initialData.json', function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
document.getElementById('cover').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
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.panoId;
|
Game.panoId = this.response.place.panoId;
|
||||||
|
Game.pov = this.response.place.pov;
|
||||||
|
|
||||||
if (this.response.history) {
|
if (this.response.history) {
|
||||||
for (var i = 0; i < this.response.history.length; ++i) {
|
for (var i = 0; i < this.response.history.length; ++i) {
|
||||||
@@ -51,10 +242,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Game.startNewRound();
|
Game.startNewRound();
|
||||||
};
|
});
|
||||||
|
|
||||||
xhr.open('GET', '/game/' + mapId + '/newPlace.json', true);
|
|
||||||
xhr.send();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reset: function () {
|
reset: function () {
|
||||||
@@ -66,8 +254,10 @@
|
|||||||
for (var i = 0; i < Game.rounds.length; ++i) {
|
for (var i = 0; i < Game.rounds.length; ++i) {
|
||||||
var round = Game.rounds[i];
|
var round = Game.rounds[i];
|
||||||
|
|
||||||
if (round.realMarker && round.guessMarker && round.line) {
|
if (round.realMarker) {
|
||||||
round.realMarker.setMap(null);
|
round.realMarker.setMap(null);
|
||||||
|
}
|
||||||
|
if (round.guessMarker) {
|
||||||
round.guessMarker.setMap(null);
|
round.guessMarker.setMap(null);
|
||||||
round.line.setMap(null);
|
round.line.setMap(null);
|
||||||
}
|
}
|
||||||
@@ -99,11 +289,13 @@
|
|||||||
var lastRound = Game.rounds[Game.rounds.length - 1];
|
var lastRound = Game.rounds[Game.rounds.length - 1];
|
||||||
|
|
||||||
lastRound.realMarker.setVisible(false);
|
lastRound.realMarker.setVisible(false);
|
||||||
|
if (lastRound.guessMarker) {
|
||||||
lastRound.guessMarker.setVisible(false);
|
lastRound.guessMarker.setVisible(false);
|
||||||
lastRound.line.setVisible(false);
|
lastRound.line.setVisible(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('cover').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')
|
||||||
@@ -111,7 +303,12 @@
|
|||||||
Game.map.setOptions({
|
Game.map.setOptions({
|
||||||
draggableCursor: 'crosshair'
|
draggableCursor: 'crosshair'
|
||||||
});
|
});
|
||||||
Game.map.fitBounds(mapBounds);
|
Game.map.fitBounds(Game.mapBounds);
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
// if it is multiplayer mode, data is sent via WS
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Game.startNewRound();
|
Game.startNewRound();
|
||||||
},
|
},
|
||||||
@@ -121,31 +318,32 @@
|
|||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
|
|
||||||
Game.loadPano(Game.panoId);
|
Game.loadPano(Game.panoId, Game.pov);
|
||||||
},
|
},
|
||||||
|
|
||||||
handleErrorResponse: function (error) {
|
handleErrorResponse: function (error) {
|
||||||
// for the time being we only handle the "no_session_found" error and reset the game
|
// for the time being we only handle the "no_session_found" error and reset the game
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
if (roomId) {
|
||||||
xhr.responseType = 'json';
|
//TODO: better error message
|
||||||
xhr.onload = function () {
|
alert('Your session is invalid, please start multiplayer again!')
|
||||||
mapBounds = this.response.bounds;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('GET', '/game/' + mapId + '/prepare.json', function () {
|
||||||
|
Game.mapBounds = this.response.bounds;
|
||||||
|
|
||||||
Game.reset();
|
Game.reset();
|
||||||
};
|
});
|
||||||
|
|
||||||
xhr.open('GET', '/game/' + mapId + '/json', true);
|
|
||||||
xhr.send();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPano: function (panoId) {
|
loadPano: function (panoId, pov) {
|
||||||
if (Game.adaptGuess) {
|
if (Game.adaptGuess) {
|
||||||
document.getElementById('guess').classList.add('adapt');
|
document.getElementById('guess').classList.add('adapt');
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.panorama.setPov({ heading: 0, pitch: 0 });
|
Game.panorama.setPov({ heading: pov.heading, pitch: pov.pitch });
|
||||||
Game.panorama.setZoom(0);
|
Game.panorama.setZoom(pov.zoom);
|
||||||
Game.panorama.setPano(panoId);
|
Game.panorama.setPano(panoId);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -162,15 +360,14 @@
|
|||||||
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('cover').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));
|
||||||
data.append('lng', String(guessPosition.lng));
|
data.append('lng', String(guessPosition.lng));
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var url = roomId ? '/multiGame/' + roomId + '/guess.json' : '/game/' + mapId + '/guess.json';
|
||||||
xhr.responseType = 'json';
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
xhr.onload = function () {
|
|
||||||
if (this.response.error) {
|
if (this.response.error) {
|
||||||
Game.handleErrorResponse(this.response.error);
|
Game.handleErrorResponse(this.response.error);
|
||||||
return;
|
return;
|
||||||
@@ -210,11 +407,18 @@
|
|||||||
document.getElementById('showSummaryButton').style.display = 'block';
|
document.getElementById('showSummaryButton').style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.panoId;
|
if (this.response.place) {
|
||||||
};
|
Game.panoId = this.response.place.panoId;
|
||||||
|
Game.pov = this.response.place.pov;
|
||||||
xhr.open('POST', '/game/' + mapId + '/guess.json', true);
|
} else {
|
||||||
xhr.send(data);
|
if (!Game.multi.owner) {
|
||||||
|
//TODO: "waiting for" disabled button
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
}
|
||||||
|
Game.panoId = null;
|
||||||
|
Game.pov = null;
|
||||||
|
}
|
||||||
|
}, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
addRealGuessPair: function (position, guessPosition, hidden) {
|
addRealGuessPair: function (position, guessPosition, hidden) {
|
||||||
@@ -229,7 +433,7 @@
|
|||||||
clickable: true,
|
clickable: true,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
icon: {
|
icon: {
|
||||||
url: '/static/img/markers/marker-green.svg',
|
url: STATIC_ROOT + '/img/markers/marker-green.svg?rev=' + REVISION,
|
||||||
size: new google.maps.Size(24, 32),
|
size: new google.maps.Size(24, 32),
|
||||||
scaledSize: new google.maps.Size(24, 32),
|
scaledSize: new google.maps.Size(24, 32),
|
||||||
anchor: new google.maps.Point(12, 32)
|
anchor: new google.maps.Point(12, 32)
|
||||||
@@ -240,6 +444,10 @@
|
|||||||
window.open('https://www.google.com/maps/search/?api=1&query=' + this.getPosition().toUrlValue(), '_blank');
|
window.open('https://www.google.com/maps/search/?api=1&query=' + this.getPosition().toUrlValue(), '_blank');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!guessPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
round.guessMarker = new google.maps.Marker({
|
round.guessMarker = new google.maps.Marker({
|
||||||
map: Game.map,
|
map: Game.map,
|
||||||
visible: !hidden,
|
visible: !hidden,
|
||||||
@@ -248,7 +456,7 @@
|
|||||||
clickable: false,
|
clickable: false,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
icon: {
|
icon: {
|
||||||
url: '/static/img/markers/marker-gray-empty.svg',
|
url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
|
||||||
size: new google.maps.Size(24, 32),
|
size: new google.maps.Size(24, 32),
|
||||||
scaledSize: new google.maps.Size(24, 32),
|
scaledSize: new google.maps.Size(24, 32),
|
||||||
anchor: new google.maps.Point(12, 32),
|
anchor: new google.maps.Point(12, 32),
|
||||||
@@ -311,7 +519,10 @@
|
|||||||
scoreInfo.children[0].style.display = 'none';
|
scoreInfo.children[0].style.display = 'none';
|
||||||
scoreInfo.children[1].style.display = 'block';
|
scoreInfo.children[1].style.display = 'block';
|
||||||
document.getElementById('showSummaryButton').style.display = null;
|
document.getElementById('showSummaryButton').style.display = null;
|
||||||
|
|
||||||
|
if (!roomId || Game.multi.owner) {
|
||||||
document.getElementById('startNewGameButton').style.display = 'block';
|
document.getElementById('startNewGameButton').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
var resultBounds = new google.maps.LatLngBounds();
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
|
|
||||||
@@ -319,7 +530,7 @@
|
|||||||
var round = Game.rounds[i];
|
var round = Game.rounds[i];
|
||||||
|
|
||||||
round.realMarker.setIcon({
|
round.realMarker.setIcon({
|
||||||
url: '/static/img/markers/marker-green-empty.svg',
|
url: STATIC_ROOT + '/img/markers/marker-green-empty.svg?rev=' + REVISION,
|
||||||
size: new google.maps.Size(24, 32),
|
size: new google.maps.Size(24, 32),
|
||||||
scaledSize: new google.maps.Size(24, 32),
|
scaledSize: new google.maps.Size(24, 32),
|
||||||
anchor: new google.maps.Point(12, 32),
|
anchor: new google.maps.Point(12, 32),
|
||||||
@@ -332,13 +543,18 @@
|
|||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
text: String(i + 1)
|
text: String(i + 1)
|
||||||
});
|
});
|
||||||
|
|
||||||
round.realMarker.setVisible(true);
|
round.realMarker.setVisible(true);
|
||||||
|
if (round.guessMarker) {
|
||||||
round.guessMarker.setVisible(true);
|
round.guessMarker.setVisible(true);
|
||||||
round.line.setVisible(true);
|
round.line.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
resultBounds.extend(round.position);
|
resultBounds.extend(round.position);
|
||||||
|
if (round.guessMarker) {
|
||||||
resultBounds.extend(round.guessPosition);
|
resultBounds.extend(round.guessPosition);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Game.map.fitBounds(resultBounds);
|
Game.map.fitBounds(resultBounds);
|
||||||
|
|
||||||
@@ -385,6 +601,8 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MapGuesser.sessionAvailableHooks.reinitializeGame = Game.prepare;
|
||||||
|
|
||||||
if (!('ontouchstart' in document.documentElement)) {
|
if (!('ontouchstart' in document.documentElement)) {
|
||||||
Game.adaptGuess = true;
|
Game.adaptGuess = true;
|
||||||
}
|
}
|
||||||
@@ -411,7 +629,7 @@
|
|||||||
clickable: false,
|
clickable: false,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
icon: {
|
icon: {
|
||||||
url: '/static/img/markers/marker-gray-empty.svg',
|
url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
|
||||||
size: new google.maps.Size(24, 32),
|
size: new google.maps.Size(24, 32),
|
||||||
scaledSize: new google.maps.Size(24, 32),
|
scaledSize: new google.maps.Size(24, 32),
|
||||||
anchor: new google.maps.Point(12, 32),
|
anchor: new google.maps.Point(12, 32),
|
||||||
@@ -444,7 +662,9 @@
|
|||||||
Game.rewriteGoogleLink();
|
Game.rewriteGoogleLink();
|
||||||
});
|
});
|
||||||
|
|
||||||
Game.initialize();
|
if (COOKIES_CONSENT) {
|
||||||
|
Game.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('showGuessButton').onclick = function () {
|
document.getElementById('showGuessButton').onclick = function () {
|
||||||
this.style.visibility = 'hidden';
|
this.style.visibility = 'hidden';
|
||||||
@@ -461,14 +681,51 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('continueButton').onclick = function () {
|
document.getElementById('continueButton').onclick = function () {
|
||||||
|
if (roomId) {
|
||||||
|
if (!Game.multi.owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/nextRound.json', function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
Game.resetRound();
|
Game.resetRound();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('showSummaryButton').onclick = function () {
|
document.getElementById('showSummaryButton').onclick = function () {
|
||||||
Game.showSummary();
|
Game.showSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('startNewGameButton').onclick = function () {
|
document.getElementById('startNewGameButton').onclick = function () {
|
||||||
|
if (roomId) {
|
||||||
|
if (!Game.multi.owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
Game.reset();
|
Game.reset();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('startMultiGameButton').onclick = function () {
|
||||||
|
if (!roomId || !Game.multi.owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('multi').style.visibility = 'hidden';
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
|
||||||
|
MapGuesser.httpRequest('POST', '/multiGame/' + roomId + '/initialData.json', function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
});
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
@@ -1,42 +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);
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.responseType = 'json';
|
|
||||||
xhr.onload = function () {
|
|
||||||
if (this.response.error) {
|
|
||||||
var errorText;
|
|
||||||
switch (this.response.error) {
|
|
||||||
case 'user_not_found':
|
|
||||||
errorText = 'No user found with the given email address.';
|
|
||||||
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('/');
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('POST', form.action, true);
|
|
||||||
xhr.send(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();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
description: null
|
description: null
|
||||||
},
|
},
|
||||||
map: null,
|
map: null,
|
||||||
|
markers: null,
|
||||||
panorama: null,
|
panorama: null,
|
||||||
selectedMarker: null,
|
selectedMarker: null,
|
||||||
added: {},
|
added: {},
|
||||||
@@ -21,15 +22,13 @@
|
|||||||
|
|
||||||
document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]';
|
document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]';
|
||||||
|
|
||||||
document.getElementById('metadata').style.visibility = 'hidden';
|
MapGuesser.hideModal();
|
||||||
|
|
||||||
document.getElementById('saveButton').disabled = false;
|
document.getElementById('saveButton').disabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
getPlace: function (placeId, marker) {
|
getPlace: function (placeId, marker) {
|
||||||
var xhr = new XMLHttpRequest();
|
MapGuesser.httpRequest('GET', '/admin/place.json/' + placeId, function () {
|
||||||
xhr.responseType = 'json';
|
|
||||||
xhr.onload = function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
if (!this.response.panoId) {
|
if (!this.response.panoId) {
|
||||||
@@ -45,10 +44,7 @@
|
|||||||
places[marker.placeId].noPano = false;
|
places[marker.placeId].noPano = false;
|
||||||
|
|
||||||
MapEditor.loadPano(this.response.panoId, places[marker.placeId].pov);
|
MapEditor.loadPano(this.response.panoId, places[marker.placeId].pov);
|
||||||
};
|
});
|
||||||
|
|
||||||
xhr.open('GET', '/admin/place.json/' + placeId, true);
|
|
||||||
xhr.send();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loadPano: function (panoId, pov) {
|
loadPano: function (panoId, pov) {
|
||||||
@@ -113,7 +109,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('metadata').classList.add('selected');
|
|
||||||
document.getElementById('map').classList.add('selected');
|
document.getElementById('map').classList.add('selected');
|
||||||
document.getElementById('control').classList.add('selected');
|
document.getElementById('control').classList.add('selected');
|
||||||
document.getElementById('noPano').style.visibility = 'hidden';
|
document.getElementById('noPano').style.visibility = 'hidden';
|
||||||
@@ -129,6 +124,8 @@
|
|||||||
MapEditor.panorama.setVisible(false);
|
MapEditor.panorama.setVisible(false);
|
||||||
|
|
||||||
if (marker.placeId) {
|
if (marker.placeId) {
|
||||||
|
MapEditor.markers.removeLayer(MapEditor.selectedMarker);
|
||||||
|
MapEditor.map.addLayer(MapEditor.selectedMarker);
|
||||||
marker.setIcon(IconCollection.iconBlue);
|
marker.setIcon(IconCollection.iconBlue);
|
||||||
marker.setZIndexOffset(2000);
|
marker.setZIndexOffset(2000);
|
||||||
|
|
||||||
@@ -168,6 +165,8 @@
|
|||||||
var placeId = MapEditor.selectedMarker.placeId
|
var placeId = MapEditor.selectedMarker.placeId
|
||||||
|
|
||||||
if (places[placeId].id && !del) {
|
if (places[placeId].id && !del) {
|
||||||
|
MapEditor.map.removeLayer(MapEditor.selectedMarker);
|
||||||
|
MapEditor.markers.addLayer(MapEditor.selectedMarker);
|
||||||
MapEditor.selectedMarker.setIcon(places[placeId].noPano ? IconCollection.iconRed : IconCollection.iconGreen);
|
MapEditor.selectedMarker.setIcon(places[placeId].noPano ? IconCollection.iconRed : IconCollection.iconGreen);
|
||||||
MapEditor.selectedMarker.setZIndexOffset(1000);
|
MapEditor.selectedMarker.setZIndexOffset(1000);
|
||||||
} else {
|
} else {
|
||||||
@@ -215,7 +214,6 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
closePlace: function (del) {
|
closePlace: function (del) {
|
||||||
document.getElementById('metadata').classList.remove('selected')
|
|
||||||
document.getElementById('map').classList.remove('selected');
|
document.getElementById('map').classList.remove('selected');
|
||||||
document.getElementById('control').classList.remove('selected');
|
document.getElementById('control').classList.remove('selected');
|
||||||
document.getElementById('noPano').style.visibility = 'hidden';
|
document.getElementById('noPano').style.visibility = 'hidden';
|
||||||
@@ -279,9 +277,7 @@
|
|||||||
data.append('deleted[]', JSON.stringify(MapEditor.deleted[placeId]));
|
data.append('deleted[]', JSON.stringify(MapEditor.deleted[placeId]));
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
MapGuesser.httpRequest('POST', '/admin/saveMap/' + mapId + '/json', function () {
|
||||||
xhr.responseType = 'json';
|
|
||||||
xhr.onload = function () {
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
if (this.response.error) {
|
if (this.response.error) {
|
||||||
@@ -305,10 +301,7 @@
|
|||||||
document.getElementById('deleted').innerHTML = '0';
|
document.getElementById('deleted').innerHTML = '0';
|
||||||
|
|
||||||
document.getElementById('saveButton').disabled = true;
|
document.getElementById('saveButton').disabled = true;
|
||||||
};
|
}, data);
|
||||||
|
|
||||||
xhr.open('POST', '/admin/saveMap/' + mapId + '/json', true);
|
|
||||||
xhr.send(data);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
replacePlaceIdsToReal: function (addedPlaces) {
|
replacePlaceIdsToReal: function (addedPlaces) {
|
||||||
@@ -322,17 +315,17 @@
|
|||||||
|
|
||||||
var IconCollection = {
|
var IconCollection = {
|
||||||
iconGreen: L.icon({
|
iconGreen: L.icon({
|
||||||
iconUrl: '/static/img/markers/marker-green.svg',
|
iconUrl: STATIC_ROOT + '/img/markers/marker-green.svg?rev' + REVISION,
|
||||||
iconSize: [24, 32],
|
iconSize: [24, 32],
|
||||||
iconAnchor: [12, 32]
|
iconAnchor: [12, 32]
|
||||||
}),
|
}),
|
||||||
iconRed: L.icon({
|
iconRed: L.icon({
|
||||||
iconUrl: '/static/img/markers/marker-red.svg',
|
iconUrl: STATIC_ROOT + '/img/markers/marker-red.svg?rev=' + REVISION,
|
||||||
iconSize: [24, 32],
|
iconSize: [24, 32],
|
||||||
iconAnchor: [12, 32]
|
iconAnchor: [12, 32]
|
||||||
}),
|
}),
|
||||||
iconBlue: L.icon({
|
iconBlue: L.icon({
|
||||||
iconUrl: '/static/img/markers/marker-blue.svg',
|
iconUrl: STATIC_ROOT + '/img/markers/marker-blue.svg?rev=' + REVISION,
|
||||||
iconSize: [24, 32],
|
iconSize: [24, 32],
|
||||||
iconAnchor: [12, 32]
|
iconAnchor: [12, 32]
|
||||||
}),
|
}),
|
||||||
@@ -341,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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -375,12 +368,16 @@
|
|||||||
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 }));
|
||||||
|
|
||||||
|
MapEditor.markers = L.markerClusterGroup({
|
||||||
|
maxClusterRadius: 50
|
||||||
|
});
|
||||||
|
|
||||||
for (var placeId in places) {
|
for (var placeId in places) {
|
||||||
if (!places.hasOwnProperty(placeId)) {
|
if (!places.hasOwnProperty(placeId)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -392,7 +389,7 @@
|
|||||||
icon: place.noPano ? IconCollection.iconRed : IconCollection.iconGreen,
|
icon: place.noPano ? IconCollection.iconRed : IconCollection.iconGreen,
|
||||||
zIndexOffset: 1000
|
zIndexOffset: 1000
|
||||||
})
|
})
|
||||||
.addTo(MapEditor.map)
|
.addTo(MapEditor.markers)
|
||||||
.on('click', function () {
|
.on('click', function () {
|
||||||
MapEditor.select(this);
|
MapEditor.select(this);
|
||||||
});
|
});
|
||||||
@@ -400,6 +397,8 @@
|
|||||||
marker.placeId = place.id;
|
marker.placeId = place.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MapEditor.map.addLayer(MapEditor.markers);
|
||||||
|
|
||||||
MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
|
MapEditor.panorama = new google.maps.StreetViewPanorama(document.getElementById('panorama'), {
|
||||||
// switch off fullscreenControl because positioning doesn't work
|
// switch off fullscreenControl because positioning doesn't work
|
||||||
fullscreenControl: false,
|
fullscreenControl: false,
|
||||||
@@ -412,14 +411,8 @@
|
|||||||
document.getElementById('mapName').onclick = function (e) {
|
document.getElementById('mapName').onclick = function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var metadata = document.getElementById('metadata');
|
MapGuesser.showModal('metadata');
|
||||||
|
|
||||||
if (metadata.style.visibility === 'visible') {
|
|
||||||
metadata.style.visibility = 'hidden';
|
|
||||||
} else {
|
|
||||||
metadata.style.visibility = 'visible';
|
|
||||||
document.getElementById('metadataForm').elements.name.select();
|
document.getElementById('metadataForm').elements.name.select();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('metadataForm').onsubmit = function (e) {
|
document.getElementById('metadataForm').onsubmit = function (e) {
|
||||||
@@ -429,7 +422,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('closeMetadataButton').onclick = function () {
|
document.getElementById('closeMetadataButton').onclick = function () {
|
||||||
document.getElementById('metadata').style.visibility = 'hidden';
|
MapGuesser.hideModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('saveButton').onclick = function () {
|
document.getElementById('saveButton').onclick = function () {
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
var MapGuesser = {
|
||||||
|
isSecure: window.location.protocol === 'https:',
|
||||||
|
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) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.onload = callback;
|
||||||
|
|
||||||
|
xhr.open(method, url, true);
|
||||||
|
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
|
||||||
|
if (method === 'POST') {
|
||||||
|
if (typeof data === 'undefined') {
|
||||||
|
data = new FormData();
|
||||||
|
}
|
||||||
|
|
||||||
|
data.append('anti_csrf_token', ANTI_CSRF_TOKEN);
|
||||||
|
|
||||||
|
xhr.send(data);
|
||||||
|
} else {
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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) {
|
||||||
|
document.getElementById(id).style.visibility = 'visible';
|
||||||
|
document.getElementById('cover').style.visibility = 'visible';
|
||||||
|
},
|
||||||
|
|
||||||
|
showModalWithContent: function (title, body, extraButtons) {
|
||||||
|
if (typeof extraButtons === 'undefined') {
|
||||||
|
extraButtons = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
MapGuesser.showModal('modal');
|
||||||
|
|
||||||
|
document.getElementById('modalTitle').textContent = title;
|
||||||
|
|
||||||
|
if (typeof body === 'object') {
|
||||||
|
document.getElementById('modalText').appendChild(body);
|
||||||
|
} else {
|
||||||
|
document.getElementById('modalText').textContent = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttons = document.getElementById('modalButtons');
|
||||||
|
buttons.textContent = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < extraButtons.length; i++) {
|
||||||
|
var extraButton = extraButtons[i];
|
||||||
|
var button = document.createElement(extraButton.type);
|
||||||
|
|
||||||
|
if (extraButton.type === 'a') {
|
||||||
|
button.classList.add('button');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < extraButton.classNames.length; i++) {
|
||||||
|
button.classList.add(extraButton.classNames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.classList.add('marginTop');
|
||||||
|
button.classList.add('marginRight');
|
||||||
|
button.textContent = extraButton.text;
|
||||||
|
|
||||||
|
if (extraButton.type === 'a') {
|
||||||
|
button.href = extraButton.href;
|
||||||
|
} else if (extraButton.type === 'button') {
|
||||||
|
button.onclick = extraButton.onclick;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
var closeButton = document.createElement('button');
|
||||||
|
|
||||||
|
closeButton.classList.add('gray');
|
||||||
|
closeButton.classList.add('marginTop');
|
||||||
|
closeButton.textContent = 'Close';
|
||||||
|
closeButton.onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
buttons.appendChild(closeButton);
|
||||||
|
},
|
||||||
|
|
||||||
|
hideModal: function () {
|
||||||
|
var modals = document.getElementsByClassName('modal');
|
||||||
|
|
||||||
|
for (var i = 0; i < modals.length; i++) {
|
||||||
|
modals[i].style.visibility = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
|
},
|
||||||
|
|
||||||
|
observeInput: function (input, buttonToToggle) {
|
||||||
|
if (input.defaultValue !== input.value) {
|
||||||
|
buttonToToggle.disabled = false;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var anchors = document.getElementsByTagName('a');
|
||||||
|
for (var i = 0; i < anchors.length; i++) {
|
||||||
|
var a = anchors[i];
|
||||||
|
if (a.href !== 'javascript:;' && a.target !== '_blank') {
|
||||||
|
a.onclick = function () {
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (COOKIES_CONSENT) {
|
||||||
|
MapGuesser.initGoogleAnalitics();
|
||||||
|
} else {
|
||||||
|
// 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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
(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();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('multiForm').onsubmit = function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var roomId = this.elements.roomId.value;
|
||||||
|
if (roomId.length !== 6) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = '/multiGame/' + this.elements.roomId.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('multiButton').onclick = function () {
|
||||||
|
MapGuesser.showModal('multi');
|
||||||
|
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
|
||||||
|
document.getElementById('multiForm').elements.roomId.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('closePlayModeButton').onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('closeMultiButton').onclick = function () {
|
||||||
|
MapGuesser.hideModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
|
||||||
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
|
var button = buttons[i];
|
||||||
|
|
||||||
|
button.onclick = function () {
|
||||||
|
MapGuesser.showModal('playMode');
|
||||||
|
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
|
||||||
|
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -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,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"leaflet": "^1.6.0"
|
"leaflet": "^1.6.0",
|
||||||
|
"leaflet.markercluster": "^1.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
leaflet.markercluster@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/leaflet.markercluster/-/leaflet.markercluster-1.4.1.tgz#b53f2c4f2ca7306ddab1dbb6f1861d5e8aa6c5e5"
|
||||||
|
integrity sha512-ZSEpE/EFApR0bJ1w/dUGwTSUvWlpalKqIzkaYdYB7jaftQA/Y2Jav+eT4CMtEYFj+ZK4mswP13Q2acnPBnhGOw==
|
||||||
|
|
||||||
leaflet@^1.6.0:
|
leaflet@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.6.0.tgz#aecbb044b949ec29469eeb31c77a88e2f448f308"
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ def updateAppInWorktree(worktreePath):
|
|||||||
subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath)
|
subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath)
|
||||||
|
|
||||||
def updateAppVersionInWorktree(worktreePath):
|
def updateAppVersionInWorktree(worktreePath):
|
||||||
pass
|
subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath)
|
||||||
#subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath)
|
|
||||||
|
|
||||||
worktrees = getDataForWorktrees()
|
worktrees = getDataForWorktrees()
|
||||||
|
|
||||||
@@ -111,10 +110,6 @@ if developmentWorktree.revision != developmentWorktree.newRevision:
|
|||||||
elif developmentWorktree.version != developmentWorktree.newVersion:
|
elif developmentWorktree.version != developmentWorktree.newVersion:
|
||||||
print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED")
|
print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED")
|
||||||
|
|
||||||
checkoutWorktree(developmentWorktree.path, developmentWorktree.branch)
|
|
||||||
|
|
||||||
print(developmentWorktree.path + " is checked out to " + developmentWorktree.branch)
|
|
||||||
|
|
||||||
updateAppVersionInWorktree(developmentWorktree.path)
|
updateAppVersionInWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
print("MapGuesser version is updated in " + developmentWorktree.path)
|
print("MapGuesser version is updated in " + developmentWorktree.path)
|
||||||
|
|||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
BRANCH_NAME=$(git symbolic-ref --short HEAD)
|
||||||
|
BRANCH_PATTERN="^(bugfix|feature|hotfix)\/([A-Z]+-[0-9]+).*"
|
||||||
|
|
||||||
|
if [[ "${BRANCH_NAME}" =~ $BRANCH_PATTERN ]]; then
|
||||||
|
TICKET_ID=$(echo $BRANCH_NAME | sed -E "s@$BRANCH_PATTERN@\\2@")
|
||||||
|
|
||||||
|
COMMIT_MESSAGE=$(head -n 1 $1)
|
||||||
|
COMMIT_MESSAGE_REGEX="^$TICKET_ID .*"
|
||||||
|
|
||||||
|
if [[ ! "${COMMIT_MESSAGE}" =~ $COMMIT_MESSAGE_REGEX ]]; then
|
||||||
|
sed -i.bak -e "1s/^/$TICKET_ID /" $1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
+7
-1
@@ -9,6 +9,9 @@ if [ -f ${ROOT_DIR}/installed ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "Installing NPM packages..."
|
||||||
|
(cd ${ROOT_DIR}/multi && npm install)
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
echo "Installing Yarn packages..."
|
||||||
(cd ${ROOT_DIR}/public/static && yarn install)
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
|
|
||||||
@@ -16,11 +19,14 @@ echo "Installing MapGuesser DB..."
|
|||||||
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/db/mapguesser.sql
|
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/db/mapguesser.sql
|
||||||
|
|
||||||
echo "Migrating DB..."
|
echo "Migrating DB..."
|
||||||
(cd ${ROOT_DIR} && ./mapg migrate)
|
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||||
|
|
||||||
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}
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
cd ${ROOT_DIR}
|
||||||
|
|
||||||
|
echo "Updating version info..."
|
||||||
|
|
||||||
|
VERSION=$(git describe --tags --always --match "Release_*" HEAD)
|
||||||
|
REVISION=$(git rev-parse --short HEAD)
|
||||||
|
REVISION_DATE=$(git show -s --format=%aI HEAD)
|
||||||
|
|
||||||
|
sed -i -E "s/const VERSION = '(.*)';/const VERSION = '${VERSION}';/" main.php
|
||||||
|
sed -i -E "s/const REVISION = '(.*)';/const REVISION = '${REVISION}';/" main.php
|
||||||
|
sed -i -E "s/const REVISION_DATE = '(.*)';/const REVISION_DATE = '${REVISION_DATE}';/" main.php
|
||||||
+12
-2
@@ -5,15 +5,25 @@ 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 NPM packages..."
|
||||||
|
(cd ${ROOT_DIR}/multi && npm install)
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
echo "Installing Yarn packages..."
|
||||||
(cd ${ROOT_DIR}/public/static && yarn install)
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
|
|
||||||
echo "Migrating DB..."
|
echo "Migrating DB..."
|
||||||
(cd ${ROOT_DIR} && ./mapg migrate)
|
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||||
|
|
||||||
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
|
||||||
+10
-10
@@ -1,7 +1,8 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Modify;
|
use DateTime;
|
||||||
use MapGuesser\Model\User;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
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;
|
||||||
@@ -9,7 +10,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
|
|
||||||
class AddUserCommand extends Command
|
class AddUserCommand extends Command
|
||||||
{
|
{
|
||||||
public function configure()
|
public function configure(): void
|
||||||
{
|
{
|
||||||
$this->setName('user:add')
|
$this->setName('user:add')
|
||||||
->setDescription('Adding of user.')
|
->setDescription('Adding of user.')
|
||||||
@@ -20,20 +21,19 @@ 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'));
|
||||||
|
$user->setActive(true);
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
if ($input->hasArgument('type')) {
|
if ($input->hasArgument('type')) {
|
||||||
$user->setType($input->getArgument('type'));
|
$user->setType($input->getArgument('type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
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(): void
|
||||||
|
{
|
||||||
|
$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 || strpos($view, 'tests') === 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
|
use MapGuesser\Repository\UserRepository;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class MaintainDatabaseCommand extends Command
|
||||||
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
|
private UserConfirmationRepository $userConfirmationRepository;
|
||||||
|
|
||||||
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->userRepository = new UserRepository();
|
||||||
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configure(): void
|
||||||
|
{
|
||||||
|
$this->setName('db:maintain')
|
||||||
|
->setDescription('Maintain database.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->deleteInactiveExpiredUsers();
|
||||||
|
$this->deleteExpiredPasswordResetters();
|
||||||
|
$this->deleteExpiredRooms();
|
||||||
|
$this->deleteExpiredSessions();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$output->writeln('<error>Maintenance failed!</error>');
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
$output->writeln((string) $e);
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('<info>Maintenance was successful!</info>');
|
||||||
|
$output->writeln('');
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteInactiveExpiredUsers(): void
|
||||||
|
{
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
foreach ($this->userRepository->getAllInactiveExpired() as $user) {
|
||||||
|
//TODO: these can be in some wrapper class
|
||||||
|
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
|
if ($userConfirmation !== null) {
|
||||||
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
|
if ($userPasswordResetter !== null) {
|
||||||
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteExpiredPasswordResetters(): void
|
||||||
|
{
|
||||||
|
foreach ($this->userPasswordResetterRepository->getAllExpired() as $passwordResetter) {
|
||||||
|
$this->pdm->deleteFromDb($passwordResetter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteExpiredRooms(): void
|
||||||
|
{
|
||||||
|
foreach ($this->multiRoomRepository->getAllExpired() as $multiRoom) {
|
||||||
|
$this->pdm->deleteFromDb($multiRoom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteExpiredSessions(): void
|
||||||
|
{
|
||||||
|
//TODO: model may be used for sessions too
|
||||||
|
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||||
|
$select->columns(['id']);
|
||||||
|
$select->where('updated', '<', (new DateTime('-7 days'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
while ($session = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
|
$modify->setId($session['id']);
|
||||||
|
$modify->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,11 +7,11 @@ use Symfony\Component\Console\Command\Command;
|
|||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class DatabaseMigration extends Command
|
class MigrateDatabaseCommand extends Command
|
||||||
{
|
{
|
||||||
public function configure()
|
public function configure(): void
|
||||||
{
|
{
|
||||||
$this->setName('migrate')
|
$this->setName('db:migrate')
|
||||||
->setDescription('Migration of database changes.');
|
->setDescription('Migration of database changes.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +88,10 @@ class DatabaseMigration extends Command
|
|||||||
$path = ROOT . '/database/migrations/' . $type;
|
$path = ROOT . '/database/migrations/' . $type;
|
||||||
$dir = opendir($path);
|
$dir = opendir($path);
|
||||||
|
|
||||||
|
if ($dir === false) {
|
||||||
|
throw new \Exception('Cannot open dir: ' . $path);
|
||||||
|
}
|
||||||
|
|
||||||
$files = [];
|
$files = [];
|
||||||
while ($file = readdir($dir)) {
|
while ($file = readdir($dir)) {
|
||||||
$filePath = $path . '/' . $file;
|
$filePath = $path . '/' . $file;
|
||||||
@@ -1,54 +1,166 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Faker\Factory;
|
||||||
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;
|
||||||
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\Multi\MultiConnector;
|
||||||
|
use MapGuesser\PersistentData\Model\MultiRoom;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
class GameController
|
class GameController
|
||||||
{
|
{
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private MultiConnector $multiConnector;
|
||||||
|
|
||||||
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->multiConnector = new MultiConnector();
|
||||||
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGame(): IContent
|
public function getGame(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$data = $this->prepareGame($mapId);
|
|
||||||
return new HtmlContent('game', $data);
|
return new HtmlContent('game', ['mapId' => $mapId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGameJson(): IContent
|
public function getNewMultiGame(): IRedirect
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$data = $this->prepareGame($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
return new JsonContent($data);
|
$roomId = bin2hex(random_bytes(3));
|
||||||
|
$token = $this->getMultiToken($roomId);
|
||||||
|
|
||||||
|
$room = new MultiRoom();
|
||||||
|
$room->setRoomId($roomId);
|
||||||
|
$room->setStateArray([
|
||||||
|
'mapId' => $mapId,
|
||||||
|
'area' => $map->getArea(),
|
||||||
|
'rounds' => [],
|
||||||
|
'currentRound' => -1
|
||||||
|
]);
|
||||||
|
$room->setMembersArray(['owner' => $token, 'all' => []]);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
|
$this->multiConnector->sendMessage('create_room', ['roomId' => $roomId]);
|
||||||
|
|
||||||
|
return new Redirect(
|
||||||
|
\Container::$routeCollection
|
||||||
|
->getRoute('multiGame')
|
||||||
|
->generateLink(['roomId' => $roomId]),
|
||||||
|
IRedirect::TEMPORARY
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepareGame(int $mapId)
|
public function getMultiGame(): IContent
|
||||||
{
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
|
||||||
|
return new HtmlContent('game', ['roomId' => $roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareGame(): IContent
|
||||||
|
{
|
||||||
|
$mapId = (int) $this->request->query('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']);
|
|
||||||
|
|
||||||
$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' => $bounds->calculateApproximateArea(),
|
'area' => $map->getArea(),
|
||||||
'rounds' => []
|
'rounds' => [],
|
||||||
|
'currentRound' => -1
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['mapId' => $mapId, 'mapName' => $map['name'], 'bounds' => $bounds->toArray()];
|
return new JsonContent([
|
||||||
|
'mapId' => $mapId,
|
||||||
|
'mapName' => $map->getName(),
|
||||||
|
'bounds' => $map->getBounds()->toArray()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareMultiGame(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$userName = $this->request->post('userName');
|
||||||
|
if (empty($userName)) {
|
||||||
|
$faker = Factory::create();
|
||||||
|
$userName = $faker->userName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
$map = $this->mapRepository->getById($state['mapId']);
|
||||||
|
$token = $this->getMultiToken($roomId);
|
||||||
|
|
||||||
|
$members = $room->getMembersArray();
|
||||||
|
|
||||||
|
if (!in_array($token, $members['all'])) {
|
||||||
|
if ($state['currentRound'] >= 0) {
|
||||||
|
return new JsonContent(['error' => 'game_already_started']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$members['all'][] = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$room->setMembersArray($members);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
|
$this->multiConnector->sendMessage('join_room', [
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $token,
|
||||||
|
'userName' => $userName
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new JsonContent([
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $token,
|
||||||
|
'owner' => $members['owner'] == $token,
|
||||||
|
'mapId' => $state['mapId'],
|
||||||
|
'mapName' => $map->getName(),
|
||||||
|
'bounds' => $map->getBounds()->toArray()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMultiToken(string $roomId): string
|
||||||
|
{
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$session->set('multiState', [
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $token
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$token = $multiState['token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
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\Multi\MultiConnector;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
|
|
||||||
class GameFlowController
|
class GameFlowController
|
||||||
@@ -13,39 +17,49 @@ class GameFlowController
|
|||||||
|
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private MultiConnector $multiConnector;
|
||||||
|
|
||||||
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
public function __construct(IRequest $request)
|
public function __construct(IRequest $request)
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->multiConnector = new MultiConnector();
|
||||||
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNewPlace(): IContent
|
public function initialData(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
$session = $this->request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
$data = ['error' => 'no_session_found'];
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($state['rounds']) === 0) {
|
if (!isset($state['currentRound']) || $state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS) {
|
||||||
$place = $this->placeRepository->getForMapWithValidPano($mapId);
|
$this->startNewGame($state, $mapId);
|
||||||
$state['rounds'][] = $place;
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
}
|
||||||
|
|
||||||
$data = ['panoId' => $place['panoId']];
|
$response = [];
|
||||||
} else {
|
|
||||||
$rounds = count($state['rounds']);
|
|
||||||
$last = $state['rounds'][$rounds - 1];
|
|
||||||
|
|
||||||
$history = [];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
for ($i = 0; $i < $rounds - 1; ++$i) {
|
$response['place'] = [
|
||||||
|
'panoId' => $last['panoId'],
|
||||||
|
'pov' => $last['pov']->toArray()
|
||||||
|
];
|
||||||
|
|
||||||
|
$response['history'] = [];
|
||||||
|
for ($i = 0; $i < $state['currentRound']; ++$i) {
|
||||||
$round = $state['rounds'][$i];
|
$round = $state['rounds'][$i];
|
||||||
$history[] = [
|
$response['history'][] = [
|
||||||
'position' => $round['position']->toArray(),
|
'position' => $round['position']->toArray(),
|
||||||
'guessPosition' => $round['guessPosition']->toArray(),
|
'guessPosition' => $round['guessPosition']->toArray(),
|
||||||
'distance' => $round['distance'],
|
'distance' => $round['distance'],
|
||||||
@@ -53,67 +67,175 @@ class GameFlowController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
return new JsonContent($response);
|
||||||
'history' => $history,
|
}
|
||||||
'panoId' => $last['panoId']
|
|
||||||
|
public function multiInitialData(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
$members = $room->getMembersArray();
|
||||||
|
|
||||||
|
if ($members['owner'] !== $multiState['token']) {
|
||||||
|
return new JsonContent(['error' => 'not_owner_of_room']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($state['currentRound'] == -1 || $state['currentRound'] >= static::NUMBER_OF_ROUNDS - 1) {
|
||||||
|
$this->startNewGame($state, $state['mapId']);
|
||||||
|
$room->setStateArray($state);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
}
|
||||||
|
|
||||||
|
$places = [];
|
||||||
|
foreach ($state['rounds'] as $round) {
|
||||||
|
$places[] = [
|
||||||
|
'position' => $round['position']->toArray(),
|
||||||
|
'panoId' => $round['panoId'],
|
||||||
|
'pov' => $round['pov']->toArray()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JsonContent($data);
|
$this->multiConnector->sendMessage('start_game', ['roomId' => $roomId, 'places' => $places]);
|
||||||
|
|
||||||
|
return new JsonContent(['ok' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function evaluateGuess(): IContent
|
public function guess(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
$session = $this->request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
$data = ['error' => 'no_session_found'];
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$last = $state['rounds'][count($state['rounds']) - 1];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
|
|
||||||
$position = $last['position'];
|
|
||||||
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
||||||
|
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
|
||||||
$distance = $this->calculateDistance($position, $guessPosition);
|
|
||||||
$score = $this->calculateScore($distance, $state['area']);
|
|
||||||
|
|
||||||
$last['guessPosition'] = $guessPosition;
|
$last['guessPosition'] = $guessPosition;
|
||||||
$last['distance'] = $distance;
|
$last['distance'] = $result['distance'];
|
||||||
$last['score'] = $score;
|
$last['score'] = $result['score'];
|
||||||
$state['rounds'][count($state['rounds']) - 1] = $last;
|
|
||||||
|
|
||||||
if (count($state['rounds']) < static::NUMBER_OF_ROUNDS) {
|
$response = [
|
||||||
$exclude = [];
|
|
||||||
|
|
||||||
foreach ($state['rounds'] as $round) {
|
|
||||||
$exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$place = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
|
|
||||||
$state['rounds'][] = $place;
|
|
||||||
$session->set('state', $state);
|
|
||||||
|
|
||||||
$panoId = $place['panoId'];
|
|
||||||
} else {
|
|
||||||
$state['rounds'] = [];
|
|
||||||
$session->set('state', $state);
|
|
||||||
|
|
||||||
$panoId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'result' => [
|
'result' => [
|
||||||
'position' => $position->toArray(),
|
'position' => $last['position']->toArray(),
|
||||||
'distance' => $distance,
|
'distance' => $result['distance'],
|
||||||
'score' => $score
|
'score' => $result['score']
|
||||||
],
|
]
|
||||||
'panoId' => $panoId
|
|
||||||
];
|
];
|
||||||
return new JsonContent($data);
|
|
||||||
|
$state['rounds'][$state['currentRound']] = $last;
|
||||||
|
$state['currentRound'] += 1;
|
||||||
|
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
|
||||||
|
$next = $state['rounds'][$state['currentRound']];
|
||||||
|
|
||||||
|
$response['place'] = [
|
||||||
|
'panoId' => $next['panoId'],
|
||||||
|
'pov' => $next['pov']->toArray()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$session->set('state', $state);
|
||||||
|
|
||||||
|
return new JsonContent($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function multiGuess(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
|
||||||
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
|
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
||||||
|
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
|
||||||
|
|
||||||
|
$this->multiConnector->sendMessage('guess', [
|
||||||
|
'roomId' => $roomId,
|
||||||
|
'token' => $multiState['token'],
|
||||||
|
'guess' => $guessPosition->toArray(),
|
||||||
|
'distance' => $result['distance'],
|
||||||
|
'score' => $result['score']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'result' => [
|
||||||
|
'position' => $last['position']->toArray(),
|
||||||
|
'distance' => $result['distance'],
|
||||||
|
'score' => $result['score']
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
return new JsonContent($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function multiNextRound(): IContent
|
||||||
|
{
|
||||||
|
$roomId = $this->request->query('roomId');
|
||||||
|
$session = $this->request->session();
|
||||||
|
|
||||||
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
$state = $room->getStateArray();
|
||||||
|
$members = $room->getMembersArray();
|
||||||
|
|
||||||
|
if ($members['owner'] !== $multiState['token']) {
|
||||||
|
return new JsonContent(['error' => 'not_owner_of_room']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$state['currentRound'] += 1;
|
||||||
|
if ($state['currentRound'] < static::NUMBER_OF_ROUNDS) {
|
||||||
|
$this->multiConnector->sendMessage('next_round', ['roomId' => $roomId, 'currentRound' => $state['currentRound']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$room->setStateArray($state);
|
||||||
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
|
return new JsonContent(['ok' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function evalueteGuess(Position $realPosition, Position $guessPosition, float $area)
|
||||||
|
{
|
||||||
|
$distance = $this->calculateDistance($realPosition, $guessPosition);
|
||||||
|
$score = $this->calculateScore($distance, $area);
|
||||||
|
|
||||||
|
return ['distance' => $distance, 'score' => $score];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function startNewGame(array &$state, int $mapId): void
|
||||||
|
{
|
||||||
|
$places = $this->placeRepository->getRandomNForMapWithValidPano($mapId, static::NUMBER_OF_ROUNDS);
|
||||||
|
|
||||||
|
$state['rounds'] = [];
|
||||||
|
$state['currentRound'] = 0;
|
||||||
|
|
||||||
|
foreach ($places as $place) {
|
||||||
|
$state['rounds'][] = [
|
||||||
|
'placeId' => $place->getId(),
|
||||||
|
'position' => $place->getPosition(),
|
||||||
|
'panoId' => $place->getPanoIdCached(),
|
||||||
|
'pov' => $place->getPov()
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateDistance(Position $realPosition, Position $guessPosition): float
|
private function calculateDistance(Position $realPosition, Position $guessPosition): float
|
||||||
@@ -123,7 +245,7 @@ class GameFlowController
|
|||||||
|
|
||||||
private function calculateScore(float $distance, float $area): int
|
private function calculateScore(float $distance, float $area): int
|
||||||
{
|
{
|
||||||
$goodness = 1.0 - ($distance / sqrt($area));
|
$goodness = 1.0 - ($distance / (sqrt($area) * 1000));
|
||||||
|
|
||||||
return (int) round(pow(static::MAX_SCORE, $goodness));
|
return (int) round(pow(static::MAX_SCORE, $goodness));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
<?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
|
||||||
|
|
||||||
|
return new JsonContent(['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,73 +1,611 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use DateInterval;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use DateTime;
|
||||||
|
use MapGuesser\Http\Request;
|
||||||
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\Model\UserPasswordResetter;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
|
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;
|
||||||
|
|
||||||
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
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();
|
||||||
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new HtmlContent('login/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGoogleLoginRedirect(): IRedirect
|
||||||
|
{
|
||||||
|
$state = bin2hex(random_bytes(16));
|
||||||
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$this->request->session()->set('oauth_state', $state);
|
||||||
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
|
$url = $oAuth->getDialogUrl(
|
||||||
|
$state,
|
||||||
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
|
||||||
|
$nonce
|
||||||
|
);
|
||||||
|
|
||||||
|
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 = [];
|
$data = [];
|
||||||
return new HtmlContent('login', $data);
|
}
|
||||||
|
|
||||||
|
return new HtmlContent('login/signup', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSignupSuccess(): IContent
|
||||||
|
{
|
||||||
|
return new HtmlContent('login/signup_success');
|
||||||
|
}
|
||||||
|
|
||||||
|
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']);
|
||||||
|
|
||||||
|
return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequestPasswordResetForm()
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new HtmlContent('login/password_reset_request', ['email' => $this->request->query('email')]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequestPasswordResetSuccess(): IContent
|
||||||
|
{
|
||||||
|
return new HtmlContent('login/password_reset_request_success');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResetPasswordForm()
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->request->query('token');
|
||||||
|
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
||||||
|
|
||||||
|
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
||||||
|
return new HtmlContent('login/reset_password', ['success' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepository->getById($resetter->getUserId());
|
||||||
|
|
||||||
|
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function login(): IContent
|
public function login(): IContent
|
||||||
{
|
{
|
||||||
$session = $this->request->session();
|
if ($this->request->user() !== null) {
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
if ($session->get('user')) {
|
|
||||||
$data = ['success' => true];
|
|
||||||
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) {
|
||||||
if ($userData === null) {
|
return new JsonContent([
|
||||||
$data = ['error' => 'user_not_found'];
|
'error' => [
|
||||||
return new JsonContent($data);
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = new User($userData);
|
$tmpUser = new User();
|
||||||
|
$tmpUser->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
|
$this->request->session()->set('tmp_user_data', [
|
||||||
|
'email' => $this->request->post('email'),
|
||||||
|
'password_hashed' => $tmpUser->getPassword()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new JsonContent([
|
||||||
|
'redirect' => [
|
||||||
|
'target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user->getActive()) {
|
||||||
|
$this->resendConfirmationEmail($user);
|
||||||
|
|
||||||
|
return new JsonContent([
|
||||||
|
'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!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
$data = ['error' => 'password_not_match'];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'error' => [
|
||||||
|
'errorText' => 'The given password is wrong. You can <a href="/password/requestReset?email=' .
|
||||||
|
urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$session->set('user', $user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
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')) {
|
||||||
|
return new HtmlContent('login/google_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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'])) {
|
||||||
|
return new HtmlContent('login/google_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
|
return new HtmlContent('login/google_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$idToken['email_verified']) {
|
||||||
|
return new HtmlContent('login/google_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
$this->request->session()->set('google_user_data', ['sub' => $idToken['sub'], 'email' => $idToken['email']]);
|
||||||
|
|
||||||
|
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('login'), []], IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function signup(): IContent
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new JsonContent(['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
|
|
||||||
|
if ($user !== null) {
|
||||||
|
if ($user->getActive()) {
|
||||||
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'There is a user already registered with the given email address, ' .
|
||||||
|
'but the given password is wrong. You can <a href="/password/requestReset?email=' .
|
||||||
|
urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => 'The given email address is not valid.']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'))) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setEmail($this->request->post('email'));
|
||||||
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$confirmation = new UserConfirmation();
|
||||||
|
$confirmation->setUser($user);
|
||||||
|
$confirmation->setToken($token);
|
||||||
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->sendConfirmationEmail($user->getEmail(), $token, $user->getCreatedDate());
|
||||||
|
|
||||||
|
$this->request->session()->delete('tmp_user_data');
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function signupWithGoogle(): IContent
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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']);
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetSignup(): IContent
|
||||||
|
{
|
||||||
|
$this->request->session()->delete('tmp_user_data');
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetGoogleSignup(): IContent
|
||||||
|
{
|
||||||
|
$this->request->session()->delete('google_user_data');
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activate()
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
|
if ($confirmation === null) {
|
||||||
|
return new HtmlContent('login/activate');
|
||||||
|
}
|
||||||
|
|
||||||
|
\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(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
|
if ($confirmation === null) {
|
||||||
|
return new HtmlContent('login/cancel', ['success' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($confirmation);
|
||||||
|
|
||||||
|
$user = $this->userRepository->getById($confirmation->getUserId());
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
return new HtmlContent('login/cancel', ['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestPasswordReset(): IContent
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new JsonContent([
|
||||||
|
'redirect' => [
|
||||||
|
'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up</a>!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$user->getActive()) {
|
||||||
|
$this->resendConfirmationEmail($user);
|
||||||
|
|
||||||
|
return new JsonContent([
|
||||||
|
'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!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$existingResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
|
|
||||||
|
if ($existingResetter !== null && $existingResetter->getExpiresDate() > new DateTime()) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'Password reset was recently requested for this account. Please check your email, or try again later!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = bin2hex(random_bytes(16));
|
||||||
|
$expires = new DateTime('+1 hour');
|
||||||
|
|
||||||
|
$passwordResetter = new UserPasswordResetter();
|
||||||
|
$passwordResetter->setUser($user);
|
||||||
|
$passwordResetter->setToken($token);
|
||||||
|
$passwordResetter->setExpiresDate($expires);
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
if ($existingResetter !== null) {
|
||||||
|
$this->pdm->deleteFromDb($existingResetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($passwordResetter);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function resetPassword(): IContent
|
||||||
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new JsonContent([
|
||||||
|
'redirect' => [
|
||||||
|
'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->request->query('token');
|
||||||
|
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
||||||
|
|
||||||
|
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
||||||
|
return new JsonContent([
|
||||||
|
'redirect' => [
|
||||||
|
'target' => '/' . \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($resetter);
|
||||||
|
|
||||||
|
$user = $this->userRepository->getById($resetter->getUserId());
|
||||||
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->request->setUser($user);
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendConfirmationEmail(string $email, string $token, DateTime $created): 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]),
|
||||||
|
'ACTIVATABLE_UNTIL' => (clone $created)->add(new DateInterval('P1D'))->format('Y-m-d H:i T')
|
||||||
|
]);
|
||||||
|
$mail->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resendConfirmationEmail(User $user): bool
|
||||||
|
{
|
||||||
|
$confirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
|
|
||||||
|
if ($confirmation === null || (clone $confirmation->getLastSentDate())->add(new DateInterval('PT1H')) > new DateTime()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
|
$this->sendConfirmationEmail($user->getEmail(), $confirmation->getToken(), $user->getCreatedDate());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendPasswordResetEmail(string $email, string $token, DateTime $expires): void
|
||||||
|
{
|
||||||
|
$mail = new Mail();
|
||||||
|
$mail->addRecipient($email);
|
||||||
|
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
|
||||||
|
$mail->setBodyFromTemplate('password-reset', [
|
||||||
|
'EMAIL' => $email,
|
||||||
|
'RESET_LINK' => $this->request->getBase() . '/' .
|
||||||
|
\Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token]),
|
||||||
|
'EXPIRES' => $expires->format('Y-m-d H:i T')
|
||||||
|
]);
|
||||||
|
$mail->send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use MapGuesser\Database\Query\Modify;
|
|
||||||
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\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;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
use MapGuesser\Util\Geo\Position;
|
use MapGuesser\Util\Panorama\Pov;
|
||||||
|
|
||||||
class MapAdminController implements ISecured
|
class MapAdminController implements ISecured
|
||||||
{
|
{
|
||||||
@@ -21,6 +21,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 +30,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,37 +48,43 @@ 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];
|
return new HtmlContent('admin/map_editor', [
|
||||||
return new HtmlContent('admin/map_editor', $data);
|
'mapId' => $mapId,
|
||||||
|
'mapName' => $map->getName(),
|
||||||
|
'mapDescription' => str_replace('<br>', "\n", $map->getDescription()),
|
||||||
|
'bounds' => $map->getBounds()->toArray(),
|
||||||
|
'places' => &$places
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPlace(): IContent
|
public function getPlace(): IContent
|
||||||
{
|
{
|
||||||
$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']];
|
return new JsonContent(['panoId' => $place->getFreshPanoId()]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveMap(): IContent
|
public function saveMap(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
if (!$mapId) {
|
\Container::$dbConnection->startTransaction();
|
||||||
$mapId = $this->addNewMap();
|
|
||||||
|
if ($mapId) {
|
||||||
|
$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'])) {
|
||||||
@@ -83,11 +92,23 @@ 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']);
|
||||||
])];
|
$place->setPov(new Pov(
|
||||||
|
(float) $placeRaw['pov']['heading'],
|
||||||
|
(float) $placeRaw['pov']['pitch'],
|
||||||
|
(float) $placeRaw['pov']['zoom']
|
||||||
|
));
|
||||||
|
|
||||||
|
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 = [];
|
||||||
@@ -97,10 +118,17 @@ 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->setPov(new Pov(
|
||||||
|
(float) $placeRaw['pov']['heading'],
|
||||||
|
(float) $placeRaw['pov']['pitch'],
|
||||||
|
(float) $placeRaw['pov']['zoom']
|
||||||
|
));
|
||||||
|
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,93 +136,81 @@ 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()
|
|
||||||
];
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
$data = ['mapId' => $mapId, 'added' => $addedIds];
|
\Container::$dbConnection->commit();
|
||||||
return new JsonContent($data);
|
|
||||||
|
return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateMapBounds(int $mapId): Bounds
|
public function deleteMap(): IContent
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$select->columns(['lat', 'lng']);
|
|
||||||
$select->where('map_id', '=', $mapId);
|
|
||||||
|
|
||||||
$result = $select->execute();
|
$map = $this->mapRepository->getById($mapId);
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->deletePlaces($map);
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($map);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deletePlaces(Map $map): void
|
||||||
|
{
|
||||||
|
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||||
|
$this->pdm->deleteFromDb($place);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function calculateMapBounds(Map $map): Bounds
|
||||||
|
{
|
||||||
$bounds = new Bounds();
|
$bounds = new Bounds();
|
||||||
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
|
||||||
$bounds->extend(new Position($place['lat'], $place['lng']));
|
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||||
|
$bounds->extend($place->getPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
|
|
||||||
$select->where('map_id', '=', $mapId);
|
|
||||||
|
|
||||||
$result = $select->execute();
|
|
||||||
|
|
||||||
$places = [];
|
$places = [];
|
||||||
|
|
||||||
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||||
//$panoId = ???
|
$noPano = $place->getPanoIdCachedTimestampDate() !== null && $place->getPanoIdCached() === null;
|
||||||
//$pov = ???
|
|
||||||
$noPano = $place['pano_id_cached_timestamp'] && $place['pano_id_cached'] === null;
|
|
||||||
|
|
||||||
$places[$place['id']] = [
|
$placeId = $place->getId();
|
||||||
'id' => $place['id'],
|
|
||||||
'lat' => $place['lat'],
|
$places[$placeId] = [
|
||||||
'lng' => $place['lng'],
|
'id' => $placeId,
|
||||||
|
'lat' => $place->getLat(),
|
||||||
|
'lng' => $place->getLng(),
|
||||||
'panoId' => null,
|
'panoId' => null,
|
||||||
'pov' => ['heading' => 0.0, 'pitch' => 0.0, 'zoom' => 0.0],
|
'pov' => $place->getPov()->toArray(),
|
||||||
'noPano' => $noPano
|
'noPano' => $noPano
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use MapGuesser\Interfaces\Authentication\IUser;
|
|||||||
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\Util\Geo\Bounds;
|
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
|
||||||
class MapsController
|
class MapsController
|
||||||
@@ -20,6 +19,7 @@ class MapsController
|
|||||||
|
|
||||||
public function getMaps(): IContent
|
public function getMaps(): IContent
|
||||||
{
|
{
|
||||||
|
//TODO: from repository - count should be added
|
||||||
$select = new Select(\Container::$dbConnection, 'maps');
|
$select = new Select(\Container::$dbConnection, 'maps');
|
||||||
$select->columns([
|
$select->columns([
|
||||||
['maps', 'id'],
|
['maps', 'id'],
|
||||||
@@ -29,6 +29,7 @@ class MapsController
|
|||||||
['maps', 'bound_west_lng'],
|
['maps', 'bound_west_lng'],
|
||||||
['maps', 'bound_north_lat'],
|
['maps', 'bound_north_lat'],
|
||||||
['maps', 'bound_east_lng'],
|
['maps', 'bound_east_lng'],
|
||||||
|
['maps', 'area'],
|
||||||
new RawExpression('COUNT(places.id) AS num_places')
|
new RawExpression('COUNT(places.id) AS num_places')
|
||||||
]);
|
]);
|
||||||
$select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
|
$select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
|
||||||
@@ -39,43 +40,43 @@ class MapsController
|
|||||||
|
|
||||||
$maps = [];
|
$maps = [];
|
||||||
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
|
$map['area'] = $this->formatMapAreaForHuman($map['area']);
|
||||||
|
|
||||||
$map['area'] = $this->formatMapAreaForHuman($bounds->calculateApproximateArea());
|
|
||||||
|
|
||||||
$maps[] = $map;
|
$maps[] = $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
$data = ['maps' => $maps, 'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)];
|
return new HtmlContent('maps', [
|
||||||
return new HtmlContent('maps', $data);
|
'maps' => $maps,
|
||||||
|
'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatMapAreaForHuman(float $area): array
|
private function formatMapAreaForHuman(float $area): array
|
||||||
{
|
{
|
||||||
if ($area < 100.0) {
|
if ($area < 0.01) {
|
||||||
|
$digits = 0;
|
||||||
|
$rounded = round($area * 1000000.0, -2);
|
||||||
|
$unit = 'm';
|
||||||
|
} elseif ($area < 0.1) {
|
||||||
|
$digits = 0;
|
||||||
|
$rounded = round($area * 1000000.0, -3);
|
||||||
|
$unit = 'm';
|
||||||
|
} elseif ($area < 1.0) {
|
||||||
|
$digits = 2;
|
||||||
|
$rounded = round($area, 2);
|
||||||
|
$unit = 'km';
|
||||||
|
} elseif ($area < 100.0) {
|
||||||
$digits = 0;
|
$digits = 0;
|
||||||
$rounded = round($area, 0);
|
$rounded = round($area, 0);
|
||||||
$unit = 'm';
|
$unit = 'km';
|
||||||
} elseif ($area < 100000.0) {
|
} elseif ($area < 10000.0) {
|
||||||
$digits = 0;
|
$digits = 0;
|
||||||
$rounded = round($area, -2);
|
$rounded = round($area, -2);
|
||||||
$unit = 'm';
|
|
||||||
} elseif ($area < 1000000.0) {
|
|
||||||
$digits = 2;
|
|
||||||
$rounded = round($area / 1000000.0, 2);
|
|
||||||
$unit = 'km';
|
|
||||||
} elseif ($area < 100000000.0) {
|
|
||||||
$digits = 0;
|
|
||||||
$rounded = round($area / 1000000.0, 0);
|
|
||||||
$unit = 'km';
|
|
||||||
} elseif ($area < 10000000000.0) {
|
|
||||||
$digits = 0;
|
|
||||||
$rounded = round($area / 1000000.0, -2);
|
|
||||||
$unit = 'km';
|
$unit = 'km';
|
||||||
} else {
|
} else {
|
||||||
$digits = 0;
|
$digits = 0;
|
||||||
$rounded = round($area / 1000000.0, -4);
|
$rounded = round($area, -4);
|
||||||
$unit = 'km';
|
$unit = 'km';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,231 @@
|
|||||||
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use MapGuesser\Http\Request;
|
||||||
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
use MapGuesser\Response\JsonContent;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
use MapGuesser\Util\JwtParser;
|
||||||
|
|
||||||
|
class UserController implements ISecured
|
||||||
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
private UserConfirmationRepository $userConfirmationRepository;
|
||||||
|
|
||||||
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
return $user !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccount(): IContent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGoogleAuthenticateRedirect(): IRedirect
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
$state = bin2hex(random_bytes(16));
|
||||||
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
$this->request->session()->set('oauth_state', $state);
|
||||||
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
|
|
||||||
|
$url = $oAuth->getDialogUrl(
|
||||||
|
$state,
|
||||||
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
|
||||||
|
$nonce,
|
||||||
|
$user->getEmail()
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Redirect($url, IRedirect::TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authenticateWithGoogle(): IContent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||||
|
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
|
$tokenData = $oAuth->getToken(
|
||||||
|
$this->request->query('code'),
|
||||||
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isset($tokenData['id_token'])) {
|
||||||
|
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
|
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($idToken['sub'] !== $user->getGoogleSub()) {
|
||||||
|
return new HtmlContent('account/google_authenticate', [
|
||||||
|
'success' => false,
|
||||||
|
'errorText' => 'This Google account is not linked to your account.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$authenticatedWithGoogleUntil = new DateTime('+45 seconds');
|
||||||
|
$this->request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil);
|
||||||
|
|
||||||
|
return new HtmlContent('account/google_authenticate', [
|
||||||
|
'success' => true,
|
||||||
|
'authenticatedWithGoogleUntil' => $authenticatedWithGoogleUntil
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeleteAccount(): IContent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
return new HtmlContent('account/delete', ['user' => $user->toArray()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAccount(): IContent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
if (!$this->confirmUserIdentity(
|
||||||
|
$user,
|
||||||
|
$this->request->session()->get('authenticated_with_google_until'),
|
||||||
|
$this->request->post('password'),
|
||||||
|
$error
|
||||||
|
)) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($this->request->post('password_new')) > 0) {
|
||||||
|
if (strlen($this->request->post('password_new')) < 6) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
||||||
|
return new JsonContent([
|
||||||
|
'error' => [
|
||||||
|
'errorText' => 'The given new passwords do not match.'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setPlainPassword($this->request->post('password_new'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAccount(): IContent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User $user
|
||||||
|
*/
|
||||||
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
if (!$this->confirmUserIdentity(
|
||||||
|
$user,
|
||||||
|
$this->request->session()->get('authenticated_with_google_until'),
|
||||||
|
$this->request->post('password'),
|
||||||
|
$error
|
||||||
|
)) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
|
if ($userConfirmation !== null) {
|
||||||
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
|
if ($userPasswordResetter !== null) {
|
||||||
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, string &$error): bool
|
||||||
|
{
|
||||||
|
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($password !== null) {
|
||||||
|
if ($user->checkPassword($password)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = 'The given current password is wrong.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = 'Could not confirm your identity. Please try again!';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ class Connection implements IConnection
|
|||||||
public function __construct(string $host, string $user, string $password, string $db, int $port = -1, string $socket = null)
|
public function __construct(string $host, string $user, string $password, string $db, int $port = -1, string $socket = null)
|
||||||
{
|
{
|
||||||
if ($port < 0) {
|
if ($port < 0) {
|
||||||
$port = ini_get('mysqli.default_port');
|
$port = (int) ini_get('mysqli.default_port');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($socket === null) {
|
if ($socket === null) {
|
||||||
$socket = ini_get('mysqli.default_socket');
|
$socket = (string) ini_get('mysqli.default_socket');
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->connection = new mysqli($host, $user, $password, $db, $port, $socket);
|
$this->connection = new mysqli($host, $user, $password, $db, $port, $socket);
|
||||||
|
|||||||
@@ -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,7 +13,7 @@ class Modify
|
|||||||
|
|
||||||
private array $attributes = [];
|
private array $attributes = [];
|
||||||
|
|
||||||
private array $original = [];
|
private ?string $externalId = null;
|
||||||
|
|
||||||
private bool $autoIncrement = true;
|
private bool $autoIncrement = true;
|
||||||
|
|
||||||
@@ -31,6 +30,13 @@ class Modify
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setExternalId($id): Modify
|
||||||
|
{
|
||||||
|
$this->externalId = $id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function setAutoIncrement(bool $autoIncrement = true): Modify
|
public function setAutoIncrement(bool $autoIncrement = true): Modify
|
||||||
{
|
{
|
||||||
$this->autoIncrement = $autoIncrement;
|
$this->autoIncrement = $autoIncrement;
|
||||||
@@ -87,7 +93,9 @@ class Modify
|
|||||||
|
|
||||||
private function insert(): void
|
private function insert(): void
|
||||||
{
|
{
|
||||||
if (!$this->autoIncrement) {
|
if ($this->externalId !== null) {
|
||||||
|
$this->attributes[$this->idName] = $this->externalId;
|
||||||
|
} elseif (!$this->autoIncrement) {
|
||||||
$this->attributes[$this->idName] = $this->generateKey();
|
$this->attributes[$this->idName] = $this->generateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,45 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
$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
|
||||||
@@ -157,6 +135,6 @@ class Modify
|
|||||||
|
|
||||||
private function generateKey(): string
|
private function generateKey(): string
|
||||||
{
|
{
|
||||||
return substr(hash('sha256', serialize($this->attributes) . random_bytes(10) . microtime()), 0, 7);
|
return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,13 +30,16 @@ class Select
|
|||||||
|
|
||||||
private array $orders = [];
|
private array $orders = [];
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
if ($table !== null) {
|
||||||
$this->table = $table;
|
$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);
|
||||||
@@ -134,7 +144,7 @@ class Select
|
|||||||
$this->limit = null;
|
$this->limit = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginate(int $page, int $itemsPerPage)
|
public function paginate(int $page, int $itemsPerPage): Select
|
||||||
{
|
{
|
||||||
$this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
|
$this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
|
||||||
|
|
||||||
@@ -298,7 +308,7 @@ class Select
|
|||||||
return implode(' ', $joins);
|
return implode(' ', $joins);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateConditions(string $type): array
|
private function generateConditions(int $type): array
|
||||||
{
|
{
|
||||||
$conditions = '';
|
$conditions = '';
|
||||||
$params = [];
|
$params = [];
|
||||||
@@ -366,9 +376,9 @@ class Select
|
|||||||
return [$conditionsString, $params];
|
return [$conditionsString, $params];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateComplexConditionFragment(string $type, Closure $conditionCallback): array
|
private function generateComplexConditionFragment(int $type, Closure $conditionCallback): array
|
||||||
{
|
{
|
||||||
$instance = new static($this->connection, $this->table);
|
$instance = new self($this->connection, $this->table);
|
||||||
$instance->tableAliases = $this->tableAliases;
|
$instance->tableAliases = $this->tableAliases;
|
||||||
|
|
||||||
$conditionCallback($instance);
|
$conditionCallback($instance);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<?php namespace MapGuesser\Database;
|
<?php namespace MapGuesser\Database;
|
||||||
|
|
||||||
class Utils {
|
class Utils {
|
||||||
public static function backtick(string $name) {
|
public static function backtick(string $name): string
|
||||||
|
{
|
||||||
return '`' . $name . '`';
|
return '`' . $name . '`';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+21
-12
@@ -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,13 +13,23 @@ 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 setQuery($query)
|
public function setUrl(string $url): void
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMethod(int $method): void
|
||||||
|
{
|
||||||
|
$this->method = $method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setQuery($query): void
|
||||||
{
|
{
|
||||||
if (is_string($query)) {
|
if (is_string($query)) {
|
||||||
$this->query = $query;
|
$this->query = $query;
|
||||||
@@ -29,22 +38,22 @@ class Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setHeaders(array $headers)
|
public function setHeaders(array $headers): void
|
||||||
{
|
{
|
||||||
$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();
|
||||||
|
|
||||||
if ($this->method === self::HTTP_GET) {
|
if ($this->method === self::HTTP_POST) {
|
||||||
$url = $this->url . '?' . $this->query;
|
|
||||||
} elseif ($this->method === self::HTTP_POST) {
|
|
||||||
$url = $this->url;
|
$url = $this->url;
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_POST, 1);
|
curl_setopt($ch, CURLOPT_POST, 1);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->query);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->query);
|
||||||
|
} else {
|
||||||
|
$url = $this->url . '?' . $this->query;
|
||||||
}
|
}
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_URL, $url);
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -12,12 +14,12 @@ class Response
|
|||||||
$this->headers = $headers;
|
$this->headers = $headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBody()
|
public function getBody(): string
|
||||||
{
|
{
|
||||||
return $this->body;
|
return $this->body;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHeaders()
|
public function getHeaders(): array
|
||||||
{
|
{
|
||||||
return $this->headers;
|
return $this->headers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,10 @@ interface IUser
|
|||||||
const PERMISSION_ADMIN = 1;
|
const PERMISSION_ADMIN = 1;
|
||||||
|
|
||||||
public function hasPermission(int $permission): bool;
|
public function hasPermission(int $permission): bool;
|
||||||
|
|
||||||
|
public function getUniqueId();
|
||||||
|
|
||||||
|
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): void;
|
||||||
|
|
||||||
|
public function setHeaders(array $headers): void;
|
||||||
|
|
||||||
|
public function send(): IResponse;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Http;
|
||||||
|
|
||||||
|
interface IResponse
|
||||||
|
{
|
||||||
|
public function getBody(): string;
|
||||||
|
|
||||||
|
public function getHeaders(): array;
|
||||||
|
}
|
||||||
@@ -4,11 +4,17 @@ use MapGuesser\Interfaces\Authentication\IUser;
|
|||||||
|
|
||||||
interface IRequest
|
interface IRequest
|
||||||
{
|
{
|
||||||
|
public function setParsedRouteParams(array &$routeParams): void;
|
||||||
|
|
||||||
|
public function getBase(): string;
|
||||||
|
|
||||||
public function query(string $key);
|
public function query(string $key);
|
||||||
|
|
||||||
public function post(string $key);
|
public function post(string $key);
|
||||||
|
|
||||||
public function session(): ISession;
|
public function session(): ISession;
|
||||||
|
|
||||||
|
public function setUser(?IUser $user): void;
|
||||||
|
|
||||||
public function user(): ?IUser;
|
public function user(): ?IUser;
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
interface IContent
|
interface IContent
|
||||||
{
|
{
|
||||||
public function &getData(): array;
|
public function setData(array $data): void;
|
||||||
|
|
||||||
public function &render(): string;
|
public function getData(): array;
|
||||||
|
|
||||||
|
public function render(): void;
|
||||||
|
|
||||||
public function getContentType(): string;
|
public function getContentType(): string;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Session;
|
||||||
|
|
||||||
|
use SessionHandlerInterface;
|
||||||
|
use SessionIdInterface;
|
||||||
|
use SessionUpdateTimestampHandlerInterface;
|
||||||
|
|
||||||
|
interface ISessionHandler extends SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<?php namespace MapGuesser\Mailing;
|
||||||
|
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
|
||||||
|
class Mail
|
||||||
|
{
|
||||||
|
private array $recipients = [];
|
||||||
|
|
||||||
|
public string $subject = '';
|
||||||
|
|
||||||
|
public string $body = '';
|
||||||
|
|
||||||
|
public function addRecipient(string $mail, ?string $name = null): void
|
||||||
|
{
|
||||||
|
$this->recipients[] = [$mail, $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubject(string $subject): void
|
||||||
|
{
|
||||||
|
$this->subject = $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBody(string $body): void
|
||||||
|
{
|
||||||
|
$this->body = $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBodyFromTemplate(string $template, array $params = []): void
|
||||||
|
{
|
||||||
|
$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) {
|
||||||
|
$this->body = str_replace('{{' . $key . '}}', $param, $this->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(): void
|
||||||
|
{
|
||||||
|
$mailer = new PHPMailer(true);
|
||||||
|
|
||||||
|
$mailer->CharSet = 'utf-8';
|
||||||
|
$mailer->Hostname = substr($_ENV['MAIL_FROM'], strpos($_ENV['MAIL_FROM'], '@') + 1);
|
||||||
|
|
||||||
|
if (!empty($_ENV['MAIL_HOST'])) {
|
||||||
|
$mailer->Mailer = 'smtp';
|
||||||
|
$mailer->Host = $_ENV['MAIL_HOST'];
|
||||||
|
$mailer->Port = !empty($_ENV['MAIL_PORT']) ? $_ENV['MAIL_PORT'] : 25;
|
||||||
|
$mailer->SMTPSecure = !empty($_ENV['MAIL_SECURE']) ? $_ENV['MAIL_SECURE'] : '';
|
||||||
|
|
||||||
|
if (!empty($_ENV['MAIL_USER'])) {
|
||||||
|
$mailer->SMTPAuth = true;
|
||||||
|
$mailer->Username = $_ENV['MAIL_USER'];
|
||||||
|
$mailer->Password = $_ENV['MAIL_PASSWORD'];
|
||||||
|
} else {
|
||||||
|
$mailer->SMTPAuth = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$mailer->Mailer = 'mail';
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailer->setFrom($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
||||||
|
$mailer->addReplyTo($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
||||||
|
|
||||||
|
$mailer->Sender = !empty($_ENV['MAIL_BOUNCE']) ? $_ENV['MAIL_BOUNCE'] : $_ENV['MAIL_FROM'];
|
||||||
|
$mailer->Subject = $this->subject;
|
||||||
|
$mailer->msgHTML($this->body);
|
||||||
|
|
||||||
|
foreach ($this->recipients as $recipient) {
|
||||||
|
$this->sendMail($mailer, $recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendMail(PHPMailer $mailer, array $recipient): void
|
||||||
|
{
|
||||||
|
$mailer->clearAddresses();
|
||||||
|
$mailer->addAddress($recipient[0], $recipient[1]);
|
||||||
|
|
||||||
|
$mailer->send();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Model;
|
|
||||||
|
|
||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
|
||||||
|
|
||||||
class User extends BaseModel implements IUser
|
|
||||||
{
|
|
||||||
private static array $types = ['user', 'admin'];
|
|
||||||
|
|
||||||
protected static array $fields = ['email', 'password', 'type'];
|
|
||||||
|
|
||||||
private string $email;
|
|
||||||
|
|
||||||
private string $password;
|
|
||||||
|
|
||||||
private string $type = 'user';
|
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
|
||||||
{
|
|
||||||
$this->email = $email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPassword(string $hashedPassword): void
|
|
||||||
{
|
|
||||||
$this->password = $hashedPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPlainPassword(string $plainPassword): void
|
|
||||||
{
|
|
||||||
$this->password = password_hash($plainPassword, PASSWORD_BCRYPT);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setType(string $type): void
|
|
||||||
{
|
|
||||||
if (in_array($type, self::$types)) {
|
|
||||||
$this->type = $type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEmail(): string
|
|
||||||
{
|
|
||||||
return $this->email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPassword(): string
|
|
||||||
{
|
|
||||||
return $this->password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getType(): string
|
|
||||||
{
|
|
||||||
return $this->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasPermission(int $permission): bool
|
|
||||||
{
|
|
||||||
switch ($permission) {
|
|
||||||
case IUser::PERMISSION_NORMAL:
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case IUser::PERMISSION_ADMIN:
|
|
||||||
return $this->type === 'admin';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkPassword(string $password): bool
|
|
||||||
{
|
|
||||||
return password_verify($password, $this->password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php namespace MapGuesser\Multi;
|
||||||
|
|
||||||
|
class MultiConnector
|
||||||
|
{
|
||||||
|
public function sendMessage(string $func, array $args = []): void
|
||||||
|
{
|
||||||
|
$message = json_encode([
|
||||||
|
'func' => $func,
|
||||||
|
'args' => $args
|
||||||
|
]);
|
||||||
|
|
||||||
|
$connection = fsockopen($_ENV['MULTI_INTERNAL_HOST'], $_ENV['MULTI_INTERNAL_PORT']);
|
||||||
|
fwrite($connection, $message);
|
||||||
|
$response = '';
|
||||||
|
while (!feof($connection)) {
|
||||||
|
$response .= fgets($connection);
|
||||||
|
}
|
||||||
|
fclose($connection);
|
||||||
|
|
||||||
|
if ($response !== 'OK') {
|
||||||
|
throw new \Exception('Sending message failed with response: ' . $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php namespace MapGuesser\OAuth;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Http\IRequest;
|
||||||
|
|
||||||
|
class GoogleOAuth
|
||||||
|
{
|
||||||
|
private static string $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||||
|
|
||||||
|
private static string $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 $nonce = null, ?string $loginHint = null): string
|
||||||
|
{
|
||||||
|
$oauthParams = [
|
||||||
|
'response_type' => 'code',
|
||||||
|
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
||||||
|
'scope' => 'openid email',
|
||||||
|
'redirect_uri' => $redirectUrl,
|
||||||
|
'state' => $state,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($nonce !== null) {
|
||||||
|
$oauthParams['nonce'] = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginHint !== null) {
|
||||||
|
$oauthParams['login_hint'] = $loginHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(string $code, string $redirectUrl): array
|
||||||
|
{
|
||||||
|
$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,88 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class MultiRoom extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'multi_rooms';
|
||||||
|
|
||||||
|
protected static array $fields = ['room_id', 'state', 'members', 'updated'];
|
||||||
|
|
||||||
|
private string $roomId = '';
|
||||||
|
|
||||||
|
private array $state = [];
|
||||||
|
|
||||||
|
private array $members = [];
|
||||||
|
|
||||||
|
private DateTime $updated;
|
||||||
|
|
||||||
|
public function setRoomId(string $roomId): void
|
||||||
|
{
|
||||||
|
$this->roomId = $roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStateArray(array $state): void
|
||||||
|
{
|
||||||
|
$this->state = $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMembersArray(array $members): void
|
||||||
|
{
|
||||||
|
$this->members = $members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setState(string $state): void
|
||||||
|
{
|
||||||
|
$this->state = unserialize($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMembers(string $members): void
|
||||||
|
{
|
||||||
|
$this->members = unserialize($members);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdatedDate(DateTime $updated): void
|
||||||
|
{
|
||||||
|
$this->updated = $updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUpdated(string $updated): void
|
||||||
|
{
|
||||||
|
$this->updated = new DateTime($updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoomId(): string
|
||||||
|
{
|
||||||
|
return $this->roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStateArray(): array
|
||||||
|
{
|
||||||
|
return $this->state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getState(): string
|
||||||
|
{
|
||||||
|
return serialize($this->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMembersArray(): array
|
||||||
|
{
|
||||||
|
return $this->members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMembers(): string
|
||||||
|
{
|
||||||
|
return serialize($this->members);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdatedDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdated(): string
|
||||||
|
{
|
||||||
|
return $this->updated->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTime;
|
||||||
|
use MapGuesser\Http\Request;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Util\Geo\Position;
|
||||||
|
use MapGuesser\Util\Panorama\Pov;
|
||||||
|
|
||||||
|
class Place extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'places';
|
||||||
|
|
||||||
|
protected static array $fields = ['map_id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp', 'pov_heading', 'pov_pitch', 'pov_zoom'];
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
private Pov $pov;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->position = new Position(0.0, 0.0);
|
||||||
|
$this->pov = new Pov(0.0, 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 setPov(Pov $pov): void
|
||||||
|
{
|
||||||
|
$this->pov = $pov;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPovHeading(float $heading): void
|
||||||
|
{
|
||||||
|
$this->pov->setHeading($heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPovPitch(float $pitch): void
|
||||||
|
{
|
||||||
|
$this->pov->setPitch($pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPovZoom(float $zoom): void
|
||||||
|
{
|
||||||
|
$this->pov->setZoom($zoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getPov(): Pov
|
||||||
|
{
|
||||||
|
return $this->pov;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPovHeading(): float
|
||||||
|
{
|
||||||
|
return $this->pov->getHeading();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPovPitch(): float
|
||||||
|
{
|
||||||
|
return $this->pov->getPitch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPovZoom(): float
|
||||||
|
{
|
||||||
|
return $this->pov->getZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
(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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
|
class User extends Model implements IUser
|
||||||
|
{
|
||||||
|
protected static string $table = 'users';
|
||||||
|
|
||||||
|
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub', 'created'];
|
||||||
|
|
||||||
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
|
private string $email = '';
|
||||||
|
|
||||||
|
private ?string $password = null;
|
||||||
|
|
||||||
|
private string $type = 'user';
|
||||||
|
|
||||||
|
private bool $active = false;
|
||||||
|
|
||||||
|
private ?string $googleSub = null;
|
||||||
|
|
||||||
|
private DateTime $created;
|
||||||
|
|
||||||
|
public function setEmail(string $email): void
|
||||||
|
{
|
||||||
|
$this->email = $email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPassword(?string $hashedPassword): void
|
||||||
|
{
|
||||||
|
$this->password = $hashedPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlainPassword(string $plainPassword): void
|
||||||
|
{
|
||||||
|
$this->password = password_hash($plainPassword, PASSWORD_BCRYPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setType(string $type): void
|
||||||
|
{
|
||||||
|
if (in_array($type, self::$types)) {
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setActive(bool $active): void
|
||||||
|
{
|
||||||
|
$this->active = $active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setGoogleSub(?string $googleSub): void
|
||||||
|
{
|
||||||
|
$this->googleSub = $googleSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedDate(DateTime $created): void
|
||||||
|
{
|
||||||
|
$this->created = $created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreated(string $created): void
|
||||||
|
{
|
||||||
|
$this->created = new DateTime($created);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEmail(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPassword(): ?string
|
||||||
|
{
|
||||||
|
return $this->password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getActive(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGoogleSub(): ?string
|
||||||
|
{
|
||||||
|
return $this->googleSub;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreated(): string
|
||||||
|
{
|
||||||
|
return $this->created->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasPermission(int $permission): bool
|
||||||
|
{
|
||||||
|
switch ($permission) {
|
||||||
|
case IUser::PERMISSION_NORMAL:
|
||||||
|
return true;
|
||||||
|
case IUser::PERMISSION_ADMIN:
|
||||||
|
return $this->type === 'admin';
|
||||||
|
default:
|
||||||
|
throw new \Exception('Permission does not exist: ' . $permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUniqueId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDisplayName(): string
|
||||||
|
{
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkPassword(string $password): bool
|
||||||
|
{
|
||||||
|
if ($this->password === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return password_verify($password, $this->password);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class UserConfirmation extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'user_confirmations';
|
||||||
|
|
||||||
|
protected static array $fields = ['user_id', 'token', 'last_sent'];
|
||||||
|
|
||||||
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
private ?int $userId = null;
|
||||||
|
|
||||||
|
private string $token = '';
|
||||||
|
|
||||||
|
private DateTime $lastSent;
|
||||||
|
|
||||||
|
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 setLastSentDate(DateTime $lastSent): void
|
||||||
|
{
|
||||||
|
$this->lastSent = $lastSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLastSent(string $lastSent): void
|
||||||
|
{
|
||||||
|
$this->lastSent = new DateTime($lastSent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?int
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(): string
|
||||||
|
{
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastSentDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->lastSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastSent(): string
|
||||||
|
{
|
||||||
|
return $this->lastSent->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
class UserPasswordResetter extends Model
|
||||||
|
{
|
||||||
|
protected static string $table = 'user_password_resetters';
|
||||||
|
|
||||||
|
protected static array $fields = ['user_id', 'token', 'expires'];
|
||||||
|
|
||||||
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
private ?int $userId = null;
|
||||||
|
|
||||||
|
private string $token = '';
|
||||||
|
|
||||||
|
private DateTime $expires;
|
||||||
|
|
||||||
|
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 setExpiresDate(DateTime $expires): void
|
||||||
|
{
|
||||||
|
$this->expires = $expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExpires(string $expires): void
|
||||||
|
{
|
||||||
|
$this->expires = new DateTime($expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): ?User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserId(): ?int
|
||||||
|
{
|
||||||
|
return $this->userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(): string
|
||||||
|
{
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpiresDate(): DateTime
|
||||||
|
{
|
||||||
|
return $this->expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpires(): string
|
||||||
|
{
|
||||||
|
return $this->expires->format('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
<?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)
|
||||||
|
{
|
||||||
|
$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);
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$model = new $type();
|
||||||
|
$this->fillWithData($data, $model);
|
||||||
|
|
||||||
|
yield $model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectFromDbById($id, string $type, bool $withRelations = false)
|
||||||
|
{
|
||||||
|
$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']);
|
|
||||||
$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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Generator;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\MultiRoom;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
|
class MultiRoomRepository
|
||||||
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById(int $id): ?MultiRoom
|
||||||
|
{
|
||||||
|
return $this->pdm->selectFromDbById($id, MultiRoom::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByRoomId(string $roomId): ?MultiRoom
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('room_id', '=', $roomId);
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, MultiRoom::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllExpired(): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('updated', '<', (new DateTime('-7 day'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, MultiRoom::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,147 +1,82 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use MapGuesser\Util\Geo\Position;
|
use Generator;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Database\Query\Modify;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\Http\Request;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
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
|
||||||
{
|
{
|
||||||
$placesWithoutPano = [];
|
return $this->pdm->selectFromDbById($placeId, Place::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllForMap(Map $map): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('map_id', '=', $map->getId());
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: use Map instead of id
|
||||||
|
public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array
|
||||||
|
{
|
||||||
|
$places = [];
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $n; ++$i) {
|
||||||
|
$place = $this->getRandomForMapWithValidPano($mapId, $exclude);
|
||||||
|
|
||||||
|
$places[] = $place;
|
||||||
|
$exclude[] = $place->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $places;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: use Map instead of id
|
||||||
|
public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place
|
||||||
|
{
|
||||||
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'];
|
$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
|
||||||
{
|
{
|
||||||
|
//TODO: omit table name here
|
||||||
$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);
|
||||||
|
|
||||||
$numberOfPlaces = $select->count(); // TODO: what if 0
|
$numberOfPlaces = $select->count();
|
||||||
|
|
||||||
|
//TODO: prevent this
|
||||||
|
if ($numberOfPlaces === 0) {
|
||||||
|
throw new \Exception('There is no selectable place (count was 0).');
|
||||||
|
}
|
||||||
|
|
||||||
$randomOffset = random_int(0, $numberOfPlaces - 1);
|
$randomOffset = random_int(0, $numberOfPlaces - 1);
|
||||||
|
|
||||||
$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,37 @@
|
|||||||
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
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): ?UserConfirmation
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loaded 100 of 158 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user