Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
APP_NAME=MapQuiz
|
||||
APP_NAME=MapGuesser
|
||||
DEV=1
|
||||
DB_HOST=mariadb
|
||||
DB_USER=mapguesser
|
||||
|
||||
@@ -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.
|
||||
|
||||
### (Production only) Create cron job
|
||||
|
||||
To maintain database (delete inactive users, old sessions etc.), the command `db:maintain` should be regularly executed. It is recommened to create a cron job that runs every hour:
|
||||
|
||||
```
|
||||
0 * * * * /path/to/your/installation/mapg db:maintain >>/var/log/cron-mapguesser.log 2>&1
|
||||
```
|
||||
|
||||
### Finalize installation
|
||||
|
||||
After you set the environment variables in the `.env` file, execute the following command:
|
||||
|
||||
@@ -13,3 +13,12 @@ pipelines:
|
||||
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
- composer install
|
||||
- vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests
|
||||
- step:
|
||||
name: Static Code Analysis
|
||||
caches:
|
||||
- composer
|
||||
script:
|
||||
- apt-get update && apt-get install -y unzip
|
||||
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
- composer install
|
||||
- vendor/bin/phpstan analyse -c phpstan.neon
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
"phpmailer/phpmailer": "^6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9"
|
||||
"phpunit/phpunit": "^9",
|
||||
"phpstan/phpstan": "^0.12.32"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
Generated
+57
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "fb37b0b958cdf02d82468cb4596613ad",
|
||||
"content-hash": "34563bfc619f47473b2e37a5639dd63e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
@@ -1379,6 +1379,62 @@
|
||||
],
|
||||
"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",
|
||||
"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,
|
||||
<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>
|
||||
<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>
|
||||
However if you want to immediately delete it, please click on the following link:<br>
|
||||
You can activate your account until {{ACTIVATABLE_UNTIL}}.
|
||||
If you don't activate your account, your email address will be permanently deleted after this point of time.
|
||||
<br><br>
|
||||
If you did not sign up on {{APP_NAME}} or changed your mind, no further action is required.
|
||||
However if you want to immediately delete your email address, please click on the following link:<br>
|
||||
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
|
||||
<br><br>
|
||||
Have fun on {{APP_NAME}}!
|
||||
|
||||
@@ -16,7 +16,7 @@ class Container
|
||||
{
|
||||
static MapGuesser\Interfaces\Database\IConnection $dbConnection;
|
||||
static MapGuesser\Routing\RouteCollection $routeCollection;
|
||||
static \SessionHandlerInterface $sessionHandler;
|
||||
static MapGuesser\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||
static MapGuesser\Interfaces\Request\IRequest $request;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ require 'main.php';
|
||||
|
||||
$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\LinkViewCommand());
|
||||
$app->add(new \MapGuesser\Cli\MaintainDatabaseCommand());
|
||||
|
||||
$app->run();
|
||||
@@ -0,0 +1,9 @@
|
||||
parameters:
|
||||
level: 5
|
||||
checkMissingIterableValueType: false
|
||||
paths:
|
||||
- main.php
|
||||
- mapg
|
||||
- web.php
|
||||
- src
|
||||
- tests
|
||||
+2
-1
@@ -26,8 +26,9 @@ if ($match !== null) {
|
||||
}
|
||||
|
||||
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);
|
||||
echo json_encode(['error' => 'no_valid_anti_csrf_token']);
|
||||
$content->render();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -298,7 +298,11 @@ input.big:focus, select.big:focus {
|
||||
|
||||
div.inputWithButton>input {
|
||||
width: 100%;
|
||||
padding: 0 83px 0 5px;
|
||||
padding: 0 83px 0 6px;
|
||||
}
|
||||
|
||||
div.inputWithButton>input:focus {
|
||||
padding: 0 82px 0 5px;
|
||||
}
|
||||
|
||||
textarea.big:focus {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
rounds: [],
|
||||
scoreSum: 0,
|
||||
panoId: null,
|
||||
pov: null,
|
||||
panorama: null,
|
||||
map: null,
|
||||
guessMarker: null,
|
||||
@@ -35,6 +36,7 @@
|
||||
}
|
||||
|
||||
Game.panoId = this.response.panoId;
|
||||
Game.pov = this.response.pov;
|
||||
|
||||
if (this.response.history) {
|
||||
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);
|
||||
|
||||
Game.loadPano(Game.panoId);
|
||||
Game.loadPano(Game.panoId, Game.pov);
|
||||
},
|
||||
|
||||
handleErrorResponse: function (error) {
|
||||
@@ -129,13 +131,13 @@
|
||||
});
|
||||
},
|
||||
|
||||
loadPano: function (panoId) {
|
||||
loadPano: function (panoId, pov) {
|
||||
if (Game.adaptGuess) {
|
||||
document.getElementById('guess').classList.add('adapt');
|
||||
}
|
||||
|
||||
Game.panorama.setPov({ heading: 0, pitch: 0 });
|
||||
Game.panorama.setZoom(0);
|
||||
Game.panorama.setPov({ heading: pov.heading, pitch: pov.pitch });
|
||||
Game.panorama.setZoom(pov.zoom);
|
||||
Game.panorama.setPano(panoId);
|
||||
},
|
||||
|
||||
@@ -199,6 +201,7 @@
|
||||
}
|
||||
|
||||
Game.panoId = this.response.panoId;
|
||||
Game.pov = this.response.pov;
|
||||
}, data);
|
||||
},
|
||||
|
||||
|
||||
+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
|
||||
|
||||
echo "Migrating DB..."
|
||||
(cd ${ROOT_DIR} && ./mapg migrate)
|
||||
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||
|
||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||
echo "Minifying JS, CSS and SVG files..."
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ echo "Installing Yarn packages..."
|
||||
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||
|
||||
echo "Migrating DB..."
|
||||
(cd ${ROOT_DIR} && ./mapg migrate)
|
||||
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||
|
||||
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||
echo "Minifying JS, CSS and SVG files..."
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php namespace MapGuesser\Cli;
|
||||
|
||||
use DateTime;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
use MapGuesser\PersistentData\Model\User;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
@@ -9,7 +10,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class AddUserCommand extends Command
|
||||
{
|
||||
public function configure()
|
||||
public function configure(): void
|
||||
{
|
||||
$this->setName('user:add')
|
||||
->setDescription('Adding of user.')
|
||||
@@ -24,6 +25,7 @@ class AddUserCommand extends Command
|
||||
$user->setEmail($input->getArgument('email'));
|
||||
$user->setPlainPassword($input->getArgument('password'));
|
||||
$user->setActive(true);
|
||||
$user->setCreatedDate(new DateTime());
|
||||
|
||||
if ($input->hasArgument('type')) {
|
||||
$user->setType($input->getArgument('type'));
|
||||
|
||||
@@ -11,7 +11,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class LinkViewCommand extends Command
|
||||
{
|
||||
public function configure()
|
||||
public function configure(): void
|
||||
{
|
||||
$this->setName('view:link')
|
||||
->setDescription('Linking of views.')
|
||||
@@ -39,7 +39,7 @@ class LinkViewCommand extends Command
|
||||
|
||||
$view = substr($file->getPath(), $folderLength) . '/' . $file->getBasename('.php');
|
||||
|
||||
if (strpos($view, 'templates') === 0) {
|
||||
if (strpos($view, 'templates') === 0 || strpos($view, 'tests') === 0) {
|
||||
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\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.');
|
||||
}
|
||||
|
||||
@@ -88,6 +88,10 @@ class DatabaseMigration extends Command
|
||||
$path = ROOT . '/database/migrations/' . $type;
|
||||
$dir = opendir($path);
|
||||
|
||||
if ($dir === false) {
|
||||
throw new \Exception('Cannot open dir: ' . $path);
|
||||
}
|
||||
|
||||
$files = [];
|
||||
while ($file = readdir($dir)) {
|
||||
$filePath = $path . '/' . $file;
|
||||
@@ -21,18 +21,18 @@ class GameController
|
||||
public function getGame(): IContent
|
||||
{
|
||||
$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
|
||||
{
|
||||
$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);
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ class GameFlowController
|
||||
$session = $this->request->session();
|
||||
|
||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||
$data = ['error' => 'no_session_found'];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => 'no_session_found']);
|
||||
}
|
||||
|
||||
if (count($state['rounds']) === 0) {
|
||||
@@ -41,7 +40,10 @@ class GameFlowController
|
||||
|
||||
$session->set('state', $state);
|
||||
|
||||
$data = ['panoId' => $place->getPanoIdCached()];
|
||||
$data = [
|
||||
'panoId' => $place->getPanoIdCached(),
|
||||
'pov' => $place->getPov()->toArray()
|
||||
];
|
||||
} else {
|
||||
$rounds = count($state['rounds']);
|
||||
$last = $state['rounds'][$rounds - 1];
|
||||
@@ -59,7 +61,10 @@ class GameFlowController
|
||||
|
||||
$data = [
|
||||
'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();
|
||||
|
||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||
$data = ['error' => 'no_session_found'];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => 'no_session_found']);
|
||||
}
|
||||
|
||||
$last = $state['rounds'][count($state['rounds']) - 1];
|
||||
@@ -103,32 +107,35 @@ class GameFlowController
|
||||
$this->addNewRoundToState($state, $place, $placesWithoutPano);
|
||||
|
||||
$panoId = $place->getPanoIdCached();
|
||||
$pov = $place->getPov()->toArray();
|
||||
} else {
|
||||
$state['rounds'] = [];
|
||||
|
||||
$panoId = null;
|
||||
$pov = null;
|
||||
}
|
||||
|
||||
$session->set('state', $state);
|
||||
|
||||
$data = [
|
||||
return new JsonContent([
|
||||
'result' => [
|
||||
'position' => $position->toArray(),
|
||||
'distance' => $distance,
|
||||
'score' => $score
|
||||
],
|
||||
'panoId' => $panoId
|
||||
];
|
||||
return new JsonContent($data);
|
||||
'panoId' => $panoId,
|
||||
'pov' => $pov
|
||||
]);
|
||||
}
|
||||
|
||||
private function addNewRoundToState(&$state, Place $place, array $placesWithoutPano): void
|
||||
private function addNewRoundToState(array &$state, Place $place, array $placesWithoutPano): void
|
||||
{
|
||||
$state['rounds'][] = [
|
||||
'placesWithoutPano' => $placesWithoutPano,
|
||||
'placeId' => $place->getId(),
|
||||
'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
|
||||
|
||||
$data = ['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')]);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php namespace MapGuesser\Controller;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use MapGuesser\Http\Request;
|
||||
use MapGuesser\Interfaces\Request\IRequest;
|
||||
use MapGuesser\Interfaces\Response\IContent;
|
||||
@@ -8,8 +10,10 @@ use MapGuesser\Mailing\Mail;
|
||||
use MapGuesser\OAuth\GoogleOAuth;
|
||||
use MapGuesser\PersistentData\Model\User;
|
||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
use MapGuesser\Repository\UserConfirmationRepository;
|
||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||
use MapGuesser\Repository\UserRepository;
|
||||
use MapGuesser\Response\HtmlContent;
|
||||
use MapGuesser\Response\JsonContent;
|
||||
@@ -26,12 +30,15 @@ class LoginController
|
||||
|
||||
private UserConfirmationRepository $userConfirmationRepository;
|
||||
|
||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||
|
||||
public function __construct(IRequest $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->pdm = new PersistentDataManager();
|
||||
$this->userRepository = new UserRepository();
|
||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||
}
|
||||
|
||||
public function getLoginForm()
|
||||
@@ -40,18 +47,23 @@ class LoginController
|
||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||
}
|
||||
|
||||
$data = [];
|
||||
return new HtmlContent('login/login', $data);
|
||||
return new HtmlContent('login/login');
|
||||
}
|
||||
|
||||
public function getGoogleLoginRedirect(): IRedirect
|
||||
{
|
||||
$state = bin2hex(random_bytes(16));
|
||||
$nonce = bin2hex(random_bytes(16));
|
||||
|
||||
$this->request->session()->set('oauth_state', $state);
|
||||
$this->request->session()->set('oauth_nonce', $nonce);
|
||||
|
||||
$oAuth = new GoogleOAuth(new Request());
|
||||
$url = $oAuth->getDialogUrl($state, $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
|
||||
$url = $oAuth->getDialogUrl(
|
||||
$state,
|
||||
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
|
||||
$nonce
|
||||
);
|
||||
|
||||
return new Redirect($url, IRedirect::TEMPORARY);
|
||||
}
|
||||
@@ -75,8 +87,7 @@ class LoginController
|
||||
|
||||
public function getSignupSuccess(): IContent
|
||||
{
|
||||
$data = [];
|
||||
return new HtmlContent('login/signup_success', $data);
|
||||
return new HtmlContent('login/signup_success');
|
||||
}
|
||||
|
||||
public function getSignupWithGoogleForm()
|
||||
@@ -93,48 +104,96 @@ class LoginController
|
||||
|
||||
$user = $this->userRepository->getByEmail($userData['email']);
|
||||
|
||||
$data = ['found' => $user !== null, 'email' => $userData['email']];
|
||||
return new HtmlContent('login/google_signup', $data);
|
||||
return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email']]);
|
||||
}
|
||||
|
||||
public function getRequestPasswordResetForm()
|
||||
{
|
||||
if ($this->request->user() !== null) {
|
||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||
}
|
||||
|
||||
return new HtmlContent('login/password_reset_request', ['email' => $this->request->query('email')]);
|
||||
}
|
||||
|
||||
public function getRequestPasswordResetSuccess(): IContent
|
||||
{
|
||||
return new HtmlContent('login/password_reset_request_success');
|
||||
}
|
||||
|
||||
public function getResetPasswordForm()
|
||||
{
|
||||
if ($this->request->user() !== null) {
|
||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||
}
|
||||
|
||||
$token = $this->request->query('token');
|
||||
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
||||
|
||||
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
||||
return new HtmlContent('login/reset_password', ['success' => false]);
|
||||
}
|
||||
|
||||
$user = $this->userRepository->getById($resetter->getUserId());
|
||||
|
||||
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail()]);
|
||||
}
|
||||
|
||||
public function login(): IContent
|
||||
{
|
||||
if ($this->request->user() !== null) {
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||
|
||||
if ($user === null) {
|
||||
if (strlen($this->request->post('password')) < 6) {
|
||||
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent([
|
||||
'error' => [
|
||||
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$tmpUser = new User();
|
||||
$tmpUser->setPlainPassword($this->request->post('password'));
|
||||
|
||||
$this->request->session()->set('tmp_user_data', ['email' => $this->request->post('email'), 'password_hashed' => $tmpUser->getPassword()]);
|
||||
$this->request->session()->set('tmp_user_data', [
|
||||
'email' => $this->request->post('email'),
|
||||
'password_hashed' => $tmpUser->getPassword()
|
||||
]);
|
||||
|
||||
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()]];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent([
|
||||
'redirect' => [
|
||||
'target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
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!']];
|
||||
return new JsonContent($data);
|
||||
$this->resendConfirmationEmail($user);
|
||||
|
||||
return new JsonContent([
|
||||
'error' => [
|
||||
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
||||
'Please check your email and click on the activation link!'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$user->checkPassword($this->request->post('password'))) {
|
||||
$data = ['error' => ['errorText' => 'The given password is wrong.']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent([
|
||||
'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);
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function loginWithGoogle()
|
||||
@@ -144,30 +203,34 @@ class LoginController
|
||||
}
|
||||
|
||||
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||
$data = [];
|
||||
return new HtmlContent('login/google_login', $data);
|
||||
return new HtmlContent('login/google_login');
|
||||
}
|
||||
|
||||
$oAuth = new GoogleOAuth(new Request());
|
||||
$tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
|
||||
$tokenData = $oAuth->getToken(
|
||||
$this->request->query('code'),
|
||||
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
|
||||
);
|
||||
|
||||
if (!isset($tokenData['id_token'])) {
|
||||
$data = [];
|
||||
return new HtmlContent('login/google_login', $data);
|
||||
return new HtmlContent('login/google_login');
|
||||
}
|
||||
|
||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||
$userData = $jwtParser->getPayload();
|
||||
$idToken = $jwtParser->getPayload();
|
||||
|
||||
if (!$userData['email_verified']) {
|
||||
$data = [];
|
||||
return new HtmlContent('login/google_login', $data);
|
||||
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||
return new HtmlContent('login/google_login');
|
||||
}
|
||||
|
||||
$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) {
|
||||
$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);
|
||||
}
|
||||
@@ -187,8 +250,7 @@ class LoginController
|
||||
public function signup(): IContent
|
||||
{
|
||||
if ($this->request->user() !== null) {
|
||||
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]]);
|
||||
}
|
||||
|
||||
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||
@@ -196,22 +258,31 @@ class LoginController
|
||||
if ($user !== null) {
|
||||
if ($user->getActive()) {
|
||||
if (!$user->checkPassword($this->request->post('password'))) {
|
||||
$data = ['error' => ['errorText' => 'There is a user already registered with the given email address, but the given password is wrong.']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent([
|
||||
'error' => [
|
||||
'errorText' => 'There is a user already registered with the given email address, ' .
|
||||
'but the given password is wrong. You can <a href="/password/requestReset?email=' .
|
||||
urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$this->request->setUser($user);
|
||||
|
||||
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]];
|
||||
} else {
|
||||
$data = ['error' => ['errorText' => 'There is a user already registered with the given email address. Please check your email and click on the activation link!']];
|
||||
$data = [
|
||||
'error' => [
|
||||
'errorText' => 'There is a user already registered with the given email address. ' .
|
||||
'Please check your email and click on the activation link!'
|
||||
]
|
||||
];
|
||||
}
|
||||
return new JsonContent($data);
|
||||
}
|
||||
|
||||
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
||||
$data = ['error' => ['errorText' => 'The given email address is not valid.']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => ['errorText' => 'The given email address is not valid.']]);
|
||||
}
|
||||
|
||||
if ($this->request->session()->has('tmp_user_data')) {
|
||||
@@ -221,52 +292,53 @@ class LoginController
|
||||
$tmpUser->setPassword($tmpUserData['password_hashed']);
|
||||
|
||||
if (!$tmpUser->checkPassword($this->request->post('password'))) {
|
||||
$data = ['error' => ['errorText' => 'The given passwords do not match.']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||
}
|
||||
} else {
|
||||
if (strlen($this->request->post('password')) < 6) {
|
||||
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
|
||||
return new JsonContent($data);
|
||||
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')) {
|
||||
$data = ['error' => ['errorText' => 'The given passwords do not match.']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||
}
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user->setEmail($this->request->post('email'));
|
||||
$user->setPlainPassword($this->request->post('password'));
|
||||
$user->setCreatedDate(new DateTime());
|
||||
|
||||
\Container::$dbConnection->startTransaction();
|
||||
|
||||
$this->pdm->saveToDb($user);
|
||||
|
||||
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
|
||||
$token = bin2hex(random_bytes(16));
|
||||
|
||||
$confirmation = new UserConfirmation();
|
||||
$confirmation->setUser($user);
|
||||
$confirmation->setToken($token);
|
||||
$confirmation->setLastSentDate(new DateTime());
|
||||
|
||||
$this->pdm->saveToDb($confirmation);
|
||||
|
||||
\Container::$dbConnection->commit();
|
||||
|
||||
$this->sendConfirmationEmail($user->getEmail(), $token);
|
||||
$this->sendConfirmationEmail($user->getEmail(), $token, $user->getCreatedDate());
|
||||
|
||||
$this->request->session()->delete('tmp_user_data');
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function signupWithGoogle(): IContent
|
||||
{
|
||||
if ($this->request->user() !== null) {
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
$userData = $this->request->session()->get('google_user_data');
|
||||
@@ -278,6 +350,7 @@ class LoginController
|
||||
|
||||
$user = new User();
|
||||
$user->setEmail($userData['email']);
|
||||
$user->setCreatedDate(new DateTime());
|
||||
} else {
|
||||
$sendWelcomeEmail = false;
|
||||
}
|
||||
@@ -294,24 +367,21 @@ class LoginController
|
||||
$this->request->session()->delete('google_user_data');
|
||||
$this->request->setUser($user);
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function resetSignup(): IContent
|
||||
{
|
||||
$this->request->session()->delete('tmp_user_data');
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function resetGoogleSignup(): IContent
|
||||
{
|
||||
$this->request->session()->delete('google_user_data');
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function activate()
|
||||
@@ -320,11 +390,10 @@ class LoginController
|
||||
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) {
|
||||
$data = [];
|
||||
return new HtmlContent('login/activate', $data);
|
||||
return new HtmlContent('login/activate');
|
||||
}
|
||||
|
||||
\Container::$dbConnection->startTransaction();
|
||||
@@ -349,11 +418,10 @@ class LoginController
|
||||
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) {
|
||||
$data = ['success' => false];
|
||||
return new HtmlContent('login/cancel', $data);
|
||||
return new HtmlContent('login/cancel', ['success' => false]);
|
||||
}
|
||||
|
||||
\Container::$dbConnection->startTransaction();
|
||||
@@ -366,23 +434,156 @@ class LoginController
|
||||
|
||||
\Container::$dbConnection->commit();
|
||||
|
||||
$data = ['success' => true];
|
||||
return new HtmlContent('login/cancel', $data);
|
||||
return new HtmlContent('login/cancel', ['success' => true]);
|
||||
}
|
||||
|
||||
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->addRecipient($email);
|
||||
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
|
||||
$mail->setBodyFromTemplate('signup', [
|
||||
'EMAIL' => $email,
|
||||
'ACTIVATE_LINK' => $this->request->getBase() . '/'. \Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
|
||||
'CANCEL_LINK' => $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
|
||||
'ACTIVATE_LINK' => $this->request->getBase() . '/' .
|
||||
\Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
|
||||
'CANCEL_LINK' => $this->request->getBase() . '/' .
|
||||
\Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
|
||||
'ACTIVATABLE_UNTIL' => (clone $created)->add(new DateInterval('P1D'))->format('Y-m-d H:i T')
|
||||
]);
|
||||
$mail->send();
|
||||
}
|
||||
|
||||
private function resendConfirmationEmail(User $user): bool
|
||||
{
|
||||
$confirmation = $this->userConfirmationRepository->getByUser($user);
|
||||
|
||||
if ($confirmation === null || (clone $confirmation->getLastSentDate())->add(new DateInterval('PT1H')) > new DateTime()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$confirmation->setLastSentDate(new DateTime());
|
||||
|
||||
$this->pdm->saveToDb($confirmation);
|
||||
|
||||
$this->sendConfirmationEmail($user->getEmail(), $confirmation->getToken(), $user->getCreatedDate());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function sendWelcomeEmail(string $email): void
|
||||
{
|
||||
$mail = new Mail();
|
||||
@@ -393,4 +594,18 @@ class LoginController
|
||||
]);
|
||||
$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;
|
||||
|
||||
use DateTime;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\Interfaces\Authentication\IUser;
|
||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||
use MapGuesser\Interfaces\Database\IResultSet;
|
||||
use MapGuesser\Interfaces\Request\IRequest;
|
||||
use MapGuesser\Interfaces\Response\IContent;
|
||||
use MapGuesser\PersistentData\Model\Map;
|
||||
@@ -15,7 +13,7 @@ use MapGuesser\Repository\PlaceRepository;
|
||||
use MapGuesser\Response\HtmlContent;
|
||||
use MapGuesser\Response\JsonContent;
|
||||
use MapGuesser\Util\Geo\Bounds;
|
||||
use MapGuesser\Util\Geo\Position;
|
||||
use MapGuesser\Util\Panorama\Pov;
|
||||
|
||||
class MapAdminController implements ISecured
|
||||
{
|
||||
@@ -57,8 +55,13 @@ class MapAdminController implements ISecured
|
||||
$places = [];
|
||||
}
|
||||
|
||||
$data = ['mapId' => $mapId, 'mapName' => $map->getName(), 'mapDescription' => str_replace('<br>', "\n", $map->getDescription()), 'bounds' => $map->getBounds()->toArray(), 'places' => &$places];
|
||||
return new HtmlContent('admin/map_editor', $data);
|
||||
return new HtmlContent('admin/map_editor', [
|
||||
'mapId' => $mapId,
|
||||
'mapName' => $map->getName(),
|
||||
'mapDescription' => str_replace('<br>', "\n", $map->getDescription()),
|
||||
'bounds' => $map->getBounds()->toArray(),
|
||||
'places' => &$places
|
||||
]);
|
||||
}
|
||||
|
||||
public function getPlace(): IContent
|
||||
@@ -67,8 +70,7 @@ class MapAdminController implements ISecured
|
||||
|
||||
$place = $this->placeRepository->getById($placeId);
|
||||
|
||||
$data = ['panoId' => $place->getFreshPanoId()];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['panoId' => $place->getFreshPanoId()]);
|
||||
}
|
||||
|
||||
public function saveMap(): IContent
|
||||
@@ -94,6 +96,11 @@ class MapAdminController implements ISecured
|
||||
$place->setMap($map);
|
||||
$place->setLat((float) $placeRaw['lat']);
|
||||
$place->setLng((float) $placeRaw['lng']);
|
||||
$place->setPov(new Pov(
|
||||
(float) $placeRaw['pov']['heading'],
|
||||
(float) $placeRaw['pov']['pitch'],
|
||||
(float) $placeRaw['pov']['zoom']
|
||||
));
|
||||
|
||||
if ($placeRaw['panoId'] === -1) {
|
||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||
@@ -114,6 +121,11 @@ class MapAdminController implements ISecured
|
||||
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
||||
$place->setLat((float) $placeRaw['lat']);
|
||||
$place->setLng((float) $placeRaw['lng']);
|
||||
$place->setPov(new Pov(
|
||||
(float) $placeRaw['pov']['heading'],
|
||||
(float) $placeRaw['pov']['pitch'],
|
||||
(float) $placeRaw['pov']['zoom']
|
||||
));
|
||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||
|
||||
$this->pdm->saveToDb($place);
|
||||
@@ -146,11 +158,11 @@ class MapAdminController implements ISecured
|
||||
|
||||
\Container::$dbConnection->commit();
|
||||
|
||||
$data = ['mapId' => $map->getId(), 'added' => $addedIds];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
|
||||
}
|
||||
|
||||
public function deleteMap() {
|
||||
public function deleteMap(): IContent
|
||||
{
|
||||
$mapId = (int) $this->request->query('mapId');
|
||||
|
||||
$map = $this->mapRepository->getById($mapId);
|
||||
@@ -163,40 +175,22 @@ class MapAdminController implements ISecured
|
||||
|
||||
\Container::$dbConnection->commit();
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
private function deletePlaces(Map $map): void
|
||||
{
|
||||
//TODO: relations?
|
||||
$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);
|
||||
|
||||
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||
$this->pdm->deleteFromDb($place);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
@@ -204,26 +198,19 @@ class MapAdminController implements ISecured
|
||||
|
||||
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 = [];
|
||||
|
||||
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||
//$panoId = ???
|
||||
//$pov = ???
|
||||
$noPano = $place['pano_id_cached_timestamp'] && $place['pano_id_cached'] === null;
|
||||
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||
$noPano = $place->getPanoIdCachedTimestampDate() !== null && $place->getPanoIdCached() === null;
|
||||
|
||||
$places[$place['id']] = [
|
||||
'id' => $place['id'],
|
||||
'lat' => $place['lat'],
|
||||
'lng' => $place['lng'],
|
||||
$placeId = $place->getId();
|
||||
|
||||
$places[$placeId] = [
|
||||
'id' => $placeId,
|
||||
'lat' => $place->getLat(),
|
||||
'lng' => $place->getLng(),
|
||||
'panoId' => null,
|
||||
'pov' => ['heading' => 0.0, 'pitch' => 0.0, 'zoom' => 0.0],
|
||||
'pov' => $place->getPov()->toArray(),
|
||||
'noPano' => $noPano
|
||||
];
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class MapsController
|
||||
|
||||
public function getMaps(): IContent
|
||||
{
|
||||
//TODO: from repository
|
||||
//TODO: from repository - count should be added
|
||||
$select = new Select(\Container::$dbConnection, 'maps');
|
||||
$select->columns([
|
||||
['maps', 'id'],
|
||||
@@ -46,8 +46,10 @@ class MapsController
|
||||
}
|
||||
|
||||
$user = $this->request->user();
|
||||
$data = ['maps' => $maps, 'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)];
|
||||
return new HtmlContent('maps', $data);
|
||||
return new HtmlContent('maps', [
|
||||
'maps' => $maps,
|
||||
'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)
|
||||
]);
|
||||
}
|
||||
|
||||
private function formatMapAreaForHuman(float $area): array
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<?php namespace MapGuesser\Controller;
|
||||
|
||||
use DateTime;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\Http\Request;
|
||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||
use MapGuesser\Interfaces\Database\IResultSet;
|
||||
use MapGuesser\Interfaces\Request\IRequest;
|
||||
use MapGuesser\Interfaces\Response\IContent;
|
||||
use MapGuesser\Interfaces\Response\IRedirect;
|
||||
use MapGuesser\OAuth\GoogleOAuth;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
use MapGuesser\PersistentData\Model\User;
|
||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||
use MapGuesser\Repository\UserConfirmationRepository;
|
||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||
use MapGuesser\Response\HtmlContent;
|
||||
use MapGuesser\Response\JsonContent;
|
||||
use MapGuesser\Response\Redirect;
|
||||
@@ -26,11 +24,14 @@ class UserController implements ISecured
|
||||
|
||||
private UserConfirmationRepository $userConfirmationRepository;
|
||||
|
||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||
|
||||
public function __construct(IRequest $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->pdm = new PersistentDataManager();
|
||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||
}
|
||||
|
||||
public function authorize(): bool
|
||||
@@ -47,8 +48,7 @@ class UserController implements ISecured
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
$data = ['user' => $user->toArray()];
|
||||
return new HtmlContent('account/account', $data);
|
||||
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
||||
}
|
||||
|
||||
public function getGoogleAuthenticateRedirect(): IRedirect
|
||||
@@ -59,14 +59,17 @@ class UserController implements ISecured
|
||||
$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()
|
||||
);
|
||||
|
||||
@@ -81,31 +84,40 @@ class UserController implements ISecured
|
||||
$user = $this->request->user();
|
||||
|
||||
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||
$data = ['success' => false];
|
||||
return new HtmlContent('account/google_authenticate', $data);
|
||||
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());
|
||||
$tokenData = $oAuth->getToken(
|
||||
$this->request->query('code'),
|
||||
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()
|
||||
);
|
||||
|
||||
if (!isset($tokenData['id_token'])) {
|
||||
$data = ['success' => false];
|
||||
return new HtmlContent('account/google_authenticate', $data);
|
||||
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||
}
|
||||
|
||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||
$userData = $jwtParser->getPayload();
|
||||
$idToken = $jwtParser->getPayload();
|
||||
|
||||
if ($userData['sub'] !== $user->getGoogleSub()) {
|
||||
$data = ['success' => false, 'errorText' => 'This Google account is not linked to your account.'];
|
||||
return new HtmlContent('account/google_authenticate', $data);
|
||||
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);
|
||||
|
||||
$data = ['success' => true, 'authenticatedWithGoogleUntil' => $authenticatedWithGoogleUntil];
|
||||
return new HtmlContent('account/google_authenticate', $data);
|
||||
return new HtmlContent('account/google_authenticate', [
|
||||
'success' => true,
|
||||
'authenticatedWithGoogleUntil' => $authenticatedWithGoogleUntil
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDeleteAccount(): IContent
|
||||
@@ -115,8 +127,7 @@ class UserController implements ISecured
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
$data = ['user' => $user->toArray()];
|
||||
return new HtmlContent('account/delete', $data);
|
||||
return new HtmlContent('account/delete', ['user' => $user->toArray()]);
|
||||
}
|
||||
|
||||
public function saveAccount(): IContent
|
||||
@@ -132,19 +143,24 @@ class UserController implements ISecured
|
||||
$this->request->post('password'),
|
||||
$error
|
||||
)) {
|
||||
$data = ['error' => ['errorText' => $error]];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||
}
|
||||
|
||||
if (strlen($this->request->post('password_new')) > 0) {
|
||||
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($data);
|
||||
return new JsonContent([
|
||||
'error' => [
|
||||
'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
||||
$data = ['error' => ['errorText' => 'The given new passwords do not match.']];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent([
|
||||
'error' => [
|
||||
'errorText' => 'The given new passwords do not match.'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
$user->setPlainPassword($this->request->post('password_new'));
|
||||
@@ -154,8 +170,7 @@ class UserController implements ISecured
|
||||
|
||||
$this->request->session()->delete('authenticated_with_google_until');
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
public function deleteAccount(): IContent
|
||||
@@ -171,27 +186,31 @@ class UserController implements ISecured
|
||||
$this->request->post('password'),
|
||||
$error
|
||||
)) {
|
||||
$data = ['error' => ['errorText' => $error]];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||
}
|
||||
|
||||
\Container::$dbConnection->startTransaction();
|
||||
|
||||
foreach ($this->userConfirmationRepository->getByUser($user) as $userConfirmation) {
|
||||
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||
if ($userConfirmation !== null) {
|
||||
$this->pdm->deleteFromDb($userConfirmation);
|
||||
}
|
||||
|
||||
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||
if ($userPasswordResetter !== null) {
|
||||
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||
}
|
||||
|
||||
$this->pdm->deleteFromDb($user);
|
||||
|
||||
\Container::$dbConnection->commit();
|
||||
|
||||
$this->request->session()->delete('authenticated_with_google_until');
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
return new JsonContent(['success' => true]);
|
||||
}
|
||||
|
||||
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, &$error): bool
|
||||
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, string &$error): bool
|
||||
{
|
||||
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
|
||||
return true;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if ($port < 0) {
|
||||
$port = ini_get('mysqli.default_port');
|
||||
$port = (int) ini_get('mysqli.default_port');
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -135,6 +135,6 @@ class Modify
|
||||
|
||||
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 $limit;
|
||||
private ?array $limit;
|
||||
|
||||
public function __construct(IConnection $connection, ?string $table = null)
|
||||
{
|
||||
@@ -144,7 +144,7 @@ class Select
|
||||
$this->limit = null;
|
||||
}
|
||||
|
||||
public function paginate(int $page, int $itemsPerPage)
|
||||
public function paginate(int $page, int $itemsPerPage): Select
|
||||
{
|
||||
$this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
|
||||
|
||||
@@ -308,7 +308,7 @@ class Select
|
||||
return implode(' ', $joins);
|
||||
}
|
||||
|
||||
private function generateConditions(string $type): array
|
||||
private function generateConditions(int $type): array
|
||||
{
|
||||
$conditions = '';
|
||||
$params = [];
|
||||
@@ -376,9 +376,9 @@ class Select
|
||||
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;
|
||||
|
||||
$conditionCallback($instance);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?php namespace MapGuesser\Database;
|
||||
|
||||
class Utils {
|
||||
public static function backtick(string $name) {
|
||||
public static function backtick(string $name): string
|
||||
{
|
||||
return '`' . $name . '`';
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ class Request implements IRequest
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
public function setQuery($query)
|
||||
public function setQuery($query): void
|
||||
{
|
||||
if (is_string($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);
|
||||
}
|
||||
@@ -47,13 +47,13 @@ class Request implements IRequest
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
if ($this->method === self::HTTP_GET) {
|
||||
$url = $this->url . '?' . $this->query;
|
||||
} elseif ($this->method === self::HTTP_POST) {
|
||||
$url = $this->url;
|
||||
if ($this->method === self::HTTP_POST) {
|
||||
$url = $this->url;
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->query);
|
||||
} else {
|
||||
$url = $this->url . '?' . $this->query;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
|
||||
@@ -14,12 +14,12 @@ class Response implements IResponse
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getHeaders()
|
||||
public function getHeaders(): array
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ interface IRequest
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
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
|
||||
{
|
||||
public function setParsedRouteParams(array &$routeParams);
|
||||
public function setParsedRouteParams(array &$routeParams): void;
|
||||
|
||||
public function getBase(): string;
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
interface IContent
|
||||
{
|
||||
public function &getData(): array;
|
||||
public function setData(array $data): void;
|
||||
|
||||
public function getData(): array;
|
||||
|
||||
public function render(): void;
|
||||
|
||||
|
||||
@@ -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->addAddress($recipient[0], $recipient[1]);
|
||||
|
||||
@@ -4,9 +4,9 @@ use MapGuesser\Interfaces\Http\IRequest;
|
||||
|
||||
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;
|
||||
|
||||
@@ -15,7 +15,7 @@ class GoogleOAuth
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function getDialogUrl(string $state, string $redirectUrl, ?string $loginHint = null): string
|
||||
public function getDialogUrl(string $state, string $redirectUrl, ?string $nonce = null, ?string $loginHint = null): string
|
||||
{
|
||||
$oauthParams = [
|
||||
'response_type' => 'code',
|
||||
@@ -23,9 +23,12 @@ class GoogleOAuth
|
||||
'scope' => 'openid email',
|
||||
'redirect_uri' => $redirectUrl,
|
||||
'state' => $state,
|
||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
||||
];
|
||||
|
||||
if ($nonce !== null) {
|
||||
$oauthParams['nonce'] = $nonce;
|
||||
}
|
||||
|
||||
if ($loginHint !== null) {
|
||||
$oauthParams['login_hint'] = $loginHint;
|
||||
}
|
||||
@@ -33,7 +36,7 @@ class GoogleOAuth
|
||||
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
||||
}
|
||||
|
||||
public function getToken(string $code, string $redirectUrl)
|
||||
public function getToken(string $code, string $redirectUrl): array
|
||||
{
|
||||
$tokenParams = [
|
||||
'code' => $code,
|
||||
|
||||
@@ -5,12 +5,13 @@ use DateTime;
|
||||
use MapGuesser\Http\Request;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
use MapGuesser\Util\Geo\Position;
|
||||
use MapGuesser\Util\Panorama\Pov;
|
||||
|
||||
class Place extends Model
|
||||
{
|
||||
protected static string $table = 'places';
|
||||
|
||||
protected static array $fields = ['map_id', 'lat', 'lng', 'pano_id_cached', 'pano_id_cached_timestamp'];
|
||||
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];
|
||||
|
||||
@@ -24,9 +25,12 @@ class Place extends Model
|
||||
|
||||
private ?DateTime $panoIdCachedTimestamp = null;
|
||||
|
||||
private Pov $pov;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->position = new Position(0.0, 0.0);
|
||||
$this->pov = new Pov(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
public function setMap(Map $map): void
|
||||
@@ -54,6 +58,26 @@ class Place extends Model
|
||||
$this->position->setLng($lng);
|
||||
}
|
||||
|
||||
public function setPov(Pov $pov): void
|
||||
{
|
||||
$this->pov = $pov;
|
||||
}
|
||||
|
||||
public function setPovHeading(float $heading): void
|
||||
{
|
||||
$this->pov->setHeading($heading);
|
||||
}
|
||||
|
||||
public function setPovPitch(float $pitch): void
|
||||
{
|
||||
$this->pov->setPitch($pitch);
|
||||
}
|
||||
|
||||
public function setPovZoom(float $zoom): void
|
||||
{
|
||||
$this->pov->setZoom($zoom);
|
||||
}
|
||||
|
||||
public function setPanoIdCached(?string $panoIdCached): void
|
||||
{
|
||||
$this->panoIdCached = $panoIdCached;
|
||||
@@ -96,6 +120,26 @@ class Place extends Model
|
||||
return $this->position->getLng();
|
||||
}
|
||||
|
||||
public function getPov(): Pov
|
||||
{
|
||||
return $this->pov;
|
||||
}
|
||||
|
||||
public function getPovHeading(): float
|
||||
{
|
||||
return $this->pov->getHeading();
|
||||
}
|
||||
|
||||
public function getPovPitch(): float
|
||||
{
|
||||
return $this->pov->getPitch();
|
||||
}
|
||||
|
||||
public function getPovZoom(): float
|
||||
{
|
||||
return $this->pov->getZoom();
|
||||
}
|
||||
|
||||
public function getFreshPanoId(bool $canBeIndoor = false): ?string
|
||||
{
|
||||
if (
|
||||
@@ -122,7 +166,7 @@ class Place extends Model
|
||||
}
|
||||
|
||||
$this->panoIdCached = $panoId;
|
||||
$this->panoIdCachedTimestamp = new DateTime('now');
|
||||
$this->panoIdCachedTimestamp = new DateTime();
|
||||
|
||||
(new PersistentDataManager())->saveToDb($this);
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<?php namespace MapGuesser\PersistentData\Model;
|
||||
|
||||
use DateTime;
|
||||
use MapGuesser\Interfaces\Authentication\IUser;
|
||||
|
||||
class User extends Model implements IUser
|
||||
{
|
||||
protected static string $table = 'users';
|
||||
|
||||
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub'];
|
||||
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub', 'created'];
|
||||
|
||||
private static array $types = ['user', 'admin'];
|
||||
|
||||
@@ -20,6 +21,8 @@ class User extends Model implements IUser
|
||||
|
||||
private ?string $googleSub = null;
|
||||
|
||||
private DateTime $created;
|
||||
|
||||
public function setEmail(string $email): void
|
||||
{
|
||||
$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
|
||||
@@ -52,6 +55,16 @@ class User extends Model implements IUser
|
||||
$this->googleSub = $googleSub;
|
||||
}
|
||||
|
||||
public function setCreatedDate(DateTime $created): void
|
||||
{
|
||||
$this->created = $created;
|
||||
}
|
||||
|
||||
public function setCreated(string $created): void
|
||||
{
|
||||
$this->created = new DateTime($created);
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
@@ -77,15 +90,25 @@ class User extends Model implements IUser
|
||||
return $this->googleSub;
|
||||
}
|
||||
|
||||
public function getCreatedDate(): DateTime
|
||||
{
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreated(): string
|
||||
{
|
||||
return $this->created->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
public function hasPermission(int $permission): bool
|
||||
{
|
||||
switch ($permission) {
|
||||
case IUser::PERMISSION_NORMAL:
|
||||
return true;
|
||||
break;
|
||||
case IUser::PERMISSION_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
|
||||
{
|
||||
if ($this->password === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return password_verify($password, $this->password);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php namespace MapGuesser\PersistentData\Model;
|
||||
|
||||
use DateTime;
|
||||
|
||||
class UserConfirmation extends Model
|
||||
{
|
||||
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];
|
||||
|
||||
@@ -14,6 +16,8 @@ class UserConfirmation extends Model
|
||||
|
||||
private string $token = '';
|
||||
|
||||
private DateTime $lastSent;
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
@@ -29,6 +33,16 @@ class UserConfirmation extends Model
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
public function setLastSentDate(DateTime $lastSent): void
|
||||
{
|
||||
$this->lastSent = $lastSent;
|
||||
}
|
||||
|
||||
public function setLastSent(string $lastSent): void
|
||||
{
|
||||
$this->lastSent = new DateTime($lastSent);
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
return $this->user;
|
||||
@@ -43,4 +57,14 @@ class UserConfirmation extends Model
|
||||
{
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -27,8 +27,9 @@ class PersistentDataManager
|
||||
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
|
||||
{
|
||||
$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();
|
||||
$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->whereId($id);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php namespace MapGuesser\Repository;
|
||||
|
||||
use Generator;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\PersistentData\Model\Map;
|
||||
use MapGuesser\PersistentData\Model\Place;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
|
||||
@@ -18,6 +20,15 @@ class PlaceRepository
|
||||
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
|
||||
{
|
||||
$placesWithoutPano = [];
|
||||
@@ -35,13 +46,20 @@ class PlaceRepository
|
||||
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->where('id', 'NOT IN', $exclude);
|
||||
$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);
|
||||
|
||||
$select->orderBy('id');
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?php namespace MapGuesser\Repository;
|
||||
|
||||
use Generator;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\Interfaces\Database\IResultSet;
|
||||
use MapGuesser\PersistentData\Model\User;
|
||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
@@ -29,11 +27,11 @@ class UserConfirmationRepository
|
||||
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->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;
|
||||
|
||||
use DateTime;
|
||||
use Generator;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\PersistentData\Model\User;
|
||||
use MapGuesser\PersistentData\PersistentDataManager;
|
||||
@@ -33,4 +35,13 @@ class UserRepository
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ class Session implements ISession
|
||||
$this->data = &$data;
|
||||
}
|
||||
|
||||
public function has($key): bool
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
public function get(string $key)
|
||||
{
|
||||
if (isset($this->data[$key])) {
|
||||
return $this->data[$key];
|
||||
@@ -25,12 +25,12 @@ class Session implements ISession
|
||||
return null;
|
||||
}
|
||||
|
||||
public function set($key, $value): void
|
||||
public function set(string $key, $value): void
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
public function delete($key): void
|
||||
public function delete(string $key): void
|
||||
{
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,12 @@ abstract class ContentBase implements IContent
|
||||
{
|
||||
protected array $data;
|
||||
|
||||
public function &getData(): array
|
||||
public function setData(array $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ class HtmlContent extends ContentBase
|
||||
{
|
||||
private string $view;
|
||||
|
||||
public function __construct(string $view, array &$data = [])
|
||||
public function __construct(string $view, array $data = [])
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->data = &$data;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function render(): void
|
||||
@@ -23,6 +23,7 @@ class HtmlContent extends ContentBase
|
||||
|
||||
require ROOT . '/cache/views/' . $this->view . '.php';
|
||||
|
||||
// @phpstan-ignore-next-line - SCRIPT_STARTED is defined in main.php
|
||||
echo '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->';
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
class JsonContent extends ContentBase
|
||||
{
|
||||
public function __construct(array &$data = [])
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = &$data;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
echo json_encode($this->data);
|
||||
|
||||
@@ -16,7 +16,7 @@ class Redirect implements IRedirect
|
||||
|
||||
public function getUrl(): string
|
||||
{
|
||||
if (preg_match('/^http(s)?/', $this->target)) {
|
||||
if (preg_match('/^http(s)?/', $this->target) === 1) {
|
||||
$link = $this->target;
|
||||
} else {
|
||||
$link = \Container::$request->getBase() . '/' . $this->target;
|
||||
|
||||
@@ -30,7 +30,7 @@ class Route
|
||||
$link = [];
|
||||
|
||||
foreach ($this->pattern as $fragment) {
|
||||
if (preg_match('/^{(\\w+)(\\?)?}$/', $fragment, $matches)) {
|
||||
if (preg_match('/^{(\\w+)(\\?)?}$/', $fragment, $matches) === 1) {
|
||||
if (isset($parameters[$matches[1]])) {
|
||||
$link[] = $parameters[$matches[1]];
|
||||
unset($parameters[$matches[1]]);
|
||||
@@ -61,7 +61,7 @@ class Route
|
||||
$parameters = [];
|
||||
|
||||
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;
|
||||
} elseif ($fragment != $this->pattern[$i]) {
|
||||
return null;
|
||||
|
||||
@@ -76,7 +76,7 @@ class RouteCollection
|
||||
|
||||
$this->searchTable[$method][$groupNumber][] = $route;
|
||||
|
||||
while (preg_match('/^{\\w+\\?}$/', end($pattern))) {
|
||||
while (preg_match('/^{\\w+\\?}$/', end($pattern)) === 1) {
|
||||
$groupNumber--;
|
||||
array_pop($pattern);
|
||||
|
||||
|
||||
@@ -4,11 +4,9 @@ use DateTime;
|
||||
use MapGuesser\Database\Query\Modify;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\Interfaces\Database\IResultSet;
|
||||
use SessionHandlerInterface;
|
||||
use SessionIdInterface;
|
||||
use SessionUpdateTimestampHandlerInterface;
|
||||
use MapGuesser\Interfaces\Session\ISessionHandler;
|
||||
|
||||
class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
|
||||
class DatabaseSessionHandler implements ISessionHandler
|
||||
{
|
||||
private bool $exists = false;
|
||||
|
||||
@@ -28,7 +26,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
||||
{
|
||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||
$select->columns(['data']);
|
||||
$select->whereId($id);
|
||||
$select->whereId(substr($id, 0, 32));
|
||||
|
||||
$result = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||
|
||||
@@ -46,16 +44,16 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||
|
||||
if ($this->exists) {
|
||||
$modify->setId($id);
|
||||
$modify->setId(substr($id, 0, 32));
|
||||
} else {
|
||||
$modify->setExternalId($id);
|
||||
$modify->setExternalId(substr($id, 0, 32));
|
||||
}
|
||||
|
||||
$modify->set('data', $data);
|
||||
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
|
||||
$modify->save();
|
||||
|
||||
$written = true;
|
||||
$this->written = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -63,37 +61,30 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
||||
public function destroy($id): bool
|
||||
{
|
||||
$modify = new Modify(\Container::$dbConnection, 'sessions');
|
||||
$modify->setId($id);
|
||||
$modify->setId(substr($id, 0, 32));
|
||||
$modify->delete();
|
||||
|
||||
$this->exists = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function gc($maxlifetime): int
|
||||
public function gc($maxlifetime): bool
|
||||
{
|
||||
$select = new Select(\Container::$dbConnection, 'sessions');
|
||||
$select->columns(['id']);
|
||||
$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();
|
||||
}
|
||||
// empty on purpose
|
||||
// old sessions are deleted by MaintainDatabaseCommand
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function create_sid(): string
|
||||
{
|
||||
return hash('sha256', random_bytes(10) . microtime());
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
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
|
||||
@@ -104,7 +95,7 @@ class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterf
|
||||
|
||||
$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->save();
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class Bounds
|
||||
|
||||
public static function createDirectly(float $southLat, float $westLng, float $northLat, float $eastLng): Bounds
|
||||
{
|
||||
$instance = new static();
|
||||
$instance = new self();
|
||||
|
||||
$instance->southLat = $southLat;
|
||||
$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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -65,7 +65,7 @@ class Linker
|
||||
while (($line = fgets($inputFileHandle)) !== false) {
|
||||
++$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]])) {
|
||||
fwrite($outputFileHandle, $sections[$matches[1]]);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ class Linker
|
||||
fclose($outputFileHandle);
|
||||
}
|
||||
|
||||
private function generateAssets(ParsedFragment $fragment, array &$sections)
|
||||
private function generateAssets(ParsedFragment $fragment, array &$sections): void
|
||||
{
|
||||
foreach ($fragment->getCss() as $cssFile) {
|
||||
$asset = $this->parseAsset($cssFile);
|
||||
@@ -134,7 +134,7 @@ class Linker
|
||||
{
|
||||
$output = [];
|
||||
|
||||
if (preg_match('/^[\w\/\.]+$/', $asset)) {
|
||||
if (preg_match('/^[\w\/\.]+$/', $asset) === 1) {
|
||||
if (
|
||||
empty($_ENV['DEV']) &&
|
||||
filesize(ROOT . '/public/static/' . $asset) < self::INLINE_ASSET_LIMIT
|
||||
|
||||
+7
-7
@@ -108,7 +108,7 @@ class Parser
|
||||
|
||||
private function matchCss(string $line): ?string
|
||||
{
|
||||
if (preg_match('/^\s*@css\((.*)\)\s*$/', $line, $matches)) {
|
||||
if (preg_match('/^\s*@css\((.*)\)\s*$/', $line, $matches) === 1) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class Parser
|
||||
|
||||
private function matchJs(string $line): ?string
|
||||
{
|
||||
if (preg_match('/^\s*@js\((.*)\)\s*$/', $line, $matches)) {
|
||||
if (preg_match('/^\s*@js\((.*)\)\s*$/', $line, $matches) === 1) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ class Parser
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class Parser
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -144,16 +144,16 @@ class Parser
|
||||
|
||||
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
|
||||
{
|
||||
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line);
|
||||
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line) === 1;
|
||||
}
|
||||
|
||||
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';
|
||||
$state = 'random_state_string';
|
||||
$nonce = 'random_nonce_string';
|
||||
$redirectUrl = 'http://example.com/oauth';
|
||||
|
||||
$requestMock = $this->getMockBuilder(IRequest::class)
|
||||
@@ -20,7 +21,7 @@ final class GoogleOAuthTest extends TestCase
|
||||
->getMock();
|
||||
$googleOAuth = new GoogleOAuth($requestMock);
|
||||
|
||||
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl);
|
||||
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl, $nonce);
|
||||
$dialogUrlParsed = explode('?', $dialogUrl);
|
||||
|
||||
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
|
||||
@@ -33,15 +34,10 @@ final class GoogleOAuthTest extends TestCase
|
||||
'scope' => 'openid email',
|
||||
'redirect_uri' => $redirectUrl,
|
||||
'state' => $state,
|
||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
||||
'nonce' => $nonce,
|
||||
];
|
||||
|
||||
$this->assertEquals($expectedQueryParams['response_type'], $dialogUrlQueryParams['response_type']);
|
||||
$this->assertEquals($expectedQueryParams['client_id'], $dialogUrlQueryParams['client_id']);
|
||||
$this->assertEquals($expectedQueryParams['scope'], $dialogUrlQueryParams['scope']);
|
||||
$this->assertEquals($expectedQueryParams['redirect_uri'], $dialogUrlQueryParams['redirect_uri']);
|
||||
$this->assertEquals($expectedQueryParams['state'], $dialogUrlQueryParams['state']);
|
||||
$this->assertMatchesRegularExpression('/^[a-f0-9]{64}$/', $dialogUrlQueryParams['nonce']);
|
||||
$this->assertEquals($expectedQueryParams, $dialogUrlQueryParams);
|
||||
}
|
||||
|
||||
public function testCanRequestToken(): void
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
use MapGuesser\PersistentData\Model\Model;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OtherModel
|
||||
{
|
||||
}
|
||||
|
||||
class DummyModel extends Model
|
||||
{
|
||||
protected static string $table = 'test_table';
|
||||
|
||||
@@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ParserTest extends TestCase
|
||||
{
|
||||
const TEST_VIEWS_PATH = __DIR__ . '/../assets/views';
|
||||
const TEST_VIEWS_PATH = __DIR__ . '/../../views/tests';
|
||||
|
||||
public function testCanParseViewWithoutExtends(): void
|
||||
{
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
@section(main)
|
||||
<h2>Account activation</h2>
|
||||
<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>
|
||||
@endsection
|
||||
@@ -10,6 +10,8 @@
|
||||
<div class="right marginTop">
|
||||
<button type="submit">Login</button>
|
||||
</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>
|
||||
<div class="center">
|
||||
<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
|
||||
@@ -22,6 +22,7 @@
|
||||
--><button id="resetSignupButton" class="gray marginTop marginLeft" type="button">Reset</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="center marginTop"><a href="/login" title="Login">Already have an account?</a></p>
|
||||
<hr>
|
||||
<div class="center">
|
||||
<a class="button yellow" href="/login/google" title="Signup with Google">Signup with Google</a>
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -31,6 +31,13 @@ Container::$routeCollection->group('signup', function (MapGuesser\Routing\RouteC
|
||||
$routeCollection->get('signup.activate', 'activate/{token}', [MapGuesser\Controller\LoginController::class, 'activate']);
|
||||
$routeCollection->get('signup.cancel', 'cancel/{token}', [MapGuesser\Controller\LoginController::class, 'cancel']);
|
||||
});
|
||||
Container::$routeCollection->group('password', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('password-requestReset', 'requestReset', [MapGuesser\Controller\LoginController::class, 'getRequestPasswordResetForm']);
|
||||
$routeCollection->post('password-requestReset-action', 'requestReset', [MapGuesser\Controller\LoginController::class, 'requestPasswordReset']);
|
||||
$routeCollection->get('password-requestReset.success', 'requestReset/success', [MapGuesser\Controller\LoginController::class, 'getRequestPasswordResetSuccess']);
|
||||
$routeCollection->get('password-reset', 'reset/{token}', [MapGuesser\Controller\LoginController::class, 'getResetPasswordForm']);
|
||||
$routeCollection->post('password-reset.action', 'reset/{token}', [MapGuesser\Controller\LoginController::class, 'resetPassword']);
|
||||
});
|
||||
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
|
||||
Container::$routeCollection->group('account', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
$routeCollection->get('account', '', [MapGuesser\Controller\UserController::class, 'getAccount']);
|
||||
@@ -60,10 +67,16 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) {
|
||||
session_set_save_handler(Container::$sessionHandler, true);
|
||||
session_start([
|
||||
'gc_maxlifetime' => 604800,
|
||||
'cookie_lifetime' => 604800,
|
||||
'gc_probability' => 0, // old sessions are deleted by MaintainDatabaseCommand
|
||||
'cookie_lifetime' => 604800, // TODO: cookie is not renewed so session can be lost
|
||||
'cookie_httponly' => true,
|
||||
'cookie_samesite' => 'Lax'
|
||||
]);
|
||||
|
||||
// this is needed to handle old type of session IDs
|
||||
if (!Container::$sessionHandler->validateId(session_id())) {
|
||||
session_regenerate_id(true);
|
||||
}
|
||||
} else {
|
||||
$_SESSION = [];
|
||||
}
|
||||
@@ -71,5 +84,5 @@ if (isset($_COOKIE['COOKIES_CONSENT'])) {
|
||||
Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
|
||||
|
||||
if (!Container::$request->session()->has('anti_csrf_token')) {
|
||||
Container::$request->session()->set('anti_csrf_token', hash('sha256', random_bytes(10) . microtime()));
|
||||
Container::$request->session()->set('anti_csrf_token', bin2hex(random_bytes(16)));
|
||||
}
|
||||
Reference in new issue
Block a user