Compare commits
2 Commits
develop
...
700d349d38
Author | SHA1 | Date | |
---|---|---|---|
700d349d38 | |||
2edfd14437 |
@ -1,5 +1,4 @@
|
|||||||
APP_NAME=MapGuesser
|
APP_NAME=MapGuesser
|
||||||
APP_URL=mapguesser.dev
|
|
||||||
DEV=1
|
DEV=1
|
||||||
DB_HOST=mariadb
|
DB_HOST=mariadb
|
||||||
DB_USER=mapguesser
|
DB_USER=mapguesser
|
||||||
@ -8,7 +7,6 @@ DB_NAME=mapguesser
|
|||||||
GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
|
GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
|
||||||
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
|
||||||
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
|
||||||
LEAFLET_TILESERVER_SUBDOMAINS=list_of_subdomains_for_the_tileserver_without_separators
|
|
||||||
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
|
||||||
STATIC_ROOT=/static
|
STATIC_ROOT=/static
|
||||||
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
MAIL_FROM=mapguesser@mapguesser-dev.ch
|
||||||
@ -21,6 +19,3 @@ MULTI_INTERNAL_HOST=multi
|
|||||||
MULTI_INTERNAL_PORT=5000
|
MULTI_INTERNAL_PORT=5000
|
||||||
MULTI_WS_URL=mapguesser-dev.ch:8090
|
MULTI_WS_URL=mapguesser-dev.ch:8090
|
||||||
MULTI_WS_PORT=8090
|
MULTI_WS_PORT=8090
|
||||||
ENABLE_GAME_FOR_GUESTS=0
|
|
||||||
RECAPTCHA_SITEKEY=your_recaptcha_sitekey
|
|
||||||
RECAPTCHA_SECRET=your_recaptcha_secret
|
|
||||||
|
106
Jenkinsfile
vendored
106
Jenkinsfile
vendored
@ -1,106 +0,0 @@
|
|||||||
pipeline {
|
|
||||||
agent {
|
|
||||||
node {
|
|
||||||
label 'mapguesser'
|
|
||||||
customWorkspace 'workspace/mapguesser'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stages {
|
|
||||||
stage('Install composer') {
|
|
||||||
environment {
|
|
||||||
COMPOSER_HOME="${WORKSPACE}/.composer"
|
|
||||||
}
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
filename 'docker/Dockerfile'
|
|
||||||
dir '.'
|
|
||||||
additionalBuildArgs '--target mapg_base'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'composer install'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Unit Testing') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
filename 'docker/Dockerfile'
|
|
||||||
dir '.'
|
|
||||||
additionalBuildArgs '--target mapg_base'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'vendor/bin/phpunit --log-junit unit_test_results.xml --testdox tests'
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
archiveArtifacts 'unit_test_results.xml'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Static Code Analysis') {
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
filename 'docker/Dockerfile'
|
|
||||||
dir '.'
|
|
||||||
additionalBuildArgs '--target mapg_base'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
sh 'php -d memory_limit=1G vendor/bin/phpstan analyse -c phpstan.neon --error-format=prettyJson > static_code_analysis_results.json'
|
|
||||||
}
|
|
||||||
post {
|
|
||||||
always {
|
|
||||||
archiveArtifacts 'static_code_analysis_results.json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Prepare Docker release') {
|
|
||||||
environment {
|
|
||||||
COMPOSER_HOME="${WORKSPACE}/.composer"
|
|
||||||
npm_config_cache="${WORKSPACE}/.npm"
|
|
||||||
}
|
|
||||||
agent {
|
|
||||||
dockerfile {
|
|
||||||
filename 'docker/Dockerfile'
|
|
||||||
dir '.'
|
|
||||||
additionalBuildArgs '--target mapg_base'
|
|
||||||
reuseNode true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
sh script: 'git clean -ffdx', label: 'Clean repository'
|
|
||||||
env.VERSION = sh(script: 'git describe --tags --always --match "Release_*" HEAD', returnStdout: true).trim()
|
|
||||||
sh script: 'docker/scripts/release.sh', label: 'Release script'
|
|
||||||
sh script: "rm -rf ${env.COMPOSER_HOME} ${env.npm_config_cache}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Release Docker image') {
|
|
||||||
steps {
|
|
||||||
script {
|
|
||||||
withDockerRegistry([credentialsId: 'gitea-system-user', url: 'https://git.esoko.eu/']) {
|
|
||||||
sh script: 'docker buildx create --use --bootstrap --platform=linux/arm64,linux/amd64 --name multi-platform-builder'
|
|
||||||
sh script: """docker buildx build \
|
|
||||||
--platform linux/amd64,linux/arm64 \
|
|
||||||
-f docker/Dockerfile \
|
|
||||||
--target mapg_release \
|
|
||||||
-t git.esoko.eu/esoko/mapguesser:${env.VERSION} \
|
|
||||||
--push \
|
|
||||||
.""",
|
|
||||||
label: 'Build Docker image'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
119
README.md
119
README.md
@ -1,16 +1,56 @@
|
|||||||
# MapGuesser
|
# MapGuesser
|
||||||
|
|
||||||
[![Build Status](https://ci.esoko.eu/job/mapguesser/job/develop/badge/icon)](https://ci.esoko.eu/job/mapguesser/job/develop/)
|
|
||||||
|
|
||||||
This is the MapGuesser Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications.
|
This is the MapGuesser Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
### Clone the Git repository
|
||||||
|
|
||||||
|
The first step is obviously cloning the repository to your machine:
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://gitea.e5tv.hu/esoko/mapguesser.git
|
||||||
|
```
|
||||||
|
|
||||||
|
All the commands listed here should be executed from the repository root.
|
||||||
|
|
||||||
|
### Setup Docker stack (recommended)
|
||||||
|
|
||||||
|
The easiest way to build up a fully working application with web server and database is to use Docker Compose with the included `docker-compose.yml`.
|
||||||
|
|
||||||
|
All you have to do is executing the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Attach shell to the container of `mapguesser_app`:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker exec -it mapguesser_app_1 bash
|
||||||
|
```
|
||||||
|
|
||||||
|
All of the following commands should be executed there.
|
||||||
|
|
||||||
|
### Manual setup (alternative)
|
||||||
|
|
||||||
|
If you don't use the Docker stack you need to install your environment manually. Check `docker-compose.yml` and `docker/Dockerfile` to see the system requirements.
|
||||||
|
|
||||||
|
### Initialize project
|
||||||
|
|
||||||
|
This command installes all of the Composer requirements and creates a copy of the example `.env` file.
|
||||||
|
|
||||||
|
```
|
||||||
|
composer create-project
|
||||||
|
```
|
||||||
|
|
||||||
### Set environment variables
|
### Set environment variables
|
||||||
|
|
||||||
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment. Check `.env.example` for reference.
|
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment.
|
||||||
|
|
||||||
**Important: `DEV` should NOT be set for production! See section Development if you want to use the application in development mode.**
|
One very important variable is `DEV`. This indicates that the application operates in development (staging) and not in production mode.
|
||||||
|
|
||||||
|
**Hint:** 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, multiplayer, etc.) are fine with the default value.
|
||||||
|
|
||||||
#### API keys
|
#### API keys
|
||||||
|
|
||||||
@ -25,80 +65,33 @@ Required API keys:
|
|||||||
* **GOOGLE_MAPS_SERVER_API_KEY**: this it used by the backend and should have access to **Street View Static API**
|
* **GOOGLE_MAPS_SERVER_API_KEY**: this it used by the backend and should have access to **Street View Static API**
|
||||||
* **GOOGLE_MAPS_JS_API_KEY**: this is used by the frontend and should have access to **Maps JavaScript API** and **Maps Static API**
|
* **GOOGLE_MAPS_JS_API_KEY**: this is used by the frontend and should have access to **Maps JavaScript API** and **Maps Static API**
|
||||||
|
|
||||||
Additionally, a tile provider is also needed for map editor. This should be configured by `LEAFLET_TILESERVER_URL`, `LEAFLET_TILESERVER_SUBDOMAINS` and `LEAFLET_TILESERVER_ATTRIBUTION`. You can find some providers here: https://wiki.openstreetmap.org/wiki/Tile_servers. OpenStreetMap's tile server is fine for testing.
|
Additionally, a tile provider is also needed for map editor. This should be configured by `LEAFLET_TILESERVER_URL` and `LEAFLET_TILESERVER_ATTRIBUTION`. You can find some providers here: https://wiki.openstreetmap.org/wiki/Tile_servers. OpenStreetMap's tile server is fine for testing.
|
||||||
Example:
|
|
||||||
```
|
|
||||||
LEAFLET_TILESERVER_URL=https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
|
|
||||||
LEAFLET_TILESERVER_SUBDOMAINS=abc
|
|
||||||
LEAFLET_TILESERVER_ATTRIBUTION="© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker Compose
|
### (Production only) Create cron job
|
||||||
|
|
||||||
Create a `docker-compose.yml` file. The example code below assumes that `.env` is placed in the same folder.
|
To maintain database (delete inactive users, old sessions etc.), the command `db:maintain` should be regularly executed. It is recommended to create a cron job that runs every hour:
|
||||||
|
|
||||||
```yml
|
|
||||||
version: '3'
|
|
||||||
services:
|
|
||||||
app:
|
|
||||||
image: git.esoko.eu/esoko/mapguesser:latest
|
|
||||||
depends_on:
|
|
||||||
mariadb:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
- 8090:8090
|
|
||||||
volumes:
|
|
||||||
- .env:/var/www/mapguesser/.env
|
|
||||||
mariadb:
|
|
||||||
image: mariadb:10.3
|
|
||||||
volumes:
|
|
||||||
- mysql:/var/lib/mysql
|
|
||||||
environment:
|
|
||||||
MYSQL_ROOT_PASSWORD: 'root'
|
|
||||||
MYSQL_DATABASE: 'mapguesser'
|
|
||||||
MYSQL_USER: 'mapguesser'
|
|
||||||
MYSQL_PASSWORD: 'mapguesser'
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "mysqladmin -u $$MYSQL_USER -p$$MYSQL_PASSWORD ping -h localhost || exit 1"]
|
|
||||||
start_period: 5s
|
|
||||||
start_interval: 1s
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
mysql:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
0 * * * * /path/to/your/installation/mapg db:maintain >>/var/log/cron-mapguesser.log 2>&1
|
||||||
Execute the following command:
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Finalize installation
|
||||||
|
|
||||||
**And you are done!** The application is ready to use. You can create the first administrative user with the following command after attaching to the `app` container:
|
After you followed the above steps, execute the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
./mapg user:add EMAIL USERNAME PASSWORD admin
|
scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
**Warning: Because of a known issue the image `mapguesser_multi` fails to run without the installation steps. You have to relauch `docker-compose up -d` after you finished the installation process.**
|
||||||
|
|
||||||
### Set environment variables
|
**And you are done!** The application is ready to use and develop. In development mode an administrative user is also created by the installation script, email is **mapg@mapg.dev**, password is **123456**. In production mode you should create the first administrative user with the following command:
|
||||||
|
|
||||||
`.env.example` should be copied to `.env` into the repo root. Only the variables for external dependencies (API keys, map attribution, etc.) should be adapted in. All other variables (for DB connection, static root, mailing, multiplayer, etc.) are fine with the default value. **`DEV=1` should be set for development!**
|
```
|
||||||
|
./mapg user:add EMAIL PASSWORD admin
|
||||||
### Docker Compose
|
|
||||||
|
|
||||||
Execute the following command from the repo root:
|
|
||||||
```bash
|
|
||||||
docker compose up -d
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**And you are done!** You can reach the application on http://localhost. The mails that are sent by the application can be found on http://localhost:8080. If needed, the database server can be directly reached on localhost:3306, or you can use Adminer web interface on http://localhost:9090
|
If you installed it in the Docker stack, you can reach it on http://localhost. The mails that are sent by the application can be found on http://localhost:8080/. If needed, the database server can be directly reached on localhost:3306.
|
||||||
|
|
||||||
You might have to attach to the `app` container, e.g. for creating users, `composer update`, etc.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -3,18 +3,15 @@
|
|||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "MapGuesser Application",
|
"description": "MapGuesser Application",
|
||||||
"license": "GNU GPL 3.0",
|
"license": "GNU GPL 3.0",
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"url": "https://git.esoko.eu/esoko/soko-web.git",
|
|
||||||
"type": "git"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"require": {
|
"require": {
|
||||||
"esoko/soko-web": "0.15"
|
"vlucas/phpdotenv": "^4.1",
|
||||||
|
"symfony/console": "^5.1",
|
||||||
|
"phpmailer/phpmailer": "^6.1",
|
||||||
|
"fzaninotto/faker": "^1.9"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^10.3",
|
"phpunit/phpunit": "^9",
|
||||||
"phpstan/phpstan": "^1.10"
|
"phpstan/phpstan": "^0.12.32"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
2018
composer.lock
generated
2018
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -15,14 +15,6 @@ CREATE TABLE `maps` (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE `migrations` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`migration` varchar(255) NOT NULL,
|
|
||||||
`type` enum('structure', 'data') NOT NULL,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `places`;
|
DROP TABLE IF EXISTS `places`;
|
||||||
CREATE TABLE `places` (
|
CREATE TABLE `places` (
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
1
database/migrations/data/20200602_2306_migrations.php
Normal file
1
database/migrations/data/20200602_2306_migrations.php
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?php //empty on purpose
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use SokoWeb\Database\Query\Modify;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection, 'maps');
|
$select = new Select(\Container::$dbConnection, 'maps');
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use SokoWeb\Database\Query\Modify;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection, 'users');
|
$select = new Select(\Container::$dbConnection, 'users');
|
||||||
$select->columns(['id']);
|
$select->columns(['id']);
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\Repository\UserRepository;
|
|
||||||
use MapGuesser\Util\UsernameGenerator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
|
|
||||||
$select = new Select(Container::$dbConnection);
|
|
||||||
$users = Container::$persistentDataManager->selectMultipleFromDb($select, User::class);
|
|
||||||
$userRepository = new UserRepository();
|
|
||||||
$usernameGenerator = new UsernameGenerator();
|
|
||||||
|
|
||||||
foreach ($users as $user) {
|
|
||||||
do {
|
|
||||||
$username = $usernameGenerator->generate();
|
|
||||||
} while ($userRepository->getByUsername($username));
|
|
||||||
|
|
||||||
$user->setUsername($username);
|
|
||||||
Container::$persistentDataManager->saveToDb($user);
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE `migrations` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`migration` varchar(255) NOT NULL,
|
||||||
|
`type` enum('structure', 'data') NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
@ -1,12 +0,0 @@
|
|||||||
CREATE TABLE `user_played_place` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
|
||||||
`place_id` int(10) unsigned NOT NULL,
|
|
||||||
`last_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
`occurrences` int(10) NOT NULL DEFAULT 1,
|
|
||||||
PRIMARY KEY(`id`),
|
|
||||||
KEY `user_id` (`user_id`),
|
|
||||||
KEY `place_id` (`place_id`),
|
|
||||||
CONSTRAINT `user_played_place_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
|
|
||||||
CONSTRAINT `user_played_place_place_id` FOREIGN KEY (`place_id`) REFERENCES `places` (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
@ -1,54 +0,0 @@
|
|||||||
CREATE TABLE `challenges` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`token` int(10) unsigned NOT NULL,
|
|
||||||
`time_limit` int(10) unsigned,
|
|
||||||
`time_limit_type` enum('game', 'round') NOT NULL DEFAULT 'game',
|
|
||||||
`no_move` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`no_pan` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`no_zoom` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
||||||
|
|
||||||
CREATE TABLE `user_in_challenge` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
|
||||||
`challenge_id` int(10) unsigned NOT NULL,
|
|
||||||
`current_round` smallint(5) signed NOT NULL DEFAULT 0,
|
|
||||||
`time_left` int(10) unsigned,
|
|
||||||
`is_owner` tinyint(1) NOT NULL DEFAULT 0,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `user_id` (`user_id`),
|
|
||||||
KEY `challenge_id` (`challenge_id`),
|
|
||||||
CONSTRAINT `user_in_challenge_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
|
|
||||||
CONSTRAINT `user_in_challenge_challenge_id` FOREIGN KEY (`challenge_id`) REFERENCES `challenges` (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
||||||
|
|
||||||
CREATE TABLE `place_in_challenge` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`place_id` int(10) unsigned NOT NULL,
|
|
||||||
`challenge_id` int(10) unsigned NOT NULL,
|
|
||||||
`round` smallint(5) unsigned NOT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `place_id` (`place_id`),
|
|
||||||
KEY `challenge_id` (`challenge_id`),
|
|
||||||
CONSTRAINT `place_in_challenge_place_id` FOREIGN KEY (`place_id`) REFERENCES `places` (`id`),
|
|
||||||
CONSTRAINT `place_in_challenge_challenge_id` FOREIGN KEY (`challenge_id`) REFERENCES `challenges` (`id`),
|
|
||||||
CONSTRAINT `unique_order_in_challenge` UNIQUE (`round`, `challenge_id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
||||||
|
|
||||||
CREATE TABLE `guesses` (
|
|
||||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`user_id` int(10) unsigned NOT NULL,
|
|
||||||
`place_in_challenge_id` int(10) unsigned NOT NULL,
|
|
||||||
`lat` decimal(8,6) NOT NULL,
|
|
||||||
`lng` decimal(9,6) NOT NULL,
|
|
||||||
`score` int(10) NOT NULL,
|
|
||||||
`distance` int(10) NOT NULL,
|
|
||||||
`time_spent` int(10),
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
KEY `user_id` (`user_id`),
|
|
||||||
KEY `place_in_challenge_id` (`place_in_challenge_id`),
|
|
||||||
CONSTRAINT `guesses_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`),
|
|
||||||
CONSTRAINT `guesses_place_in_challenge_id` FOREIGN KEY (`place_in_challenge_id`) REFERENCES `place_in_challenge` (`id`)
|
|
||||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
|
|
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE `maps`
|
|
||||||
ADD `unlisted` TINYINT(1) NOT NULL DEFAULT 0;
|
|
@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE `users`
|
|
||||||
ADD `username` VARCHAR(255) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL AFTER `email`,
|
|
||||||
ADD UNIQUE `username` (`username`);
|
|
@ -2,20 +2,22 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./docker
|
||||||
dockerfile: docker/Dockerfile
|
dockerfile: Dockerfile-app
|
||||||
target: mapg_dev
|
|
||||||
depends_on:
|
|
||||||
mariadb:
|
|
||||||
condition: service_healthy
|
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
|
volumes:
|
||||||
|
- .:/var/www/mapguesser
|
||||||
|
multi:
|
||||||
|
build:
|
||||||
|
context: ./docker
|
||||||
|
dockerfile: Dockerfile-multi
|
||||||
|
ports:
|
||||||
- 5000:5000
|
- 5000:5000
|
||||||
- 8090:8090
|
- 8090:8090
|
||||||
- 9229:9229
|
- 9229:9229
|
||||||
volumes:
|
volumes:
|
||||||
- .:/var/www/mapguesser
|
- .:/var/www/mapguesser
|
||||||
working_dir: /var/www/mapguesser
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:10.3
|
image: mariadb:10.3
|
||||||
ports:
|
ports:
|
||||||
@ -27,19 +29,6 @@ services:
|
|||||||
MYSQL_DATABASE: 'mapguesser'
|
MYSQL_DATABASE: 'mapguesser'
|
||||||
MYSQL_USER: 'mapguesser'
|
MYSQL_USER: 'mapguesser'
|
||||||
MYSQL_PASSWORD: 'mapguesser'
|
MYSQL_PASSWORD: 'mapguesser'
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "mysqladmin -u $$MYSQL_USER -p$$MYSQL_PASSWORD ping -h localhost || exit 1"]
|
|
||||||
start_period: 5s
|
|
||||||
start_interval: 1s
|
|
||||||
interval: 5s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
adminer:
|
|
||||||
image: adminer:4.8.1-standalone
|
|
||||||
ports:
|
|
||||||
- 9090:8080
|
|
||||||
environment:
|
|
||||||
- ADMINER_DEFAULT_SERVER=mariadb
|
|
||||||
mail:
|
mail:
|
||||||
image: marcopas/docker-mailslurper:latest
|
image: marcopas/docker-mailslurper:latest
|
||||||
ports:
|
ports:
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
FROM ubuntu:22.04 AS mapg_base
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
|
|
||||||
RUN apt update --fix-missing && apt install -y sudo curl git unzip mariadb-client nginx \
|
|
||||||
php-apcu php8.1-cli php8.1-curl php8.1-fpm php8.1-mbstring php8.1-mysql php8.1-zip php8.1-xml
|
|
||||||
|
|
||||||
RUN mkdir -p /run/php
|
|
||||||
COPY docker/configs/nginx.conf /etc/nginx/sites-available/default
|
|
||||||
|
|
||||||
COPY docker/scripts/install-composer.sh install-composer.sh
|
|
||||||
RUN ./install-composer.sh
|
|
||||||
|
|
||||||
COPY docker/scripts/install-nodejs.sh install-nodejs.sh
|
|
||||||
RUN ./install-nodejs.sh
|
|
||||||
RUN npm install -g uglify-js clean-css-cli svgo yarn
|
|
||||||
|
|
||||||
|
|
||||||
FROM mapg_base AS mapg_dev
|
|
||||||
|
|
||||||
RUN apt update --fix-missing && apt install -y php-xdebug
|
|
||||||
|
|
||||||
RUN echo "xdebug.remote_enable = 1" >> /etc/php/8.1/mods-available/xdebug.ini &&\
|
|
||||||
echo "xdebug.remote_autostart = 1" >> /etc/php/8.1/mods-available/xdebug.ini &&\
|
|
||||||
echo "xdebug.remote_connect_back = 1" >> /etc/php/8.1/mods-available/xdebug.ini
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 5000
|
|
||||||
EXPOSE 8090
|
|
||||||
EXPOSE 9229
|
|
||||||
ENTRYPOINT docker/scripts/entry-point-dev.sh
|
|
||||||
|
|
||||||
|
|
||||||
FROM mapg_base AS mapg_release
|
|
||||||
|
|
||||||
RUN apt update --fix-missing && apt install -y cron
|
|
||||||
|
|
||||||
WORKDIR /var/www/mapguesser
|
|
||||||
COPY ./ /var/www/mapguesser
|
|
||||||
RUN rm -rf /var/www/mapguesser/.git
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 8090
|
|
||||||
ENTRYPOINT docker/scripts/entry-point.sh
|
|
30
docker/Dockerfile-app
Normal file
30
docker/Dockerfile-app
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Install Nginx, PHP and further necessary packages
|
||||||
|
RUN apt update --fix-missing
|
||||||
|
RUN apt install -y curl git unzip mariadb-client nginx \
|
||||||
|
php-apcu php-xdebug php7.4-cli php7.4-curl php7.4-fpm php7.4-mbstring php7.4-mysql php7.4-zip php7.4-xml
|
||||||
|
|
||||||
|
# Configure Nginx with PHP
|
||||||
|
RUN mkdir -p /run/php
|
||||||
|
COPY configs/nginx.conf /etc/nginx/sites-available/default
|
||||||
|
RUN echo "xdebug.remote_enable = 1" >> /etc/php/7.4/mods-available/xdebug.ini
|
||||||
|
RUN echo "xdebug.remote_autostart = 1" >> /etc/php/7.4/mods-available/xdebug.ini
|
||||||
|
RUN echo "xdebug.remote_connect_back = 1" >> /etc/php/7.4/mods-available/xdebug.ini
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
COPY scripts/install-composer.sh install-composer.sh
|
||||||
|
RUN ./install-composer.sh
|
||||||
|
|
||||||
|
# Install Node.js and required packages
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||||
|
RUN apt install -y nodejs
|
||||||
|
RUN npm install -g uglify-js clean-css-cli svgo yarn
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
VOLUME /var/www/mapguesser
|
||||||
|
WORKDIR /var/www/mapguesser
|
||||||
|
|
||||||
|
ENTRYPOINT /usr/sbin/php-fpm7.4 -F & /usr/sbin/nginx -g 'daemon off;'
|
16
docker/Dockerfile-multi
Normal file
16
docker/Dockerfile-multi
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM ubuntu:focal
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Install necessary packages
|
||||||
|
RUN apt update --fix-missing
|
||||||
|
RUN apt install -y curl build-essential
|
||||||
|
|
||||||
|
# Install Node.js and required packages
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
|
||||||
|
RUN apt install -y nodejs
|
||||||
|
|
||||||
|
VOLUME /var/www/mapguesser
|
||||||
|
WORKDIR /var/www/mapguesser
|
||||||
|
|
||||||
|
ENTRYPOINT /usr/bin/node --inspect=0.0.0.0:9229 multi
|
@ -1,15 +1,11 @@
|
|||||||
map $http_x_forwarded_proto $forwarded_scheme {
|
|
||||||
default $scheme;
|
|
||||||
http http;
|
|
||||||
https https;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80 default_server;
|
listen 80 default_server;
|
||||||
listen [::]:80 default_server;
|
listen [::]:80 default_server;
|
||||||
|
|
||||||
root /var/www/mapguesser/public;
|
root /var/www/mapguesser/public;
|
||||||
|
|
||||||
index index.php index.html index.htm index.nginx-debian.html;
|
index index.php index.html index.htm index.nginx-debian.html;
|
||||||
|
|
||||||
server_name mapguesser-dev.ch;
|
server_name mapguesser-dev.ch;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
@ -18,8 +14,7 @@ server {
|
|||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
include snippets/fastcgi-php.conf;
|
include snippets/fastcgi-php.conf;
|
||||||
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
|
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||||
fastcgi_param REQUEST_SCHEME $forwarded_scheme;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location ~ /\.ht {
|
location ~ /\.ht {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
0 * * * * /var/www/mapguesser/mapg db:maintain
|
|
@ -1,40 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Installing Composer packages..."
|
|
||||||
if [ -f .env ]; then
|
|
||||||
composer install
|
|
||||||
else
|
|
||||||
composer create-project
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Installing NPM packages..."
|
|
||||||
(cd multi && npm install)
|
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
|
||||||
(cd public/static && yarn install)
|
|
||||||
|
|
||||||
echo "Migrating DB..."
|
|
||||||
./mapg db:migrate
|
|
||||||
|
|
||||||
echo "Set runner user based on owner of .env..."
|
|
||||||
if ! getent group mapg; then
|
|
||||||
USER_GID=$(stat -c "%g" .env)
|
|
||||||
groupadd --gid $USER_GID mapg
|
|
||||||
fi
|
|
||||||
if ! id -u mapg; then
|
|
||||||
USER_UID=$(stat -c "%u" .env)
|
|
||||||
useradd --uid $USER_UID --gid $USER_GID mapg
|
|
||||||
fi
|
|
||||||
sed -i -e "s/^user = .*$/user = mapg/g" -e "s/^group = .*$/group = mapg/g" /etc/php/8.1/fpm/pool.d/www.conf
|
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
/usr/sbin/php-fpm8.1 -F &
|
|
||||||
/usr/sbin/nginx -g 'daemon off;' &
|
|
||||||
sudo -u mapg -g mapg /usr/bin/node --inspect=0.0.0.0:9229 multi &
|
|
||||||
|
|
||||||
wait -n
|
|
||||||
|
|
||||||
exit $?
|
|
@ -1,32 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Migrating DB..."
|
|
||||||
./mapg db:migrate
|
|
||||||
|
|
||||||
echo "Installing crontab..."
|
|
||||||
/usr/bin/crontab docker/scripts/cron
|
|
||||||
|
|
||||||
echo "Set runner user based on owner of .env..."
|
|
||||||
if ! getent group mapg; then
|
|
||||||
USER_GID=$(stat -c "%g" .env)
|
|
||||||
groupadd --gid $USER_GID mapg
|
|
||||||
fi
|
|
||||||
if ! id -u mapg; then
|
|
||||||
USER_UID=$(stat -c "%u" .env)
|
|
||||||
useradd --uid $USER_UID --gid $USER_GID mapg
|
|
||||||
fi
|
|
||||||
chown mapg:mapg cache
|
|
||||||
sed -i -e "s/^user = .*$/user = mapg/g" -e "s/^group = .*$/group = mapg/g" /etc/php/8.1/fpm/pool.d/www.conf
|
|
||||||
|
|
||||||
set +e
|
|
||||||
|
|
||||||
/usr/sbin/cron -f &
|
|
||||||
/usr/sbin/php-fpm8.1 -F &
|
|
||||||
/usr/sbin/nginx -g 'daemon off;' &
|
|
||||||
sudo -u mapg -g mapg /usr/bin/node multi &
|
|
||||||
|
|
||||||
wait -n
|
|
||||||
|
|
||||||
exit $?
|
|
@ -1,14 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
apt update
|
|
||||||
apt install -y ca-certificates curl gnupg
|
|
||||||
mkdir -p /etc/apt/keyrings
|
|
||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
||||||
|
|
||||||
NODE_MAJOR=18
|
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
|
||||||
|
|
||||||
apt update
|
|
||||||
apt install -y nodejs
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "Installing Composer packages..."
|
|
||||||
composer create-project --no-dev
|
|
||||||
|
|
||||||
echo "Installing NPM packages..."
|
|
||||||
(cd multi && npm install)
|
|
||||||
|
|
||||||
echo "Installing Yarn packages..."
|
|
||||||
(cd public/static && yarn install)
|
|
||||||
|
|
||||||
echo "Updating version info..."
|
|
||||||
VERSION=$(git describe --tags --always --match "Release_*" HEAD)
|
|
||||||
REVISION=$(git rev-parse --short HEAD)
|
|
||||||
REVISION_DATE=$(git show -s --format=%aI HEAD)
|
|
||||||
sed -i -E "s/const VERSION = '(.*)';/const VERSION = '${VERSION}';/" main.php
|
|
||||||
sed -i -E "s/const REVISION = '(.*)';/const REVISION = '${REVISION}';/" main.php
|
|
||||||
sed -i -E "s/const REVISION_DATE = '(.*)';/const REVISION_DATE = '${REVISION_DATE}';/" main.php
|
|
||||||
|
|
||||||
echo "Minifying JS, CSS and SVG files..."
|
|
||||||
find public/static/js -type f -iname '*.js' -exec uglifyjs {} -c -m -o {} \;
|
|
||||||
find public/static/css -type f -iname '*.css' -exec cleancss {} -o {} \;
|
|
||||||
find public/static/img -type f -iname '*.svg' -exec svgo {} -o {} \;
|
|
||||||
|
|
||||||
echo "Linking view files..."
|
|
||||||
./mapg view:link
|
|
||||||
|
|
||||||
rm .env
|
|
12
main.php
12
main.php
@ -14,12 +14,10 @@ $dotenv->load();
|
|||||||
|
|
||||||
class Container
|
class Container
|
||||||
{
|
{
|
||||||
static SokoWeb\Interfaces\Database\IConnection $dbConnection;
|
static MapGuesser\Interfaces\Database\IConnection $dbConnection;
|
||||||
static SokoWeb\Interfaces\PersistentData\IPersistentDataManager $persistentDataManager;
|
static MapGuesser\Routing\RouteCollection $routeCollection;
|
||||||
static SokoWeb\Interfaces\Routing\IRouteCollection $routeCollection;
|
static MapGuesser\Interfaces\Session\ISessionHandler $sessionHandler;
|
||||||
static SokoWeb\Interfaces\Session\ISessionHandler $sessionHandler;
|
static MapGuesser\Interfaces\Request\IRequest $request;
|
||||||
static SokoWeb\Interfaces\Request\IRequest $request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Container::$dbConnection = new SokoWeb\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
|
||||||
Container::$persistentDataManager = new SokoWeb\PersistentData\PersistentDataManager(Container::$dbConnection);
|
|
||||||
|
2
mapg
2
mapg
@ -8,6 +8,6 @@ $app = new Symfony\Component\Console\Application('MapGuesser Console', '');
|
|||||||
$app->add(new MapGuesser\Cli\MigrateDatabaseCommand());
|
$app->add(new MapGuesser\Cli\MigrateDatabaseCommand());
|
||||||
$app->add(new MapGuesser\Cli\AddUserCommand());
|
$app->add(new MapGuesser\Cli\AddUserCommand());
|
||||||
$app->add(new MapGuesser\Cli\LinkViewCommand());
|
$app->add(new MapGuesser\Cli\LinkViewCommand());
|
||||||
$app->add(new MapGuesser\Cli\MaintainDatabaseCommand());
|
$app->add(new \MapGuesser\Cli\MaintainDatabaseCommand());
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
|
36
multi/package-lock.json
generated
36
multi/package-lock.json
generated
@ -1,43 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "mapguesser-multi",
|
"name": "mapguesser-multi",
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"lockfileVersion": 1,
|
||||||
"": {
|
|
||||||
"name": "mapguesser-multi",
|
|
||||||
"license": "GNU AGPL 3.0",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": {
|
||||||
"ws": "^7.4.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dotenv": {
|
|
||||||
"version": "8.2.0",
|
"version": "8.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||||
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==",
|
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"ws": {
|
||||||
"version": "7.4.4",
|
"version": "7.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
||||||
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
|
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw=="
|
||||||
"engines": {
|
|
||||||
"node": ">=8.3.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": "^5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,53 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require '../web.php';
|
require '../web.php';
|
||||||
|
|
||||||
|
$method = strtolower($_SERVER['REQUEST_METHOD']);
|
||||||
|
$url = substr($_SERVER['REQUEST_URI'], strlen('/'));
|
||||||
|
if (($pos = strpos($url, '?')) !== false) {
|
||||||
|
$url = substr($url, 0, $pos);
|
||||||
|
}
|
||||||
|
$url = rawurldecode($url);
|
||||||
|
|
||||||
|
$match = Container::$routeCollection->match($method, $url == '' ? [] : explode('/', $url));
|
||||||
|
|
||||||
|
if ($match !== null) {
|
||||||
|
list($route, $params) = $match;
|
||||||
|
|
||||||
|
Container::$request->setParsedRouteParams($params);
|
||||||
|
|
||||||
|
$handler = $route->getHandler();
|
||||||
|
$controller = new $handler[0](Container::$request);
|
||||||
|
|
||||||
|
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
|
||||||
|
$authorized = $controller->authorize();
|
||||||
|
} else {
|
||||||
|
$authorized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
|
||||||
|
$content = new MapGuesser\Response\JsonContent(['error' => 'no_valid_anti_csrf_token']);
|
||||||
|
header('Content-Type: text/html; charset=UTF-8', true, 403);
|
||||||
|
$content->render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authorized) {
|
||||||
|
$response = call_user_func([$controller, $handler[1]]);
|
||||||
|
|
||||||
|
if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
|
||||||
|
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
||||||
|
$response->render();
|
||||||
|
|
||||||
|
return;
|
||||||
|
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||||
|
header('Location: ' . $response->getUrl(), true, $response->getHttpCode());
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = new MapGuesser\Response\HtmlContent('error/404');
|
||||||
|
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
||||||
|
$content->render();
|
||||||
|
@ -15,17 +15,6 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
z-index: 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
#panningBlockerCover {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0;
|
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +22,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
right: 20px;
|
right: 20px;
|
||||||
z-index: 3;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#guess.result {
|
#guess.result {
|
||||||
@ -164,47 +153,6 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#goToStart {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#highscoresTable {
|
|
||||||
margin: 1em;
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#highscoresTable td, #highscoresTable th {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#highscoresTable tr:nth-child(even) {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#highscoresTable tr:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
#highscoresTable th {
|
|
||||||
padding-top: 12px;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
text-align: left;
|
|
||||||
background-color: #e8a349;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#highscoresTable tr.ownPlayer {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 899px) {
|
|
||||||
.hideOnNarrowScreen {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
#mapName {
|
#mapName {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -31,11 +31,11 @@ main {
|
|||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
p, h1, h2, h3, input, textarea, select, button, a, table, label {
|
p, h1, h2, input, textarea, select, button, a {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3 {
|
h1, h2 {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,11 +55,7 @@ h2, header.small h1 {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
p, h2 {
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p, h2, h3 {
|
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +237,7 @@ button.green:enabled:hover, button.green:enabled:focus, a.button.green:hover, a.
|
|||||||
background-color: #1b7d31;
|
background-color: #1b7d31;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text, select, textarea {
|
input, select, textarea {
|
||||||
background-color: #f9fafb;
|
background-color: #f9fafb;
|
||||||
border: solid #c8d2e1 1px;
|
border: solid #c8d2e1 1px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@ -250,26 +246,22 @@ input.text, select, textarea {
|
|||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text, select {
|
input, select {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=checkbox], input[type=radio] {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text.big, select.big, textarea.big, div.inputWithButton>input.text {
|
input.big, select.big, textarea.big, div.inputWithButton>input {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text.big, select.big, div.inputWithButton>input.text {
|
input.big, select.big, div.inputWithButton>input {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
line-height: 35px;
|
line-height: 35px;
|
||||||
padding: 0 6px;
|
padding: 0 6px;
|
||||||
@ -284,19 +276,19 @@ input.fullWidth, select.fullWidth, textarea.fullWidth {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text:disabled, select:disabled, textarea:disabled {
|
input:disabled, select:disabled, textarea:disabled {
|
||||||
background-color: #dfdfdf;
|
background-color: #dfdfdf;
|
||||||
border: solid #dfdfdf 1px;
|
border: solid #dfdfdf 1px;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text:focus, select:focus, textarea:focus {
|
input:focus, select:focus, textarea:focus {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: solid #29457f 2px;
|
border: solid #29457f 2px;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text:focus, select:focus {
|
input:focus, select:focus {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,16 +296,16 @@ textarea:focus {
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.text.big:focus, select.big:focus {
|
input.big:focus, select.big:focus {
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.inputWithButton>input.text {
|
div.inputWithButton>input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 83px 0 6px;
|
padding: 0 83px 0 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.inputWithButton>input.text:focus {
|
div.inputWithButton>input:focus {
|
||||||
padding: 0 82px 0 5px;
|
padding: 0 82px 0 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +368,7 @@ header>p>span {
|
|||||||
padding-left: 6px;
|
padding-left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
header>p>span>a:link, header>p>span>a:visited, footer>p>a:link, footer>p>a:visited {
|
header>p>span>a:link, header>p>span>a:visited {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,14 +445,11 @@ div.box {
|
|||||||
|
|
||||||
.circleControl {
|
.circleControl {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 60px;
|
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.circleControl .controlItem {
|
.circleControl .controlItem {
|
||||||
position: relative;
|
|
||||||
height: 60px;
|
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
opacity: 70%;
|
opacity: 70%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -472,8 +461,6 @@ div.box {
|
|||||||
|
|
||||||
.circleControl .controlItem div {
|
.circleControl .controlItem div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.circleControl .controlBackground {
|
.circleControl .controlBackground {
|
||||||
@ -488,6 +475,7 @@ div.box {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
margin-top: 50%;
|
margin-top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
|
-ms-transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 599px) {
|
@media screen and (max-width: 599px) {
|
||||||
@ -542,6 +530,12 @@ div.box {
|
|||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
.circleControl {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.circleControl .controlItem {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-height: 399px) {
|
@media screen and (max-height: 399px) {
|
||||||
@ -551,12 +545,6 @@ div.box {
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
.circleControl {
|
|
||||||
width: 45px;
|
|
||||||
}
|
|
||||||
.circleControl .controlItem {
|
|
||||||
height: 45px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-height: 400px) and (max-height: 499px) {
|
@media screen and (min-height: 400px) and (max-height: 499px) {
|
||||||
|
@ -13,10 +13,6 @@ div.mapItem.new {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.mapItem.unlisted {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.mapItem>div.title {
|
div.mapItem>div.title {
|
||||||
background-color: #28a745;
|
background-color: #28a745;
|
||||||
color: white;
|
color: white;
|
||||||
@ -79,10 +75,6 @@ div.mapItem>div.buttonContainer {
|
|||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#timeLimitType {
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1504px) {
|
@media screen and (min-width: 1504px) {
|
||||||
#mapContainer {
|
#mapContainer {
|
||||||
grid-template-columns: auto auto auto auto;
|
grid-template-columns: auto auto auto auto;
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
var Game = {
|
var Game = {
|
||||||
NUMBER_OF_ROUNDS: 5,
|
NUMBER_OF_ROUNDS: 5,
|
||||||
MAX_SCORE: 1000,
|
MAX_SCORE: 1000,
|
||||||
|
|
||||||
type: GameType.SINGLE,
|
|
||||||
mapBounds: null,
|
mapBounds: null,
|
||||||
multi: { token: null, owner: false },
|
multi: { token: null, owner: false },
|
||||||
rounds: [],
|
rounds: [],
|
||||||
@ -19,8 +16,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
guessMarker: null,
|
guessMarker: null,
|
||||||
adaptGuess: false,
|
adaptGuess: false,
|
||||||
googleLink: null,
|
googleLink: null,
|
||||||
history: [],
|
|
||||||
restrictions: null,
|
|
||||||
|
|
||||||
readyToContinue: true,
|
readyToContinue: true,
|
||||||
timeoutEnd: null,
|
timeoutEnd: null,
|
||||||
@ -216,24 +211,22 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getGameIdentifier: function () {
|
|
||||||
switch (Game.type) {
|
|
||||||
case GameType.SINGLE:
|
|
||||||
return '/game/' + mapId;
|
|
||||||
case GameType.MULTI:
|
|
||||||
return '/multiGame/' + roomId;
|
|
||||||
case GameType.CHALLENGE:
|
|
||||||
return '/challenge/' + challengeToken;
|
|
||||||
default:
|
|
||||||
return '/game/' + mapId;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
prepare: function () {
|
prepare: function () {
|
||||||
var data = new FormData();
|
var data = new FormData();
|
||||||
|
var userNames;
|
||||||
|
|
||||||
|
if (roomId) {
|
||||||
|
var userNames = localStorage.userNames ? JSON.parse(localStorage.userNames) : {};
|
||||||
|
if (!userNames.hasOwnProperty(roomId)) {
|
||||||
|
userNames[roomId] = prompt('Your name: ');
|
||||||
|
localStorage.userNames = JSON.stringify(userNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.append('userName', userNames[roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
var url = Game.getGameIdentifier() + '/prepare.json';
|
var url = roomId ? '/multiGame/' + roomId + '/prepare.json' : '/game/' + mapId + '/prepare.json';
|
||||||
MapGuesser.httpRequest('POST', url, function () {
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
@ -276,7 +269,7 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
MapGuesser.httpRequest('POST', Game.getGameIdentifier() + '/initialData.json', function () {
|
MapGuesser.httpRequest('POST', '/game/' + mapId + '/initialData.json', function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
document.getElementById('panoCover').style.visibility = 'hidden';
|
document.getElementById('panoCover').style.visibility = 'hidden';
|
||||||
|
|
||||||
@ -285,199 +278,24 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.loadHistory(this.response);
|
|
||||||
|
|
||||||
Game.restrictions = this.response.restrictions;
|
|
||||||
Game.displayRestrictions();
|
|
||||||
|
|
||||||
if (this.response.finished) {
|
|
||||||
|
|
||||||
Game.transitToResultMap();
|
|
||||||
Game.showSummary();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Game.panoId = this.response.place.panoId;
|
Game.panoId = this.response.place.panoId;
|
||||||
Game.pov = this.response.place.pov;
|
Game.pov = this.response.place.pov;
|
||||||
|
|
||||||
Game.startNewRound();
|
for (var i = 0; i < this.response.history.length; ++i) {
|
||||||
|
var round = this.response.history[i];
|
||||||
|
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
|
||||||
|
Game.addPositionToResultMap(true);
|
||||||
|
Game.addGuessPositionToResultMap(round.result.guessPosition, null, true);
|
||||||
|
Game.scoreSum += round.result.score;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
document.getElementById('currentRound').innerHTML = String(Game.rounds.length) + '/' + String(Game.NUMBER_OF_ROUNDS);
|
||||||
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
document.getElementById('currentScoreSum').innerHTML = String(Game.scoreSum) + '/' + String(Game.rounds.length * Game.MAX_SCORE);
|
||||||
|
|
||||||
|
Game.startNewRound();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
enableRestrictions: function () {
|
|
||||||
if (!Game.restrictions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Game.panorama.setOptions({
|
|
||||||
clickToGo: !Game.restrictions.noMove,
|
|
||||||
linksControl: !(Game.restrictions.noMove || Game.restrictions.noPan),
|
|
||||||
scrollwheel: !Game.restrictions.noZoom
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Game.restrictions.noPan) {
|
|
||||||
document.getElementById('panningBlockerCover').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.restrictions.timeLimit) {
|
|
||||||
Game.startCountdown(Game.restrictions.timeLimit, function () {
|
|
||||||
Game.guess();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
displayRestrictions: function () {
|
|
||||||
if (!Game.restrictions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var restrictionsForDisplay = [];
|
|
||||||
if (Game.restrictions.timeLimit) {
|
|
||||||
restrictionsForDisplay.push('time limit per ' + Game.restrictions.timeLimitType);
|
|
||||||
}
|
|
||||||
if (Game.restrictions.noPan) {
|
|
||||||
restrictionsForDisplay.push('no camera change');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (Game.restrictions.noMove) {
|
|
||||||
restrictionsForDisplay.push('no move');
|
|
||||||
}
|
|
||||||
if (Game.restrictions.noZoom) {
|
|
||||||
restrictionsForDisplay.push('no zoom');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restrictionsForDisplay.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create restrictions span for header
|
|
||||||
var restrictions = document.createElement('span');
|
|
||||||
restrictions.setAttribute('id', 'restrictions');
|
|
||||||
restrictions.setAttribute('class', 'hideOnNarrowScreen');
|
|
||||||
var restrictionsTitle = document.createElement('span');
|
|
||||||
restrictionsTitle.setAttribute('class', 'bold');
|
|
||||||
restrictionsTitle.innerText = 'Restrictions: ';
|
|
||||||
var restrictionsList = document.createElement('span');
|
|
||||||
restrictionsList.innerText = restrictionsForDisplay.join(', ');
|
|
||||||
restrictions.appendChild(restrictionsTitle);
|
|
||||||
restrictions.appendChild(restrictionsList);
|
|
||||||
|
|
||||||
var roundContainer = document.getElementById('roundContainer');
|
|
||||||
var header = roundContainer.parentNode;
|
|
||||||
header.insertBefore(restrictions, roundContainer);
|
|
||||||
},
|
|
||||||
|
|
||||||
disableRestrictions: function () {
|
|
||||||
|
|
||||||
Game.panorama.setOptions({
|
|
||||||
clickToGo: true,
|
|
||||||
linksControl: true,
|
|
||||||
scrollwheel: true
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('panningBlockerCover').style.display = null;
|
|
||||||
|
|
||||||
Game.startCountdown(0);
|
|
||||||
Game.timeoutEnd = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
hideRestrictions: function () {
|
|
||||||
var restrictions = document.getElementById('restrictions');
|
|
||||||
if (restrictions) {
|
|
||||||
var header = restrictions.parentNode;
|
|
||||||
header.removeChild(restrictions);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
transitToResultMap: function () {
|
|
||||||
// TODO: refactor - it is necessary for mobile
|
|
||||||
if (window.getComputedStyle(document.getElementById('guess')).visibility === 'hidden') {
|
|
||||||
document.getElementById('showGuessButton').click();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.adaptGuess) {
|
|
||||||
document.getElementById('guess').classList.remove('adapt');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Game.guessMarker) {
|
|
||||||
Game.guessMarker.setMap(null);
|
|
||||||
Game.guessMarker = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('guess').classList.add('result');
|
|
||||||
|
|
||||||
Game.map.setOptions({
|
|
||||||
draggableCursor: 'grab'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
|
||||||
document.getElementById('continueButton').style.display = 'none';
|
|
||||||
document.getElementById('showSummaryButton').style.display = 'block';
|
|
||||||
} else if (Game.type == GameType.MULTI) {
|
|
||||||
if (Game.multi.owner) {
|
|
||||||
if (!Game.readyToContinue) {
|
|
||||||
document.getElementById('continueButton').disabled = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
document.getElementById('continueButton').style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadHistory: function (response) {
|
|
||||||
if (!response.history)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Game.history = response.history;
|
|
||||||
|
|
||||||
for (var i = 0; i < Game.rounds.length; ++i) {
|
|
||||||
var round = Game.rounds[i];
|
|
||||||
|
|
||||||
if (round.realMarker) {
|
|
||||||
round.realMarker.setMap(null);
|
|
||||||
}
|
|
||||||
for (var j = 0; j < round.guessMarkers.length; ++j) {
|
|
||||||
var guessMarker = round.guessMarkers[j];
|
|
||||||
guessMarker.marker.setMap(null);
|
|
||||||
guessMarker.line.setMap(null);
|
|
||||||
if (guessMarker.info) {
|
|
||||||
guessMarker.info.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Game.rounds = [];
|
|
||||||
|
|
||||||
Game.scoreSum = 0;
|
|
||||||
for (var i = 0; i < Game.history.length; ++i) {
|
|
||||||
var round = Game.history[i];
|
|
||||||
|
|
||||||
if (round.result) {
|
|
||||||
Game.rounds.push({ position: round.position, guessPosition: round.result.guessPosition, realMarker: null, guessMarkers: [] });
|
|
||||||
Game.addPositionToResultMap(true);
|
|
||||||
if (round.result.guessPosition) {
|
|
||||||
Game.addGuessPositionToResultMap(round.result.guessPosition, round.result, true);
|
|
||||||
}
|
|
||||||
Game.scoreSum += round.result.score;
|
|
||||||
|
|
||||||
|
|
||||||
if (round.allResults !== undefined) {
|
|
||||||
for (var j = 0; j < round.allResults.length; ++j) {
|
|
||||||
var result = round.allResults[j];
|
|
||||||
if (result.guessPosition) {
|
|
||||||
Game.addGuessPositionToResultMap(result.guessPosition, result, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
reset: function () {
|
reset: function () {
|
||||||
if (Game.guessMarker) {
|
if (Game.guessMarker) {
|
||||||
Game.guessMarker.setMap(null);
|
Game.guessMarker.setMap(null);
|
||||||
@ -507,7 +325,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
distanceInfo.children[0].style.display = null;
|
distanceInfo.children[0].style.display = null;
|
||||||
distanceInfo.children[1].style.display = null;
|
distanceInfo.children[1].style.display = null;
|
||||||
distanceInfo.children[2].style.display = null;
|
distanceInfo.children[2].style.display = null;
|
||||||
document.getElementById('summaryInfo').innerHTML = "Game finished."
|
|
||||||
var scoreInfo = document.getElementById('scoreInfo');
|
var scoreInfo = document.getElementById('scoreInfo');
|
||||||
scoreInfo.children[0].style.display = null;
|
scoreInfo.children[0].style.display = null;
|
||||||
scoreInfo.children[1].style.display = null;
|
scoreInfo.children[1].style.display = null;
|
||||||
@ -522,13 +339,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
// needs to be set visible after the show guess map hid it in mobile view
|
// needs to be set visible after the show guess map hid it in mobile view
|
||||||
document.getElementById("navigation").style.visibility = 'visible';
|
document.getElementById("navigation").style.visibility = 'visible';
|
||||||
|
|
||||||
Game.disableRestrictions();
|
|
||||||
Game.hideRestrictions();
|
|
||||||
|
|
||||||
document.getElementById('panningBlockerCover').style.display = null;
|
|
||||||
|
|
||||||
Game.history = [];
|
|
||||||
|
|
||||||
Game.initialize();
|
Game.initialize();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -581,8 +391,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
// update the compass
|
// update the compass
|
||||||
const heading = Game.panorama.getPov().heading;
|
const heading = Game.panorama.getPov().heading;
|
||||||
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
||||||
|
|
||||||
Game.enableRestrictions();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleErrorResponse: function (error) {
|
handleErrorResponse: function (error) {
|
||||||
@ -602,14 +410,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.');
|
MapGuesser.showModalWithContent('Error', 'This game is already started, you cannot join.');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'game_not_found':
|
|
||||||
MapGuesser.showModalWithContent('Error', 'The game was not found by this ID. Please check the link.');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'anonymous_user':
|
|
||||||
MapGuesser.showModalWithContent('Error', 'You have to login to join this game!');
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\'');
|
MapGuesser.showModalWithContent('Error', 'Error code: \'' + error + '\'');
|
||||||
break
|
break
|
||||||
@ -637,7 +437,7 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
resultBounds.extend(position);
|
resultBounds.extend(position);
|
||||||
|
|
||||||
if (guessPosition) {
|
if (guessPosition) {
|
||||||
Game.addGuessPositionToResultMap(guessPosition, result);
|
Game.addGuessPositionToResultMap(guessPosition);
|
||||||
resultBounds.extend(guessPosition);
|
resultBounds.extend(guessPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -673,9 +473,25 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
},
|
},
|
||||||
|
|
||||||
showResultMap: function (result, resultBounds) {
|
showResultMap: function (result, resultBounds) {
|
||||||
|
// TODO: refactor - it is necessary for mobile
|
||||||
|
if (window.getComputedStyle(document.getElementById('guess')).visibility === 'hidden') {
|
||||||
|
document.getElementById('showGuessButton').click();
|
||||||
|
}
|
||||||
|
|
||||||
Game.transitToResultMap();
|
if (Game.adaptGuess) {
|
||||||
|
document.getElementById('guess').classList.remove('adapt');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Game.guessMarker) {
|
||||||
|
Game.guessMarker.setMap(null);
|
||||||
|
Game.guessMarker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('guess').classList.add('result');
|
||||||
|
|
||||||
|
Game.map.setOptions({
|
||||||
|
draggableCursor: 'grab'
|
||||||
|
});
|
||||||
Game.map.fitBounds(resultBounds);
|
Game.map.fitBounds(resultBounds);
|
||||||
|
|
||||||
var distanceInfo = document.getElementById('distanceInfo');
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
@ -694,32 +510,38 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
var scoreBar = document.getElementById('scoreBar');
|
var scoreBar = document.getElementById('scoreBar');
|
||||||
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
||||||
scoreBar.style.width = scoreBarProperties.width;
|
scoreBar.style.width = scoreBarProperties.width;
|
||||||
|
|
||||||
|
if (Game.rounds.length === Game.NUMBER_OF_ROUNDS) {
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
document.getElementById('showSummaryButton').style.display = 'block';
|
||||||
|
} else if (roomId) {
|
||||||
|
if (Game.multi.owner) {
|
||||||
|
if (!Game.readyToContinue) {
|
||||||
|
document.getElementById('continueButton').disabled = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('continueButton').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
guess: function () {
|
guess: function () {
|
||||||
|
if (!Game.guessMarker) {
|
||||||
var data = new FormData();
|
return;
|
||||||
|
|
||||||
if (Game.timeoutEnd) {
|
|
||||||
var timeLeft = Math.ceil((Game.timeoutEnd - new Date()) / 1000);
|
|
||||||
data.append('timeLeft', timeLeft);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.disableRestrictions();
|
|
||||||
|
|
||||||
if (Game.guessMarker) {
|
|
||||||
var guessPosition = Game.guessMarker.getPosition().toJSON();
|
var guessPosition = Game.guessMarker.getPosition().toJSON();
|
||||||
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
|
Game.rounds[Game.rounds.length - 1].guessPosition = guessPosition;
|
||||||
|
|
||||||
data.append('lat', String(guessPosition.lat));
|
|
||||||
data.append('lng', String(guessPosition.lng));
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('guessButton').disabled = true;
|
document.getElementById('guessButton').disabled = true;
|
||||||
document.getElementById('panoCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
var url = Game.getGameIdentifier() + '/guess.json';
|
|
||||||
|
|
||||||
|
var data = new FormData();
|
||||||
|
data.append('lat', String(guessPosition.lat));
|
||||||
|
data.append('lng', String(guessPosition.lng));
|
||||||
|
|
||||||
|
document.getElementById('loading').style.visibility = 'visible';
|
||||||
|
var url = roomId ? '/multiGame/' + roomId + '/guess.json' : '/game/' + mapId + '/guess.json';
|
||||||
MapGuesser.httpRequest('POST', url, function () {
|
MapGuesser.httpRequest('POST', url, function () {
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
document.getElementById('loading').style.visibility = 'hidden';
|
||||||
|
|
||||||
@ -728,16 +550,12 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game.loadHistory(this.response);
|
|
||||||
Game.restrictions = this.response.restrictions;
|
|
||||||
|
|
||||||
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
|
Game.receiveResult(this.response.position, guessPosition, this.response.result, this.response.allResults);
|
||||||
|
|
||||||
if (this.response.place) {
|
if (this.response.place) {
|
||||||
Game.panoId = this.response.place.panoId;
|
Game.panoId = this.response.place.panoId;
|
||||||
Game.pov = this.response.place.pov;
|
Game.pov = this.response.place.pov;
|
||||||
}
|
}
|
||||||
|
|
||||||
}, data);
|
}, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -771,8 +589,8 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
var position = round.position;
|
var position = round.position;
|
||||||
|
|
||||||
var guessMarker = { marker: null, line: null, info: null };
|
var guessMarker = { marker: null, line: null, info: null };
|
||||||
var markerSvg = result && result.userName ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg';
|
var markerSvg = result ? 'marker-gray-empty.svg' : 'marker-blue-empty.svg';
|
||||||
var markerLabel = result && result.userName ? result.userName.charAt(0).toUpperCase() : '?';
|
var markerLabel = result ? result.userName.charAt(0).toUpperCase() : '?';
|
||||||
|
|
||||||
guessMarker.marker = new google.maps.Marker({
|
guessMarker.marker = new google.maps.Marker({
|
||||||
map: Game.map,
|
map: Game.map,
|
||||||
@ -822,9 +640,8 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const userName = result.userName ? result.userName : 'me';
|
|
||||||
guessMarker.info = new google.maps.InfoWindow({
|
guessMarker.info = new google.maps.InfoWindow({
|
||||||
content: '<p class="small bold">' + userName + '</p>' +
|
content: '<p class="small bold">' + result.userName + '</p>' +
|
||||||
'<p class="small">' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points</p>',
|
'<p class="small">' + Util.printDistanceForHuman(result.distance) + ' | ' + result.score + ' points</p>',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -851,36 +668,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
return { width: percent + '%', backgroundColor: color };
|
return { width: percent + '%', backgroundColor: color };
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateHighScores: function () {
|
|
||||||
|
|
||||||
var highscores = new Map();
|
|
||||||
highscores.set('me', Game.scoreSum);
|
|
||||||
|
|
||||||
// collect the results of users who are through the last round
|
|
||||||
const round = Game.history[Game.history.length - 1];
|
|
||||||
if (round.allResults) {
|
|
||||||
for (const result of round.allResults) {
|
|
||||||
highscores.set(result.userName, result.score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add up scores only for the finishers
|
|
||||||
for (var i = Game.history.length - 2; i >= 0; --i) {
|
|
||||||
const round = Game.history[i];
|
|
||||||
if (round.allResults) {
|
|
||||||
for (const result of round.allResults) {
|
|
||||||
if (highscores.has(result.userName)) {
|
|
||||||
highscores.set(result.userName, highscores.get(result.userName) + result.score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sortedHighscores = Array.from(highscores, ([userName, score]) => ({ 'userName': userName, 'score': score }))
|
|
||||||
.sort(function (resultA, resultB) { return resultB.score - resultA.score });
|
|
||||||
return sortedHighscores;
|
|
||||||
},
|
|
||||||
|
|
||||||
showSummary: function () {
|
showSummary: function () {
|
||||||
var distanceInfo = document.getElementById('distanceInfo');
|
var distanceInfo = document.getElementById('distanceInfo');
|
||||||
distanceInfo.children[0].style.display = 'none';
|
distanceInfo.children[0].style.display = 'none';
|
||||||
@ -891,13 +678,11 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
scoreInfo.children[1].style.display = 'block';
|
scoreInfo.children[1].style.display = 'block';
|
||||||
document.getElementById('showSummaryButton').style.display = null;
|
document.getElementById('showSummaryButton').style.display = null;
|
||||||
|
|
||||||
if (Game.type == GameType.SINGLE || Game.multi.owner) {
|
if (!roomId || Game.multi.owner) {
|
||||||
document.getElementById('startNewGameButton').style.display = 'block';
|
document.getElementById('startNewGameButton').style.display = 'block';
|
||||||
if (!Game.readyToContinue) {
|
if (!Game.readyToContinue) {
|
||||||
document.getElementById('startNewGameButton').disabled = true;
|
document.getElementById('startNewGameButton').disabled = true;
|
||||||
}
|
}
|
||||||
} else if (Game.type == GameType.CHALLENGE) {
|
|
||||||
document.getElementById('goToStart').style.display = 'block';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var resultBounds = new google.maps.LatLngBounds();
|
var resultBounds = new google.maps.LatLngBounds();
|
||||||
@ -940,48 +725,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
var scoreBar = document.getElementById('scoreBar');
|
var scoreBar = document.getElementById('scoreBar');
|
||||||
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
scoreBar.style.backgroundColor = scoreBarProperties.backgroundColor;
|
||||||
scoreBar.style.width = scoreBarProperties.width;
|
scoreBar.style.width = scoreBarProperties.width;
|
||||||
|
|
||||||
Game.showHighscores();
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
showHighscores: function () {
|
|
||||||
|
|
||||||
if (Game.type == GameType.CHALLENGE) {
|
|
||||||
var highscores = this.calculateHighScores();
|
|
||||||
var summaryInfo = document.getElementById('summaryInfo');
|
|
||||||
|
|
||||||
if (highscores.length > 2) {
|
|
||||||
var table = document.getElementById('highscoresTable');
|
|
||||||
for (const result of highscores) {
|
|
||||||
var userName = document.createElement('td');
|
|
||||||
userName.innerHTML = result.userName;
|
|
||||||
var score = document.createElement('td');
|
|
||||||
score.innerHTML = result.score;
|
|
||||||
var line = document.createElement('tr');
|
|
||||||
line.appendChild(userName);
|
|
||||||
line.appendChild(score);
|
|
||||||
table.appendChild(line);
|
|
||||||
|
|
||||||
if (result.userName === 'me') {
|
|
||||||
line.setAttribute('class', 'ownPlayer');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MapGuesser.showModal('highscores');
|
|
||||||
} else if (highscores.length == 2) {
|
|
||||||
|
|
||||||
if (highscores[0].userName === 'me') {
|
|
||||||
summaryInfo.innerHTML = 'You won! <span class="hideOnNarrowScreen">' + highscores[1].userName + ' got only ' + highscores[1].score + ' points.</span>';
|
|
||||||
} else {
|
|
||||||
summaryInfo.innerHTML = 'You lost! <span class="hideOnNarrowScreen">' + highscores[0].userName + ' won with ' + highscores[0].score + ' points.</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (highscores.length == 1) {
|
|
||||||
summaryInfo.innerHTML = 'You are the first to finish. <span class="hideOnNarrowScreen">Invite your friends by sending them the link.</span>'
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
rewriteGoogleLink: function () {
|
rewriteGoogleLink: function () {
|
||||||
@ -1004,7 +747,7 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
}, 1);
|
}, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
startCountdown: function (timeout, timedOutHandler) {
|
startCountdown: function (timeout) {
|
||||||
if (Game.countdownHandler) {
|
if (Game.countdownHandler) {
|
||||||
clearInterval(Game.countdownHandler);
|
clearInterval(Game.countdownHandler);
|
||||||
}
|
}
|
||||||
@ -1026,12 +769,7 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
Game.setCountdownTime(timeLeft);
|
Game.setCountdownTime(timeLeft);
|
||||||
|
|
||||||
if (timeLeft <= 0) {
|
if (timeLeft <= 0) {
|
||||||
if (typeof timedOutHandler === 'function') {
|
|
||||||
timedOutHandler();
|
|
||||||
} else {
|
|
||||||
document.getElementById('panoCover').style.visibility = 'visible';
|
document.getElementById('panoCover').style.visibility = 'visible';
|
||||||
}
|
|
||||||
|
|
||||||
clearInterval(Game.countdownHandler);
|
clearInterval(Game.countdownHandler);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@ -1134,12 +872,6 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
document.getElementById("compass").style.transform = "translateY(-50%) rotate(" + heading + "deg)";
|
||||||
});
|
});
|
||||||
|
|
||||||
if (roomId !== null) {
|
|
||||||
Game.type = GameType.MULTI;
|
|
||||||
} else if (challengeToken !== null) {
|
|
||||||
Game.type = GameType.CHALLENGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (COOKIES_CONSENT) {
|
if (COOKIES_CONSENT) {
|
||||||
Game.prepare();
|
Game.prepare();
|
||||||
}
|
}
|
||||||
@ -1229,8 +961,4 @@ const GameType = Object.freeze({ 'SINGLE': 0, 'MULTI': 1, 'CHALLENGE': 2 });
|
|||||||
document.getElementById('compassContainer').onclick = function () {
|
document.getElementById('compassContainer').onclick = function () {
|
||||||
Game.panorama.setPov({ heading: 0, pitch: Game.panorama.getPov().pitch });
|
Game.panorama.setPov({ heading: 0, pitch: Game.panorama.getPov().pitch });
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('closeHighscoresButton').onclick = function () {
|
|
||||||
MapGuesser.hideModal();
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
MapEditor.metadata.name = form.elements.name.value;
|
MapEditor.metadata.name = form.elements.name.value;
|
||||||
MapEditor.metadata.description = form.elements.description.value;
|
MapEditor.metadata.description = form.elements.description.value;
|
||||||
MapEditor.metadata.unlisted = form.elements.unlisted.checked;
|
|
||||||
|
|
||||||
document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]';
|
document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]';
|
||||||
|
|
||||||
@ -87,7 +86,7 @@
|
|||||||
sv.getPanorama({
|
sv.getPanorama({
|
||||||
location: location,
|
location: location,
|
||||||
preference: google.maps.StreetViewPreference.NEAREST,
|
preference: google.maps.StreetViewPreference.NEAREST,
|
||||||
radius: MapEditor.map.getSearchRadius(location),
|
radius: 100,
|
||||||
source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
|
source: canBeIndoor ? google.maps.StreetViewSource.DEFAULT : google.maps.StreetViewSource.OUTDOOR
|
||||||
}, function (data, status) {
|
}, function (data, status) {
|
||||||
var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
|
var panoLocationData = status === google.maps.StreetViewStatus.OK ? data.location : null;
|
||||||
@ -255,9 +254,6 @@
|
|||||||
if (MapEditor.metadata.description !== null) {
|
if (MapEditor.metadata.description !== null) {
|
||||||
data.append('description', MapEditor.metadata.description);
|
data.append('description', MapEditor.metadata.description);
|
||||||
}
|
}
|
||||||
if (MapEditor.metadata.unlisted !== null) {
|
|
||||||
data.append('unlisted', MapEditor.metadata.unlisted);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var placeId in MapEditor.added) {
|
for (var placeId in MapEditor.added) {
|
||||||
if (!MapEditor.added.hasOwnProperty(placeId)) {
|
if (!MapEditor.added.hasOwnProperty(placeId)) {
|
||||||
@ -392,7 +388,7 @@
|
|||||||
|
|
||||||
L.tileLayer(tileUrl, {
|
L.tileLayer(tileUrl, {
|
||||||
attribution: tileAttribution,
|
attribution: tileAttribution,
|
||||||
subdomains: tileSubdomains,
|
subdomains: '1234',
|
||||||
ppi: highResData.ppi,
|
ppi: highResData.ppi,
|
||||||
tileSize: highResData.tileSize,
|
tileSize: highResData.tileSize,
|
||||||
zoomOffset: highResData.zoomOffset,
|
zoomOffset: highResData.zoomOffset,
|
||||||
@ -477,11 +473,7 @@
|
|||||||
LMapWrapper.markers.removeLayer(marker);
|
LMapWrapper.markers.removeLayer(marker);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleStreetViewCover: function () { },
|
toggleStreetViewCover: function () { }
|
||||||
|
|
||||||
getSearchRadius: function (location) {
|
|
||||||
return 100;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var GMapWrapper = {
|
var GMapWrapper = {
|
||||||
@ -634,20 +626,6 @@
|
|||||||
GMapWrapper.streetViewCover.setMap(GMapWrapper.map);
|
GMapWrapper.streetViewCover.setMap(GMapWrapper.map);
|
||||||
GMapWrapper.streetViewCoverOn = true;
|
GMapWrapper.streetViewCoverOn = true;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
getSearchRadius: function (location) {
|
|
||||||
// source: https://www.yorku.ca/mack/CHI01.htm
|
|
||||||
var movementOffset = 4;
|
|
||||||
|
|
||||||
// source: https://groups.google.com/g/google-maps-js-api-v3/c/hDRO4oHVSeM/m/osOYQYXg2oUJ?pli=1
|
|
||||||
var metersPerPixel = 156543.03392 * Math.cos(location.lat * Math.PI / 180) / Math.pow(2, GMapWrapper.map.getZoom());
|
|
||||||
|
|
||||||
var minSearchRadius = 5;
|
|
||||||
|
|
||||||
var searchRadius = Math.max(minSearchRadius, Math.round(movementOffset * metersPerPixel));
|
|
||||||
|
|
||||||
return searchRadius;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,9 +89,6 @@ var MapGuesser = {
|
|||||||
|
|
||||||
formError.style.display = 'block';
|
formError.style.display = 'block';
|
||||||
formError.innerHTML = this.response.error.errorText;
|
formError.innerHTML = this.response.error.errorText;
|
||||||
if (typeof grecaptcha !== 'undefined') {
|
|
||||||
grecaptcha.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -186,23 +183,12 @@ var MapGuesser = {
|
|||||||
document.getElementById('cover').style.visibility = 'hidden';
|
document.getElementById('cover').style.visibility = 'hidden';
|
||||||
},
|
},
|
||||||
|
|
||||||
observeInput: function (form, observedInputs) {
|
observeInput: function (input, buttonToToggle) {
|
||||||
var anyChanged = false;
|
|
||||||
|
|
||||||
for (var i = 0; i < observedInputs.length; i++) {
|
|
||||||
var input = form.elements[observedInputs[i]];
|
|
||||||
if (input.type === 'checkbox') {
|
|
||||||
if (input.defaultChecked !== input.checked) {
|
|
||||||
anyChanged = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (input.defaultValue !== input.value) {
|
if (input.defaultValue !== input.value) {
|
||||||
anyChanged = true;
|
buttonToToggle.disabled = false;
|
||||||
|
} else {
|
||||||
|
buttonToToggle.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form.elements['submit_button'].disabled = !anyChanged;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
observeInputsInForm: function (form, observedInputs) {
|
observeInputsInForm: function (form, observedInputs) {
|
||||||
@ -213,19 +199,19 @@ var MapGuesser = {
|
|||||||
case 'INPUT':
|
case 'INPUT':
|
||||||
case 'TEXTAREA':
|
case 'TEXTAREA':
|
||||||
input.oninput = function () {
|
input.oninput = function () {
|
||||||
MapGuesser.observeInput(form, observedInputs);
|
MapGuesser.observeInput(this, form.elements.submit);
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
input.onchange = function () {
|
input.onchange = function () {
|
||||||
MapGuesser.observeInput(form, observedInputs);
|
MapGuesser.observeInput(this, form.elements.submit);
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form.onreset = function () {
|
form.onreset = function () {
|
||||||
form.elements['submit_button'].disabled = true;
|
form.elements.submit.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -65,32 +65,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var Util = {
|
|
||||||
printTimeForHuman: function (time) {
|
|
||||||
const minutes = Math.floor(time / 60);
|
|
||||||
const seconds = time % 60;
|
|
||||||
var time_str = '';
|
|
||||||
|
|
||||||
if (minutes == 1) {
|
|
||||||
time_str += '1 minute';
|
|
||||||
} else if (minutes > 1) {
|
|
||||||
time_str += minutes + ' minutes';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minutes > 0 && seconds > 0) {
|
|
||||||
time_str += ' and ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seconds == 1) {
|
|
||||||
time_str += '1 second';
|
|
||||||
} else if (seconds > 1) {
|
|
||||||
time_str += seconds + ' seconds';
|
|
||||||
}
|
|
||||||
|
|
||||||
return time_str;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Maps.addStaticMaps();
|
Maps.addStaticMaps();
|
||||||
|
|
||||||
Maps.initializeDescriptionDivs();
|
Maps.initializeDescriptionDivs();
|
||||||
@ -111,42 +85,10 @@
|
|||||||
window.location.href = '/multiGame/' + this.elements.roomId.value;
|
window.location.href = '/multiGame/' + this.elements.roomId.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('challengeForm').onsubmit = function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var url = '/challenge/create.json';
|
|
||||||
var formData = new FormData(this);
|
|
||||||
|
|
||||||
document.getElementById('loading').style.visibility = 'visible';
|
|
||||||
MapGuesser.httpRequest('POST', url, function() {
|
|
||||||
document.getElementById('loading').style.visibility = 'hidden';
|
|
||||||
|
|
||||||
if (this.response.error) {
|
|
||||||
Game.handleErrorResponse(this.response.error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.location.href = '/challenge/' + this.response.challengeToken;
|
|
||||||
|
|
||||||
}, formData);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (document.getElementById('multiButton')) {
|
|
||||||
document.getElementById('multiButton').onclick = function () {
|
document.getElementById('multiButton').onclick = function () {
|
||||||
MapGuesser.showModal('multi');
|
MapGuesser.showModal('multi');
|
||||||
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
|
document.getElementById('createNewRoomButton').href = '/multiGame/new/' + this.dataset.mapId;
|
||||||
document.getElementById('multiForm').elements.roomId.select();
|
document.getElementById('multiForm').elements.roomId.select();
|
||||||
document.getElementById('playMode').style.visibility = 'hidden';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.getElementById('challengeButton')) {
|
|
||||||
document.getElementById('challengeButton').onclick = function () {
|
|
||||||
MapGuesser.showModal('challenge');
|
|
||||||
document.getElementById('playMode').style.visibility = 'hidden';
|
|
||||||
var timeLimit = document.getElementById('timeLimit').value;
|
|
||||||
document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + Util.printTimeForHuman(timeLimit);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('closePlayModeButton').onclick = function () {
|
document.getElementById('closePlayModeButton').onclick = function () {
|
||||||
@ -157,10 +99,6 @@
|
|||||||
MapGuesser.hideModal();
|
MapGuesser.hideModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('closeChallengeButton').onclick = function () {
|
|
||||||
MapGuesser.hideModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
|
var buttons = document.getElementById('mapContainer').getElementsByClassName('playButton');
|
||||||
for (var i = 0; i < buttons.length; i++) {
|
for (var i = 0; i < buttons.length; i++) {
|
||||||
var button = buttons[i];
|
var button = buttons[i];
|
||||||
@ -169,13 +107,6 @@
|
|||||||
MapGuesser.showModal('playMode');
|
MapGuesser.showModal('playMode');
|
||||||
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
|
document.getElementById('singleButton').href = '/game/' + this.dataset.mapId;
|
||||||
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
|
document.getElementById('multiButton').dataset.mapId = this.dataset.mapId;
|
||||||
document.getElementById('challengeMapId').value = this.dataset.mapId;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('timeLimit').oninput = function () {
|
|
||||||
var timeLimit = document.getElementById('timeLimit').value;
|
|
||||||
document.getElementById('timeLimitLabel').innerText = 'Time limit of ' + Util.printTimeForHuman(timeLimit);
|
|
||||||
document.getElementById('timerEnabled').checked = true;
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
162
scripts/deploy-to-multiple-worktrees.py
Executable file
162
scripts/deploy-to-multiple-worktrees.py
Executable file
@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
# Usage: ./deploy-to-multiple-worktrees.py REPO_PATH WORKTREE_DEVELOPMENT_PATH WORKTREE_PRODUCTION_PATH
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
WORKTREE_REGEX = r"^worktree (.*)\nHEAD ([a-f0-9]*)\n(?:branch refs\/heads\/(.*)|detached)$"
|
||||||
|
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
print("Usage: ./deploy-to-multiple-worktrees.py REPO_PATH WORKTREE_DEVELOPMENT_PATH WORKTREE_PRODUCTION_PATH")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
REPO = os.path.abspath(sys.argv[1])
|
||||||
|
WORKTREE_DEVELOPMENT = os.path.abspath(sys.argv[2])
|
||||||
|
WORKTREE_PRODUCTION = os.path.abspath(sys.argv[3])
|
||||||
|
|
||||||
|
class Worktree:
|
||||||
|
def __init__(self, path, branch, revision, version):
|
||||||
|
self.path = path
|
||||||
|
self.branch = branch
|
||||||
|
self.revision = revision
|
||||||
|
self.version = version
|
||||||
|
self.newRevision = None
|
||||||
|
self.newVersion = None
|
||||||
|
|
||||||
|
def getDataForWorktrees():
|
||||||
|
ret = subprocess.check_output(["git", "worktree", "list", "--porcelain"], cwd=REPO).decode().strip()
|
||||||
|
blocks = ret.split("\n\n")
|
||||||
|
|
||||||
|
worktrees = []
|
||||||
|
|
||||||
|
for block in blocks:
|
||||||
|
matches = re.search(WORKTREE_REGEX, block)
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
path = matches.group(1)
|
||||||
|
revision = matches.group(2)
|
||||||
|
branch = matches.group(3)
|
||||||
|
version = getVersion(revision)
|
||||||
|
|
||||||
|
worktrees.append(Worktree(path, branch, revision, version))
|
||||||
|
|
||||||
|
return worktrees
|
||||||
|
|
||||||
|
def findWorktree(path):
|
||||||
|
for worktree in worktrees:
|
||||||
|
if worktree.path == path:
|
||||||
|
return worktree
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getVersion(branch):
|
||||||
|
return subprocess.check_output(["git", "describe", "--tags", "--always", "--match", "Release_*", branch], cwd=REPO).decode().strip()
|
||||||
|
|
||||||
|
def getRevisionForRef(ref):
|
||||||
|
return subprocess.check_output(["git", "rev-list", "-1", ref], cwd=REPO).decode().strip()
|
||||||
|
|
||||||
|
def getLatestReleaseTag():
|
||||||
|
process = subprocess.Popen(["git", "for-each-ref", "refs/tags/Release*", "--sort=-creatordate", "--format=%(refname:short)"], stdout=subprocess.PIPE, cwd=REPO)
|
||||||
|
|
||||||
|
for line in process.stdout:
|
||||||
|
tag = line.decode().rstrip()
|
||||||
|
|
||||||
|
if isTagVerified(tag):
|
||||||
|
return tag
|
||||||
|
|
||||||
|
print(f"[WARNING] Tag '{tag}' is not verified, skipping.")
|
||||||
|
|
||||||
|
raise Exception("No verified 'Release*' tag found!")
|
||||||
|
|
||||||
|
def isTagVerified(tag):
|
||||||
|
process = subprocess.run(["git", "tag", "--verify", tag], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=REPO)
|
||||||
|
|
||||||
|
return process.returncode == 0
|
||||||
|
|
||||||
|
def updateRepoFromRemote():
|
||||||
|
subprocess.call(["git", "fetch", "origin", "--prune", "--prune-tags"], cwd=REPO)
|
||||||
|
|
||||||
|
def checkoutWorktree(worktreePath, ref):
|
||||||
|
subprocess.call(["git", "checkout", "-f", ref], cwd=worktreePath)
|
||||||
|
|
||||||
|
def cleanWorktree(worktreePath):
|
||||||
|
subprocess.call(["git", "clean", "-f", "-d"], cwd=worktreePath)
|
||||||
|
|
||||||
|
def updateAppInWorktree(worktreePath):
|
||||||
|
subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath)
|
||||||
|
|
||||||
|
def updateAppVersionInWorktree(worktreePath):
|
||||||
|
subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath)
|
||||||
|
|
||||||
|
worktrees = getDataForWorktrees()
|
||||||
|
|
||||||
|
updateRepoFromRemote()
|
||||||
|
|
||||||
|
print("Repo is updated from origin")
|
||||||
|
|
||||||
|
print("----------------------------------------------")
|
||||||
|
print("----------------------------------------------")
|
||||||
|
|
||||||
|
developmentWorktree = findWorktree(WORKTREE_DEVELOPMENT)
|
||||||
|
|
||||||
|
developmentWorktree.newRevision = getRevisionForRef(developmentWorktree.branch)
|
||||||
|
developmentWorktree.newVersion = getVersion(developmentWorktree.revision)
|
||||||
|
|
||||||
|
print("DEVELOPMENT (" + developmentWorktree.path + ") is on branch " + developmentWorktree.branch)
|
||||||
|
print(developmentWorktree.revision + " = " + developmentWorktree.branch + " (" + developmentWorktree.version + ")")
|
||||||
|
print(developmentWorktree.newRevision + " = origin/" + developmentWorktree.branch + " (" + developmentWorktree.newVersion + ")")
|
||||||
|
|
||||||
|
if developmentWorktree.revision != developmentWorktree.newRevision:
|
||||||
|
print("-> DEVELOPMENT (" + developmentWorktree.path + ") will be UPDATED")
|
||||||
|
print("----------------------------------------------")
|
||||||
|
|
||||||
|
checkoutWorktree(developmentWorktree.path, developmentWorktree.branch)
|
||||||
|
cleanWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
|
print(developmentWorktree.path + " is checked out to " + developmentWorktree.branch + " and cleaned")
|
||||||
|
|
||||||
|
updateAppInWorktree(developmentWorktree.path)
|
||||||
|
updateAppVersionInWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
|
print("MapGuesser is updated in " + developmentWorktree.path)
|
||||||
|
elif developmentWorktree.version != developmentWorktree.newVersion:
|
||||||
|
print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED")
|
||||||
|
|
||||||
|
updateAppVersionInWorktree(developmentWorktree.path)
|
||||||
|
|
||||||
|
print("MapGuesser version is updated in " + developmentWorktree.path)
|
||||||
|
else:
|
||||||
|
print("-> DEVELOPMENT (" + developmentWorktree.path + ") WON'T be updated")
|
||||||
|
|
||||||
|
print("----------------------------------------------")
|
||||||
|
print("----------------------------------------------")
|
||||||
|
|
||||||
|
productionWorktree = findWorktree(WORKTREE_PRODUCTION)
|
||||||
|
|
||||||
|
productionWorktree.newVersion = getLatestReleaseTag()
|
||||||
|
productionWorktree.newRevision = getRevisionForRef(productionWorktree.newVersion)
|
||||||
|
|
||||||
|
print("PRODUCTION (" + productionWorktree.path + ")")
|
||||||
|
print(productionWorktree.revision + " = " + productionWorktree.version)
|
||||||
|
print(productionWorktree.newRevision + " = " + productionWorktree.newVersion)
|
||||||
|
|
||||||
|
if productionWorktree.revision != productionWorktree.newRevision:
|
||||||
|
print("-> PRODUCTION (" + productionWorktree.path + ") will be UPDATED")
|
||||||
|
|
||||||
|
checkoutWorktree(productionWorktree.path, productionWorktree.newRevision)
|
||||||
|
cleanWorktree(productionWorktree.path)
|
||||||
|
|
||||||
|
print(productionWorktree.path + " is checked out to " + productionWorktree.newRevision + " and cleaned")
|
||||||
|
|
||||||
|
updateAppInWorktree(productionWorktree.path)
|
||||||
|
updateAppVersionInWorktree(productionWorktree.path)
|
||||||
|
|
||||||
|
print("MapGuesser is updated in " + productionWorktree.path)
|
||||||
|
else:
|
||||||
|
print("-> PRODUCTION (" + productionWorktree.path + ") WON'T be updated")
|
||||||
|
|
||||||
|
print("----------------------------------------------")
|
||||||
|
print("----------------------------------------------")
|
35
scripts/install.sh
Executable file
35
scripts/install.sh
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
if [ -f ${ROOT_DIR}/installed ]; then
|
||||||
|
echo "MapGuesser is already installed! To force reinstall, delete file 'installed' from the root directory!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing NPM packages..."
|
||||||
|
(cd ${ROOT_DIR}/multi && npm install)
|
||||||
|
|
||||||
|
echo "Installing Yarn packages..."
|
||||||
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
|
|
||||||
|
echo "Installing MapGuesser DB..."
|
||||||
|
mysql --host=${DB_HOST} --user=${DB_USER} --password=${DB_PASSWORD} ${DB_NAME} < ${ROOT_DIR}/database/mapguesser.sql
|
||||||
|
|
||||||
|
echo "Migrating DB..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||||
|
|
||||||
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
|
${ROOT_DIR}/scripts/minify.sh
|
||||||
|
|
||||||
|
echo "Linking view files..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg view:link)
|
||||||
|
else
|
||||||
|
echo "Creating the first user..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg user:add mapg@mapg.dev 123456 admin)
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch ${ROOT_DIR}/installed
|
11
scripts/minify.sh
Executable file
11
scripts/minify.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
find ${ROOT_DIR}/public/static/js -type f -iname '*.js' -exec uglifyjs {} -c -m -o {} \;
|
||||||
|
|
||||||
|
find ${ROOT_DIR}/public/static/css -type f -iname '*.css' -exec cleancss {} -o {} \;
|
||||||
|
|
||||||
|
find ${ROOT_DIR}/public/static/img -type f -iname '*.svg' -exec svgo {} -o {} \;
|
17
scripts/update-version.sh
Executable file
17
scripts/update-version.sh
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
cd ${ROOT_DIR}
|
||||||
|
|
||||||
|
echo "Updating version info..."
|
||||||
|
|
||||||
|
VERSION=$(git describe --tags --always --match "Release_*" HEAD)
|
||||||
|
REVISION=$(git rev-parse --short HEAD)
|
||||||
|
REVISION_DATE=$(git show -s --format=%aI HEAD)
|
||||||
|
|
||||||
|
sed -i -E "s/const VERSION = '(.*)';/const VERSION = '${VERSION}';/" main.php
|
||||||
|
sed -i -E "s/const REVISION = '(.*)';/const REVISION = '${REVISION}';/" main.php
|
||||||
|
sed -i -E "s/const REVISION_DATE = '(.*)';/const REVISION_DATE = '${REVISION_DATE}';/" main.php
|
29
scripts/update.sh
Executable file
29
scripts/update.sh
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
|
||||||
|
|
||||||
|
. ${ROOT_DIR}/.env
|
||||||
|
|
||||||
|
echo "Installing Composer packages..."
|
||||||
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
(cd ${ROOT_DIR} && composer install --no-dev)
|
||||||
|
else
|
||||||
|
(cd ${ROOT_DIR} && composer install --dev)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing NPM packages..."
|
||||||
|
(cd ${ROOT_DIR}/multi && npm install)
|
||||||
|
|
||||||
|
echo "Installing Yarn packages..."
|
||||||
|
(cd ${ROOT_DIR}/public/static && yarn install)
|
||||||
|
|
||||||
|
echo "Migrating DB..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg db:migrate)
|
||||||
|
|
||||||
|
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
|
||||||
|
echo "Minifying JS, CSS and SVG files..."
|
||||||
|
${ROOT_DIR}/scripts/minify.sh
|
||||||
|
|
||||||
|
echo "Linking view files..."
|
||||||
|
(cd ${ROOT_DIR} && ./mapg view:link)
|
||||||
|
fi
|
@ -1,6 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
@ -14,7 +15,6 @@ class AddUserCommand extends Command
|
|||||||
$this->setName('user:add')
|
$this->setName('user:add')
|
||||||
->setDescription('Adding of user.')
|
->setDescription('Adding of user.')
|
||||||
->addArgument('email', InputArgument::REQUIRED, 'Email of user')
|
->addArgument('email', InputArgument::REQUIRED, 'Email of user')
|
||||||
->addArgument('username', InputArgument::REQUIRED, 'Username of user')
|
|
||||||
->addArgument('password', InputArgument::REQUIRED, 'Password of user')
|
->addArgument('password', InputArgument::REQUIRED, 'Password of user')
|
||||||
->addArgument('type', InputArgument::OPTIONAL, 'Type of user');;
|
->addArgument('type', InputArgument::OPTIONAL, 'Type of user');;
|
||||||
}
|
}
|
||||||
@ -23,17 +23,17 @@ class AddUserCommand extends Command
|
|||||||
{
|
{
|
||||||
$user = new User();
|
$user = new User();
|
||||||
$user->setEmail($input->getArgument('email'));
|
$user->setEmail($input->getArgument('email'));
|
||||||
$user->setUsername($input->getArgument('username'));
|
|
||||||
$user->setPlainPassword($input->getArgument('password'));
|
$user->setPlainPassword($input->getArgument('password'));
|
||||||
$user->setActive(true);
|
$user->setActive(true);
|
||||||
$user->setCreatedDate(new DateTime());
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
if ($input->hasArgument('type') && $input->getArgument('type') !== null) {
|
if ($input->hasArgument('type')) {
|
||||||
$user->setType($input->getArgument('type'));
|
$user->setType($input->getArgument('type'));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
$pdm = new PersistentDataManager();
|
||||||
|
$pdm->saveToDb($user);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$output->writeln('<error>Adding user failed!</error>');
|
$output->writeln('<error>Adding user failed!</error>');
|
||||||
$output->writeln('');
|
$output->writeln('');
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use FilesystemIterator;
|
use FilesystemIterator;
|
||||||
use SokoWeb\View\Linker;
|
use MapGuesser\View\Linker;
|
||||||
use RecursiveDirectoryIterator;
|
use RecursiveDirectoryIterator;
|
||||||
use RecursiveIteratorIterator;
|
use RecursiveIteratorIterator;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\Database\Query\Modify;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
|
||||||
use MapGuesser\Repository\UserRepository;
|
use MapGuesser\Repository\UserRepository;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
@ -15,6 +15,8 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
|
|
||||||
class MaintainDatabaseCommand extends Command
|
class MaintainDatabaseCommand extends Command
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
private UserRepository $userRepository;
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
private UserConfirmationRepository $userConfirmationRepository;
|
private UserConfirmationRepository $userConfirmationRepository;
|
||||||
@ -23,17 +25,15 @@ class MaintainDatabaseCommand extends Command
|
|||||||
|
|
||||||
private MultiRoomRepository $multiRoomRepository;
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
$this->multiRoomRepository = new MultiRoomRepository();
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configure(): void
|
public function configure(): void
|
||||||
@ -73,19 +73,15 @@ class MaintainDatabaseCommand extends Command
|
|||||||
//TODO: these can be in some wrapper class
|
//TODO: these can be in some wrapper class
|
||||||
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
if ($userConfirmation !== null) {
|
if ($userConfirmation !== null) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($userConfirmation);
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
if ($userPasswordResetter !== null) {
|
if ($userPasswordResetter !== null) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($userPasswordResetter);
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
|
$this->pdm->deleteFromDb($user);
|
||||||
\Container::$persistentDataManager->deleteFromDb($userPlayedPlace);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$dbConnection->commit();
|
\Container::$dbConnection->commit();
|
||||||
@ -94,14 +90,14 @@ class MaintainDatabaseCommand extends Command
|
|||||||
private function deleteExpiredPasswordResetters(): void
|
private function deleteExpiredPasswordResetters(): void
|
||||||
{
|
{
|
||||||
foreach ($this->userPasswordResetterRepository->getAllExpired() as $passwordResetter) {
|
foreach ($this->userPasswordResetterRepository->getAllExpired() as $passwordResetter) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($passwordResetter);
|
$this->pdm->deleteFromDb($passwordResetter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteExpiredRooms(): void
|
private function deleteExpiredRooms(): void
|
||||||
{
|
{
|
||||||
foreach ($this->multiRoomRepository->getAllExpired() as $multiRoom) {
|
foreach ($this->multiRoomRepository->getAllExpired() as $multiRoom) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($multiRoom);
|
$this->pdm->deleteFromDb($multiRoom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php namespace MapGuesser\Cli;
|
<?php namespace MapGuesser\Cli;
|
||||||
|
|
||||||
use SokoWeb\Database\Query\Modify;
|
use MapGuesser\Database\Query\Modify;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
@ -19,8 +19,6 @@ class MigrateDatabaseCommand extends Command
|
|||||||
{
|
{
|
||||||
$db = \Container::$dbConnection;
|
$db = \Container::$dbConnection;
|
||||||
|
|
||||||
$this->createBaseDb();
|
|
||||||
|
|
||||||
$db->startTransaction();
|
$db->startTransaction();
|
||||||
|
|
||||||
$success = [];
|
$success = [];
|
||||||
@ -64,8 +62,10 @@ class MigrateDatabaseCommand extends Command
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createBaseDb()
|
private function readDir(string $type): array
|
||||||
{
|
{
|
||||||
|
$done = [];
|
||||||
|
|
||||||
$migrationTableExists = \Container::$dbConnection->query('SELECT count(*)
|
$migrationTableExists = \Container::$dbConnection->query('SELECT count(*)
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = \'' . $_ENV['DB_NAME'] . '\'
|
WHERE table_schema = \'' . $_ENV['DB_NAME'] . '\'
|
||||||
@ -73,16 +73,6 @@ class MigrateDatabaseCommand extends Command
|
|||||||
->fetch(IResultSet::FETCH_NUM)[0];
|
->fetch(IResultSet::FETCH_NUM)[0];
|
||||||
|
|
||||||
if ($migrationTableExists != 0) {
|
if ($migrationTableExists != 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$dbConnection->multiQuery(file_get_contents(ROOT . '/database/mapguesser.sql'));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function readDir(string $type): array
|
|
||||||
{
|
|
||||||
$done = [];
|
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection, 'migrations');
|
$select = new Select(\Container::$dbConnection, 'migrations');
|
||||||
$select->columns(['migration']);
|
$select->columns(['migration']);
|
||||||
$select->where('type', '=', $type);
|
$select->where('type', '=', $type);
|
||||||
@ -93,6 +83,7 @@ class MigrateDatabaseCommand extends Command
|
|||||||
while ($migration = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
while ($migration = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
$done[] = $migration['migration'];
|
$done[] = $migration['migration'];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$path = ROOT . '/database/migrations/' . $type;
|
$path = ROOT . '/database/migrations/' . $type;
|
||||||
$dir = opendir($path);
|
$dir = opendir($path);
|
||||||
|
@ -1,27 +1,24 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use Faker\Factory;
|
||||||
use SokoWeb\Response\HtmlContent;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use SokoWeb\Response\JsonContent;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
use MapGuesser\Multi\MultiConnector;
|
use MapGuesser\Multi\MultiConnector;
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\MultiRoom;
|
use MapGuesser\PersistentData\Model\MultiRoom;
|
||||||
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\UserInChallenge;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\Repository\ChallengeRepository;
|
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Response\Redirect;
|
||||||
use MapGuesser\Repository\UserInChallengeRepository;
|
|
||||||
use SokoWeb\Response\Redirect;
|
|
||||||
|
|
||||||
class GameController implements IAuthenticationRequired
|
class GameController
|
||||||
{
|
{
|
||||||
const NUMBER_OF_ROUNDS = 5;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
private MultiConnector $multiConnector;
|
private MultiConnector $multiConnector;
|
||||||
|
|
||||||
@ -29,37 +26,25 @@ class GameController implements IAuthenticationRequired
|
|||||||
|
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
public function __construct(IRequest $request)
|
||||||
|
|
||||||
private ChallengeRepository $challengeRepository;
|
|
||||||
|
|
||||||
private UserInChallengeRepository $userInChallengeRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->multiConnector = new MultiConnector();
|
$this->multiConnector = new MultiConnector();
|
||||||
$this->multiRoomRepository = new MultiRoomRepository();
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
|
||||||
$this->challengeRepository = new ChallengeRepository();
|
|
||||||
$this->userInChallengeRepository = new UserInChallengeRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return empty($_ENV['ENABLE_GAME_FOR_GUESTS']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGame(): IContent
|
public function getGame(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
return new HtmlContent('game', ['mapId' => $mapId]);
|
return new HtmlContent('game', ['mapId' => $mapId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getNewMultiGame(): IRedirect
|
public function getNewMultiGame(): IRedirect
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
$roomId = bin2hex(random_bytes(3));
|
$roomId = bin2hex(random_bytes(3));
|
||||||
$token = $this->getMultiToken($roomId);
|
$token = $this->getMultiToken($roomId);
|
||||||
@ -75,7 +60,7 @@ class GameController implements IAuthenticationRequired
|
|||||||
$room->setMembersArray(['owner' => $token, 'all' => []]);
|
$room->setMembersArray(['owner' => $token, 'all' => []]);
|
||||||
$room->setUpdatedDate(new DateTime());
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($room);
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
$this->multiConnector->sendMessage('create_room', ['roomId' => $roomId]);
|
$this->multiConnector->sendMessage('create_room', ['roomId' => $roomId]);
|
||||||
|
|
||||||
@ -89,85 +74,16 @@ class GameController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function getMultiGame(): IContent
|
public function getMultiGame(): IContent
|
||||||
{
|
{
|
||||||
$roomId = \Container::$request->query('roomId');
|
$roomId = $this->request->query('roomId');
|
||||||
|
|
||||||
return new HtmlContent('game', ['roomId' => $roomId]);
|
return new HtmlContent('game', ['roomId' => $roomId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getChallenge(): IContent
|
|
||||||
{
|
|
||||||
$challengeToken = \Container::$request->query('challengeToken');
|
|
||||||
|
|
||||||
return new HtmlContent('game', ['challengeToken' => $challengeToken]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createNewChallenge(): IContent
|
|
||||||
{
|
|
||||||
// create Challenge
|
|
||||||
do {
|
|
||||||
// initiliaze or if a challenge with the same token already exists
|
|
||||||
$challengeToken = mt_rand();
|
|
||||||
} while ($this->challengeRepository->getByToken($challengeToken));
|
|
||||||
|
|
||||||
$challenge = new Challenge();
|
|
||||||
$challenge->setToken($challengeToken);
|
|
||||||
$challenge->setCreatedDate(new DateTime());
|
|
||||||
|
|
||||||
if (\Container::$request->post('timerEnabled') !== null && \Container::$request->post('timeLimit') !== null) {
|
|
||||||
$challenge->setTimeLimit(\Container::$request->post('timeLimit'));
|
|
||||||
}
|
|
||||||
if (\Container::$request->post('timeLimitType') !== null) {
|
|
||||||
$challenge->setTimeLimitType(\Container::$request->post('timeLimitType'));
|
|
||||||
}
|
|
||||||
if (\Container::$request->post('noMove') !== null) {
|
|
||||||
$challenge->setNoMove(true);
|
|
||||||
}
|
|
||||||
if (\Container::$request->post('noPan') !== null) {
|
|
||||||
$challenge->setNoPan(true);
|
|
||||||
}
|
|
||||||
if (\Container::$request->post('noZoom') !== null) {
|
|
||||||
$challenge->setNoZoom(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($challenge);
|
|
||||||
|
|
||||||
// save owner/creator
|
|
||||||
|
|
||||||
$session = \Container::$request->session();
|
|
||||||
$userId = $session->get('userId');
|
|
||||||
|
|
||||||
$userInChallenge = new UserInChallenge();
|
|
||||||
$userInChallenge->setUserId($userId);
|
|
||||||
$userInChallenge->setChallenge($challenge);
|
|
||||||
$userInChallenge->setTimeLeft($challenge->getTimeLimit());
|
|
||||||
$userInChallenge->setIsOwner(true);
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($userInChallenge);
|
|
||||||
|
|
||||||
// select places
|
|
||||||
|
|
||||||
$mapId = (int) \Container::$request->post('mapId');
|
|
||||||
// $map = $this->mapRepository->getById($mapId);
|
|
||||||
|
|
||||||
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
|
|
||||||
|
|
||||||
$round = 0;
|
|
||||||
foreach ($places as $place) {
|
|
||||||
$placeInChallenge = new PlaceInChallenge();
|
|
||||||
$placeInChallenge->setPlace($place);
|
|
||||||
$placeInChallenge->setChallenge($challenge);
|
|
||||||
$placeInChallenge->setRound($round++);
|
|
||||||
\Container::$persistentDataManager->saveToDb($placeInChallenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonContent(['challengeToken' => dechex($challengeToken)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function prepareGame(): IContent
|
public function prepareGame(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
$session->set('state', [
|
$session->set('state', [
|
||||||
@ -190,23 +106,14 @@ class GameController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function prepareMultiGame(): IContent
|
public function prepareMultiGame(): IContent
|
||||||
{
|
{
|
||||||
/**
|
$roomId = $this->request->query('roomId');
|
||||||
* @var User|null $user
|
$userName = $this->request->post('userName');
|
||||||
*/
|
if (empty($userName)) {
|
||||||
$user = \Container::$request->user();
|
$faker = Factory::create();
|
||||||
if ($user === null)
|
$userName = $faker->userName;
|
||||||
{
|
|
||||||
return new JsonContent(['error' => 'anonymous_user']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$roomId = \Container::$request->query('roomId');
|
|
||||||
|
|
||||||
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
$room = $this->multiRoomRepository->getByRoomId($roomId);
|
||||||
|
|
||||||
if (!isset($room)) {
|
|
||||||
return new JsonContent(['error' => 'game_not_found']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$state = $room->getStateArray();
|
$state = $room->getStateArray();
|
||||||
$map = $this->mapRepository->getById($state['mapId']);
|
$map = $this->mapRepository->getById($state['mapId']);
|
||||||
$token = $this->getMultiToken($roomId);
|
$token = $this->getMultiToken($roomId);
|
||||||
@ -224,12 +131,12 @@ class GameController implements IAuthenticationRequired
|
|||||||
$room->setMembersArray($members);
|
$room->setMembersArray($members);
|
||||||
$room->setUpdatedDate(new DateTime());
|
$room->setUpdatedDate(new DateTime());
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($room);
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
$this->multiConnector->sendMessage('join_room', [
|
$this->multiConnector->sendMessage('join_room', [
|
||||||
'roomId' => $roomId,
|
'roomId' => $roomId,
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
'userName' => $user->getDisplayName()
|
'userName' => $userName
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
@ -242,45 +149,9 @@ class GameController implements IAuthenticationRequired
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prepareChallenge(): IContent
|
|
||||||
{
|
|
||||||
$challengeToken_str = \Container::$request->query('challengeToken');
|
|
||||||
$session = \Container::$request->session();
|
|
||||||
$userId = $session->get('userId');
|
|
||||||
|
|
||||||
if (!isset($userId))
|
|
||||||
{
|
|
||||||
return new JsonContent(['error' => 'anonymous_user']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$challenge = $this->challengeRepository->getByTokenStr($challengeToken_str);
|
|
||||||
|
|
||||||
if (!isset($challenge))
|
|
||||||
{
|
|
||||||
return new JsonContent(['error' => 'game_not_found']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->userInChallengeRepository->isUserParticipatingInChallenge($userId, $challenge)) {
|
|
||||||
// new player is joining
|
|
||||||
$userInChallenge = new UserInChallenge();
|
|
||||||
$userInChallenge->setUserId($userId);
|
|
||||||
$userInChallenge->setChallenge($challenge);
|
|
||||||
$userInChallenge->setTimeLeft($challenge->getTimeLimit());
|
|
||||||
\Container::$persistentDataManager->saveToDb($userInChallenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
$map = $this->mapRepository->getByChallenge($challenge);
|
|
||||||
|
|
||||||
return new JsonContent([
|
|
||||||
'mapId' => $map->getId(),
|
|
||||||
'mapName' => $map->getName(),
|
|
||||||
'bounds' => $map->getBounds()->toArray()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getMultiToken(string $roomId): string
|
private function getMultiToken(string $roomId): string
|
||||||
{
|
{
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
$token = bin2hex(random_bytes(16));
|
$token = bin2hex(random_bytes(16));
|
||||||
|
@ -1,64 +1,43 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use MapGuesser\Util\Geo\Position;
|
use MapGuesser\Util\Geo\Position;
|
||||||
use SokoWeb\Response\JsonContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\Multi\MultiConnector;
|
use MapGuesser\Multi\MultiConnector;
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\Guess;
|
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\Model\UserPlayedPlace;
|
|
||||||
use MapGuesser\Repository\GuessRepository;
|
|
||||||
use MapGuesser\Repository\MultiRoomRepository;
|
use MapGuesser\Repository\MultiRoomRepository;
|
||||||
use MapGuesser\Repository\PlaceInChallengeRepository;
|
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
use MapGuesser\Repository\UserInChallengeRepository;
|
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
|
||||||
|
|
||||||
class GameFlowController implements IAuthenticationRequired
|
class GameFlowController
|
||||||
{
|
{
|
||||||
const NUMBER_OF_ROUNDS = 5;
|
const NUMBER_OF_ROUNDS = 5;
|
||||||
const MAX_SCORE = 1000;
|
const MAX_SCORE = 1000;
|
||||||
|
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
private MultiConnector $multiConnector;
|
private MultiConnector $multiConnector;
|
||||||
|
|
||||||
private MultiRoomRepository $multiRoomRepository;
|
private MultiRoomRepository $multiRoomRepository;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
public function __construct(IRequest $request)
|
||||||
|
|
||||||
private UserInChallengeRepository $userInChallengeRepository;
|
|
||||||
|
|
||||||
private PlaceInChallengeRepository $placeInChallengeRepository;
|
|
||||||
|
|
||||||
private GuessRepository $guessRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->multiConnector = new MultiConnector();
|
$this->multiConnector = new MultiConnector();
|
||||||
$this->multiRoomRepository = new MultiRoomRepository();
|
$this->multiRoomRepository = new MultiRoomRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
|
||||||
$this->userInChallengeRepository = new UserInChallengeRepository();
|
|
||||||
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
|
|
||||||
$this->guessRepository = new GuessRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return empty($_ENV['ENABLE_GAME_FOR_GUESTS']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function initialData(): IContent
|
public function initialData(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
return new JsonContent(['error' => 'no_session_found']);
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
@ -95,8 +74,8 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
|
|
||||||
public function multiInitialData(): IContent
|
public function multiInitialData(): IContent
|
||||||
{
|
{
|
||||||
$roomId = \Container::$request->query('roomId');
|
$roomId = $this->request->query('roomId');
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
return new JsonContent(['error' => 'no_session_found']);
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
@ -114,7 +93,7 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
$this->startNewGame($state, $state['mapId']);
|
$this->startNewGame($state, $state['mapId']);
|
||||||
$room->setStateArray($state);
|
$room->setStateArray($state);
|
||||||
$room->setUpdatedDate(new DateTime());
|
$room->setUpdatedDate(new DateTime());
|
||||||
\Container::$persistentDataManager->saveToDb($room);
|
$this->pdm->saveToDb($room);
|
||||||
}
|
}
|
||||||
|
|
||||||
$places = [];
|
$places = [];
|
||||||
@ -131,122 +110,18 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
return new JsonContent(['ok' => true]);
|
return new JsonContent(['ok' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function prepareChallengeResponse(int $userId, Challenge $challenge, int $currentRound, bool $withHistory = false): array
|
|
||||||
{
|
|
||||||
$currentPlace = $this->placeRepository->getByRoundInChallenge($challenge, $currentRound);
|
|
||||||
|
|
||||||
// if the last round was played ($currentPlace == null) or history is explicitly requested (for initializing)
|
|
||||||
if (!isset($currentPlace) || $withHistory) {
|
|
||||||
|
|
||||||
$withRelations = ['user', 'place_in_challange', 'place'];
|
|
||||||
foreach ($this->guessRepository->getAllInChallenge($challenge, $withRelations) as $guess) {
|
|
||||||
$round = $guess->getPlaceInChallenge()->getRound();
|
|
||||||
|
|
||||||
if ($guess->getUser()->getId() === $userId) {
|
|
||||||
$response['history'][$round]['position'] =
|
|
||||||
$guess->getPlaceInChallenge()->getPlace()->getPosition()->toArray();
|
|
||||||
$response['history'][$round]['result'] = [
|
|
||||||
'guessPosition' => $guess->getPosition()->toArray(),
|
|
||||||
'distance' => $guess->getDistance(),
|
|
||||||
'score' => $guess->getScore()
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$response['history'][$round]['allResults'][] = [
|
|
||||||
'userName' => $guess->getUser()->getDisplayName(),
|
|
||||||
'guessPosition' => $guess->getPosition()->toArray(),
|
|
||||||
'distance' => $guess->getDistance(),
|
|
||||||
'score' => $guess->getScore()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setting default values for rounds without guesses (because of timeout)
|
|
||||||
for ($i = 0; $i < $currentRound; ++$i) {
|
|
||||||
if (!isset($response['history'][$i]) || !isset($response['history'][$i]['result'])) {
|
|
||||||
$response['history'][$i]['result'] = [
|
|
||||||
'guessPosition' => null,
|
|
||||||
'distance' => null,
|
|
||||||
'score' => 0
|
|
||||||
];
|
|
||||||
|
|
||||||
$response['history'][$i]['position'] =
|
|
||||||
$this->placeRepository->getByRoundInChallenge($challenge, $i)->getPosition()->toArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$response['history']['length'] = $currentRound;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isset($currentPlace)) { // game finished
|
|
||||||
$response['finished'] = true;
|
|
||||||
} else { // continue game
|
|
||||||
$response['place'] = [
|
|
||||||
'panoId' => $currentPlace->getPanoIdCached(),
|
|
||||||
'pov' => $currentPlace->getPov()->toArray()
|
|
||||||
];
|
|
||||||
|
|
||||||
$prevRound = $currentRound - 1;
|
|
||||||
if ($prevRound >= 0) {
|
|
||||||
foreach ($this->guessRepository->getAllInChallengeByRound($prevRound, $challenge, ['user']) as $guess) {
|
|
||||||
if ($guess->getUser()->getId() != $userId) {
|
|
||||||
$response['allResults'][] = [
|
|
||||||
'userName' => $guess->getUser()->getDisplayName(),
|
|
||||||
'guessPosition' => $guess->getPosition()->toArray(),
|
|
||||||
'distance' => $guess->getDistance(),
|
|
||||||
'score' => $guess->getScore()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$response['restrictions'] = [
|
|
||||||
'timeLimit' => $challenge->getTimeLimit() * 1000,
|
|
||||||
'timeLimitType' => $challenge->getTimeLimitType(),
|
|
||||||
'noMove' => $challenge->getNoMove(),
|
|
||||||
'noPan' => $challenge->getNoPan(),
|
|
||||||
'noZoom' => $challenge->getNoZoom()
|
|
||||||
];
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function challengeInitialData(): IContent
|
|
||||||
{
|
|
||||||
$session = \Container::$request->session();
|
|
||||||
$userId = $session->get('userId');
|
|
||||||
$challengeToken_str = \Container::$request->query('challengeToken');
|
|
||||||
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, ['challenge']);
|
|
||||||
|
|
||||||
if (!isset($userInChallenge)) {
|
|
||||||
return new JsonContent(['error' => 'game_not_found']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$challenge = $userInChallenge->getChallenge();
|
|
||||||
$currentRound = $userInChallenge->getCurrentRound();
|
|
||||||
|
|
||||||
$response = $this->prepareChallengeResponse($userId, $challenge, $currentRound, true);
|
|
||||||
|
|
||||||
if ($challenge->getTimeLimitType() === 'game' && $challenge->getTimeLimit() !== null && $userInChallenge->getCurrentRound() > 0) {
|
|
||||||
$timeLimit = max(10, $userInChallenge->getTimeLeft());
|
|
||||||
$response['restrictions']['timeLimit'] = $timeLimit * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonContent($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function guess(): IContent
|
public function guess(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
|
||||||
return new JsonContent(['error' => 'no_session_found']);
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$last = $state['rounds'][$state['currentRound']];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
$guessPosition = new Position((float) \Container::$request->post('lat'), (float) \Container::$request->post('lng'));
|
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
||||||
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
|
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
|
||||||
|
|
||||||
$last['guessPosition'] = $guessPosition;
|
$last['guessPosition'] = $guessPosition;
|
||||||
$last['distance'] = $result['distance'];
|
$last['distance'] = $result['distance'];
|
||||||
@ -270,35 +145,13 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
|
|
||||||
$session->set('state', $state);
|
$session->set('state', $state);
|
||||||
|
|
||||||
$this->saveVisit($last['placeId']);
|
|
||||||
|
|
||||||
return new JsonContent($response);
|
return new JsonContent($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the selected place for the round in UserPlayedPlace
|
|
||||||
private function saveVisit($placeId): void
|
|
||||||
{
|
|
||||||
$session = \Container::$request->session();
|
|
||||||
$userId = $session->get('userId');
|
|
||||||
|
|
||||||
if (isset($userId)) {
|
|
||||||
$userPlayedPlace = $this->userPlayedPlaceRepository->getByUserIdAndPlaceId($userId, $placeId);
|
|
||||||
if (!$userPlayedPlace) {
|
|
||||||
$userPlayedPlace = new UserPlayedPlace();
|
|
||||||
$userPlayedPlace->setUserId($userId);
|
|
||||||
$userPlayedPlace->setPlaceId($placeId);
|
|
||||||
} else {
|
|
||||||
$userPlayedPlace->incrementOccurrences();
|
|
||||||
}
|
|
||||||
$userPlayedPlace->setLastTimeDate(new DateTime());
|
|
||||||
\Container::$persistentDataManager->saveToDb($userPlayedPlace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function multiGuess(): IContent
|
public function multiGuess(): IContent
|
||||||
{
|
{
|
||||||
$roomId = \Container::$request->query('roomId');
|
$roomId = $this->request->query('roomId');
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
return new JsonContent(['error' => 'no_session_found']);
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
@ -308,8 +161,8 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
$state = $room->getStateArray();
|
$state = $room->getStateArray();
|
||||||
|
|
||||||
$last = $state['rounds'][$state['currentRound']];
|
$last = $state['rounds'][$state['currentRound']];
|
||||||
$guessPosition = new Position((float) \Container::$request->post('lat'), (float) \Container::$request->post('lng'));
|
$guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
|
||||||
$result = $this->evaluateGuess($last['position'], $guessPosition, $state['area']);
|
$result = $this->evalueteGuess($last['position'], $guessPosition, $state['area']);
|
||||||
|
|
||||||
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
|
$responseFromMulti = $this->multiConnector->sendMessage('guess', [
|
||||||
'roomId' => $roomId,
|
'roomId' => $roomId,
|
||||||
@ -332,74 +185,10 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
return new JsonContent($response);
|
return new JsonContent($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function challengeGuess(): IContent
|
|
||||||
{
|
|
||||||
$session = \Container::$request->session();
|
|
||||||
$userId = $session->get('userId');
|
|
||||||
$challengeToken_str = \Container::$request->query('challengeToken');
|
|
||||||
$userInChallenge = $this->userInChallengeRepository->getByUserIdAndToken($userId, $challengeToken_str, ['challenge']);
|
|
||||||
|
|
||||||
if (!isset($userInChallenge)) {
|
|
||||||
return new JsonContent(['error' => 'game_not_found']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$challenge = $userInChallenge->getChallenge();
|
|
||||||
$currentRound = $userInChallenge->getCurrentRound();
|
|
||||||
$currentPlaceInChallenge = $this->placeInChallengeRepository->getByRoundInChallenge($currentRound, $challenge, ['place', 'map']);
|
|
||||||
$currentPlace = $currentPlaceInChallenge->getPlace();
|
|
||||||
$map = $currentPlace->getMap();
|
|
||||||
|
|
||||||
// creating response
|
|
||||||
$nextRound = $currentRound + 1;
|
|
||||||
$response = $this->prepareChallengeResponse($userId, $challenge, $nextRound);
|
|
||||||
$response['position'] = $currentPlace->getPosition()->toArray();
|
|
||||||
|
|
||||||
if (\Container::$request->post('lat') && \Container::$request->post('lng')) {
|
|
||||||
$guessPosition = new Position((float) \Container::$request->post('lat'), (float) \Container::$request->post('lng'));
|
|
||||||
$result = $this->evaluateGuess($currentPlace->getPosition(), $guessPosition, $map->getArea());
|
|
||||||
|
|
||||||
// save guess
|
|
||||||
$guess = new Guess();
|
|
||||||
$guess->setUserId($userId);
|
|
||||||
$guess->setPlaceInChallenge($currentPlaceInChallenge);
|
|
||||||
$guess->setPosition($guessPosition);
|
|
||||||
$guess->setDistance($result['distance']);
|
|
||||||
$guess->setScore($result['score']);
|
|
||||||
\Container::$persistentDataManager->saveToDb($guess);
|
|
||||||
|
|
||||||
$response['result'] = $result;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// user didn't manage to guess in the round in the given timeframe
|
|
||||||
$response['result'] = ['distance' => null, 'score' => 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// save user relevant state of challenge
|
|
||||||
$userInChallenge->setCurrentRound($nextRound);
|
|
||||||
$timeLeft = \Container::$request->post('timeLeft');
|
|
||||||
if (isset($timeLeft)) {
|
|
||||||
$userInChallenge->setTimeLeft(intval($timeLeft));
|
|
||||||
}
|
|
||||||
\Container::$persistentDataManager->saveToDb($userInChallenge);
|
|
||||||
|
|
||||||
if ($challenge->getTimeLimitType() === 'game' && isset($timeLeft)) {
|
|
||||||
$timeLimit = max(10, intval($timeLeft));
|
|
||||||
$response['restrictions']['timeLimit'] = $timeLimit * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($response['history'][$currentRound]['allResults'])) {
|
|
||||||
$response['allResults'] = $response['history'][$currentRound]['allResults'];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->saveVisit($currentPlace->getId());
|
|
||||||
|
|
||||||
return new JsonContent($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function multiNextRound(): IContent
|
public function multiNextRound(): IContent
|
||||||
{
|
{
|
||||||
$roomId = \Container::$request->query('roomId');
|
$roomId = $this->request->query('roomId');
|
||||||
$session = \Container::$request->session();
|
$session = $this->request->session();
|
||||||
|
|
||||||
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
if (!($multiState = $session->get('multiState')) || $multiState['roomId'] !== $roomId) {
|
||||||
return new JsonContent(['error' => 'no_session_found']);
|
return new JsonContent(['error' => 'no_session_found']);
|
||||||
@ -420,12 +209,12 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
|
|
||||||
$room->setStateArray($state);
|
$room->setStateArray($state);
|
||||||
$room->setUpdatedDate(new DateTime());
|
$room->setUpdatedDate(new DateTime());
|
||||||
\Container::$persistentDataManager->saveToDb($room);
|
$this->pdm->saveToDb($room);
|
||||||
|
|
||||||
return new JsonContent(['ok' => true]);
|
return new JsonContent(['ok' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function evaluateGuess(Position $realPosition, Position $guessPosition, float $area)
|
private function evalueteGuess(Position $realPosition, Position $guessPosition, float $area)
|
||||||
{
|
{
|
||||||
$distance = $this->calculateDistance($realPosition, $guessPosition);
|
$distance = $this->calculateDistance($realPosition, $guessPosition);
|
||||||
$score = $this->calculateScore($distance, $area);
|
$score = $this->calculateScore($distance, $area);
|
||||||
@ -435,10 +224,7 @@ class GameFlowController implements IAuthenticationRequired
|
|||||||
|
|
||||||
private function startNewGame(array &$state, int $mapId): void
|
private function startNewGame(array &$state, int $mapId): void
|
||||||
{
|
{
|
||||||
$session = \Container::$request->session();
|
$places = $this->placeRepository->getRandomNForMapWithValidPano($mapId, static::NUMBER_OF_ROUNDS);
|
||||||
$userId = $session->get('userId');
|
|
||||||
|
|
||||||
$places = $this->placeRepository->getRandomNPlaces($mapId, static::NUMBER_OF_ROUNDS, $userId);
|
|
||||||
|
|
||||||
$state['rounds'] = [];
|
$state['rounds'] = [];
|
||||||
$state['currentRound'] = 0;
|
$state['currentRound'] = 0;
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use SokoWeb\Response\JsonContent;
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
use SokoWeb\Response\Redirect;
|
use MapGuesser\Response\JsonContent;
|
||||||
|
use MapGuesser\Response\Redirect;
|
||||||
|
|
||||||
class HomeController
|
class HomeController
|
||||||
{
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
public function getIndex(): IRedirect
|
public function getIndex(): IRedirect
|
||||||
{
|
{
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('maps')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('maps')->generateLink(), IRedirect::TEMPORARY);
|
||||||
@ -16,6 +24,6 @@ class HomeController
|
|||||||
{
|
{
|
||||||
// session starts with the request, this method just sends valid data to the client
|
// session starts with the request, this method just sends valid data to the client
|
||||||
|
|
||||||
return new JsonContent(['antiCsrfToken' => \Container::$request->session()->get('anti_csrf_token')]);
|
return new JsonContent(['antiCsrfToken' => $this->request->session()->get('anti_csrf_token')]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,56 +2,52 @@
|
|||||||
|
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\Http\Request;
|
use MapGuesser\Http\Request;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use SokoWeb\Mailing\Mail;
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
use SokoWeb\OAuth\GoogleOAuth;
|
use MapGuesser\Mailing\Mail;
|
||||||
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||||
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
|
||||||
use MapGuesser\Repository\UserRepository;
|
use MapGuesser\Repository\UserRepository;
|
||||||
use MapGuesser\Util\UsernameGenerator;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use SokoWeb\Response\HtmlContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use SokoWeb\Response\JsonContent;
|
use MapGuesser\Response\Redirect;
|
||||||
use SokoWeb\Response\Redirect;
|
use MapGuesser\Util\JwtParser;
|
||||||
use SokoWeb\Util\CaptchaValidator;
|
|
||||||
use SokoWeb\Util\JwtParser;
|
|
||||||
|
|
||||||
class LoginController
|
class LoginController
|
||||||
{
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
private UserRepository $userRepository;
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
private UserConfirmationRepository $userConfirmationRepository;
|
private UserConfirmationRepository $userConfirmationRepository;
|
||||||
|
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
public function __construct(IRequest $request)
|
||||||
|
|
||||||
private string $redirectUrl;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->userRepository = new UserRepository();
|
$this->userRepository = new UserRepository();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
|
||||||
$this->redirectUrl = \Container::$request->session()->has('redirect_after_login') ?
|
|
||||||
\Container::$request->session()->get('redirect_after_login') :
|
|
||||||
\Container::$routeCollection->getRoute('index')->generateLink();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLoginForm()
|
public function getLoginForm()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlContent('login/login', ['redirectUrl' => $this->redirectUrl]);
|
return new HtmlContent('login/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGoogleLoginRedirect(): IRedirect
|
public function getGoogleLoginRedirect(): IRedirect
|
||||||
@ -59,13 +55,13 @@ class LoginController
|
|||||||
$state = bin2hex(random_bytes(16));
|
$state = bin2hex(random_bytes(16));
|
||||||
$nonce = bin2hex(random_bytes(16));
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
\Container::$request->session()->set('oauth_state', $state);
|
$this->request->session()->set('oauth_state', $state);
|
||||||
\Container::$request->session()->set('oauth_nonce', $nonce);
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
$url = $oAuth->getDialogUrl(
|
$url = $oAuth->getDialogUrl(
|
||||||
$state,
|
$state,
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink(),
|
||||||
$nonce
|
$nonce
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -74,56 +70,50 @@ class LoginController
|
|||||||
|
|
||||||
public function getSignupForm()
|
public function getSignupForm()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Container::$request->session()->has('tmp_user_data')) {
|
if ($this->request->session()->has('tmp_user_data')) {
|
||||||
$tmpUserData = \Container::$request->session()->get('tmp_user_data');
|
$tmpUserData = $this->request->session()->get('tmp_user_data');
|
||||||
|
|
||||||
|
$data = ['email' => $tmpUserData['email']];
|
||||||
} else {
|
} else {
|
||||||
$tmpUserData = [];
|
$data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlContent('login/signup', $tmpUserData);
|
return new HtmlContent('login/signup', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSignupSuccess()
|
public function getSignupSuccess(): IContent
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
|
||||||
$this->deleteRedirectUrl();
|
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HtmlContent('login/signup_success');
|
return new HtmlContent('login/signup_success');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSignupWithGoogleForm()
|
public function getSignupWithGoogleForm()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\Container::$request->session()->has('google_user_data')) {
|
if (!$this->request->session()->has('google_user_data')) {
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('login-google')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('login-google')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
$userData = \Container::$request->session()->get('google_user_data');
|
$userData = $this->request->session()->get('google_user_data');
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmail($userData['email']);
|
$user = $this->userRepository->getByEmail($userData['email']);
|
||||||
|
|
||||||
return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email'], 'redirectUrl' => $this->redirectUrl]);
|
return new HtmlContent('login/google_signup', ['found' => $user !== null, 'email' => $userData['email']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequestPasswordResetForm()
|
public function getRequestPasswordResetForm()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new HtmlContent('login/password_reset_request', ['email' => \Container::$request->query('email')]);
|
return new HtmlContent('login/password_reset_request', ['email' => $this->request->query('email')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRequestPasswordResetSuccess(): IContent
|
public function getRequestPasswordResetSuccess(): IContent
|
||||||
@ -133,12 +123,11 @@ class LoginController
|
|||||||
|
|
||||||
public function getResetPasswordForm()
|
public function getResetPasswordForm()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = \Container::$request->query('token');
|
$token = $this->request->query('token');
|
||||||
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
||||||
|
|
||||||
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
||||||
@ -147,27 +136,19 @@ class LoginController
|
|||||||
|
|
||||||
$user = $this->userRepository->getById($resetter->getUserId());
|
$user = $this->userRepository->getById($resetter->getUserId());
|
||||||
|
|
||||||
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail(), 'redirectUrl' => $this->redirectUrl]);
|
return new HtmlContent('login/reset_password', ['success' => true, 'token' => $token, 'email' => $user->getEmail()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function login(): IContent
|
public function login(): IContent
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
filter_var(\Container::$request->post('email'), FILTER_VALIDATE_EMAIL) === false &&
|
|
||||||
preg_match('/^[a-zA-Z0-9_\-\.]+$/', \Container::$request->post('email')) !== 1
|
|
||||||
) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'This is not a valid email address or username.']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmailOrUsername(\Container::$request->post('email'));
|
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
if (strlen(\Container::$request->post('password')) < 6) {
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
@ -176,20 +157,16 @@ class LoginController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$tmpUser = new User();
|
$tmpUser = new User();
|
||||||
$tmpUser->setPlainPassword(\Container::$request->post('password'));
|
$tmpUser->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
$tmpUserData = ['password_hashed' => $tmpUser->getPassword()];
|
$this->request->session()->set('tmp_user_data', [
|
||||||
if (filter_var(\Container::$request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
'email' => $this->request->post('email'),
|
||||||
$tmpUserData['username'] = \Container::$request->post('email');
|
'password_hashed' => $tmpUser->getPassword()
|
||||||
} else {
|
]);
|
||||||
$tmpUserData['email'] = \Container::$request->post('email');
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$request->session()->set('tmp_user_data', $tmpUserData);
|
|
||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => [
|
'redirect' => [
|
||||||
'target' => \Container::$routeCollection->getRoute('signup')->generateLink()
|
'target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -199,13 +176,13 @@ class LoginController
|
|||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'User found with the given email address / username, but the account is not activated. ' .
|
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
||||||
'Please check your email and click on the activation link!'
|
'Please check your email and click on the activation link!'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$user->checkPassword(\Container::$request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given password is wrong. You can <a href="/password/requestReset?email=' .
|
'errorText' => 'The given password is wrong. You can <a href="/password/requestReset?email=' .
|
||||||
@ -214,27 +191,25 @@ class LoginController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$this->deleteRedirectUrl();
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginWithGoogle()
|
public function loginWithGoogle()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) {
|
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login');
|
||||||
}
|
}
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
$tokenData = $oAuth->getToken(
|
$tokenData = $oAuth->getToken(
|
||||||
\Container::$request->query('code'),
|
$this->request->query('code'),
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
if (!isset($tokenData['id_token'])) {
|
||||||
@ -244,7 +219,7 @@ class LoginController
|
|||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
$idToken = $jwtParser->getPayload();
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) {
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
return new HtmlContent('login/google_login');
|
return new HtmlContent('login/google_login');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,73 +230,50 @@ class LoginController
|
|||||||
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
$user = $this->userRepository->getByGoogleSub($idToken['sub']);
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
\Container::$request->session()->set('google_user_data', ['sub' => $idToken['sub'], 'email' => $idToken['email']]);
|
$this->request->session()->set('google_user_data', ['sub' => $idToken['sub'], 'email' => $idToken['email']]);
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('signup-google')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function logout(): IRedirect
|
public function logout(): IRedirect
|
||||||
{
|
{
|
||||||
\Container::$request->setUser(null);
|
$this->request->setUser(null);
|
||||||
|
|
||||||
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function signup(): IContent
|
public function signup(): IContent
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new JsonContent(['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]]);
|
||||||
return new JsonContent(['redirect' => ['target' => $this->redirectUrl]]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$newUser = new User();
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
|
|
||||||
$googleUserData = \Container::$request->session()->get('google_user_data');
|
|
||||||
if ($googleUserData !== null) {
|
|
||||||
$user = $this->userRepository->getByEmail($googleUserData['email']);
|
|
||||||
|
|
||||||
if ($user !== null) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => [
|
|
||||||
'errorText' => 'There is a user already registered with the email address of this Google account, ' .
|
|
||||||
'but Google account is not linked to the user. Please <a href="/login?email=' .
|
|
||||||
urlencode($googleUserData['email']) . '" title="Login">login</a> first to link your Google account!'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$newUser->setActive(true);
|
|
||||||
$newUser->setEmail($googleUserData['email']);
|
|
||||||
$newUser->setGoogleSub($googleUserData['sub']);
|
|
||||||
} else {
|
|
||||||
$user = $this->userRepository->getByEmailOrUsername(\Container::$request->post('email'));
|
|
||||||
|
|
||||||
if ($user !== null) {
|
if ($user !== null) {
|
||||||
if ($user->getActive()) {
|
if ($user->getActive()) {
|
||||||
if (!$user->checkPassword(\Container::$request->post('password'))) {
|
if (!$user->checkPassword($this->request->post('password'))) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'There is a user already registered with the given email address / username, ' .
|
'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=' .
|
'but the given password is wrong. You can <a href="/password/requestReset?email=' .
|
||||||
urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!'
|
urlencode($user->getEmail()) . '" title="Request password reset">request password reset</a>!'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$request->setUser($user);
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$this->deleteRedirectUrl();
|
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]];
|
||||||
$data = ['redirect' => ['target' => $this->redirectUrl]];
|
|
||||||
} else {
|
} else {
|
||||||
$data = [
|
$data = [
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'There is a user already registered with the given email address / username. ' .
|
'errorText' => 'There is a user already registered with the given email address. ' .
|
||||||
'Please check your email and click on the activation link!'
|
'Please check your email and click on the activation link!'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
@ -329,33 +281,21 @@ class LoginController
|
|||||||
return new JsonContent($data);
|
return new JsonContent($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($_ENV['RECAPTCHA_SITEKEY'])) {
|
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
||||||
if (!\Container::$request->post('g-recaptcha-response')) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'Please check "I\'m not a robot" in the reCAPTCHA box!']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$captchaValidator = new CaptchaValidator();
|
|
||||||
$captchaResponse = $captchaValidator->validate(\Container::$request->post('g-recaptcha-response'));
|
|
||||||
if (!$captchaResponse['success']) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'reCAPTCHA challenge failed. Please try again!']]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter_var(\Container::$request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given email address is not valid.']]);
|
return new JsonContent(['error' => ['errorText' => 'The given email address is not valid.']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Container::$request->session()->has('tmp_user_data')) {
|
if ($this->request->session()->has('tmp_user_data')) {
|
||||||
$tmpUserData = \Container::$request->session()->get('tmp_user_data');
|
$tmpUserData = $this->request->session()->get('tmp_user_data');
|
||||||
|
|
||||||
$tmpUser = new User();
|
$tmpUser = new User();
|
||||||
$tmpUser->setPassword($tmpUserData['password_hashed']);
|
$tmpUser->setPassword($tmpUserData['password_hashed']);
|
||||||
|
|
||||||
if (!$tmpUser->checkPassword(\Container::$request->post('password'))) {
|
if (!$tmpUser->checkPassword($this->request->post('password'))) {
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (strlen(\Container::$request->post('password')) < 6) {
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
@ -363,163 +303,156 @@ class LoginController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Container::$request->post('password') !== \Container::$request->post('password_confirm')) {
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$newUser->setActive(false);
|
$user = new User();
|
||||||
$newUser->setEmail(\Container::$request->post('email'));
|
$user->setEmail($this->request->post('email'));
|
||||||
$newUser->setPlainPassword(\Container::$request->post('password'));
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
}
|
$user->setCreatedDate(new DateTime());
|
||||||
|
|
||||||
if (strlen(\Container::$request->post('username')) > 0) {
|
\Container::$dbConnection->startTransaction();
|
||||||
$username = \Container::$request->post('username');
|
|
||||||
|
|
||||||
if (preg_match('/^[a-zA-Z0-9_\-\.]+$/', $username) !== 1) {
|
$this->pdm->saveToDb($user);
|
||||||
return new JsonContent(['error' => ['errorText' => 'Username can contain only english letters, digits, - (hyphen), . (dot), _ (underscore).']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->userRepository->getByUsername($username) !== null) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given username is already taken.']]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$usernameGenerator = new UsernameGenerator();
|
|
||||||
do {
|
|
||||||
$username = $usernameGenerator->generate();
|
|
||||||
} while ($this->userRepository->getByUsername($username));
|
|
||||||
}
|
|
||||||
|
|
||||||
$newUser->setUsername($username);
|
|
||||||
$newUser->setCreatedDate(new DateTime());
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($newUser);
|
|
||||||
|
|
||||||
if ($googleUserData !== null) {
|
|
||||||
$this->sendWelcomeEmail($newUser->getEmail());
|
|
||||||
|
|
||||||
\Container::$request->setUser($newUser);
|
|
||||||
} else {
|
|
||||||
$token = bin2hex(random_bytes(16));
|
$token = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
$confirmation = new UserConfirmation();
|
$confirmation = new UserConfirmation();
|
||||||
$confirmation->setUser($newUser);
|
$confirmation->setUser($user);
|
||||||
$confirmation->setToken($token);
|
$confirmation->setToken($token);
|
||||||
$confirmation->setLastSentDate(new DateTime());
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($confirmation);
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
$this->sendConfirmationEmail($newUser->getEmail(), $token, $newUser->getCreatedDate());
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->sendConfirmationEmail($user->getEmail(), $token, $user->getCreatedDate());
|
||||||
|
|
||||||
|
$this->request->session()->delete('tmp_user_data');
|
||||||
|
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$request->session()->delete('tmp_user_data');
|
public function signupWithGoogle(): IContent
|
||||||
\Container::$request->session()->delete('google_user_data');
|
{
|
||||||
|
if ($this->request->user() !== null) {
|
||||||
|
return new JsonContent(['success' => true]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userData = $this->request->session()->get('google_user_data');
|
||||||
|
|
||||||
|
$user = $this->userRepository->getByEmail($userData['email']);
|
||||||
|
|
||||||
|
if ($user === null) {
|
||||||
|
$sendWelcomeEmail = true;
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setEmail($userData['email']);
|
||||||
|
$user->setCreatedDate(new DateTime());
|
||||||
|
} else {
|
||||||
|
$sendWelcomeEmail = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->setActive(true);
|
||||||
|
$user->setGoogleSub($userData['sub']);
|
||||||
|
|
||||||
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
|
if ($sendWelcomeEmail) {
|
||||||
|
$this->sendWelcomeEmail($user->getEmail());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->request->session()->delete('google_user_data');
|
||||||
|
$this->request->setUser($user);
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetSignup(): IContent
|
public function resetSignup(): IContent
|
||||||
{
|
{
|
||||||
\Container::$request->session()->delete('tmp_user_data');
|
$this->request->session()->delete('tmp_user_data');
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetGoogleSignup(): IContent
|
public function resetGoogleSignup(): IContent
|
||||||
{
|
{
|
||||||
\Container::$request->session()->delete('google_user_data');
|
$this->request->session()->delete('google_user_data');
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function activate()
|
public function activate()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken(substr(\Container::$request->query('token'), 0, 32));
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
if ($confirmation === null) {
|
if ($confirmation === null) {
|
||||||
return new HtmlContent('login/activate');
|
return new HtmlContent('login/activate');
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($confirmation);
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($confirmation);
|
||||||
|
|
||||||
$user = $this->userRepository->getById($confirmation->getUserId());
|
$user = $this->userRepository->getById($confirmation->getUserId());
|
||||||
$user->setActive(true);
|
$user->setActive(true);
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
\Container::$request->setUser($user);
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$this->deleteRedirectUrl();
|
$this->request->setUser($user);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
return new Redirect(\Container::$routeCollection->getRoute('index')->generateLink(), IRedirect::TEMPORARY);
|
||||||
return new Redirect($this->redirectUrl, IRedirect::TEMPORARY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$confirmation = $this->userConfirmationRepository->getByToken(substr(\Container::$request->query('token'), 0, 32));
|
$confirmation = $this->userConfirmationRepository->getByToken(substr($this->request->query('token'), 0, 32));
|
||||||
|
|
||||||
if ($confirmation === null) {
|
if ($confirmation === null) {
|
||||||
return new HtmlContent('login/cancel', ['success' => false]);
|
return new HtmlContent('login/cancel', ['success' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($confirmation);
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($confirmation);
|
||||||
|
|
||||||
$user = $this->userRepository->getById($confirmation->getUserId());
|
$user = $this->userRepository->getById($confirmation->getUserId());
|
||||||
|
|
||||||
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
|
$this->pdm->deleteFromDb($user);
|
||||||
\Container::$persistentDataManager->deleteFromDb($userPlayedPlace);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($user);
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
return new HtmlContent('login/cancel', ['success' => true]);
|
return new HtmlContent('login/cancel', ['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function requestPasswordReset(): IContent
|
public function requestPasswordReset(): IContent
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => [
|
'redirect' => [
|
||||||
'target' => $this->redirectUrl
|
'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($_ENV['RECAPTCHA_SITEKEY'])) {
|
$user = $this->userRepository->getByEmail($this->request->post('email'));
|
||||||
if (!\Container::$request->post('g-recaptcha-response')) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'Please check "I\'m not a robot" in the reCAPTCHA box!']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$captchaValidator = new CaptchaValidator();
|
|
||||||
$captchaResponse = $captchaValidator->validate(\Container::$request->post('g-recaptcha-response'));
|
|
||||||
if (!$captchaResponse['success']) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'reCAPTCHA challenge failed. Please try again!']]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
filter_var(\Container::$request->post('email'), FILTER_VALIDATE_EMAIL) === false &&
|
|
||||||
preg_match('/^[a-zA-Z0-9_\-\.]+$/', \Container::$request->post('email')) !== 1
|
|
||||||
) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'This is not a valid email address or username.']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->userRepository->getByEmailOrUsername(\Container::$request->post('email'));
|
|
||||||
|
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'No user found with the given email address / username. You can <a href="/signup" title="Sign up">sign up</a>!'
|
'errorText' => 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up</a>!'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -529,7 +462,7 @@ class LoginController
|
|||||||
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'User found with the given email address / username, but the account is not activated. ' .
|
'errorText' => 'User found with the given email address, but the account is not activated. ' .
|
||||||
'Please check your email and click on the activation link!'
|
'Please check your email and click on the activation link!'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
@ -553,11 +486,15 @@ class LoginController
|
|||||||
$passwordResetter->setToken($token);
|
$passwordResetter->setToken($token);
|
||||||
$passwordResetter->setExpiresDate($expires);
|
$passwordResetter->setExpiresDate($expires);
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
if ($existingResetter !== null) {
|
if ($existingResetter !== null) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($existingResetter);
|
$this->pdm->deleteFromDb($existingResetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($passwordResetter);
|
$this->pdm->saveToDb($passwordResetter);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
$this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
|
$this->sendPasswordResetEmail($user->getEmail(), $token, $expires);
|
||||||
|
|
||||||
@ -567,27 +504,26 @@ class LoginController
|
|||||||
|
|
||||||
public function resetPassword(): IContent
|
public function resetPassword(): IContent
|
||||||
{
|
{
|
||||||
if (\Container::$request->user() !== null) {
|
if ($this->request->user() !== null) {
|
||||||
$this->deleteRedirectUrl();
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => [
|
'redirect' => [
|
||||||
'target' => $this->redirectUrl
|
'target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = \Container::$request->query('token');
|
$token = $this->request->query('token');
|
||||||
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
$resetter = $this->userPasswordResetterRepository->getByToken($token);
|
||||||
|
|
||||||
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
if ($resetter === null || $resetter->getExpiresDate() < new DateTime()) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'redirect' => [
|
'redirect' => [
|
||||||
'target' => \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])
|
'target' => '/' . \Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token])
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen(\Container::$request->post('password')) < 6) {
|
if (strlen($this->request->post('password')) < 6) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
'errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
@ -595,20 +531,23 @@ class LoginController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Container::$request->post('password') !== \Container::$request->post('password_confirm')) {
|
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
return new JsonContent(['error' => ['errorText' => 'The given passwords do not match.']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($resetter);
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
|
$this->pdm->deleteFromDb($resetter);
|
||||||
|
|
||||||
$user = $this->userRepository->getById($resetter->getUserId());
|
$user = $this->userRepository->getById($resetter->getUserId());
|
||||||
$user->setPlainPassword(\Container::$request->post('password'));
|
$user->setPlainPassword($this->request->post('password'));
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
\Container::$request->setUser($user);
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
|
$this->request->setUser($user);
|
||||||
|
|
||||||
$this->deleteRedirectUrl();
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,9 +558,9 @@ class LoginController
|
|||||||
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
|
$mail->setSubject('Welcome to ' . $_ENV['APP_NAME'] . ' - Activate your account');
|
||||||
$mail->setBodyFromTemplate('signup', [
|
$mail->setBodyFromTemplate('signup', [
|
||||||
'EMAIL' => $email,
|
'EMAIL' => $email,
|
||||||
'ACTIVATE_LINK' => \Container::$request->getBase() .
|
'ACTIVATE_LINK' => $this->request->getBase() . '/' .
|
||||||
\Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
|
\Container::$routeCollection->getRoute('signup.activate')->generateLink(['token' => $token]),
|
||||||
'CANCEL_LINK' => \Container::$request->getBase() .
|
'CANCEL_LINK' => $this->request->getBase() . '/' .
|
||||||
\Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
|
\Container::$routeCollection->getRoute('signup.cancel')->generateLink(['token' => $token]),
|
||||||
'ACTIVATABLE_UNTIL' => (clone $created)->add(new DateInterval('P1D'))->format('Y-m-d H:i T')
|
'ACTIVATABLE_UNTIL' => (clone $created)->add(new DateInterval('P1D'))->format('Y-m-d H:i T')
|
||||||
]);
|
]);
|
||||||
@ -638,7 +577,7 @@ class LoginController
|
|||||||
|
|
||||||
$confirmation->setLastSentDate(new DateTime());
|
$confirmation->setLastSentDate(new DateTime());
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($confirmation);
|
$this->pdm->saveToDb($confirmation);
|
||||||
|
|
||||||
$this->sendConfirmationEmail($user->getEmail(), $confirmation->getToken(), $user->getCreatedDate());
|
$this->sendConfirmationEmail($user->getEmail(), $confirmation->getToken(), $user->getCreatedDate());
|
||||||
|
|
||||||
@ -663,15 +602,10 @@ class LoginController
|
|||||||
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
|
$mail->setSubject($_ENV['APP_NAME'] . ' - Password reset');
|
||||||
$mail->setBodyFromTemplate('password-reset', [
|
$mail->setBodyFromTemplate('password-reset', [
|
||||||
'EMAIL' => $email,
|
'EMAIL' => $email,
|
||||||
'RESET_LINK' => \Container::$request->getBase() .
|
'RESET_LINK' => $this->request->getBase() . '/' .
|
||||||
\Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token]),
|
\Container::$routeCollection->getRoute('password-reset')->generateLink(['token' => $token]),
|
||||||
'EXPIRES' => $expires->format('Y-m-d H:i T')
|
'EXPIRES' => $expires->format('Y-m-d H:i T')
|
||||||
]);
|
]);
|
||||||
$mail->send();
|
$mail->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteRedirectUrl(): void
|
|
||||||
{
|
|
||||||
\Container::$request->session()->delete('redirect_after_login');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,68 +1,50 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
use SokoWeb\Interfaces\Authorization\ISecured;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Repository\ChallengeRepository;
|
|
||||||
use MapGuesser\Repository\GuessRepository;
|
|
||||||
use MapGuesser\Repository\MapRepository;
|
use MapGuesser\Repository\MapRepository;
|
||||||
use MapGuesser\Repository\PlaceInChallengeRepository;
|
|
||||||
use MapGuesser\Repository\PlaceRepository;
|
use MapGuesser\Repository\PlaceRepository;
|
||||||
use MapGuesser\Repository\UserInChallengeRepository;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
use MapGuesser\Response\JsonContent;
|
||||||
use SokoWeb\Response\HtmlContent;
|
|
||||||
use SokoWeb\Response\JsonContent;
|
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
use MapGuesser\Util\Panorama\Pov;
|
use MapGuesser\Util\Panorama\Pov;
|
||||||
|
|
||||||
class MapAdminController implements IAuthenticationRequired, ISecured
|
class MapAdminController implements ISecured
|
||||||
{
|
{
|
||||||
private static string $unnamedMapName = '[unnamed map]';
|
private static string $unnamedMapName = '[unnamed map]';
|
||||||
|
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
private MapRepository $mapRepository;
|
private MapRepository $mapRepository;
|
||||||
|
|
||||||
private PlaceRepository $placeRepository;
|
private PlaceRepository $placeRepository;
|
||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
public function __construct(IRequest $request)
|
||||||
|
|
||||||
private ChallengeRepository $challengeRepository;
|
|
||||||
|
|
||||||
private GuessRepository $guessRepository;
|
|
||||||
|
|
||||||
private PlaceInChallengeRepository $placeInChallengeRepository;
|
|
||||||
|
|
||||||
private UserInChallengeRepository $userInChallengeRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->mapRepository = new MapRepository();
|
$this->mapRepository = new MapRepository();
|
||||||
$this->placeRepository = new PlaceRepository();
|
$this->placeRepository = new PlaceRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
|
||||||
$this->challengeRepository = new ChallengeRepository();
|
|
||||||
$this->guessRepository = new GuessRepository();
|
|
||||||
$this->placeInChallengeRepository = new PlaceInChallengeRepository();
|
|
||||||
$this->userInChallengeRepository = new UserInChallengeRepository();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
return \Container::$request->user()->hasPermission(IUser::PERMISSION_ADMIN);
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
return $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMapEditor(): IContent
|
public function getMapEditor(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
if ($mapId) {
|
if ($mapId) {
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
@ -77,7 +59,6 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
'mapId' => $mapId,
|
'mapId' => $mapId,
|
||||||
'mapName' => $map->getName(),
|
'mapName' => $map->getName(),
|
||||||
'mapDescription' => str_replace('<br>', "\n", $map->getDescription()),
|
'mapDescription' => str_replace('<br>', "\n", $map->getDescription()),
|
||||||
'mapUnlisted' => $map->getUnlisted(),
|
|
||||||
'bounds' => $map->getBounds()->toArray(),
|
'bounds' => $map->getBounds()->toArray(),
|
||||||
'places' => &$places
|
'places' => &$places
|
||||||
]);
|
]);
|
||||||
@ -85,7 +66,7 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
|
|
||||||
public function getPlace(): IContent
|
public function getPlace(): IContent
|
||||||
{
|
{
|
||||||
$placeId = (int) \Container::$request->query('placeId');
|
$placeId = (int) $this->request->query('placeId');
|
||||||
|
|
||||||
$place = $this->placeRepository->getById($placeId);
|
$place = $this->placeRepository->getById($placeId);
|
||||||
|
|
||||||
@ -94,14 +75,16 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
|
|
||||||
public function saveMap(): IContent
|
public function saveMap(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
if ($mapId) {
|
if ($mapId) {
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
} else {
|
} else {
|
||||||
$map = new Map();
|
$map = new Map();
|
||||||
$map->setName(self::$unnamedMapName);
|
$map->setName(self::$unnamedMapName);
|
||||||
\Container::$persistentDataManager->saveToDb($map);
|
$this->pdm->saveToDb($map);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_POST['added'])) {
|
if (isset($_POST['added'])) {
|
||||||
@ -123,7 +106,7 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($place);
|
$this->pdm->saveToDb($place);
|
||||||
|
|
||||||
$addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $place->getId()];
|
$addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $place->getId()];
|
||||||
}
|
}
|
||||||
@ -145,7 +128,7 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
));
|
));
|
||||||
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
$place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($place);
|
$this->pdm->saveToDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +138,7 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
|
|
||||||
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
$place = $this->placeRepository->getById((int) $placeRaw['id']);
|
||||||
|
|
||||||
$this->deletePlace($place);
|
$this->pdm->deleteFromDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,65 +153,38 @@ class MapAdminController implements IAuthenticationRequired, ISecured
|
|||||||
if (isset($_POST['description'])) {
|
if (isset($_POST['description'])) {
|
||||||
$map->setDescription(str_replace(["\n", "\r\n"], '<br>', $_POST['description']));
|
$map->setDescription(str_replace(["\n", "\r\n"], '<br>', $_POST['description']));
|
||||||
}
|
}
|
||||||
if (isset($_POST['unlisted'])) {
|
|
||||||
$map->setUnlisted((bool)$_POST['unlisted']);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($map);
|
$this->pdm->saveToDb($map);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
|
return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteMap(): IContent
|
public function deleteMap(): IContent
|
||||||
{
|
{
|
||||||
$mapId = (int) \Container::$request->query('mapId');
|
$mapId = (int) $this->request->query('mapId');
|
||||||
|
|
||||||
$map = $this->mapRepository->getById($mapId);
|
$map = $this->mapRepository->getById($mapId);
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
$this->deletePlaces($map);
|
$this->deletePlaces($map);
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($map);
|
$this->pdm->deleteFromDb($map);
|
||||||
|
|
||||||
|
\Container::$dbConnection->commit();
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deletePlace(Place $place): void
|
|
||||||
{
|
|
||||||
foreach ($this->userPlayedPlaceRepository->getAllByPlace($place) as $userPlayedPlace) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($userPlayedPlace);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->challengeRepository->getAllByPlace($place) as $challenge) {
|
|
||||||
$this->deleteChallenge($challenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($place);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deletePlaces(Map $map): void
|
private function deletePlaces(Map $map): void
|
||||||
{
|
{
|
||||||
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
foreach ($this->placeRepository->getAllForMap($map) as $place) {
|
||||||
$this->deletePlace($place);
|
$this->pdm->deleteFromDb($place);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deleteChallenge(Challenge $challenge): void
|
|
||||||
{
|
|
||||||
foreach ($this->userInChallengeRepository->getAllByChallenge($challenge) as $userInChallenge) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($userInChallenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->guessRepository->getAllInChallenge($challenge, ['place_in_challange']) as $guess) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($guess);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->placeInChallengeRepository->getAllByChallenge($challenge) as $placeInChallenge) {
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($placeInChallenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($challenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function calculateMapBounds(Map $map): Bounds
|
private function calculateMapBounds(Map $map): Bounds
|
||||||
{
|
{
|
||||||
$bounds = new Bounds();
|
$bounds = new Bounds();
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use SokoWeb\Database\RawExpression;
|
use MapGuesser\Database\RawExpression;
|
||||||
use SokoWeb\Interfaces\Authentication\IUser;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
use SokoWeb\Interfaces\Database\IResultSet;
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use SokoWeb\Response\HtmlContent;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
|
use MapGuesser\Response\HtmlContent;
|
||||||
|
|
||||||
class MapsController
|
class MapsController
|
||||||
{
|
{
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
public function getMaps(): IContent
|
public function getMaps(): IContent
|
||||||
{
|
{
|
||||||
//TODO: from repository - count should be added
|
//TODO: from repository - count should be added
|
||||||
@ -22,19 +30,12 @@ class MapsController
|
|||||||
['maps', 'bound_north_lat'],
|
['maps', 'bound_north_lat'],
|
||||||
['maps', 'bound_east_lng'],
|
['maps', 'bound_east_lng'],
|
||||||
['maps', 'area'],
|
['maps', 'area'],
|
||||||
['maps', 'unlisted'],
|
|
||||||
new RawExpression('COUNT(places.id) AS num_places')
|
new RawExpression('COUNT(places.id) AS num_places')
|
||||||
]);
|
]);
|
||||||
$select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
|
$select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
|
||||||
$select->groupBy(['maps', 'id']);
|
$select->groupBy(['maps', 'id']);
|
||||||
$select->orderBy('name');
|
$select->orderBy('name');
|
||||||
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
$isAdmin = $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN);
|
|
||||||
if (!$isAdmin) {
|
|
||||||
$select->where(['maps', 'unlisted'], '=', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $select->execute();
|
$result = $select->execute();
|
||||||
|
|
||||||
$maps = [];
|
$maps = [];
|
||||||
@ -44,10 +45,10 @@ class MapsController
|
|||||||
$maps[] = $map;
|
$maps[] = $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user = $this->request->user();
|
||||||
return new HtmlContent('maps', [
|
return new HtmlContent('maps', [
|
||||||
'maps' => $maps,
|
'maps' => $maps,
|
||||||
'isLoggedIn' => $user !== null,
|
'isAdmin' => $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN)
|
||||||
'isAdmin' => $isAdmin
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,50 +1,44 @@
|
|||||||
<?php namespace MapGuesser\Controller;
|
<?php namespace MapGuesser\Controller;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\Http\Request;
|
use MapGuesser\Http\Request;
|
||||||
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
|
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||||
use SokoWeb\Interfaces\Response\IContent;
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
use SokoWeb\Interfaces\Response\IRedirect;
|
use MapGuesser\Interfaces\Response\IContent;
|
||||||
use SokoWeb\OAuth\GoogleOAuth;
|
use MapGuesser\Interfaces\Response\IRedirect;
|
||||||
|
use MapGuesser\OAuth\GoogleOAuth;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\Repository\GuessRepository;
|
|
||||||
use MapGuesser\Repository\UserRepository;
|
|
||||||
use MapGuesser\Repository\UserConfirmationRepository;
|
use MapGuesser\Repository\UserConfirmationRepository;
|
||||||
use MapGuesser\Repository\UserInChallengeRepository;
|
|
||||||
use MapGuesser\Repository\UserPasswordResetterRepository;
|
use MapGuesser\Repository\UserPasswordResetterRepository;
|
||||||
use MapGuesser\Repository\UserPlayedPlaceRepository;
|
use MapGuesser\Response\HtmlContent;
|
||||||
use SokoWeb\Response\HtmlContent;
|
use MapGuesser\Response\JsonContent;
|
||||||
use SokoWeb\Response\JsonContent;
|
use MapGuesser\Response\Redirect;
|
||||||
use SokoWeb\Response\Redirect;
|
use MapGuesser\Util\JwtParser;
|
||||||
use SokoWeb\Util\JwtParser;
|
|
||||||
|
|
||||||
class UserController implements IAuthenticationRequired
|
class UserController implements ISecured
|
||||||
{
|
{
|
||||||
private UserRepository $userRepository;
|
private IRequest $request;
|
||||||
|
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
private UserConfirmationRepository $userConfirmationRepository;
|
private UserConfirmationRepository $userConfirmationRepository;
|
||||||
|
|
||||||
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
private UserPasswordResetterRepository $userPasswordResetterRepository;
|
||||||
|
|
||||||
private UserPlayedPlaceRepository $userPlayedPlaceRepository;
|
public function __construct(IRequest $request)
|
||||||
|
|
||||||
private UserInChallengeRepository $userInChallengeRepository;
|
|
||||||
|
|
||||||
private GuessRepository $guessRepository;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
{
|
||||||
$this->userRepository = new UserRepository();
|
$this->request = $request;
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
$this->userConfirmationRepository = new UserConfirmationRepository();
|
$this->userConfirmationRepository = new UserConfirmationRepository();
|
||||||
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
$this->userPasswordResetterRepository = new UserPasswordResetterRepository();
|
||||||
$this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
|
|
||||||
$this->userInChallengeRepository = new UserInChallengeRepository();
|
|
||||||
$this->guessRepository = new GuessRepository();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isAuthenticationRequired(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
return true;
|
$user = $this->request->user();
|
||||||
|
|
||||||
|
return $user !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAccount(): IContent
|
public function getAccount(): IContent
|
||||||
@ -52,153 +46,29 @@ class UserController implements IAuthenticationRequired
|
|||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
$user = \Container::$request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
return new HtmlContent('account/account', ['user' => $user->toArray()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getGoogleConnectRedirect(): IRedirect
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
|
|
||||||
$state = bin2hex(random_bytes(16));
|
|
||||||
$nonce = bin2hex(random_bytes(16));
|
|
||||||
|
|
||||||
\Container::$request->session()->set('oauth_state', $state);
|
|
||||||
\Container::$request->session()->set('oauth_nonce', $nonce);
|
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
|
||||||
|
|
||||||
$url = $oAuth->getDialogUrl(
|
|
||||||
$state,
|
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleConnect-confirm')->generateLink(),
|
|
||||||
$nonce,
|
|
||||||
$user->getEmail()
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Redirect($url, IRedirect::TEMPORARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleConnectConfirm(): IContent
|
|
||||||
{
|
|
||||||
$defaultError = 'Authentication with Google failed. Please <a href="' . \Container::$routeCollection->getRoute('account.googleConnect')->generateLink() . '" title="Connect with Google">try again</a>!';
|
|
||||||
|
|
||||||
if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) {
|
|
||||||
return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
|
||||||
$tokenData = $oAuth->getToken(
|
|
||||||
\Container::$request->query('code'),
|
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleConnect-confirm')->generateLink()
|
|
||||||
);
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
|
||||||
return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
|
||||||
$idToken = $jwtParser->getPayload();
|
|
||||||
if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) {
|
|
||||||
return new HtmlContent('account/google_connect', ['success' => false, 'error' => $defaultError]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$anotherUser = $this->userRepository->getByGoogleSub($idToken['sub']);
|
|
||||||
if ($anotherUser !== null) {
|
|
||||||
return new HtmlContent('account/google_connect', [
|
|
||||||
'success' => false,
|
|
||||||
'error' => 'This Google account is linked to another account.'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$request->session()->set('google_user_data', $idToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
|
|
||||||
return new HtmlContent('account/google_connect', [
|
|
||||||
'success' => true,
|
|
||||||
'googleAccount' => $idToken['email'],
|
|
||||||
'userEmail' => $user->getEmail()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function connectGoogle(): IContent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
if (!$user->checkPassword(\Container::$request->post('password'))) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => [
|
|
||||||
'errorText' => 'The given password is wrong.'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$googleUserData = \Container::$request->session()->get('google_user_data');
|
|
||||||
$user->setGoogleSub($googleUserData['sub']);
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleDisconnectConfirm(): IContent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
|
|
||||||
return new HtmlContent('account/google_disconnect', [
|
|
||||||
'success' => true,
|
|
||||||
'userEmail' => $user->getEmail()
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function disconnectGoogle(): IContent
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var User $user
|
|
||||||
*/
|
|
||||||
$user = \Container::$request->user();
|
|
||||||
if (!$user->checkPassword(\Container::$request->post('password'))) {
|
|
||||||
return new JsonContent([
|
|
||||||
'error' => [
|
|
||||||
'errorText' => 'The given password is wrong.'
|
|
||||||
]
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->setGoogleSub(null);
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getGoogleAuthenticateRedirect(): IRedirect
|
public function getGoogleAuthenticateRedirect(): IRedirect
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
$user = \Container::$request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
$state = bin2hex(random_bytes(16));
|
$state = bin2hex(random_bytes(16));
|
||||||
$nonce = bin2hex(random_bytes(16));
|
$nonce = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
\Container::$request->session()->set('oauth_state', $state);
|
$this->request->session()->set('oauth_state', $state);
|
||||||
\Container::$request->session()->set('oauth_nonce', $nonce);
|
$this->request->session()->set('oauth_nonce', $nonce);
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
|
|
||||||
$url = $oAuth->getDialogUrl(
|
$url = $oAuth->getDialogUrl(
|
||||||
$state,
|
$state,
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
|
||||||
$nonce,
|
$nonce,
|
||||||
$user->getEmail()
|
$user->getEmail()
|
||||||
);
|
);
|
||||||
@ -211,16 +81,16 @@ class UserController implements IAuthenticationRequired
|
|||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
$user = \Container::$request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
if (\Container::$request->query('state') !== \Container::$request->session()->get('oauth_state')) {
|
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
|
||||||
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$oAuth = new GoogleOAuth(new Request());
|
$oAuth = new GoogleOAuth(new Request());
|
||||||
$tokenData = $oAuth->getToken(
|
$tokenData = $oAuth->getToken(
|
||||||
\Container::$request->query('code'),
|
$this->request->query('code'),
|
||||||
\Container::$request->getBase() . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()
|
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isset($tokenData['id_token'])) {
|
if (!isset($tokenData['id_token'])) {
|
||||||
@ -230,7 +100,7 @@ class UserController implements IAuthenticationRequired
|
|||||||
$jwtParser = new JwtParser($tokenData['id_token']);
|
$jwtParser = new JwtParser($tokenData['id_token']);
|
||||||
$idToken = $jwtParser->getPayload();
|
$idToken = $jwtParser->getPayload();
|
||||||
|
|
||||||
if ($idToken['nonce'] !== \Container::$request->session()->get('oauth_nonce')) {
|
if ($idToken['nonce'] !== $this->request->session()->get('oauth_nonce')) {
|
||||||
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
return new HtmlContent('account/google_authenticate', ['success' => false]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +112,7 @@ class UserController implements IAuthenticationRequired
|
|||||||
}
|
}
|
||||||
|
|
||||||
$authenticatedWithGoogleUntil = new DateTime('+45 seconds');
|
$authenticatedWithGoogleUntil = new DateTime('+45 seconds');
|
||||||
\Container::$request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil);
|
$this->request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil);
|
||||||
|
|
||||||
return new HtmlContent('account/google_authenticate', [
|
return new HtmlContent('account/google_authenticate', [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
@ -255,7 +125,7 @@ class UserController implements IAuthenticationRequired
|
|||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
$user = \Container::$request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
return new HtmlContent('account/delete', ['user' => $user->toArray()]);
|
return new HtmlContent('account/delete', ['user' => $user->toArray()]);
|
||||||
}
|
}
|
||||||
@ -265,49 +135,19 @@ class UserController implements IAuthenticationRequired
|
|||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
$user = \Container::$request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
if (!$this->confirmUserIdentity(
|
if (!$this->confirmUserIdentity(
|
||||||
$user,
|
$user,
|
||||||
\Container::$request->session()->get('authenticated_with_google_until'),
|
$this->request->session()->get('authenticated_with_google_until'),
|
||||||
\Container::$request->post('password'),
|
$this->request->post('password'),
|
||||||
$error
|
$error
|
||||||
)) {
|
)) {
|
||||||
return new JsonContent(['error' => ['errorText' => $error]]);
|
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$newEmail = \Container::$request->post('email');
|
if (strlen($this->request->post('password_new')) > 0) {
|
||||||
if ($newEmail !== $user->getEmail()) {
|
if (strlen($this->request->post('password_new')) < 6) {
|
||||||
if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given email address is not valid.']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->userRepository->getByEmail($newEmail) !== null) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given email address belongs to another account.']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->setEmail($newEmail);
|
|
||||||
}
|
|
||||||
|
|
||||||
$newUsername = \Container::$request->post('username');
|
|
||||||
if ($newUsername !== $user->getUsername()) {
|
|
||||||
if (strlen($newUsername) == 0) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'Username cannot be empty.']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preg_match('/^[a-zA-Z0-9_\-\.]+$/', $newUsername) !== 1) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'Username can contain only english letters, digits, - (hyphen), . (dot), _ (underscore).']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->userRepository->getByUsername($newUsername) !== null) {
|
|
||||||
return new JsonContent(['error' => ['errorText' => 'The given username is already taken.']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$user->setUsername($newUsername);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen(\Container::$request->post('password_new')) > 0) {
|
|
||||||
if (strlen(\Container::$request->post('password_new')) < 6) {
|
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!'
|
||||||
@ -315,7 +155,7 @@ class UserController implements IAuthenticationRequired
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\Container::$request->post('password_new') !== \Container::$request->post('password_new_confirm')) {
|
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
|
||||||
return new JsonContent([
|
return new JsonContent([
|
||||||
'error' => [
|
'error' => [
|
||||||
'errorText' => 'The given new passwords do not match.'
|
'errorText' => 'The given new passwords do not match.'
|
||||||
@ -323,12 +163,12 @@ class UserController implements IAuthenticationRequired
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->setPlainPassword(\Container::$request->post('password_new'));
|
$user->setPlainPassword($this->request->post('password_new'));
|
||||||
}
|
}
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($user);
|
$this->pdm->saveToDb($user);
|
||||||
|
|
||||||
\Container::$request->session()->delete('authenticated_with_google_until');
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
@ -338,47 +178,39 @@ class UserController implements IAuthenticationRequired
|
|||||||
/**
|
/**
|
||||||
* @var User $user
|
* @var User $user
|
||||||
*/
|
*/
|
||||||
$user = \Container::$request->user();
|
$user = $this->request->user();
|
||||||
|
|
||||||
if (!$this->confirmUserIdentity(
|
if (!$this->confirmUserIdentity(
|
||||||
$user,
|
$user,
|
||||||
\Container::$request->session()->get('authenticated_with_google_until'),
|
$this->request->session()->get('authenticated_with_google_until'),
|
||||||
\Container::$request->post('password'),
|
$this->request->post('password'),
|
||||||
$error
|
$error
|
||||||
)) {
|
)) {
|
||||||
return new JsonContent(['error' => ['errorText' => $error]]);
|
return new JsonContent(['error' => ['errorText' => $error]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
\Container::$dbConnection->startTransaction();
|
||||||
|
|
||||||
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
$userConfirmation = $this->userConfirmationRepository->getByUser($user);
|
||||||
if ($userConfirmation !== null) {
|
if ($userConfirmation !== null) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($userConfirmation);
|
$this->pdm->deleteFromDb($userConfirmation);
|
||||||
}
|
}
|
||||||
|
|
||||||
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
$userPasswordResetter = $this->userPasswordResetterRepository->getByUser($user);
|
||||||
if ($userPasswordResetter !== null) {
|
if ($userPasswordResetter !== null) {
|
||||||
\Container::$persistentDataManager->deleteFromDb($userPasswordResetter);
|
$this->pdm->deleteFromDb($userPasswordResetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->userPlayedPlaceRepository->getAllByUser($user) as $userPlayedPlace) {
|
$this->pdm->deleteFromDb($user);
|
||||||
\Container::$persistentDataManager->deleteFromDb($userPlayedPlace);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->userInChallengeRepository->getAllByUser($user) as $userInChallenge) {
|
\Container::$dbConnection->commit();
|
||||||
\Container::$persistentDataManager->deleteFromDb($userInChallenge);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($this->guessRepository->getAllByUser($user) as $guess) {
|
$this->request->session()->delete('authenticated_with_google_until');
|
||||||
\Container::$persistentDataManager->deleteFromDb($guess);
|
|
||||||
}
|
|
||||||
|
|
||||||
\Container::$persistentDataManager->deleteFromDb($user);
|
|
||||||
|
|
||||||
\Container::$request->session()->delete('authenticated_with_google_until');
|
|
||||||
|
|
||||||
return new JsonContent(['success' => true]);
|
return new JsonContent(['success' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, ?string &$error): bool
|
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, string &$error): bool
|
||||||
{
|
{
|
||||||
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
|
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
|
||||||
return true;
|
return true;
|
||||||
|
114
src/Database/Mysql/Connection.php
Normal file
114
src/Database/Mysql/Connection.php
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Mysql;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Database\IStatement;
|
||||||
|
use mysqli;
|
||||||
|
|
||||||
|
class Connection implements IConnection
|
||||||
|
{
|
||||||
|
private mysqli $connection;
|
||||||
|
|
||||||
|
public function __construct(string $host, string $user, string $password, string $db, int $port = -1, string $socket = null)
|
||||||
|
{
|
||||||
|
if ($port < 0) {
|
||||||
|
$port = (int) ini_get('mysqli.default_port');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($socket === null) {
|
||||||
|
$socket = (string) ini_get('mysqli.default_socket');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection = new mysqli($host, $user, $password, $db, $port, $socket);
|
||||||
|
|
||||||
|
if ($this->connection->connect_error) {
|
||||||
|
throw new \Exception('Connection failed: ' . $this->connection->connect_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->connection->set_charset('utf8mb4')) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->connection->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startTransaction(): void
|
||||||
|
{
|
||||||
|
if (!$this->connection->autocommit(false)) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commit(): void
|
||||||
|
{
|
||||||
|
if (!$this->connection->commit() || !$this->connection->autocommit(true)) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rollback(): void
|
||||||
|
{
|
||||||
|
if (!$this->connection->rollback() || !$this->connection->autocommit(true)) {
|
||||||
|
throw new \Exception($this->connection->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query(string $query): ?IResultSet
|
||||||
|
{
|
||||||
|
if (!($result = $this->connection->query($query))) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result !== true) {
|
||||||
|
return new ResultSet($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function multiQuery(string $query): array
|
||||||
|
{
|
||||||
|
if (!$this->connection->multi_query($query)) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = [];
|
||||||
|
do {
|
||||||
|
if ($result = $this->connection->store_result()) {
|
||||||
|
$ret[] = new ResultSet($result);
|
||||||
|
} else {
|
||||||
|
$ret[] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->connection->more_results();
|
||||||
|
} while ($this->connection->next_result());
|
||||||
|
|
||||||
|
if ($this->connection->error) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare(string $query): IStatement
|
||||||
|
{
|
||||||
|
if (!($stmt = $this->connection->prepare($query))) {
|
||||||
|
throw new \Exception($this->connection->error . '. Query: ' . $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Statement($stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastId(): int
|
||||||
|
{
|
||||||
|
return $this->connection->insert_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectedRows(): int
|
||||||
|
{
|
||||||
|
return $this->connection->affected_rows;
|
||||||
|
}
|
||||||
|
}
|
62
src/Database/Mysql/ResultSet.php
Normal file
62
src/Database/Mysql/ResultSet.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Mysql;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use mysqli_result;
|
||||||
|
|
||||||
|
class ResultSet implements IResultSet
|
||||||
|
{
|
||||||
|
private mysqli_result $result;
|
||||||
|
|
||||||
|
public function __construct(mysqli_result $result)
|
||||||
|
{
|
||||||
|
$this->result = $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetch(int $type = IResultSet::FETCH_ASSOC): ?array
|
||||||
|
{
|
||||||
|
return $this->result->fetch_array($this->convertFetchType($type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchAll(int $type = IResultSet::FETCH_ASSOC): array
|
||||||
|
{
|
||||||
|
return $this->result->fetch_all($this->convertFetchType($type));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchOneColumn(string $valueName, string $keyName = null): array
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
while ($r = $this->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
if (isset($keyName)) {
|
||||||
|
$array[$r[$keyName]] = $r[$valueName];
|
||||||
|
} else {
|
||||||
|
$array[] = $r[$valueName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertFetchType(int $type): int
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case IResultSet::FETCH_ASSOC:
|
||||||
|
$internal_type = MYSQLI_ASSOC;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IResultSet::FETCH_BOTH:
|
||||||
|
$internal_type = MYSQLI_BOTH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IResultSet::FETCH_NUM:
|
||||||
|
$internal_type = MYSQLI_NUM;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$internal_type = MYSQLI_BOTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $internal_type;
|
||||||
|
}
|
||||||
|
}
|
79
src/Database/Mysql/Statement.php
Normal file
79
src/Database/Mysql/Statement.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Mysql;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Interfaces\Database\IStatement;
|
||||||
|
use mysqli_stmt;
|
||||||
|
|
||||||
|
class Statement implements IStatement
|
||||||
|
{
|
||||||
|
private mysqli_stmt $stmt;
|
||||||
|
|
||||||
|
public function __construct(mysqli_stmt $stmt)
|
||||||
|
{
|
||||||
|
$this->stmt = $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->stmt->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(array $params = []): ?IResultSet
|
||||||
|
{
|
||||||
|
if ($params) {
|
||||||
|
$ref_params = [''];
|
||||||
|
|
||||||
|
foreach ($params as &$param) {
|
||||||
|
$type = gettype($param);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case 'integer':
|
||||||
|
case 'double':
|
||||||
|
case 'string':
|
||||||
|
$t = $type[0];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'NULL':
|
||||||
|
$t = 's';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
$param = (string) (int) $param;
|
||||||
|
$t = 's';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
$param = json_encode($param);
|
||||||
|
$t = 's';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($t)) {
|
||||||
|
throw new \Exception('Data type ' . $type . ' not supported!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ref_params[] = &$param;
|
||||||
|
$ref_params[0] .= $t;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!call_user_func_array([$this->stmt, 'bind_param'], $ref_params)) {
|
||||||
|
throw new \Exception($this->stmt->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->stmt->execute()) {
|
||||||
|
throw new \Exception($this->stmt->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result_set = $this->stmt->get_result()) {
|
||||||
|
return new ResultSet($result_set);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAffectedRows(): int
|
||||||
|
{
|
||||||
|
return $this->stmt->affected_rows;
|
||||||
|
}
|
||||||
|
}
|
140
src/Database/Query/Modify.php
Executable file
140
src/Database/Query/Modify.php
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Query;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Database\Utils;
|
||||||
|
|
||||||
|
class Modify
|
||||||
|
{
|
||||||
|
private IConnection $connection;
|
||||||
|
|
||||||
|
private string $table;
|
||||||
|
|
||||||
|
private string $idName = 'id';
|
||||||
|
|
||||||
|
private array $attributes = [];
|
||||||
|
|
||||||
|
private ?string $externalId = null;
|
||||||
|
|
||||||
|
private bool $autoIncrement = true;
|
||||||
|
|
||||||
|
public function __construct(IConnection $connection, string $table)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->table = $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIdName(string $idName): Modify
|
||||||
|
{
|
||||||
|
$this->idName = $idName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExternalId($id): Modify
|
||||||
|
{
|
||||||
|
$this->externalId = $id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAutoIncrement(bool $autoIncrement = true): Modify
|
||||||
|
{
|
||||||
|
$this->autoIncrement = $autoIncrement;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fill(array $attributes): Modify
|
||||||
|
{
|
||||||
|
$this->attributes = array_merge($this->attributes, $attributes);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $name, $value): Modify
|
||||||
|
{
|
||||||
|
$this->attributes[$name] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id): Modify
|
||||||
|
{
|
||||||
|
$this->attributes[$this->idName] = $id;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->attributes[$this->idName];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
if (isset($this->attributes[$this->idName])) {
|
||||||
|
$this->update();
|
||||||
|
} else {
|
||||||
|
$this->insert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(): void
|
||||||
|
{
|
||||||
|
if (!isset($this->attributes[$this->idName])) {
|
||||||
|
throw new \Exception('No primary key specified!');
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = 'DELETE FROM ' . Utils::backtick($this->table) . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute([$this->idName => $this->attributes[$this->idName]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function insert(): void
|
||||||
|
{
|
||||||
|
if ($this->externalId !== null) {
|
||||||
|
$this->attributes[$this->idName] = $this->externalId;
|
||||||
|
} elseif (!$this->autoIncrement) {
|
||||||
|
$this->attributes[$this->idName] = $this->generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = $this->generateColumnsWithBinding(array_keys($this->attributes));
|
||||||
|
|
||||||
|
$query = 'INSERT INTO ' . Utils::backtick($this->table) . ' SET ' . $set;
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute($this->attributes);
|
||||||
|
|
||||||
|
if ($this->autoIncrement) {
|
||||||
|
$this->attributes[$this->idName] = $this->connection->lastId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update(): void
|
||||||
|
{
|
||||||
|
$attributes = $this->attributes;
|
||||||
|
unset($attributes[$this->idName]);
|
||||||
|
|
||||||
|
$set = $this->generateColumnsWithBinding(array_keys($attributes));
|
||||||
|
|
||||||
|
$query = 'UPDATE ' . Utils::backtick($this->table) . ' SET ' . $set . ' WHERE ' . Utils::backtick($this->idName) . '=?';
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare($query);
|
||||||
|
$stmt->execute(array_merge($attributes, [$this->idName => $this->attributes[$this->idName]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateColumnsWithBinding(array $columns): string
|
||||||
|
{
|
||||||
|
array_walk($columns, function(&$value, $key) {
|
||||||
|
$value = Utils::backtick($value) . '=?';
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateKey(): string
|
||||||
|
{
|
||||||
|
return substr(hash('sha256', serialize($this->attributes) . random_bytes(5) . microtime()), 0, 7);
|
||||||
|
}
|
||||||
|
}
|
412
src/Database/Query/Select.php
Normal file
412
src/Database/Query/Select.php
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
<?php namespace MapGuesser\Database\Query;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use MapGuesser\Interfaces\Database\IConnection;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\Database\RawExpression;
|
||||||
|
use MapGuesser\Database\Utils;
|
||||||
|
|
||||||
|
class Select
|
||||||
|
{
|
||||||
|
const CONDITION_WHERE = 0;
|
||||||
|
|
||||||
|
const CONDITION_HAVING = 1;
|
||||||
|
|
||||||
|
private IConnection $connection;
|
||||||
|
|
||||||
|
private string $table;
|
||||||
|
|
||||||
|
private string $idName = 'id';
|
||||||
|
|
||||||
|
private array $tableAliases = [];
|
||||||
|
|
||||||
|
private array $joins = [];
|
||||||
|
|
||||||
|
private array $columns = [];
|
||||||
|
|
||||||
|
private array $conditions = [self::CONDITION_WHERE => [], self::CONDITION_HAVING => []];
|
||||||
|
|
||||||
|
private array $groups = [];
|
||||||
|
|
||||||
|
private array $orders = [];
|
||||||
|
|
||||||
|
private ?array $limit;
|
||||||
|
|
||||||
|
public function __construct(IConnection $connection, ?string $table = null)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
|
||||||
|
if ($table !== null) {
|
||||||
|
$this->table = $table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIdName(string $idName): Select
|
||||||
|
{
|
||||||
|
$this->idName = $idName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTableAliases(array $tableAliases): Select
|
||||||
|
{
|
||||||
|
$this->tableAliases = array_merge($this->tableAliases, $tableAliases);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function from(string $table): Select
|
||||||
|
{
|
||||||
|
$this->table = $table;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function columns(array $columns): Select
|
||||||
|
{
|
||||||
|
$this->columns = array_merge($this->columns, $columns);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function innerJoin($table, $column1, string $relation, $column2): Select
|
||||||
|
{
|
||||||
|
$this->addJoin('INNER', $table, $column1, $relation, $column2);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leftJoin($table, $column1, string $relation, $column2): Select
|
||||||
|
{
|
||||||
|
$this->addJoin('LEFT', $table, $column1, $relation, $column2);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereId($value): Select
|
||||||
|
{
|
||||||
|
$this->addWhereCondition('AND', $this->idName, '=', $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function where($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addWhereCondition('AND', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orWhere($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addWhereCondition('OR', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function having($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addHavingCondition('AND', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orHaving($column, string $relation = null, $value = null): Select
|
||||||
|
{
|
||||||
|
$this->addHavingCondition('OR', $column, $relation, $value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function groupBy($column): Select
|
||||||
|
{
|
||||||
|
$this->groups[] = $column;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderBy($column, string $type = 'ASC'): Select
|
||||||
|
{
|
||||||
|
$this->orders[] = [$column, $type];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function limit(int $limit, int $offset = 0): Select
|
||||||
|
{
|
||||||
|
$this->limit = [$limit, $offset];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetLimit(): void
|
||||||
|
{
|
||||||
|
$this->limit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function paginate(int $page, int $itemsPerPage): Select
|
||||||
|
{
|
||||||
|
$this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(): IResultSet
|
||||||
|
{
|
||||||
|
list($query, $params) = $this->generateQuery();
|
||||||
|
|
||||||
|
return $this->connection->prepare($query)->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
if (count($this->groups) > 0 || count($this->conditions[self::CONDITION_HAVING]) > 0) {
|
||||||
|
$orders = $this->orders;
|
||||||
|
|
||||||
|
$this->orders = [];
|
||||||
|
|
||||||
|
list($query, $params) = $this->generateQuery();
|
||||||
|
|
||||||
|
$result = $this->connection->prepare('SELECT COUNT(*) num_rows FROM (' . $query . ') x')
|
||||||
|
->execute($params)
|
||||||
|
->fetch(IResultSet::FETCH_NUM);
|
||||||
|
|
||||||
|
$this->orders = $orders;
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
} else {
|
||||||
|
$columns = $this->columns;
|
||||||
|
$orders = $this->orders;
|
||||||
|
|
||||||
|
$this->columns = [new RawExpression('COUNT(*) num_rows')];
|
||||||
|
$this->orders = [];
|
||||||
|
|
||||||
|
list($query, $params) = $this->generateQuery();
|
||||||
|
|
||||||
|
$result = $this->connection->prepare($query)
|
||||||
|
->execute($params)
|
||||||
|
->fetch(IResultSet::FETCH_NUM);
|
||||||
|
|
||||||
|
$this->columns = $columns;
|
||||||
|
$this->orders = $orders;
|
||||||
|
|
||||||
|
return $result[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addJoin(string $type, $table, $column1, string $relation, $column2): void
|
||||||
|
{
|
||||||
|
$this->joins[] = [$type, $table, $column1, $relation, $column2];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addWhereCondition(string $logic, $column, string $relation, $value): void
|
||||||
|
{
|
||||||
|
$this->conditions[self::CONDITION_WHERE][] = [$logic, $column, $relation, $value];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addHavingCondition(string $logic, $column, string $relation, $value): void
|
||||||
|
{
|
||||||
|
$this->conditions[self::CONDITION_HAVING][] = [$logic, $column, $relation, $value];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateQuery(): array
|
||||||
|
{
|
||||||
|
$queryString = 'SELECT ' . $this->generateColumns() . ' FROM ' . $this->generateTable($this->table, true);
|
||||||
|
|
||||||
|
if (count($this->joins) > 0) {
|
||||||
|
$queryString .= ' ' . $this->generateJoins();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->conditions[self::CONDITION_WHERE]) > 0) {
|
||||||
|
list($wheres, $whereParams) = $this->generateConditions(self::CONDITION_WHERE);
|
||||||
|
|
||||||
|
$queryString .= ' WHERE ' . $wheres;
|
||||||
|
} else {
|
||||||
|
$whereParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->groups) > 0) {
|
||||||
|
$queryString .= ' GROUP BY ' . $this->generateGroupBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->conditions[self::CONDITION_HAVING]) > 0) {
|
||||||
|
list($havings, $havingParams) = $this->generateConditions(self::CONDITION_HAVING);
|
||||||
|
|
||||||
|
$queryString .= ' HAVING ' . $havings;
|
||||||
|
} else {
|
||||||
|
$havingParams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($this->orders) > 0) {
|
||||||
|
$queryString .= ' ORDER BY ' . $this->generateOrderBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->limit)) {
|
||||||
|
$queryString .= ' LIMIT ' . $this->limit[1] . ', ' . $this->limit[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$queryString, array_merge($whereParams, $havingParams)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateTable($table, bool $defineAlias = false): string
|
||||||
|
{
|
||||||
|
if ($table instanceof RawExpression) {
|
||||||
|
return (string) $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->tableAliases[$table])) {
|
||||||
|
return ($defineAlias ? Utils::backtick($this->tableAliases[$table]) . ' ' . Utils::backtick($table) : Utils::backtick($table));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils::backtick($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateColumn($column): string
|
||||||
|
{
|
||||||
|
if ($column instanceof RawExpression) {
|
||||||
|
return (string) $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($column)) {
|
||||||
|
$out = '';
|
||||||
|
|
||||||
|
if ($column[0]) {
|
||||||
|
$out .= $this->generateTable($column[0]) . '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= Utils::backtick($column[1]);
|
||||||
|
|
||||||
|
if (!empty($column[2])) {
|
||||||
|
$out .= ' ' . Utils::backtick($column[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $out;
|
||||||
|
} else {
|
||||||
|
return Utils::backtick($column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateColumns(): string
|
||||||
|
{
|
||||||
|
$columns = $this->columns;
|
||||||
|
|
||||||
|
array_walk($columns, function (&$value, $key) {
|
||||||
|
$value = $this->generateColumn($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateJoins(): string
|
||||||
|
{
|
||||||
|
$joins = $this->joins;
|
||||||
|
|
||||||
|
array_walk($joins, function (&$value, $key) {
|
||||||
|
$value = $value[0] . ' JOIN ' . $this->generateTable($value[1], true) . ' ON ' . $this->generateColumn($value[2]) . ' ' . $value[3] . ' ' . $this->generateColumn($value[4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(' ', $joins);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateConditions(int $type): array
|
||||||
|
{
|
||||||
|
$conditions = '';
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($this->conditions[$type] as $condition) {
|
||||||
|
list($logic, $column, $relation, $value) = $condition;
|
||||||
|
|
||||||
|
if ($column instanceof Closure) {
|
||||||
|
list($conditionsStringFragment, $paramsFragment) = $this->generateComplexConditionFragment($type, $column);
|
||||||
|
} else {
|
||||||
|
list($conditionsStringFragment, $paramsFragment) = $this->generateConditionFragment($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($conditions !== '') {
|
||||||
|
$conditions .= ' ' . $logic . ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditions .= $conditionsStringFragment;
|
||||||
|
$params = array_merge($params, $paramsFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$conditions, $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateConditionFragment(array $condition): array
|
||||||
|
{
|
||||||
|
list($logic, $column, $relation, $value) = $condition;
|
||||||
|
|
||||||
|
if ($column instanceof RawExpression) {
|
||||||
|
return [(string) $column, []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditionsString = $this->generateColumn($column) . ' ';
|
||||||
|
|
||||||
|
if ($value === null) {
|
||||||
|
return [$conditionsString . ($relation == '=' ? 'IS NULL' : 'IS NOT NULL'), []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$conditionsString .= strtoupper($relation) . ' ';;
|
||||||
|
|
||||||
|
switch ($relation = strtolower($relation)) {
|
||||||
|
case 'between':
|
||||||
|
$params = [$value[0], $value[1]];
|
||||||
|
|
||||||
|
$conditionsString .= '? AND ?';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'in':
|
||||||
|
case 'not in':
|
||||||
|
$params = $value;
|
||||||
|
|
||||||
|
if (count($value) > 0) {
|
||||||
|
$conditionsString .= '(' . implode(', ', array_fill(0, count($value), '?')) . ')';
|
||||||
|
} else {
|
||||||
|
$conditionsString = $relation == 'in' ? '0' : '1';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$params = [$value];
|
||||||
|
|
||||||
|
$conditionsString .= '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$conditionsString, $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateComplexConditionFragment(int $type, Closure $conditionCallback): array
|
||||||
|
{
|
||||||
|
$instance = new self($this->connection, $this->table);
|
||||||
|
$instance->tableAliases = $this->tableAliases;
|
||||||
|
|
||||||
|
$conditionCallback($instance);
|
||||||
|
|
||||||
|
list($conditions, $params) = $instance->generateConditions($type);
|
||||||
|
|
||||||
|
return ['(' . $conditions . ')', $params];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateGroupBy(): string
|
||||||
|
{
|
||||||
|
$groups = $this->groups;
|
||||||
|
|
||||||
|
array_walk($groups, function (&$value, $key) {
|
||||||
|
$value = $this->generateColumn($value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateOrderBy(): string
|
||||||
|
{
|
||||||
|
$orders = $this->orders;
|
||||||
|
|
||||||
|
array_walk($orders, function (&$value, $key) {
|
||||||
|
$value = $this->generateColumn($value[0]) . ' ' . strtoupper($value[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return implode(',', $orders);
|
||||||
|
}
|
||||||
|
}
|
16
src/Database/RawExpression.php
Normal file
16
src/Database/RawExpression.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php namespace MapGuesser\Database;
|
||||||
|
|
||||||
|
class RawExpression
|
||||||
|
{
|
||||||
|
private string $expression;
|
||||||
|
|
||||||
|
public function __construct(string $expression)
|
||||||
|
{
|
||||||
|
$this->expression = $expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->expression;
|
||||||
|
}
|
||||||
|
}
|
8
src/Database/Utils.php
Normal file
8
src/Database/Utils.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace MapGuesser\Database;
|
||||||
|
|
||||||
|
class Utils {
|
||||||
|
public static function backtick(string $name): string
|
||||||
|
{
|
||||||
|
return '`' . $name . '`';
|
||||||
|
}
|
||||||
|
}
|
102
src/Http/Request.php
Normal file
102
src/Http/Request.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php namespace MapGuesser\Http;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Http\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Http\IResponse;
|
||||||
|
|
||||||
|
class Request implements IRequest
|
||||||
|
{
|
||||||
|
private string $url;
|
||||||
|
|
||||||
|
private int $method;
|
||||||
|
|
||||||
|
private string $query = '';
|
||||||
|
|
||||||
|
private array $headers = [];
|
||||||
|
|
||||||
|
public function __construct(string $url = '', int $method = self::HTTP_GET)
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
$this->method = $method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUrl(string $url): void
|
||||||
|
{
|
||||||
|
$this->url = $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMethod(int $method): void
|
||||||
|
{
|
||||||
|
$this->method = $method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setQuery($query): void
|
||||||
|
{
|
||||||
|
if (is_string($query)) {
|
||||||
|
$this->query = $query;
|
||||||
|
} else {
|
||||||
|
$this->query = http_build_query($query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHeaders(array $headers): void
|
||||||
|
{
|
||||||
|
$this->headers = array_merge($this->headers, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(): IResponse
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
|
||||||
|
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);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, 'MapGuesser cURL/1.0');
|
||||||
|
|
||||||
|
if (count($this->headers) > 0) {
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseHeaders = [];
|
||||||
|
curl_setopt(
|
||||||
|
$ch,
|
||||||
|
CURLOPT_HEADERFUNCTION,
|
||||||
|
function ($ch, $header) use (&$responseHeaders) {
|
||||||
|
$len = strlen($header);
|
||||||
|
$header = explode(':', $header, 2);
|
||||||
|
|
||||||
|
if (count($header) < 2) {
|
||||||
|
return $len;
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseHeaders[strtolower(trim($header[0]))][] = trim($header[1]);
|
||||||
|
|
||||||
|
return $len;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$responseBody = curl_exec($ch);
|
||||||
|
|
||||||
|
if ($responseBody === false) {
|
||||||
|
$error = curl_error($ch);
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
throw new \Exception($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
return new Response($responseBody, $responseHeaders);
|
||||||
|
}
|
||||||
|
}
|
26
src/Http/Response.php
Normal file
26
src/Http/Response.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php namespace MapGuesser\Http;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Http\IResponse;
|
||||||
|
|
||||||
|
class Response implements IResponse
|
||||||
|
{
|
||||||
|
private string $body;
|
||||||
|
|
||||||
|
private array $headers;
|
||||||
|
|
||||||
|
public function __construct(string $body, array $headers)
|
||||||
|
{
|
||||||
|
$this->body = $body;
|
||||||
|
$this->headers = $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBody(): string
|
||||||
|
{
|
||||||
|
return $this->body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeaders(): array
|
||||||
|
{
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
}
|
16
src/Interfaces/Authentication/IUser.php
Normal file
16
src/Interfaces/Authentication/IUser.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Authentication;
|
||||||
|
|
||||||
|
interface IUser
|
||||||
|
{
|
||||||
|
const PERMISSION_NORMAL = 0;
|
||||||
|
|
||||||
|
const PERMISSION_ADMIN = 1;
|
||||||
|
|
||||||
|
public function hasPermission(int $permission): bool;
|
||||||
|
|
||||||
|
public function getUniqueId();
|
||||||
|
|
||||||
|
public function getDisplayName(): string;
|
||||||
|
|
||||||
|
public function checkPassword(string $password): bool;
|
||||||
|
}
|
6
src/Interfaces/Authorization/ISecured.php
Normal file
6
src/Interfaces/Authorization/ISecured.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Authorization;
|
||||||
|
|
||||||
|
interface ISecured
|
||||||
|
{
|
||||||
|
public function authorize(): bool;
|
||||||
|
}
|
20
src/Interfaces/Database/IConnection.php
Normal file
20
src/Interfaces/Database/IConnection.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IConnection
|
||||||
|
{
|
||||||
|
public function startTransaction(): void;
|
||||||
|
|
||||||
|
public function commit(): void;
|
||||||
|
|
||||||
|
public function rollback(): void;
|
||||||
|
|
||||||
|
public function query(string $query): ?IResultSet;
|
||||||
|
|
||||||
|
public function multiQuery(string $query): array;
|
||||||
|
|
||||||
|
public function prepare(string $query): IStatement;
|
||||||
|
|
||||||
|
public function lastId(): int;
|
||||||
|
|
||||||
|
public function getAffectedRows(): int;
|
||||||
|
}
|
16
src/Interfaces/Database/IResultSet.php
Normal file
16
src/Interfaces/Database/IResultSet.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IResultSet
|
||||||
|
{
|
||||||
|
const FETCH_ASSOC = 0;
|
||||||
|
|
||||||
|
const FETCH_NUM = 1;
|
||||||
|
|
||||||
|
const FETCH_BOTH = 2;
|
||||||
|
|
||||||
|
public function fetch(int $type): ?array;
|
||||||
|
|
||||||
|
public function fetchAll(int $type): array;
|
||||||
|
|
||||||
|
public function fetchOneColumn(string $valueName, string $keyName): array;
|
||||||
|
}
|
8
src/Interfaces/Database/IStatement.php
Normal file
8
src/Interfaces/Database/IStatement.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Database;
|
||||||
|
|
||||||
|
interface IStatement
|
||||||
|
{
|
||||||
|
public function execute(array $params): ?IResultSet;
|
||||||
|
|
||||||
|
public function getAffectedRows(): int;
|
||||||
|
}
|
18
src/Interfaces/Http/IRequest.php
Normal file
18
src/Interfaces/Http/IRequest.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Http;
|
||||||
|
|
||||||
|
interface IRequest
|
||||||
|
{
|
||||||
|
const HTTP_GET = 0;
|
||||||
|
|
||||||
|
const HTTP_POST = 1;
|
||||||
|
|
||||||
|
public function setUrl(string $url): void;
|
||||||
|
|
||||||
|
public function setMethod(int $method): void;
|
||||||
|
|
||||||
|
public function setQuery($query): void;
|
||||||
|
|
||||||
|
public function setHeaders(array $headers): void;
|
||||||
|
|
||||||
|
public function send(): IResponse;
|
||||||
|
}
|
8
src/Interfaces/Http/IResponse.php
Normal file
8
src/Interfaces/Http/IResponse.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Http;
|
||||||
|
|
||||||
|
interface IResponse
|
||||||
|
{
|
||||||
|
public function getBody(): string;
|
||||||
|
|
||||||
|
public function getHeaders(): array;
|
||||||
|
}
|
20
src/Interfaces/Request/IRequest.php
Normal file
20
src/Interfaces/Request/IRequest.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Request;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
|
||||||
|
interface IRequest
|
||||||
|
{
|
||||||
|
public function setParsedRouteParams(array &$routeParams): void;
|
||||||
|
|
||||||
|
public function getBase(): string;
|
||||||
|
|
||||||
|
public function query(string $key);
|
||||||
|
|
||||||
|
public function post(string $key);
|
||||||
|
|
||||||
|
public function session(): ISession;
|
||||||
|
|
||||||
|
public function setUser(?IUser $user): void;
|
||||||
|
|
||||||
|
public function user(): ?IUser;
|
||||||
|
}
|
12
src/Interfaces/Request/ISession.php
Normal file
12
src/Interfaces/Request/ISession.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Request;
|
||||||
|
|
||||||
|
interface ISession
|
||||||
|
{
|
||||||
|
public function has(string $key): bool;
|
||||||
|
|
||||||
|
public function get(string $key);
|
||||||
|
|
||||||
|
public function set(string $key, $value): void;
|
||||||
|
|
||||||
|
public function delete(string $key): void;
|
||||||
|
}
|
12
src/Interfaces/Response/IContent.php
Normal file
12
src/Interfaces/Response/IContent.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Response;
|
||||||
|
|
||||||
|
interface IContent
|
||||||
|
{
|
||||||
|
public function setData(array $data): void;
|
||||||
|
|
||||||
|
public function getData(): array;
|
||||||
|
|
||||||
|
public function render(): void;
|
||||||
|
|
||||||
|
public function getContentType(): string;
|
||||||
|
}
|
12
src/Interfaces/Response/IRedirect.php
Normal file
12
src/Interfaces/Response/IRedirect.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Response;
|
||||||
|
|
||||||
|
interface IRedirect
|
||||||
|
{
|
||||||
|
const PERMANENT = 1;
|
||||||
|
|
||||||
|
const TEMPORARY = 2;
|
||||||
|
|
||||||
|
public function getUrl(): string;
|
||||||
|
|
||||||
|
public function getHttpCode(): int;
|
||||||
|
}
|
9
src/Interfaces/Session/ISessionHandler.php
Normal file
9
src/Interfaces/Session/ISessionHandler.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php namespace MapGuesser\Interfaces\Session;
|
||||||
|
|
||||||
|
use SessionHandlerInterface;
|
||||||
|
use SessionIdInterface;
|
||||||
|
use SessionUpdateTimestampHandlerInterface;
|
||||||
|
|
||||||
|
interface ISessionHandler extends SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
|
||||||
|
{
|
||||||
|
}
|
87
src/Mailing/Mail.php
Normal file
87
src/Mailing/Mail.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php namespace MapGuesser\Mailing;
|
||||||
|
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
|
||||||
|
class Mail
|
||||||
|
{
|
||||||
|
private array $recipients = [];
|
||||||
|
|
||||||
|
public string $subject = '';
|
||||||
|
|
||||||
|
public string $body = '';
|
||||||
|
|
||||||
|
public function addRecipient(string $mail, ?string $name = null): void
|
||||||
|
{
|
||||||
|
$this->recipients[] = [$mail, $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubject(string $subject): void
|
||||||
|
{
|
||||||
|
$this->subject = $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBody(string $body): void
|
||||||
|
{
|
||||||
|
$this->body = $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBodyFromTemplate(string $template, array $params = []): void
|
||||||
|
{
|
||||||
|
$this->body = file_get_contents(ROOT . '/mail/' . $template . '.html');
|
||||||
|
|
||||||
|
$baseParameters = [
|
||||||
|
'APP_NAME' => $_ENV['APP_NAME'],
|
||||||
|
'BASE_URL' => \Container::$request->getBase(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$params = array_merge($baseParameters, $params);
|
||||||
|
|
||||||
|
foreach ($params as $key => $param) {
|
||||||
|
$this->body = str_replace('{{' . $key . '}}', $param, $this->body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(): void
|
||||||
|
{
|
||||||
|
$mailer = new PHPMailer(true);
|
||||||
|
|
||||||
|
$mailer->CharSet = 'utf-8';
|
||||||
|
$mailer->Hostname = substr($_ENV['MAIL_FROM'], strpos($_ENV['MAIL_FROM'], '@') + 1);
|
||||||
|
|
||||||
|
if (!empty($_ENV['MAIL_HOST'])) {
|
||||||
|
$mailer->Mailer = 'smtp';
|
||||||
|
$mailer->Host = $_ENV['MAIL_HOST'];
|
||||||
|
$mailer->Port = !empty($_ENV['MAIL_PORT']) ? $_ENV['MAIL_PORT'] : 25;
|
||||||
|
$mailer->SMTPSecure = !empty($_ENV['MAIL_SECURE']) ? $_ENV['MAIL_SECURE'] : '';
|
||||||
|
|
||||||
|
if (!empty($_ENV['MAIL_USER'])) {
|
||||||
|
$mailer->SMTPAuth = true;
|
||||||
|
$mailer->Username = $_ENV['MAIL_USER'];
|
||||||
|
$mailer->Password = $_ENV['MAIL_PASSWORD'];
|
||||||
|
} else {
|
||||||
|
$mailer->SMTPAuth = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$mailer->Mailer = 'mail';
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailer->setFrom($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
||||||
|
$mailer->addReplyTo($_ENV['MAIL_FROM'], $_ENV['APP_NAME']);
|
||||||
|
|
||||||
|
$mailer->Sender = !empty($_ENV['MAIL_BOUNCE']) ? $_ENV['MAIL_BOUNCE'] : $_ENV['MAIL_FROM'];
|
||||||
|
$mailer->Subject = $this->subject;
|
||||||
|
$mailer->msgHTML($this->body);
|
||||||
|
|
||||||
|
foreach ($this->recipients as $recipient) {
|
||||||
|
$this->sendMail($mailer, $recipient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendMail(PHPMailer $mailer, array $recipient): void
|
||||||
|
{
|
||||||
|
$mailer->clearAddresses();
|
||||||
|
$mailer->addAddress($recipient[0], $recipient[1]);
|
||||||
|
|
||||||
|
$mailer->send();
|
||||||
|
}
|
||||||
|
}
|
56
src/OAuth/GoogleOAuth.php
Normal file
56
src/OAuth/GoogleOAuth.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php namespace MapGuesser\OAuth;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Http\IRequest;
|
||||||
|
|
||||||
|
class GoogleOAuth
|
||||||
|
{
|
||||||
|
private static string $dialogUrlBase = 'https://accounts.google.com/o/oauth2/v2/auth';
|
||||||
|
|
||||||
|
private static string $tokenUrlBase = 'https://oauth2.googleapis.com/token';
|
||||||
|
|
||||||
|
private IRequest $request;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDialogUrl(string $state, string $redirectUrl, ?string $nonce = null, ?string $loginHint = null): string
|
||||||
|
{
|
||||||
|
$oauthParams = [
|
||||||
|
'response_type' => 'code',
|
||||||
|
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
||||||
|
'scope' => 'openid email',
|
||||||
|
'redirect_uri' => $redirectUrl,
|
||||||
|
'state' => $state,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($nonce !== null) {
|
||||||
|
$oauthParams['nonce'] = $nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginHint !== null) {
|
||||||
|
$oauthParams['login_hint'] = $loginHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getToken(string $code, string $redirectUrl): array
|
||||||
|
{
|
||||||
|
$tokenParams = [
|
||||||
|
'code' => $code,
|
||||||
|
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
|
||||||
|
'client_secret' => $_ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
|
||||||
|
'redirect_uri' => $redirectUrl,
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->request->setUrl(self::$tokenUrlBase);
|
||||||
|
$this->request->setMethod(IRequest::HTTP_POST);
|
||||||
|
$this->request->setQuery($tokenParams);
|
||||||
|
$response = $this->request->send();
|
||||||
|
|
||||||
|
return json_decode($response->getBody(), true);
|
||||||
|
}
|
||||||
|
}
|
@ -1,113 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class Challenge extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'challenges';
|
|
||||||
|
|
||||||
protected static array $fields = ['token', 'time_limit', 'time_limit_type', 'no_move', 'no_pan', 'no_zoom', 'created'];
|
|
||||||
|
|
||||||
protected static array $relations = [];
|
|
||||||
|
|
||||||
private int $token;
|
|
||||||
|
|
||||||
private ?int $timeLimit = null;
|
|
||||||
|
|
||||||
private static array $timeLimitTypes = ['game', 'round'];
|
|
||||||
|
|
||||||
private string $timeLimitType = 'game';
|
|
||||||
|
|
||||||
private bool $noMove = false;
|
|
||||||
|
|
||||||
private bool $noPan = false;
|
|
||||||
|
|
||||||
private bool $noZoom = false;
|
|
||||||
|
|
||||||
private DateTime $created;
|
|
||||||
|
|
||||||
public function setToken(int $token): void
|
|
||||||
{
|
|
||||||
$this->token = $token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTimeLimit(?int $timeLimit): void
|
|
||||||
{
|
|
||||||
if (isset($timeLimit)) {
|
|
||||||
$this->timeLimit = $timeLimit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTimeLimitType(string $timeLimitType): void
|
|
||||||
{
|
|
||||||
if (in_array($timeLimitType, self::$timeLimitTypes)) {
|
|
||||||
$this->timeLimitType = $timeLimitType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNoMove(bool $noMove): void
|
|
||||||
{
|
|
||||||
$this->noMove = $noMove;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNoPan(bool $noPan): void
|
|
||||||
{
|
|
||||||
$this->noPan = $noPan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setNoZoom(bool $noZoom): void
|
|
||||||
{
|
|
||||||
$this->noZoom = $noZoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreatedDate(DateTime $created): void
|
|
||||||
{
|
|
||||||
$this->created = $created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreated(string $created): void
|
|
||||||
{
|
|
||||||
$this->created = new DateTime($created);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getToken(): int
|
|
||||||
{
|
|
||||||
return $this->token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTimeLimit(): ?int
|
|
||||||
{
|
|
||||||
return $this->timeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTimeLimitType(): string
|
|
||||||
{
|
|
||||||
return $this->timeLimitType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNoMove(): bool
|
|
||||||
{
|
|
||||||
return $this->noMove;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNoPan(): bool
|
|
||||||
{
|
|
||||||
return $this->noPan;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getNoZoom(): bool
|
|
||||||
{
|
|
||||||
return $this->noZoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->created;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreated(): string
|
|
||||||
{
|
|
||||||
return $this->created->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
use MapGuesser\Util\Geo\Position;
|
|
||||||
|
|
||||||
class Guess extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'guesses';
|
|
||||||
|
|
||||||
protected static array $fields = ['user_id', 'place_in_challenge_id', 'lat', 'lng', 'score', 'distance', 'time_spent'];
|
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class, 'place_in_challenge' => PlaceInChallenge::class];
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
private ?PlaceInChallenge $placeInChallenge = null;
|
|
||||||
|
|
||||||
private ?int $placeInChallengeId = null;
|
|
||||||
|
|
||||||
private Position $position;
|
|
||||||
|
|
||||||
private int $score = 0;
|
|
||||||
|
|
||||||
private int $distance = 0;
|
|
||||||
|
|
||||||
private int $timeSpent = 0;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->position = new Position(0.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPlaceInChallenge(PlaceInChallenge $placeInChallenge): void
|
|
||||||
{
|
|
||||||
$this->placeInChallenge = $placeInChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPlaceInChallengeId(int $placeInChallengeId): void
|
|
||||||
{
|
|
||||||
$this->placeInChallengeId = $placeInChallengeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPosition(Position $position): void
|
|
||||||
{
|
|
||||||
$this->position = $position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLat(float $lat): void
|
|
||||||
{
|
|
||||||
$this->position->setLat($lat);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLng(float $lng): void
|
|
||||||
{
|
|
||||||
$this->position->setLng($lng);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setScore(int $score): void
|
|
||||||
{
|
|
||||||
$this->score = $score;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDistance(int $distance): void
|
|
||||||
{
|
|
||||||
$this->distance = $distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTimeSpent(int $timeSpent): void
|
|
||||||
{
|
|
||||||
$this->timeSpent = $timeSpent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPlaceInChallenge(): ?PlaceInChallenge
|
|
||||||
{
|
|
||||||
return $this->placeInChallenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPlaceInChallengeId(): ?int
|
|
||||||
{
|
|
||||||
return $this->placeInChallengeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPosition(): Position
|
|
||||||
{
|
|
||||||
return $this->position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLat(): float
|
|
||||||
{
|
|
||||||
return $this->position->getLat();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLng(): float
|
|
||||||
{
|
|
||||||
return $this->position->getLng();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getScore(): int
|
|
||||||
{
|
|
||||||
return $this->score;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDistance(): int
|
|
||||||
{
|
|
||||||
return $this->distance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTimeSpent(): ?int
|
|
||||||
{
|
|
||||||
return $this->timeSpent;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,12 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
use MapGuesser\Util\Geo\Bounds;
|
use MapGuesser\Util\Geo\Bounds;
|
||||||
|
|
||||||
class Map extends Model
|
class Map extends Model
|
||||||
{
|
{
|
||||||
protected static string $table = 'maps';
|
protected static string $table = 'maps';
|
||||||
|
|
||||||
protected static array $fields = ['name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng', 'area', 'unlisted'];
|
protected static array $fields = ['name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng', 'area'];
|
||||||
|
|
||||||
private string $name = '';
|
private string $name = '';
|
||||||
|
|
||||||
@ -17,8 +16,6 @@ class Map extends Model
|
|||||||
|
|
||||||
private float $area = 0.0;
|
private float $area = 0.0;
|
||||||
|
|
||||||
private bool $unlisted = false;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0);
|
$this->bounds = Bounds::createDirectly(-90.0, -180.0, 90.0, 180.0);
|
||||||
@ -64,11 +61,6 @@ class Map extends Model
|
|||||||
$this->area = $area;
|
$this->area = $area;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUnlisted(bool $unlisted): void
|
|
||||||
{
|
|
||||||
$this->unlisted = $unlisted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string
|
public function getName(): string
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
@ -108,9 +100,4 @@ class Map extends Model
|
|||||||
{
|
{
|
||||||
return $this->area;
|
return $this->area;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUnlisted(): bool
|
|
||||||
{
|
|
||||||
return $this->unlisted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
69
src/PersistentData/Model/Model.php
Normal file
69
src/PersistentData/Model/Model.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
|
abstract class Model
|
||||||
|
{
|
||||||
|
protected static string $table;
|
||||||
|
|
||||||
|
protected static array $fields;
|
||||||
|
|
||||||
|
protected static array $relations = [];
|
||||||
|
|
||||||
|
protected $id = null;
|
||||||
|
|
||||||
|
private array $snapshot = [];
|
||||||
|
|
||||||
|
public static function getTable(): string
|
||||||
|
{
|
||||||
|
return static::$table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getFields(): array
|
||||||
|
{
|
||||||
|
return array_merge(['id'], static::$fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return static::$relations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setId($id): void
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(): array
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
foreach (self::getFields() as $key) {
|
||||||
|
$method = 'get' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($this, $method)) {
|
||||||
|
$array[$key] = $this->$method();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveSnapshot(): void
|
||||||
|
{
|
||||||
|
$this->snapshot = $this->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resetSnapshot(): void
|
||||||
|
{
|
||||||
|
$this->snapshot = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSnapshot(): array
|
||||||
|
{
|
||||||
|
return $this->snapshot;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class MultiRoom extends Model
|
class MultiRoom extends Model
|
||||||
{
|
{
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
use MapGuesser\Http\Request;
|
||||||
use SokoWeb\Http\Request;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
use MapGuesser\Util\Geo\Position;
|
use MapGuesser\Util\Geo\Position;
|
||||||
use MapGuesser\Util\Panorama\Pov;
|
use MapGuesser\Util\Panorama\Pov;
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ class Place extends Model
|
|||||||
return $this->pov->getZoom();
|
return $this->pov->getZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFreshPanoId(): ?string
|
public function getFreshPanoId(bool $canBeIndoor = false): ?string
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
$this->panoIdCachedTimestamp !== null &&
|
$this->panoIdCachedTimestamp !== null &&
|
||||||
@ -152,17 +152,23 @@ class Place extends Model
|
|||||||
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
|
$request = new Request('https://maps.googleapis.com/maps/api/streetview/metadata', Request::HTTP_GET);
|
||||||
$request->setQuery([
|
$request->setQuery([
|
||||||
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
'key' => $_ENV['GOOGLE_MAPS_SERVER_API_KEY'],
|
||||||
'location' => $this->position->getLat() . ',' . $this->position->getLng()
|
'location' => $this->position->getLat() . ',' . $this->position->getLng(),
|
||||||
|
'source' => $canBeIndoor ? 'default' : 'outdoor'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response = $request->send();
|
$response = $request->send();
|
||||||
$panoData = json_decode($response->getBody(), true);
|
$panoData = json_decode($response->getBody(), true);
|
||||||
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
|
$panoId = $panoData['status'] === 'OK' ? $panoData['pano_id'] : null;
|
||||||
|
|
||||||
|
// enable indoor panos if no outdoor found
|
||||||
|
if ($panoId === null && !$canBeIndoor) {
|
||||||
|
return $this->getFreshPanoId(true);
|
||||||
|
}
|
||||||
|
|
||||||
$this->panoIdCached = $panoId;
|
$this->panoIdCached = $panoId;
|
||||||
$this->panoIdCachedTimestamp = new DateTime();
|
$this->panoIdCachedTimestamp = new DateTime();
|
||||||
|
|
||||||
\Container::$persistentDataManager->saveToDb($this);
|
(new PersistentDataManager())->saveToDb($this);
|
||||||
|
|
||||||
return $panoId;
|
return $panoId;
|
||||||
}
|
}
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class PlaceInChallenge extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'place_in_challenge';
|
|
||||||
|
|
||||||
protected static array $fields = ['place_id', 'challenge_id', 'round'];
|
|
||||||
|
|
||||||
protected static array $relations = ['place' => Place::class, 'challenge' => Challenge::class];
|
|
||||||
|
|
||||||
private ?Place $place = null;
|
|
||||||
|
|
||||||
private ?int $placeId = null;
|
|
||||||
|
|
||||||
private ?Challenge $challenge = null;
|
|
||||||
|
|
||||||
private ?int $challengeId = null;
|
|
||||||
|
|
||||||
private int $round;
|
|
||||||
|
|
||||||
public function setPlace(Place $place): void
|
|
||||||
{
|
|
||||||
$this->place = $place;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPlaceId(int $placeId): void
|
|
||||||
{
|
|
||||||
$this->placeId = $placeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setChallenge(Challenge $challenge): void
|
|
||||||
{
|
|
||||||
$this->challenge = $challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setChallengeId(int $challengeId): void
|
|
||||||
{
|
|
||||||
$this->challengeId = $challengeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setRound(int $round): void
|
|
||||||
{
|
|
||||||
$this->round = $round;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPlace(): ?Place
|
|
||||||
{
|
|
||||||
return $this->place;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPlaceId(): ?int
|
|
||||||
{
|
|
||||||
return $this->placeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChallenge(): ?Challenge
|
|
||||||
{
|
|
||||||
return $this->challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChallengeId(): ?int
|
|
||||||
{
|
|
||||||
return $this->challengeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRound(): int
|
|
||||||
{
|
|
||||||
return $this->round;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +1,18 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
use SokoWeb\Interfaces\Authentication\IUser;
|
|
||||||
|
|
||||||
class User extends Model implements IUser
|
class User extends Model implements IUser
|
||||||
{
|
{
|
||||||
protected static string $table = 'users';
|
protected static string $table = 'users';
|
||||||
|
|
||||||
protected static array $fields = ['email', 'username', 'password', 'type', 'active', 'google_sub', 'created'];
|
protected static array $fields = ['email', 'password', 'type', 'active', 'google_sub', 'created'];
|
||||||
|
|
||||||
private static array $types = ['user', 'admin'];
|
private static array $types = ['user', 'admin'];
|
||||||
|
|
||||||
private string $email = '';
|
private string $email = '';
|
||||||
|
|
||||||
private string $username = '';
|
|
||||||
|
|
||||||
private ?string $password = null;
|
private ?string $password = null;
|
||||||
|
|
||||||
private string $type = 'user';
|
private string $type = 'user';
|
||||||
@ -31,11 +28,6 @@ class User extends Model implements IUser
|
|||||||
$this->email = $email;
|
$this->email = $email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setUsername(string $username): void
|
|
||||||
{
|
|
||||||
$this->username = $username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPassword(?string $hashedPassword): void
|
public function setPassword(?string $hashedPassword): void
|
||||||
{
|
{
|
||||||
$this->password = $hashedPassword;
|
$this->password = $hashedPassword;
|
||||||
@ -78,11 +70,6 @@ class User extends Model implements IUser
|
|||||||
return $this->email;
|
return $this->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsername(): string
|
|
||||||
{
|
|
||||||
return $this->username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPassword(): ?string
|
public function getPassword(): ?string
|
||||||
{
|
{
|
||||||
return $this->password;
|
return $this->password;
|
||||||
@ -132,7 +119,7 @@ class User extends Model implements IUser
|
|||||||
|
|
||||||
public function getDisplayName(): string
|
public function getDisplayName(): string
|
||||||
{
|
{
|
||||||
return $this->username;
|
return $this->email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkPassword(string $password): bool
|
public function checkPassword(string $password): bool
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class UserConfirmation extends Model
|
class UserConfirmation extends Model
|
||||||
{
|
{
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class UserInChallenge extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'user_in_challenge';
|
|
||||||
|
|
||||||
protected static array $fields = ['user_id', 'challenge_id', 'current_round', 'time_left', 'is_owner'];
|
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class, 'challenge' => Challenge::class];
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
private ?Challenge $challenge = null;
|
|
||||||
|
|
||||||
private ?int $challengeId = null;
|
|
||||||
|
|
||||||
private int $currentRound = 0;
|
|
||||||
|
|
||||||
private ?int $timeLeft = null;
|
|
||||||
|
|
||||||
private bool $isOwner = false;
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setChallenge(Challenge $challenge): void
|
|
||||||
{
|
|
||||||
$this->challenge = $challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setChallengeId(int $challengeId): void
|
|
||||||
{
|
|
||||||
$this->challengeId = $challengeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCurrentRound(int $currentRound): void
|
|
||||||
{
|
|
||||||
$this->currentRound = $currentRound;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTimeLeft(?int $timeLeft): void
|
|
||||||
{
|
|
||||||
if (isset($timeLeft)) {
|
|
||||||
$this->timeLeft = max(0, $timeLeft);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setIsOwner(bool $isOwner): void
|
|
||||||
{
|
|
||||||
$this->isOwner = $isOwner;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChallenge(): ?Challenge
|
|
||||||
{
|
|
||||||
return $this->challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getChallengeId(): ?int
|
|
||||||
{
|
|
||||||
return $this->challengeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrentRound(): int
|
|
||||||
{
|
|
||||||
return $this->currentRound;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTimeLeft(): ?int
|
|
||||||
{
|
|
||||||
return $this->timeLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getIsOwner(): bool
|
|
||||||
{
|
|
||||||
return $this->isOwner;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
<?php namespace MapGuesser\PersistentData\Model;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class UserPasswordResetter extends Model
|
class UserPasswordResetter extends Model
|
||||||
{
|
{
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
<?php namespace MapGuesser\PersistentData\Model;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use SokoWeb\PersistentData\Model\Model;
|
|
||||||
|
|
||||||
class UserPlayedPlace extends Model
|
|
||||||
{
|
|
||||||
protected static string $table = 'user_played_place';
|
|
||||||
|
|
||||||
protected static array $fields = ['user_id', 'place_id', 'last_time', 'occurrences'];
|
|
||||||
|
|
||||||
protected static array $relations = ['user' => User::class, 'place' => Place::class];
|
|
||||||
|
|
||||||
private ?User $user = null;
|
|
||||||
|
|
||||||
private ?int $userId = null;
|
|
||||||
|
|
||||||
private ?Place $place = null;
|
|
||||||
|
|
||||||
private ?int $placeId = null;
|
|
||||||
|
|
||||||
private DateTime $lastTime;
|
|
||||||
|
|
||||||
private int $occurrences = 1;
|
|
||||||
|
|
||||||
public function setUser(User $user): void
|
|
||||||
{
|
|
||||||
$this->user = $user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUserId(int $userId): void
|
|
||||||
{
|
|
||||||
$this->userId = $userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPlace(Place $place): void
|
|
||||||
{
|
|
||||||
$this->place = $place;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPlaceId(int $placeId): void
|
|
||||||
{
|
|
||||||
$this->placeId = $placeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLastTimeDate(DateTime $lastTime): void
|
|
||||||
{
|
|
||||||
$this->lastTime = $lastTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLastTime(string $lastTime): void
|
|
||||||
{
|
|
||||||
$this->lastTime = new DateTime($lastTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setOccurrences(int $occurrences): void
|
|
||||||
{
|
|
||||||
$this->occurrences = $occurrences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function incrementOccurrences(): void
|
|
||||||
{
|
|
||||||
$this->occurrences++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUser(): ?User
|
|
||||||
{
|
|
||||||
return $this->user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserId(): ?int
|
|
||||||
{
|
|
||||||
return $this->userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPlace(): ?Place
|
|
||||||
{
|
|
||||||
return $this->place;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPlaceId(): ?int
|
|
||||||
{
|
|
||||||
return $this->placeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastTimeDate(): DateTime
|
|
||||||
{
|
|
||||||
return $this->lastTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastTime(): string
|
|
||||||
{
|
|
||||||
return $this->lastTime->format('Y-m-d H:i:s');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOccurrences(): int
|
|
||||||
{
|
|
||||||
return $this->occurrences;
|
|
||||||
}
|
|
||||||
}
|
|
226
src/PersistentData/PersistentDataManager.php
Normal file
226
src/PersistentData/PersistentDataManager.php
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<?php namespace MapGuesser\PersistentData;
|
||||||
|
|
||||||
|
use Generator;
|
||||||
|
use MapGuesser\Database\Query\Modify;
|
||||||
|
use MapGuesser\Database\Query\Select;
|
||||||
|
use MapGuesser\Interfaces\Database\IResultSet;
|
||||||
|
use MapGuesser\PersistentData\Model\Model;
|
||||||
|
|
||||||
|
class PersistentDataManager
|
||||||
|
{
|
||||||
|
public function selectFromDb(Select $select, string $type, bool $withRelations = false)
|
||||||
|
{
|
||||||
|
$select = $this->createSelect($select, $type, $withRelations);
|
||||||
|
|
||||||
|
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($data === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = new $type();
|
||||||
|
$this->fillWithData($data, $model);
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
|
||||||
|
{
|
||||||
|
$select = $this->createSelect($select, $type, $withRelations);
|
||||||
|
$result = $select->execute();
|
||||||
|
|
||||||
|
while ($data = $result->fetch(IResultSet::FETCH_ASSOC)) {
|
||||||
|
$model = new $type();
|
||||||
|
$this->fillWithData($data, $model);
|
||||||
|
|
||||||
|
yield $model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectFromDbById($id, string $type, bool $withRelations = false)
|
||||||
|
{
|
||||||
|
$select = new Select(\Container::$dbConnection);
|
||||||
|
$select->whereId($id);
|
||||||
|
|
||||||
|
return $this->selectFromDb($select, $type, $withRelations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fillWithData(array $data, Model $model): void
|
||||||
|
{
|
||||||
|
$relations = $model::getRelations();
|
||||||
|
$relationData = [];
|
||||||
|
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
if ($this->extractRelationData($key, $value, $relationData, $relations)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$method = 'set' . str_replace('_', '', ucwords($key, '_'));
|
||||||
|
|
||||||
|
if (method_exists($model, $method)) {
|
||||||
|
$model->$method($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->setRelations($model, $relationData);
|
||||||
|
|
||||||
|
$model->saveSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRelationsFromDb(Model $model, bool $recursive): void
|
||||||
|
{
|
||||||
|
foreach ($model::getRelations() as $relation => $relationType) {
|
||||||
|
$camel = str_replace('_', '', ucwords($relation, '_'));
|
||||||
|
|
||||||
|
$methodGet = 'get' . $camel . 'Id';
|
||||||
|
$methodSet = 'set' . $camel;
|
||||||
|
|
||||||
|
$relationId = $model->$methodGet();
|
||||||
|
|
||||||
|
if ($relationId !== null) {
|
||||||
|
$relationModel = $this->selectFromDbById($relationId, $relationType, $recursive);
|
||||||
|
|
||||||
|
$model->$methodSet($relationModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveToDb(Model $model): void
|
||||||
|
{
|
||||||
|
$this->syncRelations($model);
|
||||||
|
|
||||||
|
$modified = $model->toArray();
|
||||||
|
$id = $model->getId();
|
||||||
|
|
||||||
|
$modify = new Modify(\Container::$dbConnection, $model::getTable());
|
||||||
|
|
||||||
|
if ($id !== null) {
|
||||||
|
$original = $model->getSnapshot();
|
||||||
|
|
||||||
|
foreach ($original as $key => $value) {
|
||||||
|
if ($value === $modified[$key]) {
|
||||||
|
unset($modified[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($modified) > 0) {
|
||||||
|
$modify->setId($id);
|
||||||
|
$modify->fill($modified);
|
||||||
|
$modify->save();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$modify->fill($modified);
|
||||||
|
$modify->save();
|
||||||
|
|
||||||
|
$model->setId($modify->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
$model->saveSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteFromDb(Model $model): void
|
||||||
|
{
|
||||||
|
$modify = new Modify(\Container::$dbConnection, $model::getTable());
|
||||||
|
$modify->setId($model->getId());
|
||||||
|
$modify->delete();
|
||||||
|
|
||||||
|
$model->setId(null);
|
||||||
|
$model->resetSnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createSelect(Select $select, string $type, bool $withRelations = false): Select
|
||||||
|
{
|
||||||
|
$table = call_user_func([$type, 'getTable']);
|
||||||
|
$fields = call_user_func([$type, 'getFields']);
|
||||||
|
|
||||||
|
$select->from($table);
|
||||||
|
|
||||||
|
//TODO: only with some relations?
|
||||||
|
if ($withRelations) {
|
||||||
|
$relations = call_user_func([$type, 'getRelations']);
|
||||||
|
|
||||||
|
$columns = [];
|
||||||
|
|
||||||
|
foreach ($fields as $field) {
|
||||||
|
$columns[] = [$table, $field];
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns = array_merge($columns, $this->getRelationColumns($relations));
|
||||||
|
|
||||||
|
$this->leftJoinRelations($select, $table, $relations);
|
||||||
|
$select->columns($columns);
|
||||||
|
} else {
|
||||||
|
$select->columns($fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $select;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRelationColumns(array $relations): array
|
||||||
|
{
|
||||||
|
$columns = [];
|
||||||
|
|
||||||
|
foreach ($relations as $relation => $relationType) {
|
||||||
|
$relationTable = call_user_func([$relationType, 'getTable']);
|
||||||
|
foreach (call_user_func([$relationType, 'getFields']) as $relationField) {
|
||||||
|
$columns[] = [$relationTable, $relationField, $relation . '__' . $relationField];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function leftJoinRelations(Select $select, string $table, array $relations): void
|
||||||
|
{
|
||||||
|
foreach ($relations as $relation => $relationType) {
|
||||||
|
$relationTable = call_user_func([$relationType, 'getTable']);
|
||||||
|
$select->leftJoin($relationTable, [$relationTable, 'id'], '=', [$table, $relation . '_id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extractRelationData(string $key, $value, array &$relationData, array $relations): bool
|
||||||
|
{
|
||||||
|
$found = false;
|
||||||
|
|
||||||
|
foreach ($relations as $relation => $relationType) {
|
||||||
|
if (substr($key, 0, strlen($relation . '__')) === $relation . '__') {
|
||||||
|
$found = true;
|
||||||
|
$relationData[$relation][substr($key, strlen($relation . '__'))] = $value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $found;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setRelations(Model $model, array &$relations): void
|
||||||
|
{
|
||||||
|
foreach ($model::getRelations() as $relation => $relationType) {
|
||||||
|
if (isset($relations[$relation])) {
|
||||||
|
$object = new $relationType();
|
||||||
|
|
||||||
|
$this->fillWithData($relations[$relation], $object);
|
||||||
|
|
||||||
|
$method = 'set' . str_replace('_', '', ucwords($relation, '_'));
|
||||||
|
|
||||||
|
$model->$method($object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function syncRelations(Model $model): void
|
||||||
|
{
|
||||||
|
foreach ($model::getRelations() as $relation => $relationType) {
|
||||||
|
$camel = str_replace('_', '', ucwords($relation, '_'));
|
||||||
|
|
||||||
|
$methodGet = 'get' . $camel;
|
||||||
|
$methodSet = 'set' . $camel . 'Id';
|
||||||
|
|
||||||
|
$relationModel = $model->$methodGet();
|
||||||
|
|
||||||
|
if ($relationModel !== null) {
|
||||||
|
$model->$methodSet($relationModel->getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,67 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
|
|
||||||
class ChallengeRepository
|
|
||||||
{
|
|
||||||
public function getById(int $challengeId): ?Challenge
|
|
||||||
{
|
|
||||||
return \Container::$persistentDataManager->selectFromDbById($challengeId, Challenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByToken(int $token): ?Challenge
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('token', '=', $token);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, Challenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByTokenStr(string $token_str): ?Challenge
|
|
||||||
{
|
|
||||||
// validate token string
|
|
||||||
foreach (str_split($token_str) as $char) {
|
|
||||||
if (!(('0' <= $char && $char <= '9') || ('a' <= $char && $char <= 'f'))) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// convert token to int
|
|
||||||
$token = hexdec($token_str);
|
|
||||||
|
|
||||||
return $this->getByToken($token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByParticipant(User $user): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('user_in_challenge', ['challenge', 'id'], '=', ['user_in_challenge', 'challenge_id']);
|
|
||||||
$select->innerJoin('users', ['users', 'id'], '=', ['user_in_challenge', 'user_id']);
|
|
||||||
$select->where(['user_in_challenge', 'user_id'], '=', $user->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Challenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByOwner(User $user): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('user_in_challenge', ['challenge', 'id'], '=', ['user_in_challenge', 'challenge_id']);
|
|
||||||
$select->innerJoin('users', ['users', 'id'], '=', ['user_in_challenge', 'user_id']);
|
|
||||||
$select->where(['user_in_challenge', 'user_id'], '=', $user->getId());
|
|
||||||
$select->where(['user_in_challenge', 'is_owner'], '=', true);
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Challenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByPlace(Place $place): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['challenges', 'id'], '=', ['place_in_challenge', 'challenge_id']);
|
|
||||||
$select->where(['place_in_challenge', 'place_id'], '=', $place->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Challenge::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\Guess;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
|
||||||
|
|
||||||
class GuessRepository
|
|
||||||
{
|
|
||||||
public function getAllByUser(User $user): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $user->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Guess::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByUserAndChallenge(User $user, Challenge $challenge): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
|
|
||||||
$select->where('user_id', '=', $user->getId());
|
|
||||||
$select->where(['place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Guess::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByUserAndPlaceInChallenge(User $user, Challenge $challenge, Place $place): ?Guess
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
|
|
||||||
$select->where('user_id', '=', $user->getId());
|
|
||||||
$select->where(['place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->where(['place_in_challenge', 'place_id'], '=', $place->getId());
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, Guess::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllInChallengeByUser(int $userId, Challenge $challenge): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
|
|
||||||
$select->where('user_id', '=', $userId);
|
|
||||||
$select->where(['place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->orderBy(['place_in_challenge', 'round']);
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Guess::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllInChallenge(Challenge $challenge, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
if (count($withRelations)) {
|
|
||||||
$necessaryRelations = ['place_in_challenge'];
|
|
||||||
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
|
|
||||||
}
|
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where(['guesses__place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->orderBy(['guesses__place_in_challenge', 'round']);
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Guess::class, true, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllInChallengeByRound(int $round, Challenge $challenge, array $withRelations = []): Generator
|
|
||||||
{
|
|
||||||
if (count($withRelations)) {
|
|
||||||
$necessaryRelations = ['place_in_challenge'];
|
|
||||||
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
|
|
||||||
}
|
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where(['guesses__place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->where(['guesses__place_in_challenge', 'round'], '=', $round);
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Guess::class, true, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByPlace(Place $place): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['place_in_challenge', 'id'], '=', ['guesses', 'place_in_challenge_id']);
|
|
||||||
$select->where(['place_in_challenge', 'place_id'], '=', $place->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Guess::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +1,19 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class MapRepository
|
class MapRepository
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function getById(int $mapId): ?Map
|
public function getById(int $mapId): ?Map
|
||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($mapId, Map::class);
|
return $this->pdm->selectFromDbById($mapId, Map::class);
|
||||||
}
|
|
||||||
|
|
||||||
public function getByPlace(Place $place): ?Map
|
|
||||||
{
|
|
||||||
return $this->getById($place->getMapId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByChallenge(Challenge $challenge): ?Map
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('places', ['maps', 'id'], '=', ['places', 'map_id']);
|
|
||||||
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
|
|
||||||
$select->where(['place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->limit(1);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, Map::class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,22 @@
|
|||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Generator;
|
use Generator;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\MultiRoom;
|
use MapGuesser\PersistentData\Model\MultiRoom;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class MultiRoomRepository
|
class MultiRoomRepository
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function getById(int $id): ?MultiRoom
|
public function getById(int $id): ?MultiRoom
|
||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($id, MultiRoom::class);
|
return $this->pdm->selectFromDbById($id, MultiRoom::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByRoomId(string $roomId): ?MultiRoom
|
public function getByRoomId(string $roomId): ?MultiRoom
|
||||||
@ -17,7 +25,7 @@ class MultiRoomRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('room_id', '=', $roomId);
|
$select->where('room_id', '=', $roomId);
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, MultiRoom::class);
|
return $this->pdm->selectFromDb($select, MultiRoom::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllExpired(): Generator
|
public function getAllExpired(): Generator
|
||||||
@ -25,6 +33,6 @@ class MultiRoomRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('updated', '<', (new DateTime('-7 day'))->format('Y-m-d H:i:s'));
|
$select->where('updated', '<', (new DateTime('-7 day'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, MultiRoom::class);
|
yield from $this->pdm->selectMultipleFromDb($select, MultiRoom::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\PersistentData\Model\PlaceInChallenge;
|
|
||||||
|
|
||||||
class PlaceInChallengeRepository
|
|
||||||
{
|
|
||||||
public function getAllByPlace(Place $place, array $withRelations = []) : Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('place_id', '=', $place->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, PlaceInChallenge::class, true, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByChallenge(Challenge $challenge) : Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, PlaceInChallenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByPlaceAndChallenge(Place $place, Challenge $challenge) : ?PlaceInChallenge
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('place_id', '=', $place->getId());
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, PlaceInChallenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByRoundInChallenge(int $round, Challenge $challenge, array $withRelations = []): ?PlaceInChallenge
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
$select->orderBy('round');
|
|
||||||
$select->limit(1, $round);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, PlaceInChallenge::class, true, $withRelations);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +1,23 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\Map;
|
use MapGuesser\PersistentData\Model\Map;
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
use MapGuesser\PersistentData\Model\Place;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class PlaceRepository
|
class PlaceRepository
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function getById(int $placeId): ?Place
|
public function getById(int $placeId): ?Place
|
||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($placeId, Place::class);
|
return $this->pdm->selectFromDbById($placeId, Place::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllForMap(Map $map): Generator
|
public function getAllForMap(Map $map): Generator
|
||||||
@ -18,38 +25,16 @@ class PlaceRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('map_id', '=', $map->getId());
|
$select->where('map_id', '=', $map->getId());
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Place::class);
|
yield from $this->pdm->selectMultipleFromDb($select, Place::class);
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: use Map and User instead of id
|
|
||||||
public function getRandomNPlaces(int $mapId, int $n, ?int $userId): array
|
|
||||||
{
|
|
||||||
if (!isset($userId)) { // anonymous single player
|
|
||||||
return $this->getRandomNForMapWithValidPano($mapId, $n);
|
|
||||||
} else { // authorized user or multiplayer game with selection based on what the host played before
|
|
||||||
$unvisitedPlaces = $this->getRandomUnvisitedNForMapWithValidPano($mapId, $n, $userId);
|
|
||||||
if (count($unvisitedPlaces) == $n) {
|
|
||||||
return $unvisitedPlaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oldPlaces = $this->getRandomOldNForMapWithValidPano($mapId, $n - count($unvisitedPlaces), $userId);
|
|
||||||
|
|
||||||
return array_merge($unvisitedPlaces, $oldPlaces);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: use Map instead of id
|
//TODO: use Map instead of id
|
||||||
private function getRandomNForMapWithValidPano(int $mapId, int $n): array
|
public function getRandomNForMapWithValidPano(int $mapId, int $n, array $exclude = []): array
|
||||||
{
|
{
|
||||||
$places = [];
|
$places = [];
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$select->where('map_id', '=', $mapId);
|
|
||||||
$numberOfPlaces = $select->count();
|
|
||||||
|
|
||||||
$exclude = [];
|
|
||||||
for ($i = 1; $i <= $n; ++$i) {
|
for ($i = 1; $i <= $n; ++$i) {
|
||||||
$place = $this->getRandomForMapWithValidPano($numberOfPlaces, $select, $exclude);
|
$place = $this->getRandomForMapWithValidPano($mapId, $exclude);
|
||||||
|
|
||||||
$places[] = $place;
|
$places[] = $place;
|
||||||
$exclude[] = $place->getId();
|
$exclude[] = $place->getId();
|
||||||
@ -58,15 +43,11 @@ class PlaceRepository
|
|||||||
return $places;
|
return $places;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRandomForMapWithValidPano(int $numberOfPlaces, Select $select, array &$exclude, ?callable $pickRandomInt = null): ?Place
|
//TODO: use Map instead of id
|
||||||
|
public function getRandomForMapWithValidPano(int $mapId, array $exclude = []): Place
|
||||||
{
|
{
|
||||||
do {
|
do {
|
||||||
$numberOfPlacesLeft = $numberOfPlaces - count($exclude);
|
$place = $this->selectRandomFromDbForMap($mapId, $exclude);
|
||||||
$place = $this->selectRandomFromDbForMap($numberOfPlacesLeft, $select, $exclude, $pickRandomInt);
|
|
||||||
if ($place === null) {
|
|
||||||
// there is no more never visited place left
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$panoId = $place->getFreshPanoId();
|
$panoId = $place->getFreshPanoId();
|
||||||
if ($panoId === null) {
|
if ($panoId === null) {
|
||||||
@ -77,118 +58,25 @@ class PlaceRepository
|
|||||||
return $place;
|
return $place;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function selectRandomFromDbForMap(int $numberOfPlacesLeft, Select $select, array $exclude, ?callable $pickRandomInt): ?Place
|
private function selectRandomFromDbForMap(int $mapId, array $exclude): Place
|
||||||
{
|
{
|
||||||
if ($numberOfPlacesLeft <= 0)
|
//TODO: omit table name here
|
||||||
return null;
|
$select = new Select(\Container::$dbConnection, 'places');
|
||||||
|
$select->where('id', 'NOT IN', $exclude);
|
||||||
|
$select->where('map_id', '=', $mapId);
|
||||||
|
|
||||||
if (!isset($pickRandomInt)) {
|
$numberOfPlaces = $select->count();
|
||||||
$randomOffset = random_int(0, $numberOfPlacesLeft - 1);
|
|
||||||
} else {
|
//TODO: prevent this
|
||||||
$randomOffset = $pickRandomInt($numberOfPlacesLeft);
|
if ($numberOfPlaces === 0) {
|
||||||
|
throw new \Exception('There is no selectable place (count was 0).');
|
||||||
}
|
}
|
||||||
|
|
||||||
$select->where('id', 'NOT IN', $exclude);
|
$randomOffset = random_int(0, $numberOfPlaces - 1);
|
||||||
|
|
||||||
|
$select->orderBy('id');
|
||||||
$select->limit(1, $randomOffset);
|
$select->limit(1, $randomOffset);
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, Place::class);
|
return $this->pdm->selectFromDb($select, Place::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never visited places
|
|
||||||
private function getRandomUnvisitedNForMapWithValidPano(int $mapId, int $n, int $userId): array
|
|
||||||
{
|
|
||||||
$places = [];
|
|
||||||
$exclude = [];
|
|
||||||
|
|
||||||
// list of places visited by user
|
|
||||||
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
|
|
||||||
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
|
|
||||||
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
|
|
||||||
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
|
|
||||||
|
|
||||||
// count the places never visited
|
|
||||||
$selectUnvisited = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$selectUnvisited->leftJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
|
||||||
$selectUnvisited->where(['places', 'map_id'], '=', $mapId);
|
|
||||||
$selectUnvisited->where(['places_by_current_user', 'last_time'], '=', null);
|
|
||||||
$numberOfUnvisitedPlaces = $selectUnvisited->count();
|
|
||||||
|
|
||||||
// look for as many new places as possible but maximum $n
|
|
||||||
do {
|
|
||||||
$place = $this->getRandomForMapWithValidPano($numberOfUnvisitedPlaces, $selectUnvisited, $exclude);
|
|
||||||
if (isset($place)) {
|
|
||||||
$places[] = $place;
|
|
||||||
$exclude[] = $place->getId();
|
|
||||||
}
|
|
||||||
} while (count($places) < $n && isset($place));
|
|
||||||
|
|
||||||
return $places;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Places visited in the longest time
|
|
||||||
private function getRandomOldNForMapWithValidPano(int $mapId, int $n, int $userId): array
|
|
||||||
{
|
|
||||||
$places = [];
|
|
||||||
$exclude = [];
|
|
||||||
|
|
||||||
// list of places visited by user
|
|
||||||
$selectPlacesByCurrentUser = new Select(\Container::$dbConnection, 'user_played_place');
|
|
||||||
$selectPlacesByCurrentUser->columns(['place_id', 'last_time']);
|
|
||||||
$selectPlacesByCurrentUser->where('user_id', '=', $userId);
|
|
||||||
$selectPlacesByCurrentUser->setDerivedTableAlias('places_by_current_user');
|
|
||||||
|
|
||||||
// count places that were visited at least once
|
|
||||||
$selectOldPlaces = new Select(\Container::$dbConnection, 'places');
|
|
||||||
$selectOldPlaces->innerJoin($selectPlacesByCurrentUser, ['places', 'id'], '=', ['places_by_current_user', 'place_id']);
|
|
||||||
$selectOldPlaces->where(['places', 'map_id'], '=', $mapId);
|
|
||||||
$numberOfOldPlaces = $selectOldPlaces->count();
|
|
||||||
|
|
||||||
// set order by datetime, oldest first
|
|
||||||
$selectOldPlaces->orderBy(['places_by_current_user', 'last_time']);
|
|
||||||
|
|
||||||
// selection algorithm with preference (weighting) for older places using Box-Muller transform
|
|
||||||
$pickGaussianRandomInt = function($numberOfPlaces) {
|
|
||||||
$stdev = 0.2;
|
|
||||||
$avg = 0.0;
|
|
||||||
$x = mt_rand() / mt_getrandmax();
|
|
||||||
$y = mt_rand() / mt_getrandmax();
|
|
||||||
$randomNum = abs(sqrt(-2 * log($x)) * cos(2 * pi() * $y) * $stdev + $avg);
|
|
||||||
return (int) min($randomNum * $numberOfPlaces, $numberOfPlaces - 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// look for n - numberOfUnvisitedPlaces places
|
|
||||||
while (count($places) < $n)
|
|
||||||
{
|
|
||||||
$place = $this->getRandomForMapWithValidPano($numberOfOldPlaces, $selectOldPlaces, $exclude, $pickGaussianRandomInt);
|
|
||||||
if (isset($place))
|
|
||||||
{
|
|
||||||
$places[] = $place;
|
|
||||||
$exclude[] = $place->getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $places;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByRoundInChallenge(Challenge $challenge, int $round): ?Place
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
|
|
||||||
$select->where(['place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->orderBy(['place_in_challenge', 'round']);
|
|
||||||
$select->limit(1, $round);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, Place::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllInChallenge(Challenge $challenge): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->innerJoin('place_in_challenge', ['places', 'id'], '=', ['place_in_challenge', 'place_id']);
|
|
||||||
$select->where(['place_in_challenge', 'challenge_id'], '=', $challenge->getId());
|
|
||||||
$select->orderBy(['place_in_challenge', 'round']);
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, Place::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
<?php namespace MapGuesser\Repository;
|
||||||
|
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserConfirmation;
|
use MapGuesser\PersistentData\Model\UserConfirmation;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class UserConfirmationRepository
|
class UserConfirmationRepository
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function getById(int $userConfirmationId): ?UserConfirmation
|
public function getById(int $userConfirmationId): ?UserConfirmation
|
||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($userConfirmationId, UserConfirmation::class);
|
return $this->pdm->selectFromDbById($userConfirmationId, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByToken(string $token): ?UserConfirmation
|
public function getByToken(string $token): ?UserConfirmation
|
||||||
@ -16,7 +24,7 @@ class UserConfirmationRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('token', '=', $token);
|
$select->where('token', '=', $token);
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserConfirmation::class);
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByUser(User $user): ?UserConfirmation
|
public function getByUser(User $user): ?UserConfirmation
|
||||||
@ -24,6 +32,6 @@ class UserConfirmationRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('user_id', '=', $user->getId());
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserConfirmation::class);
|
return $this->pdm->selectFromDb($select, UserConfirmation::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\Challenge;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\Model\UserInChallenge;
|
|
||||||
|
|
||||||
class UserInChallengeRepository
|
|
||||||
{
|
|
||||||
public function getAllByUser(User $user) : Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $user->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserInChallenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByChallenge(Challenge $challenge) : Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserInChallenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByChallengeWithUsers(Challenge $challenge) : Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserInChallenge::class, true, ['user']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByUserIdAndChallenge(int $userId, Challenge $challenge): ?UserInChallenge
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $userId);
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserInChallenge::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByUserIdAndToken(int $userId, string $token_str, array $withRelations = []): ?UserInChallenge
|
|
||||||
{
|
|
||||||
if (count($withRelations)) {
|
|
||||||
$necessaryRelations = ['challange'];
|
|
||||||
$withRelations = array_unique(array_merge($withRelations, $necessaryRelations));
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate token string
|
|
||||||
if (!ctype_xdigit($token_str)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// convert token to int
|
|
||||||
$token = hexdec($token_str);
|
|
||||||
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $userId);
|
|
||||||
$select->where(['user_in_challenge__challenge', 'token'], '=', $token);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserInChallenge::class, true, $withRelations);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isUserParticipatingInChallenge(int $userId, Challenge $challenge): bool
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection, 'user_in_challenge');
|
|
||||||
$select->where('user_id', '=', $userId);
|
|
||||||
$select->where('challenge_id', '=', $challenge->getId());
|
|
||||||
|
|
||||||
return $select->count() != 0;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,15 +2,23 @@
|
|||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Generator;
|
use Generator;
|
||||||
use SokoWeb\Database\Query\Select;
|
use MapGuesser\Database\Query\Select;
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
use MapGuesser\PersistentData\Model\UserPasswordResetter;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class UserPasswordResetterRepository
|
class UserPasswordResetterRepository
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function getById(int $userConfirmationId): ?UserPasswordResetter
|
public function getById(int $userConfirmationId): ?UserPasswordResetter
|
||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($userConfirmationId, UserPasswordResetter::class);
|
return $this->pdm->selectFromDbById($userConfirmationId, UserPasswordResetter::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByToken(string $token): ?UserPasswordResetter
|
public function getByToken(string $token): ?UserPasswordResetter
|
||||||
@ -18,7 +26,7 @@ class UserPasswordResetterRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('token', '=', $token);
|
$select->where('token', '=', $token);
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserPasswordResetter::class);
|
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByUser(User $user): ?UserPasswordResetter
|
public function getByUser(User $user): ?UserPasswordResetter
|
||||||
@ -26,7 +34,7 @@ class UserPasswordResetterRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('user_id', '=', $user->getId());
|
$select->where('user_id', '=', $user->getId());
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserPasswordResetter::class);
|
return $this->pdm->selectFromDb($select, UserPasswordResetter::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllExpired(): Generator
|
public function getAllExpired(): Generator
|
||||||
@ -34,6 +42,6 @@ class UserPasswordResetterRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
|
$select->where('expires', '<', (new DateTime())->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserPasswordResetter::class);
|
yield from $this->pdm->selectMultipleFromDb($select, UserPasswordResetter::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
<?php namespace MapGuesser\Repository;
|
|
||||||
|
|
||||||
use Generator;
|
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
|
||||||
use MapGuesser\PersistentData\Model\Place;
|
|
||||||
use MapGuesser\PersistentData\Model\UserPlayedPlace;
|
|
||||||
|
|
||||||
class UserPlayedPlaceRepository
|
|
||||||
{
|
|
||||||
public function getByUser(User $user): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $user->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserPlayedPlace::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByPlace(Place $place): Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('place_id', '=', $place->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserPlayedPlace::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAllByUser(User $user) : Generator
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $user->getId());
|
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, UserPlayedPlace::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByUserIdAndPlaceId(int $userId, int $placeId) : ?UserPlayedPlace
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('user_id', '=', $userId);
|
|
||||||
$select->where('place_id', '=', $placeId);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, UserPlayedPlace::class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,16 +2,22 @@
|
|||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Generator;
|
use Generator;
|
||||||
use SokoWeb\Interfaces\Repository\IUserRepository;
|
use MapGuesser\Database\Query\Select;
|
||||||
use SokoWeb\Database\Query\Select;
|
|
||||||
use MapGuesser\PersistentData\Model\Guess;
|
|
||||||
use MapGuesser\PersistentData\Model\User;
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
|
||||||
class UserRepository implements IUserRepository
|
class UserRepository
|
||||||
{
|
{
|
||||||
|
private PersistentDataManager $pdm;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->pdm = new PersistentDataManager();
|
||||||
|
}
|
||||||
|
|
||||||
public function getById(int $userId): ?User
|
public function getById(int $userId): ?User
|
||||||
{
|
{
|
||||||
return \Container::$persistentDataManager->selectFromDbById($userId, User::class);
|
return $this->pdm->selectFromDbById($userId, User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByEmail(string $email): ?User
|
public function getByEmail(string $email): ?User
|
||||||
@ -19,24 +25,7 @@ class UserRepository implements IUserRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('email', '=', $email);
|
$select->where('email', '=', $email);
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, User::class);
|
return $this->pdm->selectFromDb($select, User::class);
|
||||||
}
|
|
||||||
|
|
||||||
public function getByUsername(string $username): ?User
|
|
||||||
{
|
|
||||||
$select = new Select(\Container::$dbConnection);
|
|
||||||
$select->where('username', '=', $username);
|
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, User::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getByEmailOrUsername(string $emailOrUsername): ?User
|
|
||||||
{
|
|
||||||
if (filter_var($emailOrUsername, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
return $this->getByEmail($emailOrUsername);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->getByUsername($emailOrUsername);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getByGoogleSub(string $sub): ?User
|
public function getByGoogleSub(string $sub): ?User
|
||||||
@ -44,7 +33,7 @@ class UserRepository implements IUserRepository
|
|||||||
$select = new Select(\Container::$dbConnection);
|
$select = new Select(\Container::$dbConnection);
|
||||||
$select->where('google_sub', '=', $sub);
|
$select->where('google_sub', '=', $sub);
|
||||||
|
|
||||||
return \Container::$persistentDataManager->selectFromDb($select, User::class);
|
return $this->pdm->selectFromDb($select, User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAllInactiveExpired(): Generator
|
public function getAllInactiveExpired(): Generator
|
||||||
@ -53,11 +42,6 @@ class UserRepository implements IUserRepository
|
|||||||
$select->where('active', '=', false);
|
$select->where('active', '=', false);
|
||||||
$select->where('created', '<', (new DateTime('-1 day'))->format('Y-m-d H:i:s'));
|
$select->where('created', '<', (new DateTime('-1 day'))->format('Y-m-d H:i:s'));
|
||||||
|
|
||||||
yield from \Container::$persistentDataManager->selectMultipleFromDb($select, User::class);
|
yield from $this->pdm->selectMultipleFromDb($select, User::class);
|
||||||
}
|
|
||||||
|
|
||||||
public function getByGuess(Guess $guess): ?User
|
|
||||||
{
|
|
||||||
return $this->getById($guess->getUserId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
93
src/Request/Request.php
Normal file
93
src/Request/Request.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php namespace MapGuesser\Request;
|
||||||
|
|
||||||
|
use MapGuesser\Interfaces\Authentication\IUser;
|
||||||
|
use MapGuesser\Interfaces\Request\IRequest;
|
||||||
|
use MapGuesser\Interfaces\Request\ISession;
|
||||||
|
use MapGuesser\PersistentData\Model\User;
|
||||||
|
use MapGuesser\PersistentData\PersistentDataManager;
|
||||||
|
use MapGuesser\Repository\UserRepository;
|
||||||
|
|
||||||
|
class Request implements IRequest
|
||||||
|
{
|
||||||
|
private string $base;
|
||||||
|
|
||||||
|
private array $get;
|
||||||
|
|
||||||
|
private array $routeParams = [];
|
||||||
|
|
||||||
|
private array $post;
|
||||||
|
|
||||||
|
private Session $session;
|
||||||
|
|
||||||
|
private UserRepository $userRepository;
|
||||||
|
|
||||||
|
private ?User $user = null;
|
||||||
|
|
||||||
|
public function __construct(string $base, array &$get, array &$post, array &$session)
|
||||||
|
{
|
||||||
|
$this->base = $base;
|
||||||
|
$this->get = &$get;
|
||||||
|
$this->post = &$post;
|
||||||
|
$this->session = new Session($session);
|
||||||
|
|
||||||
|
$this->userRepository = new UserRepository();
|
||||||
|
|
||||||
|
$userId = $this->session->get('userId');
|
||||||
|
|
||||||
|
if ($userId !== null) {
|
||||||
|
$this->user = $this->userRepository->getById($userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setParsedRouteParams(array &$routeParams): void
|
||||||
|
{
|
||||||
|
$this->routeParams = &$routeParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBase(): string
|
||||||
|
{
|
||||||
|
return $this->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function query($key)
|
||||||
|
{
|
||||||
|
if (isset($this->get[$key])) {
|
||||||
|
return $this->get[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->routeParams[$key])) {
|
||||||
|
return $this->routeParams[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function post($key)
|
||||||
|
{
|
||||||
|
if (isset($this->post[$key])) {
|
||||||
|
return $this->post[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function session(): ISession
|
||||||
|
{
|
||||||
|
return $this->session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUser(?IUser $user): void
|
||||||
|
{
|
||||||
|
if ($user === null) {
|
||||||
|
$this->session->delete('userId');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->session->set('userId', $user->getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function user(): ?IUser
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user