Compare commits
85
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22f4abd82d
|
||
|
|
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
|
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
APP_NAME=MapQuiz
|
APP_NAME=MapGuesser
|
||||||
DEV=1
|
DEV=1
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
.env
|
.env
|
||||||
installed
|
installed
|
||||||
unit_test_results.xml
|
|
||||||
vendor
|
vendor
|
||||||
@@ -9,7 +9,7 @@ This is the MapGuesser Application project. This is a game about guessing where
|
|||||||
The first step is obviously cloning the repository to your machine:
|
The first step is obviously cloning the repository to your machine:
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://bitbucket.org/esoko/mapguesser.git
|
git clone https://gitea.e5tv.hu/esoko/mapguesser.git
|
||||||
```
|
```
|
||||||
|
|
||||||
All the commands listed here should be executed from the repository root.
|
All the commands listed here should be executed from the repository root.
|
||||||
@@ -46,6 +46,14 @@ One very important variable is `DEV`. This indicates that the application operat
|
|||||||
|
|
||||||
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.
|
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
|
### Finalize installation
|
||||||
|
|
||||||
After you set the environment variables in the `.env` file, execute the following command:
|
After you set the environment variables in the `.env` file, execute the following command:
|
||||||
|
|||||||
@@ -13,3 +13,14 @@ pipelines:
|
|||||||
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
- composer install
|
- composer install
|
||||||
- vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests
|
- 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
|
||||||
+2
-1
@@ -9,7 +9,8 @@
|
|||||||
"phpmailer/phpmailer": "^6.1"
|
"phpmailer/phpmailer": "^6.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9"
|
"phpunit/phpunit": "^9",
|
||||||
|
"phpstan/phpstan": "^0.12.32"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
Generated
+57
-1
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "fb37b0b958cdf02d82468cb4596613ad",
|
"content-hash": "34563bfc619f47473b2e37a5639dd63e",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "phpmailer/phpmailer",
|
"name": "phpmailer/phpmailer",
|
||||||
@@ -1379,6 +1379,62 @@
|
|||||||
],
|
],
|
||||||
"time": "2020-03-05T15:02:03+00:00"
|
"time": "2020-03-05T15:02:03+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpstan/phpstan",
|
||||||
|
"version": "0.12.32",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
|
"reference": "d03863f504c8432b3de4d1881f73f6acb8c0e67c"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d03863f504c8432b3de4d1881f73f6acb8c0e67c",
|
||||||
|
"reference": "d03863f504c8432b3de4d1881f73f6acb8c0e67c",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.1"
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"phpstan/phpstan-shim": "*"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"phpstan",
|
||||||
|
"phpstan.phar"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "0.12-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"description": "PHPStan - PHP Static Analysis Tool",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/ondrejmirtes",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.patreon.com/phpstan",
|
||||||
|
"type": "patreon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-07-01T11:57:52+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
|
|||||||
@@ -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,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>
|
||||||
+7
-3
@@ -1,10 +1,14 @@
|
|||||||
Hi,
|
Hi,
|
||||||
<br><br>
|
<br><br>
|
||||||
You recently signed up on {{APP_NAME}} with this email address ({{EMAIL}}). To activate your account, please click on the following link:<br>
|
You recently signed up on {{APP_NAME}} with this email address ({{EMAIL}}).
|
||||||
|
To activate your account, please click on the following link:<br>
|
||||||
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
If you did not sign up on {{APP_NAME}} or changed your mind, no further action is required, your email address will be deleted soon.<br>
|
You can activate your account until {{ACTIVATABLE_UNTIL}}.
|
||||||
However if you want to immediately delete it, please click on the following link:<br>
|
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>
|
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
Have fun on {{APP_NAME}}!
|
Have fun on {{APP_NAME}}!
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ 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 \SessionHandlerInterface $sessionHandler;
|
static MapGuesser\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||||
static MapGuesser\Interfaces\Request\IRequest $request;
|
static MapGuesser\Interfaces\Request\IRequest $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +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\LinkViewCommand());
|
||||||
|
$app->add(new \MapGuesser\Cli\MaintainDatabaseCommand());
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
parameters:
|
||||||
|
level: 5
|
||||||
|
checkMissingIterableValueType: false
|
||||||
|
paths:
|
||||||
|
- main.php
|
||||||
|
- mapg
|
||||||
|
- web.php
|
||||||
|
- src
|
||||||
|
- tests
|
||||||
+4
-3
@@ -26,8 +26,9 @@ if ($match !== null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
|
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);
|
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||||||
echo json_encode(['error' => 'no_valid_anti_csrf_token']);
|
$content->render();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ if ($match !== null) {
|
|||||||
|
|
||||||
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) {
|
||||||
@@ -49,4 +50,4 @@ if ($match !== null) {
|
|||||||
|
|
||||||
$content = new MapGuesser\Response\HtmlContent('error/404');
|
$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);
|
||||||
echo $content->render();
|
$content->render();
|
||||||
@@ -153,13 +153,27 @@ button, a.button {
|
|||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.small {
|
button.small, div.inputWithButton>button {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
line-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;
|
||||||
@@ -191,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,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%;
|
||||||
@@ -253,14 +281,34 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
div.modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
@@ -356,9 +404,17 @@ div.buttonContainer>button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#cookiesNotice {
|
#cookiesNotice {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: 20px;
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
|
border: solid #888888 1px;
|
||||||
|
border-radius: 3px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
rounds: [],
|
rounds: [],
|
||||||
scoreSum: 0,
|
scoreSum: 0,
|
||||||
panoId: null,
|
panoId: null,
|
||||||
|
pov: null,
|
||||||
panorama: null,
|
panorama: null,
|
||||||
map: null,
|
map: null,
|
||||||
guessMarker: null,
|
guessMarker: null,
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.panoId;
|
Game.panoId = this.response.panoId;
|
||||||
|
Game.pov = this.response.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) {
|
||||||
@@ -116,7 +118,7 @@
|
|||||||
|
|
||||||
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) {
|
||||||
@@ -129,13 +131,13 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
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);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -199,6 +201,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Game.panoId = this.response.panoId;
|
Game.panoId = this.response.panoId;
|
||||||
|
Game.pov = this.response.pov;
|
||||||
}, data);
|
}, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -244,4 +244,23 @@ var MapGuesser = {
|
|||||||
document.getElementById('cover').onclick = function () {
|
document.getElementById('cover').onclick = function () {
|
||||||
MapGuesser.hideModal();
|
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();
|
||||||
|
};
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
@@ -8,9 +8,12 @@
|
|||||||
var imgContainer = imgContainers[i];
|
var imgContainer = imgContainers[i];
|
||||||
|
|
||||||
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
|
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
|
||||||
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&' +
|
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&path=color:0x0000ff40|weight:3|fillcolor:0x0000ff20|' +
|
||||||
'visible=' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
|
imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
|
||||||
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng +
|
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
|
||||||
|
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng + '|' +
|
||||||
|
imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundEastLng + '|' +
|
||||||
|
imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng +
|
||||||
'&key=' + GOOGLE_MAPS_JS_API_KEY;
|
'&key=' + GOOGLE_MAPS_JS_API_KEY;
|
||||||
|
|
||||||
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
|
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ 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..."
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ 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..."
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
@@ -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.')
|
||||||
@@ -23,6 +24,8 @@ class AddUserCommand extends Command
|
|||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($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'));
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
|
|
||||||
class LinkViewCommand extends Command
|
class LinkViewCommand extends Command
|
||||||
{
|
{
|
||||||
public function configure()
|
public function configure(): void
|
||||||
{
|
{
|
||||||
$this->setName('view:link')
|
$this->setName('view:link')
|
||||||
->setDescription('Linking of views.')
|
->setDescription('Linking of views.')
|
||||||
@@ -39,7 +39,7 @@ class LinkViewCommand extends Command
|
|||||||
|
|
||||||
$view = substr($file->getPath(), $folderLength) . '/' . $file->getBasename('.php');
|
$view = substr($file->getPath(), $folderLength) . '/' . $file->getBasename('.php');
|
||||||
|
|
||||||
if (strpos($view, 'templates') === 0) {
|
if (strpos($view, 'templates') === 0 || strpos($view, 'tests') === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<?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\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;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
$this->userRepository = new UserRepository();
|
||||||
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
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->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 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;
|
||||||
@@ -21,18 +21,18 @@ class GameController
|
|||||||
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', $this->prepareGame($mapId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGameJson(): IContent
|
public function getGameJson(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$data = $this->prepareGame($mapId);
|
|
||||||
return new JsonContent($data);
|
return new JsonContent($this->prepareGame($mapId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepareGame(int $mapId)
|
private function prepareGame(int $mapId): array
|
||||||
{
|
{
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ class GameFlowController
|
|||||||
$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 (count($state['rounds']) === 0) {
|
||||||
@@ -41,7 +40,10 @@ class GameFlowController
|
|||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
$data = ['panoId' => $place->getPanoIdCached()];
|
$data = [
|
||||||
|
'panoId' => $place->getPanoIdCached(),
|
||||||
|
'pov' => $place->getPov()->toArray()
|
||||||
|
];
|
||||||
} else {
|
} else {
|
||||||
$rounds = count($state['rounds']);
|
$rounds = count($state['rounds']);
|
||||||
$last = $state['rounds'][$rounds - 1];
|
$last = $state['rounds'][$rounds - 1];
|
||||||
@@ -59,7 +61,10 @@ class GameFlowController
|
|||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'history' => $history,
|
'history' => $history,
|
||||||
'panoId' => $last['panoId']
|
'panoId' => $last['panoId'],
|
||||||
|
'pov' => isset($last['pov']) ? // should be checked not to break with old sessions
|
||||||
|
$last['pov']->toArray() :
|
||||||
|
['heading' => 0.0, 'pitch' => 0.0, 'zoom' => 0.0],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +78,7 @@ class GameFlowController
|
|||||||
$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'][count($state['rounds']) - 1];
|
||||||
@@ -103,32 +107,35 @@ class GameFlowController
|
|||||||
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
||||||
|
|
||||||
$panoId = $place->getPanoIdCached();
|
$panoId = $place->getPanoIdCached();
|
||||||
|
$pov = $place->getPov()->toArray();
|
||||||
} else {
|
} else {
|
||||||
$state['rounds'] = [];
|
$state['rounds'] = [];
|
||||||
|
|
||||||
$panoId = null;
|
$panoId = null;
|
||||||
|
$pov = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
$data = [
|
return new JsonContent([
|
||||||
'result' => [
|
'result' => [
|
||||||
'position' => $position->toArray(),
|
'position' => $position->toArray(),
|
||||||
'distance' => $distance,
|
'distance' => $distance,
|
||||||
'score' => $score
|
'score' => $score
|
||||||
],
|
],
|
||||||
'panoId' => $panoId
|
'panoId' => $panoId,
|
||||||
];
|
'pov' => $pov
|
||||||
return new JsonContent($data);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addNewRoundToState(&$state, Place $place, array $placesWithoutPano): void
|
private function addNewRoundToState(array &$state, Place $place, array $placesWithoutPano): void
|
||||||
{
|
{
|
||||||
$state['rounds'][] = [
|
$state['rounds'][] = [
|
||||||
'placesWithoutPano' => $placesWithoutPano,
|
'placesWithoutPano' => $placesWithoutPano,
|
||||||
'placeId' => $place->getId(),
|
'placeId' => $place->getId(),
|
||||||
'position' => $place->getPosition(),
|
'position' => $place->getPosition(),
|
||||||
'panoId' => $place->getPanoIdCached()
|
'panoId' => $place->getPanoIdCached(),
|
||||||
|
'pov' => $place->getPov()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ class HomeController
|
|||||||
{
|
{
|
||||||
// session starts with the request, this method just sends valid data to the client
|
// session starts with the request, this method just sends valid data to the client
|
||||||
|
|
||||||
$data = ['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')];
|
return new JsonContent(['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTime;
|
||||||
use MapGuesser\Http\Request;
|
use MapGuesser\Http\Request;
|
||||||
use MapGuesser\Interfaces\Request\IRequest;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
@@ -8,8 +10,10 @@ use MapGuesser\Mailing\Mail;
|
|||||||
use MapGuesser\OAuth\GoogleOAuth;
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||||
|
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
use MapGuesser\Repository\UserRepository;
|
use MapGuesser\Repository\UserRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
@@ -26,12 +30,15 @@ class LoginController
|
|||||||
|
|
||||||
private UserConfirmationRepository $userConfirmationRepository;
|
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->pdm = new PersistentDataManager();
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLoginForm()
|
public function getLoginForm()
|
||||||
@@ -40,18 +47,23 @@ class LoginController
|
|||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [];
|
return new HtmlContent('login/login');
|
||||||
return new HtmlContent('login/login', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGoogleLoginRedirect(): IRedirect
|
public function getGoogleLoginRedirect(): IRedirect
|
||||||
{
|
{
|
||||||
$state = bin2hex(random_bytes(16));
|
$state = bin2hex(random_bytes(16));
|
||||||
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
$this->request->session()->set('oauth_state', $state);
|
$this->request->session()->set('oauth_state', $state);
|
||||||
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
$url = $oAuth->getDialogUrl($state, $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
|
$url = $oAuth->getDialogUrl(
|
||||||
|
$state,
|
||||||
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
|
||||||
|
$nonce
|
||||||
|
);
|
||||||
|
|
||||||
return new Redirect($url, IRedirect::TEMPORARY);
|
return new Redirect($url, IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
@@ -75,8 +87,7 @@ class LoginController
|
|||||||
|
|
||||||
public function getSignupSuccess(): IContent
|
public function getSignupSuccess(): IContent
|
||||||
{
|
{
|
||||||
$data = [];
|
return new HtmlContent('login/signup_success');
|
||||||
return new HtmlContent('login/signup_success', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSignupWithGoogleForm()
|
public function getSignupWithGoogleForm()
|
||||||
@@ -93,48 +104,96 @@ class LoginController
|
|||||||
|
|
||||||
$user = $this->userRepository->getByEmail($userData['email']);
|
$user = $this->userRepository->getByEmail($userData['email']);
|
||||||
|
|
||||||
$data = ['found' => $user !== null, 'email' => $userData['email']];
|
return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email']]);
|
||||||
return new HtmlContent('login/google_signup', $data);
|
}
|
||||||
|
|
||||||
|
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
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
if (strlen($this->request->post('password')) < 6) {
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'error' => [
|
||||||
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmpUser = new User();
|
$tmpUser = new User();
|
||||||
$tmpUser->setPlainPassword($this->request->post('password'));
|
$tmpUser->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
$this->request->session()->set('tmp_user_data', ['email' => $this->request->post('email'), 'password_hashed' => $tmpUser->getPassword()]);
|
$this->request->session()->set('tmp_user_data', [
|
||||||
|
'email' => $this->request->post('email'),
|
||||||
|
'password_hashed' => $tmpUser->getPassword()
|
||||||
|
]);
|
||||||
|
|
||||||
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()]];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'redirect' => [
|
||||||
|
'target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->getActive()) {
|
if (!$user->getActive()) {
|
||||||
$data = ['error' => ['errorText' => 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!']];
|
$this->resendConfirmationEmail($user);
|
||||||
return new JsonContent($data);
|
|
||||||
|
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' => ['errorText' => 'The given password is wrong.']];
|
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>!'
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginWithGoogle()
|
public function loginWithGoogle()
|
||||||
@@ -144,30 +203,34 @@ class LoginController
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||||
$data = [];
|
return new HtmlContent('login/google_login');
|
||||||
return new HtmlContent('login/google_login', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
$tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
|
$tokenData = $oAuth->getToken(
|
||||||
|
$this->request->query('code'),
|
||||||
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
|
||||||
|
);
|
||||||
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
if (!isset($tokenData['id_token'])) {
|
||||||
$data = [];
|
return new HtmlContent('login/google_login');
|
||||||
return new HtmlContent('login/google_login', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
$userData = $jwtParser->getPayload();
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
if (!$userData['email_verified']) {
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
$data = [];
|
return new HtmlContent('login/google_login');
|
||||||
return new HtmlContent('login/google_login', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByGoogleSub($userData['sub']);
|
if (!$idToken['email_verified']) {
|
||||||
|
return new HtmlContent('login/google_login');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
$this->request->session()->set('google_user_data', $userData);
|
$this->request->session()->set('google_user_data', ['sub' => $idToken['sub'], 'email' => $idToken['email']]);
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
@@ -187,8 +250,7 @@ class LoginController
|
|||||||
public function signup(): IContent
|
public function signup(): IContent
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]];
|
return new JsonContent(['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
@@ -196,22 +258,31 @@ class LoginController
|
|||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
if ($user->getActive()) {
|
if ($user->getActive()) {
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
$data = ['error' => ['errorText' => 'There is a user already registered with the given email address, but the given password is wrong.']];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'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);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]];
|
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]];
|
||||||
} else {
|
} 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!']];
|
$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);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
||||||
$data = ['error' => ['errorText' => 'The given email address is not valid.']];
|
return new JsonContent(['error' => ['errorText' => 'The given email address is not valid.']]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->request->session()->has('tmp_user_data')) {
|
if ($this->request->session()->has('tmp_user_data')) {
|
||||||
@@ -221,52 +292,53 @@ class LoginController
|
|||||||
$tmpUser->setPassword($tmpUserData['password_hashed']);
|
$tmpUser->setPassword($tmpUserData['password_hashed']);
|
||||||
|
|
||||||
if (!$tmpUser->checkPassword($this->request->post('password'))) {
|
if (!$tmpUser->checkPassword($this->request->post('password'))) {
|
||||||
$data = ['error' => ['errorText' => 'The given passwords do not match.']];
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (strlen($this->request->post('password')) < 6) {
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'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')) {
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
$data = ['error' => ['errorText' => 'The given passwords do not match.']];
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($this->request->post('email'));
|
$user->setEmail($this->request->post('email'));
|
||||||
$user->setPlainPassword($this->request->post('password'));
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
$confirmation = new UserConfirmation();
|
$confirmation = new UserConfirmation();
|
||||||
$confirmation->setUser($user);
|
$confirmation->setUser($user);
|
||||||
$confirmation->setToken($token);
|
$confirmation->setToken($token);
|
||||||
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
$this->pdm->saveToDb($confirmation);
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$this->sendConfirmationEmail($user->getEmail(), $token);
|
$this->sendConfirmationEmail($user->getEmail(), $token, $user->getCreatedDate());
|
||||||
|
|
||||||
$this->request->session()->delete('tmp_user_data');
|
$this->request->session()->delete('tmp_user_data');
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signupWithGoogle(): IContent
|
public function signupWithGoogle(): IContent
|
||||||
{
|
{
|
||||||
if ($this->request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$userData = $this->request->session()->get('google_user_data');
|
$userData = $this->request->session()->get('google_user_data');
|
||||||
@@ -278,6 +350,7 @@ class LoginController
|
|||||||
|
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($userData['email']);
|
$user->setEmail($userData['email']);
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
} else {
|
} else {
|
||||||
$sendWelcomeEmail = false;
|
$sendWelcomeEmail = false;
|
||||||
}
|
}
|
||||||
@@ -294,24 +367,21 @@ class LoginController
|
|||||||
$this->request->session()->delete('google_user_data');
|
$this->request->session()->delete('google_user_data');
|
||||||
$this->request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetSignup(): IContent
|
public function resetSignup(): IContent
|
||||||
{
|
{
|
||||||
$this->request->session()->delete('tmp_user_data');
|
$this->request->session()->delete('tmp_user_data');
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetGoogleSignup(): IContent
|
public function resetGoogleSignup(): IContent
|
||||||
{
|
{
|
||||||
$this->request->session()->delete('google_user_data');
|
$this->request->session()->delete('google_user_data');
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function activate()
|
public function activate()
|
||||||
@@ -320,11 +390,10 @@ class LoginController
|
|||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
if ($confirmation === null) {
|
if ($confirmation === null) {
|
||||||
$data = [];
|
return new HtmlContent('login/activate');
|
||||||
return new HtmlContent('login/activate', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
@@ -349,11 +418,10 @@ class LoginController
|
|||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken($this->request->query('token'));
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
if ($confirmation === null) {
|
if ($confirmation === null) {
|
||||||
$data = ['success' => false];
|
return new HtmlContent('login/cancel', ['success' => false]);
|
||||||
return new HtmlContent('login/cancel', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
@@ -366,23 +434,156 @@ class LoginController
|
|||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new HtmlContent('login/cancel', ['success' => true]);
|
||||||
return new HtmlContent('login/cancel', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendConfirmationEmail(string $email, string $token): void
|
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 = new Mail();
|
||||||
$mail->addRecipient($email);
|
$mail->addRecipient($email);
|
||||||
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
|
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
|
||||||
$mail->setBodyFromTemplate('signup', [
|
$mail->setBodyFromTemplate('signup', [
|
||||||
'EMAIL' => $email,
|
'EMAIL' => $email,
|
||||||
'ACTIVATE_LINK' => $this->request->getBase() . '/'. \Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
|
'ACTIVATE_LINK' => $this->request->getBase() . '/' .
|
||||||
'CANCEL_LINK' => $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
|
\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();
|
$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
|
private function sendWelcomeEmail(string $email): void
|
||||||
{
|
{
|
||||||
$mail = new Mail();
|
$mail = new Mail();
|
||||||
@@ -393,4 +594,18 @@ class LoginController
|
|||||||
]);
|
]);
|
||||||
$mail->send();
|
$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,10 +1,8 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
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\Map;
|
||||||
@@ -15,7 +13,7 @@ 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
|
||||||
{
|
{
|
||||||
@@ -57,8 +55,13 @@ class MapAdminController implements ISecured
|
|||||||
$places = [];
|
$places = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = ['mapId' => $mapId, 'mapName' => $map->getName(), 'mapDescription' => str_replace('<br>', "\n", $map->getDescription()), 'bounds' => $map->getBounds()->toArray(), 'places' => &$places];
|
return new HtmlContent('admin/map_editor', [
|
||||||
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
|
||||||
@@ -67,8 +70,7 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
$place = $this->placeRepository->getById($placeId);
|
$place = $this->placeRepository->getById($placeId);
|
||||||
|
|
||||||
$data = ['panoId' => $place->getFreshPanoId()];
|
return new JsonContent(['panoId' => $place->getFreshPanoId()]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveMap(): IContent
|
public function saveMap(): IContent
|
||||||
@@ -94,6 +96,11 @@ class MapAdminController implements ISecured
|
|||||||
$place->setMap($map);
|
$place->setMap($map);
|
||||||
$place->setLat((float) $placeRaw['lat']);
|
$place->setLat((float) $placeRaw['lat']);
|
||||||
$place->setLng((float) $placeRaw['lng']);
|
$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) {
|
if ($placeRaw['panoId'] === -1) {
|
||||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||||
@@ -114,6 +121,11 @@ class MapAdminController implements ISecured
|
|||||||
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
||||||
$place->setLat((float) $placeRaw['lat']);
|
$place->setLat((float) $placeRaw['lat']);
|
||||||
$place->setLng((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'));
|
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||||
|
|
||||||
$this->pdm->saveToDb($place);
|
$this->pdm->saveToDb($place);
|
||||||
@@ -146,11 +158,11 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['mapId' => $map->getId(), 'added' => $addedIds];
|
return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteMap() {
|
public function deleteMap(): IContent
|
||||||
|
{
|
||||||
$mapId = (int) $this->request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
@@ -163,40 +175,22 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['success' => true];
|
return new JsonContent(['success' => true]);
|
||||||
return new JsonContent($data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deletePlaces(Map $map): void
|
private function deletePlaces(Map $map): void
|
||||||
{
|
{
|
||||||
//TODO: relations?
|
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select->columns(Place::getFields());
|
|
||||||
$select->where('map_id', '=', $map->getId());
|
|
||||||
|
|
||||||
$result = $select->execute();
|
|
||||||
|
|
||||||
while ($placeData = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
|
||||||
$place = new Place();
|
|
||||||
|
|
||||||
$this->pdm->fillWithData($placeData, $place);
|
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($place);
|
$this->pdm->deleteFromDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function calculateMapBounds(Map $map): Bounds
|
private function calculateMapBounds(Map $map): Bounds
|
||||||
{
|
{
|
||||||
//TODO: from repository or relations
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select->columns(['lat', 'lng']);
|
|
||||||
$select->where('map_id', '=', $map->getId());
|
|
||||||
|
|
||||||
$result = $select->execute();
|
|
||||||
|
|
||||||
$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;
|
||||||
@@ -204,26 +198,19 @@ class MapAdminController implements ISecured
|
|||||||
|
|
||||||
private function &getPlaces(Map $map): array
|
private function &getPlaces(Map $map): array
|
||||||
{
|
{
|
||||||
//TODO: from repository or relations
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select->columns(['id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp']);
|
|
||||||
$select->where('map_id', '=', $map->getId());
|
|
||||||
|
|
||||||
$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
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class MapsController
|
|||||||
|
|
||||||
public function getMaps(): IContent
|
public function getMaps(): IContent
|
||||||
{
|
{
|
||||||
//TODO: from repository
|
//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'],
|
||||||
@@ -46,8 +46,10 @@ class MapsController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$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
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use DateTime;
|
||||||
|
use MapGuesser\Http\Request;
|
||||||
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\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
use MapGuesser\Response\HtmlContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
use MapGuesser\Util\JwtParser;
|
||||||
|
|
||||||
class UserController implements ISecured
|
class UserController implements ISecured
|
||||||
{
|
{
|
||||||
@@ -20,11 +24,14 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
private UserConfirmationRepository $userConfirmationRepository;
|
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->pdm = new PersistentDataManager();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
@@ -41,8 +48,76 @@ class UserController implements ISecured
|
|||||||
*/
|
*/
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
$data = ['user' => $user->toArray()];
|
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
||||||
return new HtmlContent('account/account', $data);
|
}
|
||||||
|
|
||||||
|
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
|
public function getDeleteAccount(): IContent
|
||||||
@@ -52,8 +127,7 @@ class UserController implements ISecured
|
|||||||
*/
|
*/
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
$data = ['user' => $user->toArray()];
|
return new HtmlContent('account/delete', ['user' => $user->toArray()]);
|
||||||
return new HtmlContent('account/delete', $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveAccount(): IContent
|
public function saveAccount(): IContent
|
||||||
@@ -63,20 +137,30 @@ class UserController implements ISecured
|
|||||||
*/
|
*/
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$this->confirmUserIdentity(
|
||||||
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
|
$user,
|
||||||
return new JsonContent($data);
|
$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')) > 0) {
|
||||||
if (strlen($this->request->post('password_new')) < 6) {
|
if (strlen($this->request->post('password_new')) < 6) {
|
||||||
$data = ['error' => ['errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!']];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'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')) {
|
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
||||||
$data = ['error' => ['errorText' => 'The given new passwords do not match.']];
|
return new JsonContent([
|
||||||
return new JsonContent($data);
|
'error' => [
|
||||||
|
'errorText' => 'The given new passwords do not match.'
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->setPlainPassword($this->request->post('password_new'));
|
$user->setPlainPassword($this->request->post('password_new'));
|
||||||
@@ -84,8 +168,9 @@ class UserController implements ISecured
|
|||||||
|
|
||||||
$this->pdm->saveToDb($user);
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
$data = ['success' => true];
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
return new JsonContent($data);
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteAccount(): IContent
|
public function deleteAccount(): IContent
|
||||||
@@ -95,22 +180,52 @@ class UserController implements ISecured
|
|||||||
*/
|
*/
|
||||||
$user = $this->request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
if (!$user->checkPassword($this->request->post('password'))) {
|
if (!$this->confirmUserIdentity(
|
||||||
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
|
$user,
|
||||||
return new JsonContent($data);
|
$this->request->session()->get('authenticated_with_google_until'),
|
||||||
|
$this->request->post('password'),
|
||||||
|
$error
|
||||||
|
)) {
|
||||||
|
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$dbConnection->startTransaction();
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
foreach ($this->userConfirmationRepository->getByUser($user) as $userConfirmation) {
|
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
|
if ($userConfirmation !== null) {
|
||||||
$this->pdm->deleteFromDb($userConfirmation);
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
|
if ($userPasswordResetter !== null) {
|
||||||
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
|
}
|
||||||
|
|
||||||
$this->pdm->deleteFromDb($user);
|
$this->pdm->deleteFromDb($user);
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$data = ['success' => true];
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
return new JsonContent($data);
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -135,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,7 +30,7 @@ class Select
|
|||||||
|
|
||||||
private array $orders = [];
|
private array $orders = [];
|
||||||
|
|
||||||
private array $limit;
|
private ?array $limit;
|
||||||
|
|
||||||
public function __construct(IConnection $connection, ?string $table = null)
|
public function __construct(IConnection $connection, ?string $table = null)
|
||||||
{
|
{
|
||||||
@@ -144,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);
|
||||||
|
|
||||||
@@ -308,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 = [];
|
||||||
@@ -376,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 . '`';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class Request implements IRequest
|
|||||||
$this->method = $method;
|
$this->method = $method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setQuery($query)
|
public function setQuery($query): void
|
||||||
{
|
{
|
||||||
if (is_string($query)) {
|
if (is_string($query)) {
|
||||||
$this->query = $query;
|
$this->query = $query;
|
||||||
@@ -38,7 +38,7 @@ class Request implements IRequest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -47,13 +47,13 @@ class Request implements IRequest
|
|||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
|
|
||||||
if ($this->method === self::HTTP_GET) {
|
if ($this->method === self::HTTP_POST) {
|
||||||
$url = $this->url . '?' . $this->query;
|
$url = $this->url;
|
||||||
} elseif ($this->method === self::HTTP_POST) {
|
|
||||||
$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);
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ class Response implements IResponse
|
|||||||
$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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ interface IRequest
|
|||||||
|
|
||||||
public function setMethod(int $method): void;
|
public function setMethod(int $method): void;
|
||||||
|
|
||||||
public function setQuery($query);
|
public function setQuery($query): void;
|
||||||
|
|
||||||
public function setHeaders(array $headers);
|
public function setHeaders(array $headers): void;
|
||||||
|
|
||||||
public function send(): IResponse;
|
public function send(): IResponse;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
interface IResponse
|
interface IResponse
|
||||||
{
|
{
|
||||||
public function getBody();
|
public function getBody(): string;
|
||||||
|
|
||||||
public function getHeaders();
|
public function getHeaders(): array;
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ use MapGuesser\Interfaces\Authentication\IUser;
|
|||||||
|
|
||||||
interface IRequest
|
interface IRequest
|
||||||
{
|
{
|
||||||
public function setParsedRouteParams(array &$routeParams);
|
public function setParsedRouteParams(array &$routeParams): void;
|
||||||
|
|
||||||
public function getBase(): string;
|
public function getBase(): string;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -77,7 +77,7 @@ class Mail
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function sendMail(PHPMailer $mailer, array $recipient)
|
private function sendMail(PHPMailer $mailer, array $recipient): void
|
||||||
{
|
{
|
||||||
$mailer->clearAddresses();
|
$mailer->clearAddresses();
|
||||||
$mailer->addAddress($recipient[0], $recipient[1]);
|
$mailer->addAddress($recipient[0], $recipient[1]);
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use MapGuesser\Interfaces\Http\IRequest;
|
|||||||
|
|
||||||
class GoogleOAuth
|
class GoogleOAuth
|
||||||
{
|
{
|
||||||
private static $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
private static string $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||||
|
|
||||||
private static $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
private static string $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
||||||
|
|
||||||
private IRequest $request;
|
private IRequest $request;
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ class GoogleOAuth
|
|||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDialogUrl(string $state, string $redirectUrl): string
|
public function getDialogUrl(string $state, string $redirectUrl, ?string $nonce = null, ?string $loginHint = null): string
|
||||||
{
|
{
|
||||||
$oauthParams = [
|
$oauthParams = [
|
||||||
'response_type' => 'code',
|
'response_type' => 'code',
|
||||||
@@ -23,13 +23,20 @@ class GoogleOAuth
|
|||||||
'scope' => 'openid email',
|
'scope' => 'openid email',
|
||||||
'redirect_uri' => $redirectUrl,
|
'redirect_uri' => $redirectUrl,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if ($nonce !== null) {
|
||||||
|
$oauthParams['nonce'] = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginHint !== null) {
|
||||||
|
$oauthParams['login_hint'] = $loginHint;
|
||||||
|
}
|
||||||
|
|
||||||
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getToken(string $code, string $redirectUrl)
|
public function getToken(string $code, string $redirectUrl): array
|
||||||
{
|
{
|
||||||
$tokenParams = [
|
$tokenParams = [
|
||||||
'code' => $code,
|
'code' => $code,
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use DateTime;
|
|||||||
use MapGuesser\Http\Request;
|
use MapGuesser\Http\Request;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Util\Geo\Position;
|
use MapGuesser\Util\Geo\Position;
|
||||||
|
use MapGuesser\Util\Panorama\Pov;
|
||||||
|
|
||||||
class Place extends Model
|
class Place extends Model
|
||||||
{
|
{
|
||||||
protected static string $table = 'places';
|
protected static string $table = 'places';
|
||||||
|
|
||||||
protected static array $fields = ['map_id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp'];
|
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];
|
protected static array $relations = ['map' => Map::class];
|
||||||
|
|
||||||
@@ -24,9 +25,12 @@ class Place extends Model
|
|||||||
|
|
||||||
private ?DateTime $panoIdCachedTimestamp = null;
|
private ?DateTime $panoIdCachedTimestamp = null;
|
||||||
|
|
||||||
|
private Pov $pov;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->position = new Position(0.0, 0.0);
|
$this->position = new Position(0.0, 0.0);
|
||||||
|
$this->pov = new Pov(0.0, 0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMap(Map $map): void
|
public function setMap(Map $map): void
|
||||||
@@ -54,6 +58,26 @@ class Place extends Model
|
|||||||
$this->position->setLng($lng);
|
$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
|
public function setPanoIdCached(?string $panoIdCached): void
|
||||||
{
|
{
|
||||||
$this->panoIdCached = $panoIdCached;
|
$this->panoIdCached = $panoIdCached;
|
||||||
@@ -96,6 +120,26 @@ class Place extends Model
|
|||||||
return $this->position->getLng();
|
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
|
public function getFreshPanoId(bool $canBeIndoor = false): ?string
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
@@ -122,7 +166,7 @@ class Place extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->panoIdCached = $panoId;
|
$this->panoIdCached = $panoId;
|
||||||
$this->panoIdCachedTimestamp = new DateTime('now');
|
$this->panoIdCachedTimestamp = new DateTime();
|
||||||
|
|
||||||
(new PersistentDataManager())->saveToDb($this);
|
(new PersistentDataManager())->saveToDb($this);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use MapGuesser\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
class User extends Model implements IUser
|
class User extends Model implements IUser
|
||||||
{
|
{
|
||||||
protected static string $table = 'users';
|
protected static string $table = 'users';
|
||||||
|
|
||||||
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub'];
|
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub', 'created'];
|
||||||
|
|
||||||
private static array $types = ['user', 'admin'];
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ class User extends Model implements IUser
|
|||||||
|
|
||||||
private ?string $googleSub = null;
|
private ?string $googleSub = null;
|
||||||
|
|
||||||
|
private DateTime $created;
|
||||||
|
|
||||||
public function setEmail(string $email): void
|
public function setEmail(string $email): void
|
||||||
{
|
{
|
||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
@@ -42,9 +45,9 @@ class User extends Model implements IUser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setActive($active): void
|
public function setActive(bool $active): void
|
||||||
{
|
{
|
||||||
$this->active = (bool) $active;
|
$this->active = $active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setGoogleSub(?string $googleSub): void
|
public function setGoogleSub(?string $googleSub): void
|
||||||
@@ -52,6 +55,16 @@ class User extends Model implements IUser
|
|||||||
$this->googleSub = $googleSub;
|
$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
|
public function getEmail(): string
|
||||||
{
|
{
|
||||||
return $this->email;
|
return $this->email;
|
||||||
@@ -77,15 +90,25 @@ class User extends Model implements IUser
|
|||||||
return $this->googleSub;
|
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
|
public function hasPermission(int $permission): bool
|
||||||
{
|
{
|
||||||
switch ($permission) {
|
switch ($permission) {
|
||||||
case IUser::PERMISSION_NORMAL:
|
case IUser::PERMISSION_NORMAL:
|
||||||
return true;
|
return true;
|
||||||
break;
|
|
||||||
case IUser::PERMISSION_ADMIN:
|
case IUser::PERMISSION_ADMIN:
|
||||||
return $this->type === 'admin';
|
return $this->type === 'admin';
|
||||||
break;
|
default:
|
||||||
|
throw new \Exception('Permission does not exist: ' . $permission);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +124,10 @@ class User extends Model implements IUser
|
|||||||
|
|
||||||
public function checkPassword(string $password): bool
|
public function checkPassword(string $password): bool
|
||||||
{
|
{
|
||||||
|
if ($this->password === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return password_verify($password, $this->password);
|
return password_verify($password, $this->password);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
class UserConfirmation extends Model
|
class UserConfirmation extends Model
|
||||||
{
|
{
|
||||||
protected static string $table = 'user_confirmations';
|
protected static string $table = 'user_confirmations';
|
||||||
|
|
||||||
protected static array $fields = ['user_id', 'token'];
|
protected static array $fields = ['user_id', 'token', 'last_sent'];
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class];
|
protected static array $relations = ['user' => User::class];
|
||||||
|
|
||||||
@@ -14,6 +16,8 @@ class UserConfirmation extends Model
|
|||||||
|
|
||||||
private string $token = '';
|
private string $token = '';
|
||||||
|
|
||||||
|
private DateTime $lastSent;
|
||||||
|
|
||||||
public function setUser(User $user): void
|
public function setUser(User $user): void
|
||||||
{
|
{
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
@@ -29,6 +33,16 @@ class UserConfirmation extends Model
|
|||||||
$this->token = $token;
|
$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
|
public function getUser(): ?User
|
||||||
{
|
{
|
||||||
return $this->user;
|
return $this->user;
|
||||||
@@ -43,4 +57,14 @@ class UserConfirmation extends Model
|
|||||||
{
|
{
|
||||||
return $this->token;
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ use MapGuesser\PersistentData\Model\Model;
|
|||||||
|
|
||||||
class PersistentDataManager
|
class PersistentDataManager
|
||||||
{
|
{
|
||||||
public function selectFromDb(Select $select, string $type, bool $withRelations = false): ?Model
|
public function selectFromDb(Select $select, string $type, bool $withRelations = false)
|
||||||
{
|
{
|
||||||
$select = $this->createSelect($select, $type, $withRelations);
|
$select = $this->createSelect($select, $type, $withRelations);
|
||||||
|
|
||||||
@@ -27,8 +27,9 @@ class PersistentDataManager
|
|||||||
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
|
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
|
||||||
{
|
{
|
||||||
$select = $this->createSelect($select, $type, $withRelations);
|
$select = $this->createSelect($select, $type, $withRelations);
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
while ($data = $select->execute()->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$model = new $type();
|
$model = new $type();
|
||||||
$this->fillWithData($data, $model);
|
$this->fillWithData($data, $model);
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ class PersistentDataManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function selectFromDbById($id, string $type, bool $withRelations = false): ?Model
|
public function selectFromDbById($id, string $type, bool $withRelations = false)
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->whereId($id);
|
$select->whereId($id);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
@@ -18,6 +20,15 @@ class PlaceRepository
|
|||||||
return $this->pdm->selectFromDbById($placeId, Place::class);
|
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 getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place
|
public function getRandomForMapWithValidPano(int $mapId, array $exclude = [], array &$placesWithoutPano): Place
|
||||||
{
|
{
|
||||||
$placesWithoutPano = [];
|
$placesWithoutPano = [];
|
||||||
@@ -35,13 +46,20 @@ class PlaceRepository
|
|||||||
return $place;
|
return $place;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectRandomFromDbForMap(int $mapId, array $exclude): ?Place
|
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->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');
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
@@ -29,11 +27,11 @@ class UserConfirmationRepository
|
|||||||
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByUser(User $user): Generator
|
public function getByUser(User $user): ?UserConfirmation
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('user_id', '=', $user->getId());
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
yield from $this->pdm->selectMultipleFromDb($select, UserConfirmation::class);
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Generator;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
|
class UserPasswordResetterRepository
|
||||||
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getById(int $userConfirmationId): ?UserPasswordResetter
|
||||||
|
{
|
||||||
|
return $this->pdm->selectFromDbById($userConfirmationId, UserPasswordResetter::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByToken(string $token): ?UserPasswordResetter
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('token', '=', $token);
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getByUser(User $user): ?UserPasswordResetter
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
|
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllExpired(): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, UserPasswordResetter::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Generator;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\PersistentDataManager;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
@@ -33,4 +35,13 @@ class UserRepository
|
|||||||
|
|
||||||
return $this->pdm->selectFromDb($select, User::class);
|
return $this->pdm->selectFromDb($select, User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAllInactiveExpired(): Generator
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->where('active', '=', false);
|
||||||
|
$select->where('created', '<', (new DateTime('-1 day'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
|
yield from $this->pdm->selectMultipleFromDb($select, User::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ class Request implements IRequest
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setParsedRouteParams(array &$routeParams)
|
public function setParsedRouteParams(array &$routeParams): void
|
||||||
{
|
{
|
||||||
$this->routeParams = &$routeParams;
|
$this->routeParams = &$routeParams;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ class Session implements ISession
|
|||||||
$this->data = &$data;
|
$this->data = &$data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function has($key): bool
|
public function has(string $key): bool
|
||||||
{
|
{
|
||||||
return isset($this->data[$key]);
|
return isset($this->data[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get($key)
|
public function get(string $key)
|
||||||
{
|
{
|
||||||
if (isset($this->data[$key])) {
|
if (isset($this->data[$key])) {
|
||||||
return $this->data[$key];
|
return $this->data[$key];
|
||||||
@@ -25,12 +25,12 @@ class Session implements ISession
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function set($key, $value): void
|
public function set(string $key, $value): void
|
||||||
{
|
{
|
||||||
$this->data[$key] = $value;
|
$this->data[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete($key): void
|
public function delete(string $key): void
|
||||||
{
|
{
|
||||||
unset($this->data[$key]);
|
unset($this->data[$key]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,17 @@ abstract class ContentBase implements IContent
|
|||||||
{
|
{
|
||||||
protected array $data;
|
protected array $data;
|
||||||
|
|
||||||
public function &getData(): array
|
public function setData(array $data): void
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(): array
|
||||||
{
|
{
|
||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract public function &render(): string;
|
abstract public function render(): void;
|
||||||
|
|
||||||
abstract public function getContentType(): string;
|
abstract public function getContentType(): string;
|
||||||
}
|
}
|
||||||
@@ -6,13 +6,13 @@ class HtmlContent extends ContentBase
|
|||||||
{
|
{
|
||||||
private string $view;
|
private string $view;
|
||||||
|
|
||||||
public function __construct(string $view, array &$data = [])
|
public function __construct(string $view, array $data = [])
|
||||||
{
|
{
|
||||||
$this->view = $view;
|
$this->view = $view;
|
||||||
$this->data = &$data;
|
$this->data = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function &render(): string
|
public function render(): void
|
||||||
{
|
{
|
||||||
if (!empty($_ENV['DEV'])) {
|
if (!empty($_ENV['DEV'])) {
|
||||||
$generator = new Linker($this->view);
|
$generator = new Linker($this->view);
|
||||||
@@ -21,14 +21,10 @@ class HtmlContent extends ContentBase
|
|||||||
|
|
||||||
extract($this->data);
|
extract($this->data);
|
||||||
|
|
||||||
ob_start();
|
|
||||||
require ROOT . '/cache/views/' . $this->view . '.php';
|
require ROOT . '/cache/views/' . $this->view . '.php';
|
||||||
$content = ob_get_contents();
|
|
||||||
ob_end_clean();
|
|
||||||
|
|
||||||
$content .= '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->';
|
// @phpstan-ignore-next-line - SCRIPT_STARTED is defined in main.php
|
||||||
|
echo '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->';
|
||||||
return $content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContentType(): string
|
public function getContentType(): string
|
||||||
|
|||||||
@@ -2,18 +2,17 @@
|
|||||||
|
|
||||||
class JsonContent extends ContentBase
|
class JsonContent extends ContentBase
|
||||||
{
|
{
|
||||||
public function __construct(array &$data = [])
|
public function __construct(array $data = [])
|
||||||
{
|
{
|
||||||
$this->data = &$data;
|
$this->data = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function &render(): string
|
public function render(): void
|
||||||
{
|
{
|
||||||
|
// @phpstan-ignore-next-line - SCRIPT_STARTED is defined in main.php
|
||||||
$this->data['__debug__runtime'] = round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1);
|
$this->data['__debug__runtime'] = round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1);
|
||||||
|
|
||||||
$content = json_encode($this->data);
|
echo json_encode($this->data);
|
||||||
|
|
||||||
return $content;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContentType(): string
|
public function getContentType(): string
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Redirect implements IRedirect
|
|||||||
|
|
||||||
public function getUrl(): string
|
public function getUrl(): string
|
||||||
{
|
{
|
||||||
if (preg_match('/^http(s)?/', $this->target)) {
|
if (preg_match('/^http(s)?/', $this->target) === 1) {
|
||||||
$link = $this->target;
|
$link = $this->target;
|
||||||
} else {
|
} else {
|
||||||
$link = \Container::$request->getBase() . '/' . $this->target;
|
$link = \Container::$request->getBase() . '/' . $this->target;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Route
|
|||||||
$link = [];
|
$link = [];
|
||||||
|
|
||||||
foreach ($this->pattern as $fragment) {
|
foreach ($this->pattern as $fragment) {
|
||||||
if (preg_match('/^{(\\w+)(\\?)?}$/', $fragment, $matches)) {
|
if (preg_match('/^{(\\w+)(\\?)?}$/', $fragment, $matches) === 1) {
|
||||||
if (isset($parameters[$matches[1]])) {
|
if (isset($parameters[$matches[1]])) {
|
||||||
$link[] = $parameters[$matches[1]];
|
$link[] = $parameters[$matches[1]];
|
||||||
unset($parameters[$matches[1]]);
|
unset($parameters[$matches[1]]);
|
||||||
@@ -61,7 +61,7 @@ class Route
|
|||||||
$parameters = [];
|
$parameters = [];
|
||||||
|
|
||||||
foreach ($path as $i => $fragment) {
|
foreach ($path as $i => $fragment) {
|
||||||
if (preg_match('/^{(\\w+)(?:\\?)?}$/', $this->pattern[$i], $matches)) {
|
if (preg_match('/^{(\\w+)(?:\\?)?}$/', $this->pattern[$i], $matches) === 1) {
|
||||||
$parameters[$matches[1]] = $fragment;
|
$parameters[$matches[1]] = $fragment;
|
||||||
} elseif ($fragment != $this->pattern[$i]) {
|
} elseif ($fragment != $this->pattern[$i]) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class RouteCollection
|
|||||||
|
|
||||||
$this->searchTable[$method][$groupNumber][] = $route;
|
$this->searchTable[$method][$groupNumber][] = $route;
|
||||||
|
|
||||||
while (preg_match('/^{\\w+\\?}$/', end($pattern))) {
|
while (preg_match('/^{\\w+\\?}$/', end($pattern)) === 1) {
|
||||||
$groupNumber--;
|
$groupNumber--;
|
||||||
array_pop($pattern);
|
array_pop($pattern);
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ use DateTime;
|
|||||||
use MapGuesser\Database\Query\Modify;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use MapGuesser\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use SessionHandlerInterface;
|
use MapGuesser\Interfaces\Session\ISessionHandler;
|
||||||
use SessionIdInterface;
|
|
||||||
use SessionUpdateTimestampHandlerInterface;
|
|
||||||
|
|
||||||
class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
|
class DatabaseSessionHandler implements ISessionHandler
|
||||||
{
|
{
|
||||||
private bool $exists = false;
|
private bool $exists = false;
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||||
$select->columns(['data']);
|
$select->columns(['data']);
|
||||||
$select->whereId($id);
|
$select->whereId(substr($id, 0, 32));
|
||||||
|
|
||||||
$result = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
$result = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
@@ -46,16 +44,16 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
|
|
||||||
if ($this->exists) {
|
if ($this->exists) {
|
||||||
$modify->setId($id);
|
$modify->setId(substr($id, 0, 32));
|
||||||
} else {
|
} else {
|
||||||
$modify->setExternalId($id);
|
$modify->setExternalId(substr($id, 0, 32));
|
||||||
}
|
}
|
||||||
|
|
||||||
$modify->set('data', $data);
|
$modify->set('data', $data);
|
||||||
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
$modify->save();
|
$modify->save();
|
||||||
|
|
||||||
$written = true;
|
$this->written = true;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -63,37 +61,30 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
public function destroy($id): bool
|
public function destroy($id): bool
|
||||||
{
|
{
|
||||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
$modify->setId($id);
|
$modify->setId(substr($id, 0, 32));
|
||||||
$modify->delete();
|
$modify->delete();
|
||||||
|
|
||||||
|
$this->exists = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function gc($maxlifetime): int
|
public function gc($maxlifetime): bool
|
||||||
{
|
{
|
||||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
// empty on purpose
|
||||||
$select->columns(['id']);
|
// old sessions are deleted by MaintainDatabaseCommand
|
||||||
$select->where('updated', '<', (new DateTime('-' . $maxlifetime . ' seconds'))->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();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create_sid(): string
|
public function create_sid(): string
|
||||||
{
|
{
|
||||||
return hash('sha256', random_bytes(10) . microtime());
|
return bin2hex(random_bytes(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateId($id): bool
|
public function validateId($id): bool
|
||||||
{
|
{
|
||||||
return preg_match('/^[a-f0-9]{64}$/', $id);
|
return preg_match('/^[a-f0-9]{32}$/', $id) === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateTimestamp($id, $data): bool
|
public function updateTimestamp($id, $data): bool
|
||||||
@@ -104,7 +95,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
|||||||
|
|
||||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||||
|
|
||||||
$modify->setId($id);
|
$modify->setId(substr($id, 0, 32));
|
||||||
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
$modify->save();
|
$modify->save();
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Bounds
|
|||||||
|
|
||||||
public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
|
public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
|
||||||
{
|
{
|
||||||
$instance = new static();
|
$instance = new self();
|
||||||
|
|
||||||
$instance->southLat = $southLat;
|
$instance->southLat = $southLat;
|
||||||
$instance->westLng = $westLng;
|
$instance->westLng = $westLng;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class JwtParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setToken(string $token)
|
public function setToken(string $token): void
|
||||||
{
|
{
|
||||||
$this->token = explode('.', str_replace(['_', '-'], ['/', '+'], $token));
|
$this->token = explode('.', str_replace(['_', '-'], ['/', '+'], $token));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php namespace MapGuesser\Util\Panorama;
|
||||||
|
|
||||||
|
class Pov
|
||||||
|
{
|
||||||
|
private float $heading;
|
||||||
|
|
||||||
|
private float $pitch;
|
||||||
|
|
||||||
|
private float $zoom;
|
||||||
|
|
||||||
|
public function __construct(float $heading, float $pitch, float $zoom)
|
||||||
|
{
|
||||||
|
$this->heading = $heading;
|
||||||
|
$this->pitch = $pitch;
|
||||||
|
$this->zoom = $zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHeading(float $heading): void
|
||||||
|
{
|
||||||
|
$this->heading = $heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPitch(float $pitch): void
|
||||||
|
{
|
||||||
|
$this->pitch = $pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setZoom(float $zoom): void
|
||||||
|
{
|
||||||
|
$this->zoom = $zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeading(): float
|
||||||
|
{
|
||||||
|
return $this->heading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPitch(): float
|
||||||
|
{
|
||||||
|
return $this->pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getZoom(): float
|
||||||
|
{
|
||||||
|
return $this->zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'heading' => $this->heading,
|
||||||
|
'pitch' => $this->pitch,
|
||||||
|
'zoom' => $this->zoom
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
-2
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
class Linker
|
class Linker
|
||||||
{
|
{
|
||||||
|
const INLINE_ASSET_LIMIT = 2000;
|
||||||
|
|
||||||
private string $view;
|
private string $view;
|
||||||
|
|
||||||
public function __construct(string $view)
|
public function __construct(string $view)
|
||||||
@@ -14,7 +16,7 @@ class Linker
|
|||||||
$input = ROOT . '/views/' . $this->view . '.php';
|
$input = ROOT . '/views/' . $this->view . '.php';
|
||||||
|
|
||||||
$temporaryFiles = [];
|
$temporaryFiles = [];
|
||||||
$sections = [];
|
$sections = ['externalCss' => '', 'inlineCss' => '', 'externalJs' => '', 'inlineJs' => ''];
|
||||||
$extra = ['', ''];
|
$extra = ['', ''];
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@@ -23,6 +25,8 @@ class Linker
|
|||||||
|
|
||||||
$extends = $fragment->getExtends();
|
$extends = $fragment->getExtends();
|
||||||
|
|
||||||
|
$this->generateAssets($fragment, $sections);
|
||||||
|
|
||||||
$sections = array_merge($sections, $fragment->getSections()); //TODO: detect if section defined multiple times
|
$sections = array_merge($sections, $fragment->getSections()); //TODO: detect if section defined multiple times
|
||||||
$extra[0] = $fragment->getExtra()[0] . $extra[0];
|
$extra[0] = $fragment->getExtra()[0] . $extra[0];
|
||||||
$extra[1] = $extra[1] . $fragment->getExtra()[1];
|
$extra[1] = $extra[1] . $fragment->getExtra()[1];
|
||||||
@@ -61,7 +65,7 @@ class Linker
|
|||||||
while (($line = fgets($inputFileHandle)) !== false) {
|
while (($line = fgets($inputFileHandle)) !== false) {
|
||||||
++$lineNumber;
|
++$lineNumber;
|
||||||
|
|
||||||
if (preg_match('/^\s*@yields\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
|
if (preg_match('/^\s*@yields\(\'([\w\/]+)\'\)\s*$/', $line, $matches) === 1) {
|
||||||
if (isset($sections[$matches[1]])) {
|
if (isset($sections[$matches[1]])) {
|
||||||
fwrite($outputFileHandle, $sections[$matches[1]]);
|
fwrite($outputFileHandle, $sections[$matches[1]]);
|
||||||
}
|
}
|
||||||
@@ -100,4 +104,49 @@ class Linker
|
|||||||
fclose($inputFileHandle);
|
fclose($inputFileHandle);
|
||||||
fclose($outputFileHandle);
|
fclose($outputFileHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateAssets(ParsedFragment $fragment, array &$sections): void
|
||||||
|
{
|
||||||
|
foreach ($fragment->getCss() as $cssFile) {
|
||||||
|
$asset = $this->parseAsset($cssFile);
|
||||||
|
if (isset($asset['code'])) {
|
||||||
|
$sections['inlineCss'] .= '<style>' . PHP_EOL;
|
||||||
|
$sections['inlineCss'] .= $asset['code'];
|
||||||
|
$sections['inlineCss'] .= '</style>' . PHP_EOL;
|
||||||
|
} elseif (isset($asset['file'])) {
|
||||||
|
$sections['externalCss'] .= '<link href="' . $asset['file'] . '" rel="stylesheet">' . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($fragment->getJs() as $jsFile) {
|
||||||
|
$asset = $this->parseAsset($jsFile);
|
||||||
|
if (isset($asset['code'])) {
|
||||||
|
$sections['inlineJs'] .= '<script>' . PHP_EOL;
|
||||||
|
$sections['inlineJs'] .= $asset['code'];
|
||||||
|
$sections['inlineJs'] .= '</script>' . PHP_EOL;
|
||||||
|
} elseif (isset($asset['file'])) {
|
||||||
|
$sections['externalJs'] .= '<script src="' . $asset['file'] . '"></script>' . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseAsset(string $asset): array
|
||||||
|
{
|
||||||
|
$output = [];
|
||||||
|
|
||||||
|
if (preg_match('/^[\w\/\.]+$/', $asset) === 1) {
|
||||||
|
if (
|
||||||
|
empty($_ENV['DEV']) &&
|
||||||
|
filesize(ROOT . '/public/static/' . $asset) < self::INLINE_ASSET_LIMIT
|
||||||
|
) {
|
||||||
|
$output['code'] = file_get_contents(ROOT . '/public/static/' . $asset);
|
||||||
|
} else {
|
||||||
|
$output['file'] = '<?= $_ENV[\'STATIC_ROOT\'] ?>/' . $asset . '?rev=<?= REVISION ?>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$output['file'] = $asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,19 @@ class ParsedFragment
|
|||||||
{
|
{
|
||||||
private ?string $extends;
|
private ?string $extends;
|
||||||
|
|
||||||
|
private array $css;
|
||||||
|
|
||||||
|
private array $js;
|
||||||
|
|
||||||
private array $sections;
|
private array $sections;
|
||||||
|
|
||||||
private array $extra;
|
private array $extra;
|
||||||
|
|
||||||
public function __construct(?string $extends, array $sections, array $extra)
|
public function __construct(?string $extends, array $css, array $js, array $sections, array $extra)
|
||||||
{
|
{
|
||||||
$this->extends = $extends;
|
$this->extends = $extends;
|
||||||
|
$this->css = $css;
|
||||||
|
$this->js = $js;
|
||||||
$this->sections = $sections;
|
$this->sections = $sections;
|
||||||
$this->extra = $extra;
|
$this->extra = $extra;
|
||||||
}
|
}
|
||||||
@@ -20,6 +26,16 @@ class ParsedFragment
|
|||||||
return $this->extends;
|
return $this->extends;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCss(): array
|
||||||
|
{
|
||||||
|
return $this->css;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getJs(): array
|
||||||
|
{
|
||||||
|
return $this->js;
|
||||||
|
}
|
||||||
|
|
||||||
public function getSections(): array
|
public function getSections(): array
|
||||||
{
|
{
|
||||||
return $this->sections;
|
return $this->sections;
|
||||||
|
|||||||
+38
-7
@@ -11,11 +11,12 @@ class Parser
|
|||||||
|
|
||||||
public function parse(): ParsedFragment
|
public function parse(): ParsedFragment
|
||||||
{
|
{
|
||||||
|
|
||||||
$sectionOpen = null;
|
$sectionOpen = null;
|
||||||
$extraOpen = false;
|
$extraOpen = false;
|
||||||
|
|
||||||
$extends = null;
|
$extends = null;
|
||||||
|
$js = [];
|
||||||
|
$css = [];
|
||||||
$sections = [];
|
$sections = [];
|
||||||
$extra = ['', ''];
|
$extra = ['', ''];
|
||||||
|
|
||||||
@@ -28,6 +29,18 @@ class Parser
|
|||||||
while (($line = fgets($fileHandle)) !== false) {
|
while (($line = fgets($fileHandle)) !== false) {
|
||||||
++$lineNumber;
|
++$lineNumber;
|
||||||
|
|
||||||
|
if (($cssMatched = $this->matchCss($line)) !== null) {
|
||||||
|
$css[] = $cssMatched;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($jsMatched = $this->matchJs($line)) !== null) {
|
||||||
|
$js[] = $jsMatched;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (($extendsMatched = $this->matchExtends($line)) !== null) {
|
if (($extendsMatched = $this->matchExtends($line)) !== null) {
|
||||||
if ($extends !== null) {
|
if ($extends !== null) {
|
||||||
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - There is already an \'@extends\' declared.');
|
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - There is already an \'@extends\' declared.');
|
||||||
@@ -90,12 +103,30 @@ class Parser
|
|||||||
|
|
||||||
fclose($fileHandle);
|
fclose($fileHandle);
|
||||||
|
|
||||||
return new ParsedFragment($extends, $sections, $extra);
|
return new ParsedFragment($extends, $css, $js, $sections, $extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchCss(string $line): ?string
|
||||||
|
{
|
||||||
|
if (preg_match('/^\s*@css\((.*)\)\s*$/', $line, $matches) === 1) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchJs(string $line): ?string
|
||||||
|
{
|
||||||
|
if (preg_match('/^\s*@js\((.*)\)\s*$/', $line, $matches) === 1) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function matchExtends(string $line): ?string
|
private function matchExtends(string $line): ?string
|
||||||
{
|
{
|
||||||
if (preg_match('/^\s*@extends\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
|
if (preg_match('/^\s*@extends\(([\w\/]+)\)\s*$/', $line, $matches) === 1) {
|
||||||
return $matches[1];
|
return $matches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +135,7 @@ class Parser
|
|||||||
|
|
||||||
private function matchSection(string $line): ?string
|
private function matchSection(string $line): ?string
|
||||||
{
|
{
|
||||||
if (preg_match('/^\s*@section\(\'(\w+)\'\)\s*$/', $line, $matches)) {
|
if (preg_match('/^\s*@section\((\w+)\)\s*$/', $line, $matches) === 1) {
|
||||||
return $matches[1];
|
return $matches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,16 +144,16 @@ class Parser
|
|||||||
|
|
||||||
private function matchEndSection(string $line): bool
|
private function matchEndSection(string $line): bool
|
||||||
{
|
{
|
||||||
return preg_match('/^\s*@endsection(?:\(\))?\s*$/', $line);
|
return preg_match('/^\s*@endsection(?:\(\))?\s*$/', $line) === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function matchExtra(string $line): bool
|
private function matchExtra(string $line): bool
|
||||||
{
|
{
|
||||||
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line);
|
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line) === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function matchEndExtra(string $line): bool
|
private function matchEndExtra(string $line): bool
|
||||||
{
|
{
|
||||||
return preg_match('/^\s*@endextra(?:\(\))?\s*$/', $line);
|
return preg_match('/^\s*@endextra(?:\(\))?\s*$/', $line) === 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ final class GoogleOAuthTest extends TestCase
|
|||||||
{
|
{
|
||||||
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz';
|
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz';
|
||||||
$state = 'random_state_string';
|
$state = 'random_state_string';
|
||||||
|
$nonce = 'random_nonce_string';
|
||||||
$redirectUrl = 'http://example.com/oauth';
|
$redirectUrl = 'http://example.com/oauth';
|
||||||
|
|
||||||
$requestMock = $this->getMockBuilder(IRequest::class)
|
$requestMock = $this->getMockBuilder(IRequest::class)
|
||||||
@@ -20,7 +21,7 @@ final class GoogleOAuthTest extends TestCase
|
|||||||
->getMock();
|
->getMock();
|
||||||
$googleOAuth = new GoogleOAuth($requestMock);
|
$googleOAuth = new GoogleOAuth($requestMock);
|
||||||
|
|
||||||
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl);
|
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl, $nonce);
|
||||||
$dialogUrlParsed = explode('?', $dialogUrl);
|
$dialogUrlParsed = explode('?', $dialogUrl);
|
||||||
|
|
||||||
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
|
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
|
||||||
@@ -33,15 +34,10 @@ final class GoogleOAuthTest extends TestCase
|
|||||||
'scope' => 'openid email',
|
'scope' => 'openid email',
|
||||||
'redirect_uri' => $redirectUrl,
|
'redirect_uri' => $redirectUrl,
|
||||||
'state' => $state,
|
'state' => $state,
|
||||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
'nonce' => $nonce,
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->assertEquals($expectedQueryParams['response_type'], $dialogUrlQueryParams['response_type']);
|
$this->assertEquals($expectedQueryParams, $dialogUrlQueryParams);
|
||||||
$this->assertEquals($expectedQueryParams['client_id'], $dialogUrlQueryParams['client_id']);
|
|
||||||
$this->assertEquals($expectedQueryParams['scope'], $dialogUrlQueryParams['scope']);
|
|
||||||
$this->assertEquals($expectedQueryParams['redirect_uri'], $dialogUrlQueryParams['redirect_uri']);
|
|
||||||
$this->assertEquals($expectedQueryParams['state'], $dialogUrlQueryParams['state']);
|
|
||||||
$this->assertMatchesRegularExpression('/^[a-f0-9]{64}$/', $dialogUrlQueryParams['nonce']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanRequestToken(): void
|
public function testCanRequestToken(): void
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
use MapGuesser\PersistentData\Model\Model;
|
use MapGuesser\PersistentData\Model\Model;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class OtherModel
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
class DummyModel extends Model
|
class DummyModel extends Model
|
||||||
{
|
{
|
||||||
protected static string $table = 'test_table';
|
protected static string $table = 'test_table';
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
<?php namespace MapGuesser\Tests\View\Parser;
|
||||||
|
|
||||||
|
use MapGuesser\View\ParsedFragment;
|
||||||
|
use MapGuesser\View\Parser;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class ParserTest extends TestCase
|
||||||
|
{
|
||||||
|
const TEST_VIEWS_PATH = __DIR__ . '/../../views/tests';
|
||||||
|
|
||||||
|
public function testCanParseViewWithoutExtends(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_without_extends.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
$fragment = $parser->parse();
|
||||||
|
|
||||||
|
$expected = new ParsedFragment(
|
||||||
|
null,
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
['', '']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanParseViewWithAssets(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_with_assets.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
$fragment = $parser->parse();
|
||||||
|
|
||||||
|
$expected = new ParsedFragment(
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
'test.css'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'test.js',
|
||||||
|
'test_<?= $some[\'expression\'] ?>'
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
['', '']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanParseViewWithExtends(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_with_extends.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
$fragment = $parser->parse();
|
||||||
|
|
||||||
|
$expected = new ParsedFragment(
|
||||||
|
'parent',
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'section1' => '<div>Test HTML with @extends</div>' . "\n"
|
||||||
|
],
|
||||||
|
['', '']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanParseComplexView(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_complex.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
$fragment = $parser->parse();
|
||||||
|
|
||||||
|
$expected = new ParsedFragment(
|
||||||
|
'parent',
|
||||||
|
[
|
||||||
|
'test1.css'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'test1.js',
|
||||||
|
'test2_<?= $some[\'expression\'] ?>'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'section1' => '<div>Test HTML with @extends - section 1</div>' . "\n",
|
||||||
|
'section2' => '<div>Test HTML with @extends - section 2</div>' . "\n"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<?php phpinfo() ?>' . "\n",
|
||||||
|
'<?php $a = \'test_string\' ?>' . "\n" . 'EXTRA' . "\n",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsIfMultipleExtendsGiven(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_multiple_extends.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
|
||||||
|
$this->expectExceptionMessage('Error in file ' . $file . ' in line 3 - There is already an \'@extends\' declared.');
|
||||||
|
|
||||||
|
$parser->parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsIfSectionWithoutExtendsGiven(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_section_without_extends.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
|
||||||
|
$this->expectExceptionMessage('Error in file ' . $file . ' in line 1 - \'@section\' has no meaning if view extends nothing.');
|
||||||
|
|
||||||
|
$parser->parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsIfOpeningSectionBeforePreviousClosed(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_multiple_sections_open.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
|
||||||
|
$this->expectExceptionMessage('Parse error in file ' . $file . ' in line 4 - A \'@section\' is already open (no \'@endsection\' found).');
|
||||||
|
|
||||||
|
$parser->parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFailsIfClosingSectionWhenNoSectionIsOpen(): void
|
||||||
|
{
|
||||||
|
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_section_not_open.php');
|
||||||
|
|
||||||
|
$parser = new Parser($file);
|
||||||
|
|
||||||
|
$this->expectExceptionMessage('Parse error in file ' . $file . ' in line 4 - Cannot end section until no \'@section\' is open.');
|
||||||
|
|
||||||
|
$parser->parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,27 @@
|
|||||||
@extends('templates/layout_normal')
|
@js(js/account/account.js)
|
||||||
|
|
||||||
@section('main')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
<h2>Account</h2>
|
<h2>Account</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="accountForm" action="/account" method="post" data-observe-inputs="password_new,password_new_confirm">
|
<form id="accountForm" action="/account" method="post" data-observe-inputs="password_new,password_new_confirm">
|
||||||
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
|
<?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
|
||||||
|
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
|
||||||
|
<div class="inputWithButton">
|
||||||
|
<input type="password" name="password" placeholder="Current password" required minlength="6" autofocus><!--
|
||||||
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($user['password'] !== null): ?>
|
||||||
|
<p class="justify small">Please confirm your identity with your password to modify your account.</p>
|
||||||
|
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
|
||||||
|
<?php elseif ($user['google_sub'] !== null): ?>
|
||||||
|
<p class="justify small">Please confirm your identity with Google to modify your account.</p>
|
||||||
|
<div class="inputWithButton">
|
||||||
|
<input type="text" name="password" placeholder="Authenticate with Google..." disabled><!--
|
||||||
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<hr>
|
<hr>
|
||||||
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
|
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
|
||||||
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
|
||||||
|
|||||||
@@ -1,14 +1,32 @@
|
|||||||
@extends('templates/layout_normal')
|
@js(js/account/account.js)
|
||||||
|
|
||||||
@section('main')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
<h2>Delete account</h2>
|
<h2>Delete account</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="deleteAccountForm" action="/account/delete" method="post" data-redirect-on-success="/">
|
<form id="deleteAccountForm" action="/account/delete" method="post" data-redirect-on-success="/">
|
||||||
<p class="justify">Are you sure you want to delete your account? This cannot be undone!</p>
|
<p class="justify marginBottom">Are you sure you want to delete your account? This cannot be undone!</p>
|
||||||
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
|
<?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
|
||||||
|
<p class="justify small">Please confirm your identity with your password or with Google to delete your account.</p>
|
||||||
|
<div class="inputWithButton">
|
||||||
|
<input type="password" name="password" placeholder="Current password" required minlength="6" autofocus><!--
|
||||||
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($user['password'] !== null): ?>
|
||||||
|
<p class="justify small">Please confirm your identity with your password to delete your account.</p>
|
||||||
|
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
|
||||||
|
<?php elseif ($user['google_sub'] !== null): ?>
|
||||||
|
<p class="justify small">Please confirm your identity with Google to delete your account.</p>
|
||||||
|
<div class="inputWithButton">
|
||||||
|
<input type="text" name="password" placeholder="Authenticate with Google..." disabled><!--
|
||||||
|
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<p id="deleteAccountFormError" class="formError justify marginTop"></p>
|
<p id="deleteAccountFormError" class="formError justify marginTop"></p>
|
||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button class="red" type="submit" name="submit">Delete account</button>
|
<button class="red marginRight" type="submit" name="submit">Delete account</button><!--
|
||||||
|
--><a class="button gray marginTop" href="/account" title="Back to account">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
@js(js/account/google_authenticate.js)
|
||||||
|
|
||||||
|
@extends(templates/layout_minimal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
|
<h2>Authenticate with Google</h2>
|
||||||
|
<?php if (!$success): ?>
|
||||||
|
<div class="box">
|
||||||
|
<p class="error justify">
|
||||||
|
<?php if (isset($errorText)): ?>
|
||||||
|
<?= $errorText ?>
|
||||||
|
<?php else: ?>
|
||||||
|
Authentication with Google failed.
|
||||||
|
<?php endif; ?>
|
||||||
|
Please <a id="closeWindowButton" href="javascript:;" title="Close">close this window/tab</a> and try again!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section(pageScript)
|
||||||
|
<script>
|
||||||
|
var success = <?= $success ? 'true' : 'false' ?>;
|
||||||
|
<?php if (isset($authenticatedWithGoogleUntil)): ?>
|
||||||
|
var authenticatedWithGoogleUntil = new Date('<?= $authenticatedWithGoogleUntil->format('c') ?>');
|
||||||
|
<?php endif; ?>
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
+14
-21
@@ -1,23 +1,16 @@
|
|||||||
@extra
|
@css(node_modules/leaflet/dist/leaflet.css)
|
||||||
<?php
|
@css(css/map_editor.css)
|
||||||
$cssFiles = [
|
@css(node_modules/leaflet.markercluster/dist/MarkerCluster.css)
|
||||||
'node_modules/leaflet/dist/leaflet.css',
|
@css(node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css)
|
||||||
'css/map_editor.css',
|
|
||||||
'node_modules/leaflet.markercluster/dist/MarkerCluster.css',
|
|
||||||
'node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css'
|
|
||||||
];
|
|
||||||
$jsFiles = [
|
|
||||||
'node_modules/leaflet/dist/leaflet.js',
|
|
||||||
'node_modules/leaflet.markercluster/dist/leaflet.markercluster.js',
|
|
||||||
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'],
|
|
||||||
'js/map_editor.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
@endextra
|
|
||||||
|
|
||||||
@extends('templates/layout_full')
|
@js(node_modules/leaflet/dist/leaflet.js)
|
||||||
|
@js(node_modules/leaflet.markercluster/dist/leaflet.markercluster.js)
|
||||||
|
@js(https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>)
|
||||||
|
@js(js/map_editor.js)
|
||||||
|
|
||||||
@section('subheader')
|
@extends(templates/layout_full)
|
||||||
|
|
||||||
|
@section(subheader)
|
||||||
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
|
||||||
--><span><!--
|
--><span><!--
|
||||||
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
|
||||||
@@ -45,7 +38,7 @@ $jsFiles = [
|
|||||||
--></span>
|
--></span>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('pagemodal')
|
@section(pagemodal)
|
||||||
<div id="metadata" class="modal">
|
<div id="metadata" class="modal">
|
||||||
<h2>Edit map data</h2>
|
<h2>Edit map data</h2>
|
||||||
<form id="metadataForm" class="marginTop" data-no-submit="true">
|
<form id="metadataForm" class="marginTop" data-no-submit="true">
|
||||||
@@ -59,7 +52,7 @@ $jsFiles = [
|
|||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="noPano">
|
<div id="noPano">
|
||||||
@@ -75,7 +68,7 @@ $jsFiles = [
|
|||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('pagescript')
|
@section(pageScript)
|
||||||
<script>
|
<script>
|
||||||
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
||||||
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>404 | Page not found</h2>
|
<h2>404 | Page not found</h2>
|
||||||
<p>The requested URL was not found on this server. <a href="/" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
|
<p>The requested URL was not found on this server. <a href="/" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
|
||||||
@endsection
|
@endsection
|
||||||
+8
-15
@@ -1,24 +1,17 @@
|
|||||||
@extra
|
@css(css/game.css)
|
||||||
<?php
|
|
||||||
$cssFiles = [
|
|
||||||
'css/game.css'
|
|
||||||
];
|
|
||||||
$jsFiles = [
|
|
||||||
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'],
|
|
||||||
'js/game.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
@endextra
|
|
||||||
|
|
||||||
@extends('templates/layout_full')
|
@js(https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>)
|
||||||
|
@js(js/game.js)
|
||||||
|
|
||||||
@section('subheader')
|
@extends(templates/layout_full)
|
||||||
|
|
||||||
|
@section(subheader)
|
||||||
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
<span id="mapName" class="bold"><?= $mapName ?></span><!--
|
||||||
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
--><span>Round <span id="currentRound" class="bold"></span></span><!--
|
||||||
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<div id="panoCover"></div>
|
<div id="panoCover"></div>
|
||||||
<div id="panorama"></div>
|
<div id="panorama"></div>
|
||||||
<div id="showGuessButtonContainer">
|
<div id="showGuessButtonContainer">
|
||||||
@@ -55,7 +48,7 @@ $jsFiles = [
|
|||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('pagescript')
|
@section(pageScript)
|
||||||
<script>
|
<script>
|
||||||
var mapId = <?= $mapId ?>;
|
var mapId = <?= $mapId ?>;
|
||||||
var mapBounds = <?= json_encode($bounds) ?>;
|
var mapBounds = <?= json_encode($bounds) ?>;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Account activation</h2>
|
<h2>Account activation</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
|
<p class="error justify">Activation failed. Please check the link you entered, or retry <a href="/signup" title="Sign up">signing up</a>!</p>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Account cancellation</h2>
|
<h2>Account cancellation</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<?php if ($success) : ?>
|
<?php if ($success) : ?>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Login up with Google</h2>
|
<h2>Login up with Google</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p class="error justify">Authenticating with Google failed. Please <a href="/login/google" title="Login with Google">retry</a>!</p>
|
<p class="error justify">Authentication with Google failed. Please <a href="/login/google" title="Login with Google">try again</a>!</p>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
@extra
|
@js(js/login/google_signup.js)
|
||||||
<?php
|
|
||||||
$jsFiles = [
|
|
||||||
'js/login/google_signup.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
@endextra
|
|
||||||
|
|
||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Sign up</h2>
|
<h2>Sign up</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/">
|
<form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
|
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
<div class="right marginTop">
|
<div class="right marginTop">
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="center marginTop"><a href="/password/requestReset" title="Request password reset">Forgot your password?</a></p>
|
||||||
|
<p class="center marginTop"><a href="/signup" title="Sign up">New to <?= $_ENV['APP_NAME'] ?>?</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
|
<a class="button yellow" href="/login/google" title="Login with Google">Login with Google</a>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
|
<h2>Request password reset</h2>
|
||||||
|
<div class="box">
|
||||||
|
<form id="passwordResetForm" action="/password/requestReset" method="post" data-redirect-on-success="/password/requestReset/success">
|
||||||
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= isset($email) ? $email : '' ?>" required autofocus>
|
||||||
|
<p id="passwordResetFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right marginTop">
|
||||||
|
<button type="submit">Continue</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
|
<h2>Request password reset</h2>
|
||||||
|
<div class="box">
|
||||||
|
<p class="justify">Password reset was successfully requested. Please check your email and click on the link to reset your password!</p>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
|
@section(main)
|
||||||
|
<h2>Reset password</h2>
|
||||||
|
<div class="box">
|
||||||
|
<?php if ($success) : ?>
|
||||||
|
<form id="resetPasswordForm" action="/password/reset/<?= $token ?>" method="post" data-redirect-on-success="/">
|
||||||
|
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $email ?>" disabled>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6" autofocus>
|
||||||
|
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation" required minlength="6">
|
||||||
|
<p id="resetPasswordFormError" class="formError justify marginTop"></p>
|
||||||
|
<div class="right">
|
||||||
|
<button class="marginTop" type="submit">Reset password</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="error justify">Confirming your identity failed. Please check the link you entered, or retry <a href="/password/requestReset" title="Request password reset">requesting password reset</a>!</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -1,14 +1,8 @@
|
|||||||
@extra
|
@js(js/login/signup.js)
|
||||||
<?php
|
|
||||||
$jsFiles = [
|
|
||||||
'js/login/signup.js',
|
|
||||||
];
|
|
||||||
?>
|
|
||||||
@endextra
|
|
||||||
|
|
||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Sign up</h2>
|
<h2>Sign up</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success">
|
<form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success">
|
||||||
@@ -28,6 +22,7 @@ $jsFiles = [
|
|||||||
--><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
|
--><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="center marginTop"><a href="/login" title="Login">Already have an account?</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
|
<a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<h2>Sign up</h2>
|
<h2>Sign up</h2>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p class="justify">Sign up was successful. Please check your email and click on the activation link to activate your account!</p>
|
<p class="justify">Sign up was successful. Please check your email and click on the activation link to activate your account!</p>
|
||||||
|
|||||||
+6
-15
@@ -1,20 +1,11 @@
|
|||||||
@extra
|
@css(css/maps.css)
|
||||||
<?php
|
@js(js/maps.js)
|
||||||
$cssFiles = [
|
TODO: condition!
|
||||||
'css/maps.css'
|
@js(js/maps_admin.js)
|
||||||
];
|
|
||||||
$jsFiles = [
|
|
||||||
'js/maps.js',
|
|
||||||
];
|
|
||||||
if ($isAdmin) {
|
|
||||||
$jsFiles[] = 'js/maps_admin.js';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
@endextra
|
|
||||||
|
|
||||||
@extends('templates/layout_normal')
|
@extends(templates/layout_normal)
|
||||||
|
|
||||||
@section('main')
|
@section(main)
|
||||||
<div id="mapContainer">
|
<div id="mapContainer">
|
||||||
<?php foreach ($maps as $map): ?>
|
<?php foreach ($maps as $map): ?>
|
||||||
<div class="mapItem">
|
<div class="mapItem">
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
@extends('templates/mapguesser')
|
@extends(templates/mapguesser)
|
||||||
|
|
||||||
@section('content')
|
@section(content)
|
||||||
<header class="small">
|
<header class="small">
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>" alt="<?= $_ENV['APP_NAME'] ?>"><!--
|
||||||
--><span><?= $_ENV['APP_NAME'] ?></span>
|
--><span><?= $_ENV['APP_NAME'] ?></span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
@extends(templates/mapguesser)
|
||||||
|
|
||||||
|
@section(content)
|
||||||
|
<header>
|
||||||
|
<h1>
|
||||||
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>" alt="<?= $_ENV['APP_NAME'] ?>"><!--
|
||||||
|
--><?= $_ENV['APP_NAME'] ?>
|
||||||
|
</h1>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
@yields('main')
|
||||||
|
</main>
|
||||||
|
@endsection
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
@extends('templates/mapguesser')
|
@extends(templates/mapguesser)
|
||||||
@section('content')
|
|
||||||
|
@section(content)
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
||||||
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
|
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>" alt="<?= $_ENV['APP_NAME'] ?>"><!--
|
||||||
--><span><?= $_ENV['APP_NAME'] ?></span>
|
--><span><?= $_ENV['APP_NAME'] ?></span>
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@@ -1,37 +1,32 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="<?= $_ENV['APP_NAME'] ?> – A game about guessing where you are based on a street view panorama.">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="theme-color" content="#198231">
|
||||||
<title><?= $_ENV['APP_NAME'] ?></title>
|
<title><?= $_ENV['APP_NAME'] ?></title>
|
||||||
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
|
<?php if (preg_match('/^(http(s)?:)?\/\//', $_ENV['STATIC_ROOT']) === 1): ?>
|
||||||
<?php if (isset($cssFiles)) : ?>
|
<link href="<?= $_ENV['STATIC_ROOT'] ?>" rel="preconnect">
|
||||||
<?php foreach ($cssFiles as $cssFile) : ?>
|
|
||||||
<?php
|
|
||||||
if (!preg_match('/^http(s)?/', $cssFile)) {
|
|
||||||
$cssFile = $_ENV['STATIC_ROOT'] . '/' . $cssFile . '?rev=' . REVISION;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<link href="<?= $cssFile ?>" rel="stylesheet">
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.gstatic.com" rel="preconnect">
|
||||||
|
<link href="https://maps.googleapis.com" rel="preconnect">
|
||||||
|
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
|
||||||
|
<link href="https://www.googletagmanager.com" rel="preconnect">
|
||||||
|
<link href="https://www.google-analytics.com" rel="preconnect">
|
||||||
|
<?php endif; ?>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
|
||||||
|
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
|
||||||
|
@yields('externalCss')
|
||||||
|
@yields('inlineCss')
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
|
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/96x96.png?rev=<?= REVISION ?>">
|
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/96x96.png?rev=<?= REVISION ?>">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
|
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
|
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
|
|
||||||
<div id="cookiesNotice">
|
|
||||||
<p class="small">
|
|
||||||
<?= $_ENV['APP_NAME'] ?> uses cookies to improve user experience. By using the app or clicking 'Agree', you consent to our use of cookies.
|
|
||||||
</p>
|
|
||||||
<button id="agreeCookiesButton" class="small marginTop">Agree</button>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
|
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64" alt="Loading">
|
||||||
</div>
|
</div>
|
||||||
<div id="cover"></div>
|
<div id="cover"></div>
|
||||||
<div id="modal" class="modal">
|
<div id="modal" class="modal">
|
||||||
@@ -41,6 +36,14 @@
|
|||||||
</div>
|
</div>
|
||||||
@yields('pagemodal')
|
@yields('pagemodal')
|
||||||
@yields('content')
|
@yields('content')
|
||||||
|
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
|
||||||
|
<div id="cookiesNotice">
|
||||||
|
<p class="small">
|
||||||
|
<?= $_ENV['APP_NAME'] ?> uses cookies to improve user experience. By using the app or clicking 'Agree', you consent to our use of cookies.
|
||||||
|
</p>
|
||||||
|
<button id="agreeCookiesButton" class="small marginTop">Agree</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
<script>
|
<script>
|
||||||
const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
|
const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
|
||||||
const REVISION = '<?= REVISION ?>';
|
const REVISION = '<?= REVISION ?>';
|
||||||
@@ -49,46 +52,11 @@
|
|||||||
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
|
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
|
||||||
const GOOGLE_ANALITICS_ID = '<?= $_ENV['GOOGLE_ANALITICS_ID'] ?>';
|
const GOOGLE_ANALITICS_ID = '<?= $_ENV['GOOGLE_ANALITICS_ID'] ?>';
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
const COOKIES_CONSENT = <?= isset($_COOKIE['COOKIES_CONSENT']) ? 'true' : 'false' ?>;
|
||||||
</script>
|
</script>
|
||||||
@yields('pagescript')
|
@yields('pageScript')
|
||||||
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
|
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
|
||||||
<?php if (isset($jsFiles)) : ?>
|
@yields('externalJs')
|
||||||
<?php foreach ($jsFiles as $jsFile) : ?>
|
@yields('inlineJs')
|
||||||
<?php
|
|
||||||
if (!preg_match('/^http(s)?/', $jsFile)) {
|
|
||||||
$jsFile = $_ENV['STATIC_ROOT'] . '/' . $jsFile . '?rev=' . REVISION;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<script src="<?= $jsFile ?>"></script>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
// we don't want user to agree cookies when clicking on the notice itself
|
|
||||||
document.getElementById('cookiesNotice').onclick = function (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('agreeCookiesButton').onclick = function () {
|
|
||||||
MapGuesser.agreeCookies();
|
|
||||||
|
|
||||||
document.getElementById('cookiesNotice').style.display = 'none';
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onclick = function () {
|
|
||||||
MapGuesser.agreeCookies();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php else: ?>
|
|
||||||
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
|
|
||||||
<script>
|
|
||||||
(function () {
|
|
||||||
MapGuesser.initGoogleAnalitics();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
@css(test1.css)
|
||||||
|
@js(test1.js)
|
||||||
|
@js(test2_<?= $some['expression'] ?>)
|
||||||
|
|
||||||
|
@extra
|
||||||
|
<?php phpinfo() ?>
|
||||||
|
@endextra
|
||||||
|
|
||||||
|
@extends(parent)
|
||||||
|
|
||||||
|
@section(section1)
|
||||||
|
<div>Test HTML with @extends - section 1</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section(section2)
|
||||||
|
<div>Test HTML with @extends - section 2</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@extra
|
||||||
|
<?php $a = 'test_string' ?>
|
||||||
|
EXTRA
|
||||||
|
@endextra
|
||||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user