Compare commits

..
Author SHA1 Message Date
bence 84cdb84c1b MAPG-123 [WIP] use MutationObserver 2020-06-14 22:05:41 +02:00
bence 15b5869d5a Merged in bugfix/MAPG-45-secure-profile-page (pull request #114)
MAPG-45 secure profile page
2020-06-14 19:05:35 +00:00
bence bd2b28ef17 MAPG-45 secure profile page 2020-06-14 21:04:20 +02:00
bence 6d431717f8 Merged in bugfix/MAPG-69-fix-mail-template-processing (pull request #113)
MAPG-69 make emails HTML explicitly
2020-06-14 18:53:44 +00:00
bence 4ac18dfc84 MAPG-69 make emails HTML explicitly 2020-06-14 20:52:42 +02:00
bence afbd0b4e20 Merged in feature/MAPG-69-implement-google-registration (pull request #112)
Feature/MAPG-69 implement google registration
2020-06-14 18:45:25 +00:00
bence 062718dd75 MAPG-69 implement password modification 2020-06-14 20:43:13 +02:00
bence 20102f0577 MAPG-69 implement sign up functionality 2020-06-14 20:43:13 +02:00
bence a69ba3a99b MAPG-69 unifying some style and text 2020-06-14 20:43:13 +02:00
bence 66b21ec710 MAPG-69 add active flag for users and check if user is active before login 2020-06-14 20:43:13 +02:00
bence 28ed02091a MAPG-69 add mailing 2020-06-14 20:43:13 +02:00
bence 334df3462b MAPG-69 add menu with user info to header 2020-06-14 17:43:14 +02:00
bence 01db72796b MAPG-69 fix migration - transaction should not be started in that scope 2020-06-14 17:12:29 +02:00
bence c50c5ed422 MAPG-69 initialize Request earlier
add Request to global Container
add base URL to Request
2020-06-14 17:11:48 +02:00
bence 142c91f36b Merged in bugfix/MAPG-138-fix-js-minifying (pull request #111)
MAPG-138 use traditional for loop for compatibility
2020-06-14 09:33:54 +00:00
bence 1269c876e9 MAPG-138 use traditional for loop for compatibility 2020-06-14 11:33:13 +02:00
bence 44e441fac0 Merged in bugfix/MAPG-137-fix-errors-reported-by-speed-an (pull request #110)
Bugfix/MAPG-137 fix errors reported by speed an
2020-06-14 09:25:47 +00:00
bence f85340cd00 MAPG-137 optimize png images (favicons) 2020-06-14 11:21:07 +02:00
bence ba83f73c31 MAPG-137 add width and height to images where possible 2020-06-14 11:12:18 +02:00
bence 86f05f7738 MAPG-137 place static maps into container to avoid large layout shifts 2020-06-14 11:11:37 +02:00
bence 61ce915ec0 Merged in hotfix/MAPG-125-ability-to-delete-maps (pull request #109)
MAPG-125 re-add select map name when opening modal
2020-06-14 00:53:38 +00:00
40 changed files with 782 additions and 74 deletions

No files matched your search

+3
View File
@@ -8,3 +8,6 @@ GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
STATIC_ROOT=/static STATIC_ROOT=/static
MAIL_FROM=mapguesser@mapguesser-dev.ch
MAIL_HOST=mail
MAIL_PORT=2500
+2 -1
View File
@@ -5,7 +5,8 @@
"license": "GNU GPL 3.0", "license": "GNU GPL 3.0",
"require": { "require": {
"vlucas/phpdotenv": "^4.1", "vlucas/phpdotenv": "^4.1",
"symfony/console": "^5.1" "symfony/console": "^5.1",
"phpmailer/phpmailer": "^6.1"
}, },
"require-dev": {}, "require-dev": {},
"autoload": { "autoload": {
Generated
+69 -1
View File
@@ -4,8 +4,76 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "13a0eaff2786786caff2be86ac704fc7", "content-hash": "67a75c3149ef859545476427e7f2f686",
"packages": [ "packages": [
{
"name": "phpmailer/phpmailer",
"version": "v6.1.6",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"reference": "c2796cb1cb99d7717290b48c4e6f32cb6c60b7b3",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-filter": "*",
"php": ">=5.5.0"
},
"require-dev": {
"doctrine/annotations": "^1.2",
"friendsofphp/php-cs-fixer": "^2.2",
"phpunit/phpunit": "^4.8 || ^5.7"
},
"suggest": {
"ext-mbstring": "Needed to send email in multibyte encoding charset",
"hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
},
"type": "library",
"autoload": {
"psr-4": {
"PHPMailer\\PHPMailer\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-only"
],
"authors": [
{
"name": "Marcus Bointon",
"email": "phpmailer@synchromedia.co.uk"
},
{
"name": "Jim Jagielski",
"email": "jimjag@gmail.com"
},
{
"name": "Andy Prevost",
"email": "codeworxtech@users.sourceforge.net"
},
{
"name": "Brent R. Matzelle"
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"funding": [
{
"url": "https://github.com/synchro",
"type": "github"
}
],
"time": "2020-05-27T12:24:03+00:00"
},
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.7.3", "version": "1.7.3",
@@ -10,8 +10,6 @@ $select->columns(['id', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat',
$result = $select->execute(); $result = $select->execute();
\Container::$dbConnection->startTransaction();
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) { while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']); $bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
@@ -20,5 +18,3 @@ while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
$modify->set('area', $bounds->calculateApproximateArea()); $modify->set('area', $bounds->calculateApproximateArea());
$modify->save(); $modify->save();
} }
\Container::$dbConnection->commit();
@@ -0,0 +1,17 @@
<?php
use MapGuesser\Database\Query\Modify;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet;
$select = new Select(\Container::$dbConnection, 'users');
$select->columns(['id']);
$result = $select->execute();
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
$modify = new Modify(\Container::$dbConnection, 'users');
$modify->setId($map['id']);
$modify->set('active', true);
$modify->save();
}
@@ -0,0 +1,14 @@
CREATE TABLE `user_confirmations` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`token` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `token` (`token`),
CONSTRAINT `user_confirmations_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
ALTER TABLE
`users`
ADD
`active` tinyint(1) NOT NULL DEFAULT 0;
+6
View File
@@ -8,6 +8,7 @@ services:
- .:/var/www/mapguesser - .:/var/www/mapguesser
links: links:
- 'mariadb' - 'mariadb'
- 'mail'
mariadb: mariadb:
image: mariadb:10.1 image: mariadb:10.1
volumes: volumes:
@@ -17,5 +18,10 @@ services:
MYSQL_DATABASE: 'mapguesser' MYSQL_DATABASE: 'mapguesser'
MYSQL_USER: 'mapguesser' MYSQL_USER: 'mapguesser'
MYSQL_PASSWORD: 'mapguesser' MYSQL_PASSWORD: 'mapguesser'
mail:
image: marcopas/docker-mailslurper:latest
ports:
- 8080:8080
- 8085:8085
volumes: volumes:
mysql: mysql:
+13
View File
@@ -0,0 +1,13 @@
Hi,
<br><br>
You recently signed up on MapGuesser with this email address ({{EMAIL}}). To activate your account, please click on the following link:<br>
<a href="{{ACTIVATE_LINK}}" title="Account activation">{{ACTIVATE_LINK}}</a>
<br><br>
If you did not sign up on MapGuesser or changed your mind, no further action is required, your email address will be deleted soon.<br>
However if you want to immediately delete it, please click on the following link:<br>
<a href="{{CANCEL_LINK}}" title="Sign up cancellation">{{CANCEL_LINK}}</a>
<br><br>
Have fun on MapGuesser!
<br><br>
Regards,<br>
MapGuesser
+1
View File
@@ -15,6 +15,7 @@ class Container
static MapGuesser\Interfaces\Database\IConnection $dbConnection; static MapGuesser\Interfaces\Database\IConnection $dbConnection;
static MapGuesser\Routing\RouteCollection $routeCollection; static MapGuesser\Routing\RouteCollection $routeCollection;
static \SessionHandlerInterface $sessionHandler; static \SessionHandlerInterface $sessionHandler;
static MapGuesser\Interfaces\Request\IRequest $request;
} }
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']); Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
+4 -5
View File
@@ -2,7 +2,6 @@
require '../web.php'; require '../web.php';
$host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
$method = strtolower($_SERVER['REQUEST_METHOD']); $method = strtolower($_SERVER['REQUEST_METHOD']);
$url = substr($_SERVER['REQUEST_URI'], strlen('/')); $url = substr($_SERVER['REQUEST_URI'], strlen('/'));
if (($pos = strpos($url, '?')) !== false) { if (($pos = strpos($url, '?')) !== false) {
@@ -15,10 +14,10 @@ $match = Container::$routeCollection->match($method, explode('/', $url));
if ($match !== null) { if ($match !== null) {
list($route, $params) = $match; list($route, $params) = $match;
$request = new MapGuesser\Request\Request($_GET, $params, $_POST, $_SESSION); Container::$request->setParsedRouteParams($params);
$handler = $route->getHandler(); $handler = $route->getHandler();
$controller = new $handler[0]($request); $controller = new $handler[0](Container::$request);
if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) { if ($controller instanceof MapGuesser\Interfaces\Authorization\ISecured) {
$authorized = $controller->authorize(); $authorized = $controller->authorize();
@@ -26,7 +25,7 @@ if ($match !== null) {
$authorized = true; $authorized = true;
} }
if ($method === 'post' && $request->post('anti_csrf_token') !== $request->session()->get('anti_csrf_token')) { if ($method === 'post' && Container::$request->post('anti_csrf_token') !== Container::$request->session()->get('anti_csrf_token')) {
header('Content-Type: text/html; charset=UTF-8', true, 403); header('Content-Type: text/html; charset=UTF-8', true, 403);
echo json_encode(['error' => 'no_valid_anti_csrf_token']); echo json_encode(['error' => 'no_valid_anti_csrf_token']);
return; return;
@@ -41,7 +40,7 @@ if ($match !== null) {
return; return;
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) { } elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
header('Location: ' . $host . '/' . $response->getUrl(), true, $response->getHttpCode()); header('Location: ' . Container::$request->getBase() . '/' . $response->getUrl(), true, $response->getHttpCode());
return; return;
} }
+14 -13
View File
@@ -69,8 +69,9 @@ sub {
bottom: -0.4em; bottom: -0.4em;
} }
.mono { hr {
font-family: 'Roboto Mono', monospace; border: solid #bbbbbb 1px;
margin: 10px 0;
} }
.bold { .bold {
@@ -261,13 +262,18 @@ div.modal {
visibility: hidden; visibility: hidden;
} }
p.formError { p.error, p.formError {
color: #7f2929; color: #7f2929;
font-weight: 500; font-weight: 500;
}
p.formError {
display: none; display: none;
} }
div.header { div.header {
display: grid;
grid-template-columns: auto auto;
background-color: #333333; background-color: #333333;
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
@@ -275,30 +281,25 @@ div.header {
color: white; color: white;
} }
div.header>div.grid {
display: grid;
grid-template-columns: auto auto;
}
div.header.small { div.header.small {
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
} }
div.header>div.grid>:nth-child(2) { div.header>p.header {
line-height: inherit; line-height: inherit;
text-align: right; text-align: right;
} }
div.header>div.grid>:nth-child(2)>span { div.header>p.header>span {
padding-left: 6px; padding-left: 6px;
} }
div.header>div.grid>:nth-child(2)>span>a:link, div.header>div.grid>:nth-child(2)>span>a:visited { div.header>p.header>span>a:link, div.header>p.header>span>a:visited {
color: inherit; color: inherit;
} }
div.header>div.grid>:nth-child(2)>span:not(:last-child) { div.header>p.header>span:not(:last-child) {
border-right: solid white 1px; border-right: solid white 1px;
padding-right: 6px; padding-right: 6px;
} }
@@ -337,7 +338,7 @@ div.box {
} }
@media screen and (max-width: 599px) { @media screen and (max-width: 599px) {
div.header.small h1 span { div.header h1 span {
display: none; display: none;
} }
button, a.button { button, a.button {
+8 -1
View File
@@ -26,8 +26,15 @@ div.mapItem>div.title>p.title {
font-size: 18px; font-size: 18px;
} }
div.mapItem>img { div.mapItem>div.imgContainer {
width: 100%; width: 100%;
padding-top: 50%;
background: #cccccc;
}
div.mapItem>div.imgContainer>img {
width: 100%;
margin-top: -50%
} }
div.mapItem>div.inner { div.mapItem>div.inner {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15 -8
View File
@@ -336,7 +336,10 @@
}, },
rewriteGoogleLink: function () { rewriteGoogleLink: function () {
if (!Game.googleLink) { if (Game.googleLink) {
return;
}
var anchors = document.getElementById('panorama').getElementsByTagName('a'); var anchors = document.getElementById('panorama').getElementsByTagName('a');
for (var i = 0; i < anchors.length; i++) { for (var i = 0; i < anchors.length; i++) {
var a = anchors[i]; var a = anchors[i];
@@ -345,14 +348,22 @@
break; break;
} }
} }
if (!Game.googleLink) {
return;
} }
setTimeout(function () { var observer = new MutationObserver(function(mutationsList, observer) {
if (Game.googleLink) { for (let mutation of mutationsList) {
if (mutation.attributeName === 'href' && Game.googleLink.href !== 'https://maps.google.com/maps') {
console.log(Game.googleLink.href + ' is changed.')
Game.googleLink.title = 'Google Maps'; Game.googleLink.title = 'Google Maps';
Game.googleLink.href = 'https://maps.google.com/maps'; Game.googleLink.href = 'https://maps.google.com/maps';
} }
}, 1); }
});
observer.observe(Game.googleLink, { attributes: true });
} }
}; };
@@ -425,10 +436,6 @@
Game.rewriteGoogleLink(); Game.rewriteGoogleLink();
}); });
Game.panorama.addListener('pov_changed', function () {
Game.rewriteGoogleLink();
});
Game.initialize(); Game.initialize();
document.getElementById('showGuessButton').onclick = function () { document.getElementById('showGuessButton').onclick = function () {
+4 -1
View File
@@ -13,7 +13,10 @@
var errorText; var errorText;
switch (this.response.error) { switch (this.response.error) {
case 'user_not_found': case 'user_not_found':
errorText = 'No user found with the given email address.'; errorText = 'No user found with the given email address. You can <a href="/signup" title="Sign up">sign up here</a>!';
break;
case 'user_not_active':
errorText = 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!';
break; break;
case 'password_not_match': case 'password_not_match':
errorText = 'The given password is wrong.' errorText = 'The given password is wrong.'
+15 -6
View File
@@ -43,15 +43,16 @@ var MapGuesser = {
var buttons = document.getElementById('modalButtons'); var buttons = document.getElementById('modalButtons');
buttons.textContent = ''; buttons.textContent = '';
for (extraButton of extraButtons) { for (var i = 0; i < extraButtons.length; i++) {
var extraButton = extraButtons[i];
var button = document.createElement(extraButton.type); var button = document.createElement(extraButton.type);
if (extraButton.type === 'a') { if (extraButton.type === 'a') {
button.classList.add('button'); button.classList.add('button');
} }
for (className of extraButton.classNames) { for (var i = 0; i < extraButton.classNames.length; i++) {
button.classList.add(className); button.classList.add(extraButton.classNames[i]);
} }
button.classList.add('marginTop'); button.classList.add('marginTop');
@@ -71,7 +72,7 @@ var MapGuesser = {
closeButton.classList.add('gray'); closeButton.classList.add('gray');
closeButton.classList.add('marginTop'); closeButton.classList.add('marginTop');
closeButton.textContent = 'Cancel'; closeButton.textContent = 'Close';
closeButton.onclick = function () { closeButton.onclick = function () {
MapGuesser.hideModal(); MapGuesser.hideModal();
}; };
@@ -82,11 +83,19 @@ var MapGuesser = {
hideModal: function () { hideModal: function () {
var modals = document.getElementsByClassName('modal'); var modals = document.getElementsByClassName('modal');
for (modal of modals) { for (var i = 0; i < modals.length; i++) {
modal.style.visibility = 'hidden'; modals[i].style.visibility = 'hidden';
} }
document.getElementById('cover').style.visibility = 'hidden'; document.getElementById('cover').style.visibility = 'hidden';
},
toggleDisableOnChange: function (input, button) {
if (input.defaultValue !== input.value) {
button.disabled = false;
} else {
button.disabled = true;
}
} }
}; };
+51
View File
@@ -0,0 +1,51 @@
(function () {
var form = document.getElementById('profileForm');
form.elements.password_new.onkeyup = function () {
MapGuesser.toggleDisableOnChange(this, form.elements.save);
};
form.elements.password_new_confirm.onkeyup = function () {
MapGuesser.toggleDisableOnChange(this, form.elements.save);
};
form.onsubmit = function (e) {
document.getElementById('loading').style.visibility = 'visible';
e.preventDefault();
var formData = new FormData(form);
MapGuesser.httpRequest('POST', form.action, function () {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
var errorText;
switch (this.response.error) {
case 'password_not_match':
errorText = 'The given current password is wrong.'
break;
case 'passwords_too_short':
errorText = 'The given new password is too short. Please choose a password that is at least 6 characters long!'
break;
case 'passwords_not_match':
errorText = 'The given new passwords do not match.'
break;
}
var profileFormError = document.getElementById('profileFormError');
profileFormError.style.display = 'block';
profileFormError.innerHTML = errorText;
form.elements.password_new.select();
return;
}
document.getElementById('profileFormError').style.display = 'none';
form.reset();
form.elements.save.disabled = true;
form.elements.password_new.focus();
}, formData);
};
})();
+47
View File
@@ -0,0 +1,47 @@
(function () {
var form = document.getElementById('signupForm');
form.onsubmit = function (e) {
document.getElementById('loading').style.visibility = 'visible';
e.preventDefault();
var formData = new FormData(form);
MapGuesser.httpRequest('POST', form.action, function () {
document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) {
var errorText;
switch (this.response.error) {
case 'passwords_too_short':
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
break;
case 'passwords_not_match':
errorText = 'The given passwords do not match.'
break;
case 'user_found':
errorText = 'There is a user already registered with the given email address. Please <a href="/login" title="Login">login here</a>!';
break;
case 'not_active_user_found':
errorText = 'There is a user already registered with the given email address. Please check your email and click on the activation link!';
break;
}
var signupFormError = document.getElementById('signupFormError');
signupFormError.style.display = 'block';
signupFormError.innerHTML = errorText;
form.elements.email.select();
return;
}
document.getElementById('signupFormError').style.display = 'none';
form.reset();
form.elements.email.focus();
MapGuesser.showModalWithContent('Sign up successful', 'Sign up was successful. Please check your email and click on the activation link to activate your account!');
}, formData);
};
})();
+6 -1
View File
@@ -53,6 +53,11 @@ class LoginController
$user = new User($userData); $user = new User($userData);
if (!$user->getActive()) {
$data = ['error' => 'user_not_active'];
return new JsonContent($data);
}
if (!$user->checkPassword($this->request->post('password'))) { if (!$user->checkPassword($this->request->post('password'))) {
$data = ['error' => 'password_not_match']; $data = ['error' => 'password_not_match'];
return new JsonContent($data); return new JsonContent($data);
@@ -68,6 +73,6 @@ class LoginController
{ {
$this->request->session()->delete('user'); $this->request->session()->delete('user');
return new Redirect([\Container::$routeCollection->getRoute('login'), []], IRedirect::TEMPORARY); return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
} }
} }
+192
View File
@@ -0,0 +1,192 @@
<?php namespace MapGuesser\Controller;
use MapGuesser\Database\Query\Modify;
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\Mailing\Mail;
use MapGuesser\Model\User;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
use MapGuesser\Response\Redirect;
class SignupController
{
private IRequest $request;
public function __construct(IRequest $request)
{
$this->request = $request;
}
public function getSignupForm()
{
$session = $this->request->session();
if ($session->get('user')) {
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
}
$data = [];
return new HtmlContent('signup/signup', $data);
}
public function signup(): IContent
{
$session = $this->request->session();
if ($session->get('user')) {
//TODO: return with some error
$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) {
$user = new User($userData);
if ($user->getActive()) {
$data = ['error' => 'user_found'];
} else {
$data = ['error' => 'not_active_user_found'];
}
return new JsonContent($data);
}
if (strlen($this->request->post('password')) < 6) {
$data = ['error' => 'passwords_too_short'];
return new JsonContent($data);
}
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
$data = ['error' => 'passwords_not_match'];
return new JsonContent($data);
}
$user = new User([
'email' => $this->request->post('email'),
]);
$user->setPlainPassword($this->request->post('password'));
\Container::$dbConnection->startTransaction();
$modify = new Modify(\Container::$dbConnection, 'users');
$modify->fill($user->toArray());
$modify->save();
$userId = $modify->getId();
$token = hash('sha256', serialize($user) . random_bytes(10) . microtime());
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
$modify->set('user_id', $userId);
$modify->set('token', $token);
$modify->save();
\Container::$dbConnection->commit();
$this->sendConfirmationEmail($user->getEmail(), $token);
$data = ['success' => true];
return new JsonContent($data);
}
public function activate()
{
$session = $this->request->session();
if ($session->get('user')) {
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
}
$select = new Select(\Container::$dbConnection, 'user_confirmations');
$select->columns(['id', 'user_id']);
$select->where('token', '=', $this->request->query('token'));
$confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
if ($confirmation === null) {
$data = [];
return new HtmlContent('signup/activate', $data);
}
\Container::$dbConnection->startTransaction();
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
$modify->setId($confirmation['id']);
$modify->delete();
$modify = new Modify(\Container::$dbConnection, 'users');
$modify->setId($confirmation['user_id']);
$modify->set('active', true);
$modify->save();
\Container::$dbConnection->commit();
$select = new Select(\Container::$dbConnection, 'users');
$select->columns(User::getFields());
$select->whereId($confirmation['user_id']);
$userData = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
$user = new User($userData);
$session->set('user', $user);
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
}
public function cancel()
{
$session = $this->request->session();
if ($session->get('user')) {
return new Redirect([\Container::$routeCollection->getRoute('index'), []], IRedirect::TEMPORARY);
}
$select = new Select(\Container::$dbConnection, 'user_confirmations');
$select->columns(['id', 'user_id']);
$select->where('token', '=', $this->request->query('token'));
$confirmation = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
if ($confirmation === null) {
$data = ['success' => false];
return new HtmlContent('signup/cancel', $data);
}
\Container::$dbConnection->startTransaction();
$modify = new Modify(\Container::$dbConnection, 'user_confirmations');
$modify->setId($confirmation['id']);
$modify->delete();
$modify = new Modify(\Container::$dbConnection, 'users');
$modify->setId($confirmation['user_id']);
$modify->delete();
\Container::$dbConnection->commit();
$data = ['success' => true];
return new HtmlContent('signup/cancel', $data);
}
private function sendConfirmationEmail($email, $token): void
{
$mail = new Mail();
$mail->addRecipient($email);
$mail->setSubject('Welcome to MapGuesser - Activate your account');
$mail->setBodyFromTemplate('signup', [
'EMAIL' => $email,
'ACTIVATE_LINK' => $this->request->getBase() . '/signup/activate/' . $token,
'CANCEL_LINK' => $this->request->getBase() . '/signup/cancel/' . $token,
]);
$mail->send();
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php namespace MapGuesser\Controller;
use MapGuesser\Database\Query\Modify;
use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
class UserController implements ISecured
{
private IRequest $request;
public function __construct(IRequest $request)
{
$this->request = $request;
}
public function authorize(): bool
{
$user = $this->request->user();
return $user !== null;
}
public function getProfile(): IContent
{
$user = $this->request->user();
$data = ['user' => $user->toArray()];
return new HtmlContent('profile', $data);
}
public function saveProfile(): IContent
{
$user = $this->request->user();
if (!$user->checkPassword($this->request->post('password'))) {
$data = ['error' => 'password_not_match'];
return new JsonContent($data);
}
if (strlen($this->request->post('password_new')) > 0) {
if (strlen($this->request->post('password_new')) < 6) {
$data = ['error' => 'passwords_too_short'];
return new JsonContent($data);
}
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
$data = ['error' => 'passwords_not_match'];
return new JsonContent($data);
}
$user->setPlainPassword($this->request->post('password_new'));
}
$modify = new Modify(\Container::$dbConnection, 'users');
$modify->fill($user->toArray());
$modify->save();
$data = ['success' => true];
return new JsonContent($data);
}
}
+2
View File
@@ -7,4 +7,6 @@ interface IUser
const PERMISSION_ADMIN = 1; const PERMISSION_ADMIN = 1;
public function hasPermission(int $permission): bool; public function hasPermission(int $permission): bool;
public function getDisplayName(): string;
} }
+4
View File
@@ -4,6 +4,10 @@ use MapGuesser\Interfaces\Authentication\IUser;
interface IRequest interface IRequest
{ {
public function setParsedRouteParams(array &$routeParams);
public function getBase(): string;
public function query(string $key); public function query(string $key);
public function post(string $key); public function post(string $key);
+80
View File
@@ -0,0 +1,80 @@
<?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');
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'], 'MapGuesser');
$mailer->addReplyTo($_ENV['MAIL_FROM'], 'MapGuesser');
$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)
{
$mailer->clearAddresses();
$mailer->addAddress($recipient[0], $recipient[1]);
$mailer->send();
}
}
+18 -1
View File
@@ -6,7 +6,7 @@ class User extends BaseModel implements IUser
{ {
private static array $types = ['user', 'admin']; private static array $types = ['user', 'admin'];
protected static array $fields = ['email', 'password', 'type']; protected static array $fields = ['email', 'password', 'type', 'active'];
private string $email; private string $email;
@@ -14,6 +14,8 @@ class User extends BaseModel implements IUser
private string $type = 'user'; private string $type = 'user';
private bool $active = false;
public function setEmail(string $email): void public function setEmail(string $email): void
{ {
$this->email = $email; $this->email = $email;
@@ -36,6 +38,11 @@ class User extends BaseModel implements IUser
} }
} }
public function setActive($active): void
{
$this->active = (bool) $active;
}
public function getEmail(): string public function getEmail(): string
{ {
return $this->email; return $this->email;
@@ -51,6 +58,11 @@ class User extends BaseModel implements IUser
return $this->type; return $this->type;
} }
public function getActive(): bool
{
return $this->active;
}
public function hasPermission(int $permission): bool public function hasPermission(int $permission): bool
{ {
switch ($permission) { switch ($permission) {
@@ -63,6 +75,11 @@ class User extends BaseModel implements IUser
} }
} }
public function getDisplayName(): string
{
return $this->email;
}
public function checkPassword(string $password): bool public function checkPassword(string $password): bool
{ {
return password_verify($password, $this->password); return password_verify($password, $this->password);
+15 -4
View File
@@ -3,26 +3,37 @@
use MapGuesser\Interfaces\Authentication\IUser; use MapGuesser\Interfaces\Authentication\IUser;
use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Request\ISession; use MapGuesser\Interfaces\Request\ISession;
use MapGuesser\Model\User;
class Request implements IRequest class Request implements IRequest
{ {
private string $base;
private array $get; private array $get;
private array $routeParams; private array $routeParams = [];
private array $post; private array $post;
private Session $session; private Session $session;
public function __construct(array &$get, array &$routeParams, array &$post, array &$session) public function __construct(string $base, array &$get, array &$post, array &$session)
{ {
$this->base = $base;
$this->get = &$get; $this->get = &$get;
$this->routeParams = &$routeParams;
$this->post = &$post; $this->post = &$post;
$this->session = new Session($session); $this->session = new Session($session);
} }
public function setParsedRouteParams(array &$routeParams)
{
$this->routeParams = &$routeParams;
}
public function getBase(): string
{
return $this->base;
}
public function query($key) public function query($key)
{ {
if (isset($this->get[$key])) { if (isset($this->get[$key])) {
+6 -8
View File
@@ -14,18 +14,17 @@ $jsFiles = [
?> ?>
<?php require ROOT . '/views/templates/main_header.php'; ?> <?php require ROOT . '/views/templates/main_header.php'; ?>
<div class="header small"> <div class="header small">
<div class="grid">
<h1> <h1>
<a href="/maps" title="Back to playable maps"> <a href="/maps" title="Back to playable maps">
<img class="inline" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"> <img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
<span>MapGuesser</span> <span>MapGuesser</span>
</a> </a>
</h1> </h1>
<p> <p class="header">
<span class="bold"><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!-- <span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
--><span><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> --><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/> <path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/>
<path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/> <path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/>
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
@@ -34,14 +33,14 @@ $jsFiles = [
--></span><!-- --></span><!--
--><span><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> --><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
</svg> </svg>
<span id="edited" class="bold">0</span><!-- <span id="edited" class="bold">0</span><!--
--></span><!-- --></span><!--
--><span><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> --><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/> <path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/>
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
</svg> </svg>
@@ -49,7 +48,6 @@ $jsFiles = [
--></span> --></span>
</p> </p>
</div> </div>
</div>
<div id="metadata" class="modal"> <div id="metadata" class="modal">
<h2>Edit map data</h2> <h2>Edit map data</h2>
<form id="metadataForm" class="marginTop"> <form id="metadataForm" class="marginTop">
+2 -4
View File
@@ -9,20 +9,18 @@ $jsFiles = [
?> ?>
<?php require ROOT . '/views/templates/main_header.php'; ?> <?php require ROOT . '/views/templates/main_header.php'; ?>
<div class="header small"> <div class="header small">
<div class="grid">
<h1> <h1>
<a href="/maps" title="Back to playable maps"> <a href="/maps" title="Back to playable maps">
<img class="inline" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"> <img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
<span>MapGuesser</span> <span>MapGuesser</span>
</a> </a>
</h1> </h1>
<p> <p class="header">
<span id="mapName" class="bold"><?= $mapName ?></span><!-- <span id="mapName" class="bold"><?= $mapName ?></span><!--
--><span>Round <span id="currentRound" class="bold"></span></span><!-- --><span>Round <span id="currentRound" class="bold"></span></span><!--
--><span>Score <span id="currentScoreSum" class="bold"></span></span> --><span>Score <span id="currentScoreSum" class="bold"></span></span>
</p> </p>
</div> </div>
</div>
<div id="guessCover"></div> <div id="guessCover"></div>
<div id="panorama"></div> <div id="panorama"></div>
<div id="showGuessButtonContainer"> <div id="showGuessButtonContainer">
+1 -1
View File
@@ -11,7 +11,7 @@ $jsFiles = [
<form id="loginForm" action="/login" method="post"> <form id="loginForm" action="/login" method="post">
<input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus> <input class="big fullWidth" type="email" name="email" placeholder="Email address" autofocus>
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password"> <input class="big fullWidth marginTop" type="password" name="password" placeholder="Password">
<p id="loginFormError" class="formError marginTop"></p> <p id="loginFormError" class="formError justify marginTop"></p>
<div class="right marginTop"> <div class="right marginTop">
<button type="submit">Login</button> <button type="submit">Login</button>
</div> </div>
+3 -1
View File
@@ -13,7 +13,9 @@ $cssFiles = [
<div class="title"> <div class="title">
<p class="title"><?= $map['name'] ?></p> <p class="title"><?= $map['name'] ?></p>
</div> </div>
<img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"> <div class="imgContainer">
<img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>" alt="Map area">
</div>
<div class="inner"> <div class="inner">
<div class="info"> <div class="info">
<p> <p>
+25
View File
@@ -0,0 +1,25 @@
<?php
$jsFiles = [
'js/profile.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<div class="main">
<h2>Profile</h2>
<div class="box">
<form id="profileForm" action="/profile" method="post">
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?>
<input class="big fullWidth" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
<input class="big fullWidth marginTop" type="password" name="password_new" placeholder="New password" autofocus>
<input class="big fullWidth marginTop" type="password" name="password_new_confirm" placeholder="New password confirmation">
<hr>
<input class="big fullWidth" type="password" name="password" placeholder="Current password">
<p id="profileFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit" name="save" disabled>Save</button>
</div>
</form>
</div>
</div>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+9
View File
@@ -0,0 +1,9 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<div class="main">
<h2>Account activation</h2>
<div class="box">
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
</div>
</div>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+13
View File
@@ -0,0 +1,13 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<div class="main">
<h2>Account cancellation</h2>
<div class="box">
<?php if ($success) : ?>
<p class="justify">Cancellation was successfull. You can <a href="/signup" title="Sign up">sign up</a> any time if you want!</p>
<?php else: ?>
<p class="error justify">Cancellation failed. Please check the link you entered! Maybe the account was already deleted, in this case no further action is required.</p>
<?php endif; ?>
</div>
</div>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+22
View File
@@ -0,0 +1,22 @@
<?php
$jsFiles = [
'js/signup.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<div class="main">
<h2>Sign up</h2>
<div class="box">
<form id="signupForm" action="/signup" 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">
<input class="big fullWidth marginTop" type="password" name="password_confirm" placeholder="Password confirmation">
<p id="signupFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit">Sign up</button>
</div>
</form>
</div>
</div>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+17 -2
View File
@@ -1,8 +1,23 @@
<div class="header"> <div class="header">
<h1> <h1>
<a href="/" title="MapGuesser"> <a href="/" title="MapGuesser">
<img class="inline" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"> <img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
MapGuesser <span>MapGuesser</span>
</a> </a>
</h1> </h1>
<p class="header">
<?php if (Container::$request->user()) : ?>
<span><a href="/profile" title="Profile">
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
<svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
</svg>
<?= Container::$request->user()->getDisplayName() ?><!--
--></a></span><!--
--><span><a href="/logout" title="Logout">Logout</a></span>
<?php else : ?>
<span><a href="/signup" title="Login">Sign up</a></span><!--
--><span><a href="/login" title="Login">Login</a></span>
<?php endif; ?>
</p>
</div> </div>
+2 -2
View File
@@ -23,11 +23,11 @@
</head> </head>
<body> <body>
<div id="loading"> <div id="loading">
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>"> <img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
</div> </div>
<div id="cover"></div> <div id="cover"></div>
<div id="modal" class="modal"> <div id="modal" class="modal">
<h2 id="modalTitle"></h2> <h2 id="modalTitle"></h2>
<p id="modalText" class="marginTop"></p> <p id="modalText" class="justify marginTop"></p>
<div id="modalButtons" class="right"></div> <div id="modalButtons" class="right"></div>
</div> </div>
+10 -2
View File
@@ -15,7 +15,13 @@ Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']); Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']); Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']); Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
Container::$routeCollection->get('signup', 'signup', [MapGuesser\Controller\SignupController::class, 'getSignupForm']);
Container::$routeCollection->post('signup-action', 'signup', [MapGuesser\Controller\SignupController::class, 'signup']);
Container::$routeCollection->get('signup.activate', 'signup/activate/{token}', [MapGuesser\Controller\SignupController::class, 'activate']);
Container::$routeCollection->get('signup.cancel', 'signup/cancel/{token}', [MapGuesser\Controller\SignupController::class, 'cancel']);
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']); Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
Container::$routeCollection->get('profile', 'profile', [MapGuesser\Controller\UserController::class, 'getProfile']);
Container::$routeCollection->post('profile-action', 'profile', [MapGuesser\Controller\UserController::class, 'saveProfile']);
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']); Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) { Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']); $routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
@@ -40,6 +46,8 @@ session_start([
'cookie_samesite' => 'Lax' 'cookie_samesite' => 'Lax'
]); ]);
if (!isset($_SESSION['anti_csrf_token'])) { Container::$request = new MapGuesser\Request\Request($_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'], $_GET, $_POST, $_SESSION);
$_SESSION['anti_csrf_token'] = hash('sha256', random_bytes(10) . microtime());
if (!Container::$request->session()->has('anti_csrf_token')) {
Container::$request->session()->set('anti_csrf_token', hash('sha256', random_bytes(10) . microtime()));
} }