Merged in feature/MAPG-115-guard-endpoints-based-on-user-g (pull request #89)
Feature/MAPG-115 guard endpoints based on user g
This commit is contained in:
		
						commit
						2f2a7968a0
					
				
							
								
								
									
										8
									
								
								database/migrations/structure/20200606_2352_users.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								database/migrations/structure/20200606_2352_users.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
CREATE TABLE `users` (
 | 
			
		||||
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 | 
			
		||||
  `email` varchar(100) NOT NULL,
 | 
			
		||||
  `password` varchar(60) NOT NULL,
 | 
			
		||||
  `type` enum('user', 'admin') NOT NULL,
 | 
			
		||||
  PRIMARY KEY (`id`),
 | 
			
		||||
  UNIQUE KEY `email` (`email`)
 | 
			
		||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
 | 
			
		||||
							
								
								
									
										1
									
								
								mapg
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								mapg
									
									
									
									
									
								
							@ -6,5 +6,6 @@ require 'main.php';
 | 
			
		||||
$app = new Symfony\Component\Console\Application('MapGuesser Console', '');
 | 
			
		||||
 | 
			
		||||
$app->add(new MapGuesser\Cli\DatabaseMigration());
 | 
			
		||||
$app->add(new MapGuesser\Cli\AddUserCommand());
 | 
			
		||||
 | 
			
		||||
$app->run();
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,9 @@ if (($pos = strpos($url, '?')) !== false) {
 | 
			
		||||
$url = rawurldecode($url);
 | 
			
		||||
 | 
			
		||||
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
 | 
			
		||||
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
 | 
			
		||||
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
 | 
			
		||||
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
 | 
			
		||||
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
 | 
			
		||||
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
 | 
			
		||||
    $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
 | 
			
		||||
@ -30,15 +33,32 @@ $match = Container::$routeCollection->match($method, explode('/', $url));
 | 
			
		||||
if ($match !== null) {
 | 
			
		||||
    list($route, $params) = $match;
 | 
			
		||||
 | 
			
		||||
    $response = $route->callController($params);
 | 
			
		||||
    $request = new MapGuesser\Request\Request($_GET, $params, $_POST, $_SESSION);
 | 
			
		||||
 | 
			
		||||
    if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
 | 
			
		||||
        header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
 | 
			
		||||
        echo $response->render();
 | 
			
		||||
    } elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
 | 
			
		||||
        header('Location: ' . $host . '/' . $response->getUrl(), true, $response->getHttpCode());
 | 
			
		||||
    $handler = $route->getHandler();
 | 
			
		||||
    $controller = new $handler[0]($request);
 | 
			
		||||
 | 
			
		||||
    if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
 | 
			
		||||
        $authorized = $controller->authorize();
 | 
			
		||||
    } else {
 | 
			
		||||
        $authorized = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($authorized) {
 | 
			
		||||
        $response = call_user_func([$controller, $handler[1]]);
 | 
			
		||||
 | 
			
		||||
        if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
 | 
			
		||||
            header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
 | 
			
		||||
            echo $response->render();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        } elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
 | 
			
		||||
            header('Location: ' . $host . '/' . $response->getUrl(), true, $response->getHttpCode());
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
} else {
 | 
			
		||||
    header('Content-Type: text/html; charset=UTF-8', true, 404);
 | 
			
		||||
    require ROOT . '/views/error/404.php';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
header('Content-Type: text/html; charset=UTF-8', true, 404);
 | 
			
		||||
require ROOT . '/views/error/404.php';
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ button::-moz-focus-inner, input::-moz-focus-inner {
 | 
			
		||||
    border: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p, h1, h2, button, a {
 | 
			
		||||
p, h1, h2, input, textarea, select, button, a {
 | 
			
		||||
    font-family: 'Roboto', sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -93,6 +93,10 @@ sub {
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right {
 | 
			
		||||
    text-align: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
svg.inline, img.inline {
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    width: 1em;
 | 
			
		||||
@ -158,6 +162,54 @@ button.red:hover, button.red:focus, a.button.red:hover, a.button.red:focus {
 | 
			
		||||
    background-color: #7f2929;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input, select, textarea {
 | 
			
		||||
    background-color: #f9fafb;
 | 
			
		||||
    border: solid #c8d2e1 1px;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    padding: 4px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    font-size: 15px;
 | 
			
		||||
    font-weight: 300;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea {
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
    resize: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input.big, select.big, textarea.big {
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input.fullWidth, select.fullWidth, textarea.fullWidth {
 | 
			
		||||
    display: block;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input:disabled, select:disabled, textarea:disabled {
 | 
			
		||||
    background-color: #dfdfdf;
 | 
			
		||||
    border: solid #dfdfdf 1px;
 | 
			
		||||
    color: #000000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input:focus, select:focus, textarea:focus {
 | 
			
		||||
    background-color: #ffffff;
 | 
			
		||||
    border: solid #29457f 2px;
 | 
			
		||||
    padding: 3px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input.big:focus, select.big:focus, textarea.big:focus {
 | 
			
		||||
    padding: 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
p.formError {
 | 
			
		||||
    color: #7f2929;
 | 
			
		||||
    font-weight: 500;
 | 
			
		||||
    display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.header {
 | 
			
		||||
    background-color: #333333;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
@ -200,6 +252,15 @@ div.buttonContainer>button {
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.box {
 | 
			
		||||
    width: 576px;
 | 
			
		||||
    background-color: #eeeeee;
 | 
			
		||||
    border-radius: 3px;
 | 
			
		||||
    margin: 10px auto;
 | 
			
		||||
    padding: 10px;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media screen and (max-width: 599px) {
 | 
			
		||||
    div.header.small h1 span {
 | 
			
		||||
        display: none;
 | 
			
		||||
@ -208,4 +269,7 @@ div.buttonContainer>button {
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
    div.box {
 | 
			
		||||
        width: initial;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										42
									
								
								public/static/js/login.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								public/static/js/login.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
(function () {
 | 
			
		||||
    var form = document.getElementById('loginForm');
 | 
			
		||||
 | 
			
		||||
    form.onsubmit = function (e) {
 | 
			
		||||
        document.getElementById('loading').style.visibility = 'visible';
 | 
			
		||||
 | 
			
		||||
        e.preventDefault();
 | 
			
		||||
 | 
			
		||||
        var formData = new FormData(form);
 | 
			
		||||
 | 
			
		||||
        var xhr = new XMLHttpRequest();
 | 
			
		||||
        xhr.responseType = 'json';
 | 
			
		||||
        xhr.onload = function () {
 | 
			
		||||
            document.getElementById('loading').style.visibility = 'hidden';
 | 
			
		||||
 | 
			
		||||
            if (this.response.error) {
 | 
			
		||||
                var errorText;
 | 
			
		||||
                switch (this.response.error) {
 | 
			
		||||
                    case 'user_not_found':
 | 
			
		||||
                        errorText = 'No user found with the given email address.';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'password_not_match':
 | 
			
		||||
                        errorText = 'The given password is wrong.'
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var loginFormError = document.getElementById('loginFormError');
 | 
			
		||||
                loginFormError.style.display = 'block';
 | 
			
		||||
                loginFormError.innerHTML = errorText;
 | 
			
		||||
 | 
			
		||||
                form.elements.email.select();
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            window.location.replace('/');
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        xhr.open('POST', form.action, true);
 | 
			
		||||
        xhr.send(formData);
 | 
			
		||||
    };
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										51
									
								
								src/Cli/AddUserCommand.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/Cli/AddUserCommand.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
<?php namespace MapGuesser\Cli;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Database\Query\Modify;
 | 
			
		||||
use MapGuesser\Model\User;
 | 
			
		||||
use Symfony\Component\Console\Command\Command;
 | 
			
		||||
use Symfony\Component\Console\Input\InputArgument;
 | 
			
		||||
use Symfony\Component\Console\Input\InputInterface;
 | 
			
		||||
use Symfony\Component\Console\Output\OutputInterface;
 | 
			
		||||
 | 
			
		||||
class AddUserCommand extends Command
 | 
			
		||||
{
 | 
			
		||||
    public function configure()
 | 
			
		||||
    {
 | 
			
		||||
        $this->setName('user:add')
 | 
			
		||||
            ->setDescription('Adding of user.')
 | 
			
		||||
            ->addArgument('email', InputArgument::REQUIRED, 'Email of user')
 | 
			
		||||
            ->addArgument('password', InputArgument::REQUIRED, 'Password of user')
 | 
			
		||||
            ->addArgument('type', InputArgument::OPTIONAL, 'Type of user');;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function execute(InputInterface $input, OutputInterface $output): int
 | 
			
		||||
    {
 | 
			
		||||
        $user = new User([
 | 
			
		||||
            'email' => $input->getArgument('email'),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $user->setPlainPassword($input->getArgument('password'));
 | 
			
		||||
 | 
			
		||||
        if ($input->hasArgument('type')) {
 | 
			
		||||
            $user->setType($input->getArgument('type'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $modify = new Modify(\Container::$dbConnection, 'users');
 | 
			
		||||
            $modify->fill($user->toArray());
 | 
			
		||||
            $modify->save();
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            $output->writeln('<error>Adding user failed!</error>');
 | 
			
		||||
            $output->writeln('');
 | 
			
		||||
 | 
			
		||||
            $output->writeln((string) $e);
 | 
			
		||||
            $output->writeln('');
 | 
			
		||||
 | 
			
		||||
            return 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $output->writeln('<info>User was successfully added!</info>');
 | 
			
		||||
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Database\Query\Select;
 | 
			
		||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
			
		||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
			
		||||
use MapGuesser\Util\Geo\Bounds;
 | 
			
		||||
use MapGuesser\Response\HtmlContent;
 | 
			
		||||
use MapGuesser\Response\JsonContent;
 | 
			
		||||
@ -9,16 +10,23 @@ use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
 | 
			
		||||
class GameController
 | 
			
		||||
{
 | 
			
		||||
    public function getGame(array $parameters): IContent
 | 
			
		||||
    private IRequest $request;
 | 
			
		||||
 | 
			
		||||
    public function __construct(IRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $parameters['mapId'];
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getGame(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
        $data = $this->prepareGame($mapId);
 | 
			
		||||
        return new HtmlContent('game', $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getGameJson(array $parameters): IContent
 | 
			
		||||
    public function getGameJson(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $parameters['mapId'];
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
        $data = $this->prepareGame($mapId);
 | 
			
		||||
        return new JsonContent($data);
 | 
			
		||||
    }
 | 
			
		||||
@ -27,12 +35,14 @@ class GameController
 | 
			
		||||
    {
 | 
			
		||||
        $bounds = $this->getMapBounds($mapId);
 | 
			
		||||
 | 
			
		||||
        if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $mapId) {
 | 
			
		||||
            $_SESSION['state'] = [
 | 
			
		||||
        $session = $this->request->session();
 | 
			
		||||
 | 
			
		||||
        if (($state = $session->get('state')) && $state['mapId'] !== $mapId) {
 | 
			
		||||
            $session->set('state', [
 | 
			
		||||
                'mapId' => $mapId,
 | 
			
		||||
                'area' => $bounds->calculateApproximateArea(),
 | 
			
		||||
                'rounds' => []
 | 
			
		||||
            ];
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ['mapId' => $mapId, 'bounds' => $bounds->toArray()];
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										73
									
								
								src/Controller/LoginController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/Controller/LoginController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Database\Query\Select;
 | 
			
		||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
			
		||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IRedirect;
 | 
			
		||||
use MapGuesser\Model\User;
 | 
			
		||||
use MapGuesser\Response\HtmlContent;
 | 
			
		||||
use MapGuesser\Response\JsonContent;
 | 
			
		||||
use MapGuesser\Response\Redirect;
 | 
			
		||||
 | 
			
		||||
class LoginController
 | 
			
		||||
{
 | 
			
		||||
    private IRequest $request;
 | 
			
		||||
 | 
			
		||||
    public function __construct(IRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLoginForm()
 | 
			
		||||
    {
 | 
			
		||||
        $session = $this->request->session();
 | 
			
		||||
 | 
			
		||||
        if ($session->get('user')) {
 | 
			
		||||
            return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = [];
 | 
			
		||||
        return new HtmlContent('login', $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function login(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $session = $this->request->session();
 | 
			
		||||
 | 
			
		||||
        if ($session->get('user')) {
 | 
			
		||||
            $data = ['success' => true];
 | 
			
		||||
            return new JsonContent($data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $select = new Select(\Container::$dbConnection, 'users');
 | 
			
		||||
        $select->columns(User::getFields());
 | 
			
		||||
        $select->where('email', '=', $this->request->post('email'));
 | 
			
		||||
 | 
			
		||||
        $userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
 | 
			
		||||
 | 
			
		||||
        if ($userData === null) {
 | 
			
		||||
            $data = ['error' => 'user_not_found'];
 | 
			
		||||
            return new JsonContent($data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $user = new User($userData);
 | 
			
		||||
 | 
			
		||||
        if (!$user->checkPassword($this->request->post('password'))) {
 | 
			
		||||
            $data = ['error' => 'password_not_match'];
 | 
			
		||||
            return new JsonContent($data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $session->set('user', $user);
 | 
			
		||||
 | 
			
		||||
        $data = ['success' => true];
 | 
			
		||||
        return new JsonContent($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function logout(): IRedirect
 | 
			
		||||
    {
 | 
			
		||||
        $this->request->session()->delete('user');
 | 
			
		||||
 | 
			
		||||
        return new Redirect([\Container::$routeCollection->getRoute('login'), []], IRedirect::TEMPORARY);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,22 +1,35 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Database\Query\Select;
 | 
			
		||||
use MapGuesser\Interfaces\Authentication\IUser;
 | 
			
		||||
use MapGuesser\Interfaces\Authorization\ISecured;
 | 
			
		||||
use MapGuesser\Interfaces\Database\IResultSet;
 | 
			
		||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
use MapGuesser\Repository\PlaceRepository;
 | 
			
		||||
use MapGuesser\Response\HtmlContent;
 | 
			
		||||
use MapGuesser\Response\JsonContent;
 | 
			
		||||
use MapGuesser\Util\Geo\Bounds;
 | 
			
		||||
 | 
			
		||||
class MapAdminController
 | 
			
		||||
class MapAdminController implements ISecured
 | 
			
		||||
{
 | 
			
		||||
    private IRequest $request;
 | 
			
		||||
 | 
			
		||||
    private PlaceRepository $placeRepository;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    public function __construct(IRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
        $this->placeRepository = new PlaceRepository();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function authorize(): bool
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->request->user();
 | 
			
		||||
 | 
			
		||||
        return $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getMaps(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        //TODO
 | 
			
		||||
@ -24,9 +37,9 @@ class MapAdminController
 | 
			
		||||
        return new HtmlContent('admin/maps');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getMapEditor(array $parameters): IContent
 | 
			
		||||
    public function getMapEditor(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $parameters['mapId'];
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
 | 
			
		||||
        $bounds = $this->getMapBounds($mapId);
 | 
			
		||||
 | 
			
		||||
@ -36,9 +49,9 @@ class MapAdminController
 | 
			
		||||
        return new HtmlContent('admin/map_editor', $data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPlace(array $parameters)
 | 
			
		||||
    public function getPlace()
 | 
			
		||||
    {
 | 
			
		||||
        $placeId = (int) $parameters['placeId'];
 | 
			
		||||
        $placeId = (int) $this->request->query('placeId');
 | 
			
		||||
 | 
			
		||||
        $placeData = $this->placeRepository->getById($placeId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<?php namespace MapGuesser\Controller;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
			
		||||
use MapGuesser\Util\Geo\Position;
 | 
			
		||||
use MapGuesser\Response\JsonContent;
 | 
			
		||||
use MapGuesser\Interfaces\Response\IContent;
 | 
			
		||||
@ -10,34 +11,40 @@ class PositionController
 | 
			
		||||
    const NUMBER_OF_ROUNDS = 5;
 | 
			
		||||
    const MAX_SCORE = 1000;
 | 
			
		||||
 | 
			
		||||
    private IRequest $request;
 | 
			
		||||
 | 
			
		||||
    private PlaceRepository $placeRepository;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    public function __construct(IRequest $request)
 | 
			
		||||
    {
 | 
			
		||||
        $this->request = $request;
 | 
			
		||||
        $this->placeRepository = new PlaceRepository();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPosition(array $parameters): IContent
 | 
			
		||||
    public function getPosition(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $parameters['mapId'];
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
 | 
			
		||||
        if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $mapId) {
 | 
			
		||||
        $session = $this->request->session();
 | 
			
		||||
 | 
			
		||||
        if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
 | 
			
		||||
            $data = ['error' => 'no_session_found'];
 | 
			
		||||
            return new JsonContent($data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (count($_SESSION['state']['rounds']) === 0) {
 | 
			
		||||
        if (count($state['rounds']) === 0) {
 | 
			
		||||
            $newPosition = $this->placeRepository->getForMapWithValidPano($mapId);
 | 
			
		||||
            $_SESSION['state']['rounds'][] = $newPosition;
 | 
			
		||||
            $state['rounds'][] = $newPosition;
 | 
			
		||||
            $session->set('state', $state);
 | 
			
		||||
 | 
			
		||||
            $data = ['panoId' => $newPosition['panoId']];
 | 
			
		||||
        } else {
 | 
			
		||||
            $rounds = count($_SESSION['state']['rounds']);
 | 
			
		||||
            $last = $_SESSION['state']['rounds'][$rounds - 1];
 | 
			
		||||
            $rounds = count($state['rounds']);
 | 
			
		||||
            $last = $state['rounds'][$rounds - 1];
 | 
			
		||||
 | 
			
		||||
            $history = [];
 | 
			
		||||
            for ($i = 0; $i < $rounds - 1; ++$i) {
 | 
			
		||||
                $round = $_SESSION['state']['rounds'][$i];
 | 
			
		||||
                $round = $state['rounds'][$i];
 | 
			
		||||
                $history[] = [
 | 
			
		||||
                    'position' => $round['position']->toArray(),
 | 
			
		||||
                    'guessPosition' => $round['guessPosition']->toArray(),
 | 
			
		||||
@ -55,41 +62,45 @@ class PositionController
 | 
			
		||||
        return new JsonContent($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function evaluateGuess(array $parameters): IContent
 | 
			
		||||
    public function evaluateGuess(): IContent
 | 
			
		||||
    {
 | 
			
		||||
        $mapId = (int) $parameters['mapId'];
 | 
			
		||||
        $mapId = (int) $this->request->query('mapId');
 | 
			
		||||
 | 
			
		||||
        if (!isset($_SESSION['state']) || $_SESSION['state']['mapId'] !== $mapId) {
 | 
			
		||||
        $session = $this->request->session();
 | 
			
		||||
 | 
			
		||||
        if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
 | 
			
		||||
            $data = ['error' => 'no_session_found'];
 | 
			
		||||
            return new JsonContent($data);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $last = &$_SESSION['state']['rounds'][count($_SESSION['state']['rounds']) - 1];
 | 
			
		||||
        $last = $state['rounds'][count($state['rounds']) - 1];
 | 
			
		||||
 | 
			
		||||
        $position = $last['position'];
 | 
			
		||||
        $guessPosition = new Position((float) $_POST['lat'], (float) $_POST['lng']);
 | 
			
		||||
 | 
			
		||||
        $last['guessPosition'] = $guessPosition;
 | 
			
		||||
        $guessPosition = new Position((float) $this->request->post('lat'), (float) $this->request->post('lng'));
 | 
			
		||||
 | 
			
		||||
        $distance = $this->calculateDistance($position, $guessPosition);
 | 
			
		||||
        $score = $this->calculateScore($distance, $_SESSION['state']['area']);
 | 
			
		||||
        $score = $this->calculateScore($distance, $state['area']);
 | 
			
		||||
 | 
			
		||||
        $last['guessPosition'] = $guessPosition;
 | 
			
		||||
        $last['distance'] = $distance;
 | 
			
		||||
        $last['score'] = $score;
 | 
			
		||||
        $state['rounds'][count($state['rounds']) - 1] = $last;
 | 
			
		||||
 | 
			
		||||
        if (count($_SESSION['state']['rounds']) < static::NUMBER_OF_ROUNDS) {
 | 
			
		||||
        if (count($state['rounds']) < static::NUMBER_OF_ROUNDS) {
 | 
			
		||||
            $exclude = [];
 | 
			
		||||
 | 
			
		||||
            foreach ($_SESSION['state']['rounds'] as $round) {
 | 
			
		||||
            foreach ($state['rounds'] as $round) {
 | 
			
		||||
                $exclude = array_merge($exclude, $round['placesWithoutPano'], [$round['placeId']]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $newPosition = $this->placeRepository->getForMapWithValidPano($mapId, $exclude);
 | 
			
		||||
            $_SESSION['state']['rounds'][] = $newPosition;
 | 
			
		||||
            $state['rounds'][] = $newPosition;
 | 
			
		||||
            $session->set('state', $state);
 | 
			
		||||
 | 
			
		||||
            $panoId = $newPosition['panoId'];
 | 
			
		||||
        } else {
 | 
			
		||||
            $_SESSION['state']['rounds'] = [];
 | 
			
		||||
            $state['rounds'] = [];
 | 
			
		||||
            $session->set('state', $state);
 | 
			
		||||
 | 
			
		||||
            $panoId = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								src/Interfaces/Authentication/IUser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Interfaces/Authentication/IUser.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
<?php namespace MapGuesser\Interfaces\Authentication;
 | 
			
		||||
 | 
			
		||||
interface IUser
 | 
			
		||||
{
 | 
			
		||||
    const PERMISSION_NORMAL = 0;
 | 
			
		||||
 | 
			
		||||
    const PERMISSION_ADMIN = 1;
 | 
			
		||||
 | 
			
		||||
    public function hasPermission(int $permission): 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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/Interfaces/Request/IRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Interfaces/Request/IRequest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
<?php namespace MapGuesser\Interfaces\Request;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Interfaces\Authentication\IUser;
 | 
			
		||||
 | 
			
		||||
interface IRequest
 | 
			
		||||
{
 | 
			
		||||
    public function query(string $key);
 | 
			
		||||
 | 
			
		||||
    public function post(string $key);
 | 
			
		||||
 | 
			
		||||
    public function session(): ISession;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								src/Model/BaseModel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/Model/BaseModel.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
<?php namespace MapGuesser\Model;
 | 
			
		||||
 | 
			
		||||
abstract class BaseModel
 | 
			
		||||
{
 | 
			
		||||
    protected static array $fields;
 | 
			
		||||
 | 
			
		||||
    protected $id = null;
 | 
			
		||||
 | 
			
		||||
    public static function getFields(): array
 | 
			
		||||
    {
 | 
			
		||||
        return array_merge(['id'], static::$fields);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __construct(array $data)
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($data as $key => $value) {
 | 
			
		||||
            $method = 'set' . str_replace('_', '', ucwords($key, '_'));
 | 
			
		||||
 | 
			
		||||
            if (method_exists($this, $method)) {
 | 
			
		||||
                $this->$method($value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setId($id): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->id = $id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getId()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function toArray(): array
 | 
			
		||||
    {
 | 
			
		||||
        $array = [];
 | 
			
		||||
 | 
			
		||||
        foreach (self::getFields() as $key) {
 | 
			
		||||
            $method = 'get' . str_replace('_', '', ucwords($key, '_'));
 | 
			
		||||
 | 
			
		||||
            if (method_exists($this, $method)) {
 | 
			
		||||
                $array[$key] = $this->$method();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $array;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								src/Model/User.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/Model/User.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
<?php namespace MapGuesser\Model;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Interfaces\Authentication\IUser;
 | 
			
		||||
 | 
			
		||||
class User extends BaseModel implements IUser
 | 
			
		||||
{
 | 
			
		||||
    private static array $types = ['user', 'admin'];
 | 
			
		||||
 | 
			
		||||
    protected static array $fields = ['email', 'password', 'type'];
 | 
			
		||||
 | 
			
		||||
    private string $email;
 | 
			
		||||
 | 
			
		||||
    private string $password;
 | 
			
		||||
 | 
			
		||||
    private string $type = 'user';
 | 
			
		||||
 | 
			
		||||
    public function setEmail(string $email): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->email = $email;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPassword(string $hashedPassword): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->password = $hashedPassword;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPlainPassword(string $plainPassword): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->password = password_hash($plainPassword, PASSWORD_BCRYPT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setType(string $type): void
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array($type, self::$types)) {
 | 
			
		||||
            $this->type = $type;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getEmail(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->email;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPassword(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->password;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getType(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasPermission(int $permission): bool
 | 
			
		||||
    {
 | 
			
		||||
        switch ($permission) {
 | 
			
		||||
            case IUser::PERMISSION_NORMAL:
 | 
			
		||||
                return true;
 | 
			
		||||
                break;
 | 
			
		||||
            case IUser::PERMISSION_ADMIN:
 | 
			
		||||
                return $this->type === 'admin';
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function checkPassword(string $password): bool
 | 
			
		||||
    {
 | 
			
		||||
        return password_verify($password, $this->password);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								src/Request/Request.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/Request/Request.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,57 @@
 | 
			
		||||
<?php namespace MapGuesser\Request;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Interfaces\Authentication\IUser;
 | 
			
		||||
use MapGuesser\Interfaces\Request\IRequest;
 | 
			
		||||
use MapGuesser\Interfaces\Request\ISession;
 | 
			
		||||
use MapGuesser\Model\User;
 | 
			
		||||
 | 
			
		||||
class Request implements IRequest
 | 
			
		||||
{
 | 
			
		||||
    private array $get;
 | 
			
		||||
 | 
			
		||||
    private array $routeParams;
 | 
			
		||||
 | 
			
		||||
    private array $post;
 | 
			
		||||
 | 
			
		||||
    private Session $session;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array &$get, array &$routeParams, array &$post, array &$session)
 | 
			
		||||
    {
 | 
			
		||||
        $this->get = &$get;
 | 
			
		||||
        $this->routeParams = &$routeParams;
 | 
			
		||||
        $this->post = &$post;
 | 
			
		||||
        $this->session = new Session($session);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 user(): ?IUser
 | 
			
		||||
    {
 | 
			
		||||
        return $this->session->get('user');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/Request/Session.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Request/Session.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
<?php namespace MapGuesser\Request;
 | 
			
		||||
 | 
			
		||||
use MapGuesser\Interfaces\Request\ISession;
 | 
			
		||||
 | 
			
		||||
class Session implements ISession
 | 
			
		||||
{
 | 
			
		||||
    private array $data;
 | 
			
		||||
 | 
			
		||||
    public function __construct(array &$data)
 | 
			
		||||
    {
 | 
			
		||||
        $this->data = &$data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function has($key): bool
 | 
			
		||||
    {
 | 
			
		||||
        return isset($this->data[$key]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get($key)
 | 
			
		||||
    {
 | 
			
		||||
        if (isset($this->data[$key])) {
 | 
			
		||||
            return $this->data[$key];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function set($key, $value): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->data[$key] = $value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function delete($key): void
 | 
			
		||||
    {
 | 
			
		||||
        unset($this->data[$key]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -20,6 +20,11 @@ class Route
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getHandler(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->handler;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function generateLink(array $parameters = []): string
 | 
			
		||||
    {
 | 
			
		||||
        $link = [];
 | 
			
		||||
@ -51,14 +56,6 @@ class Route
 | 
			
		||||
        return implode('/', $link) . $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function callController(array $parameters)
 | 
			
		||||
    {
 | 
			
		||||
        $controllerName = $this->handler[0];
 | 
			
		||||
        $controller = new $controllerName();
 | 
			
		||||
 | 
			
		||||
        return call_user_func([$controller, $this->handler[1]], $parameters);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testAgainst(array $path): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								views/login.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								views/login.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
<?php require ROOT . '/views/templates/main_header.php'; ?>
 | 
			
		||||
<?php require ROOT . '/views/templates/header.php'; ?>
 | 
			
		||||
<div class="main">
 | 
			
		||||
    <h2>Login</h2>
 | 
			
		||||
    <div class="box">
 | 
			
		||||
        <form id="loginForm" action="/login" method="post">
 | 
			
		||||
            <input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
 | 
			
		||||
            <input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
 | 
			
		||||
            <p id="loginFormError" class="formError marginTop"></p>
 | 
			
		||||
            <div class="right marginTop">
 | 
			
		||||
                <button type="submit">Login</button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<script src="/static/js/login.js"></script>
 | 
			
		||||
<?php require ROOT . '/views/templates/main_footer.php'; ?>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user