Compare commits

...
Author SHA1 Message Date
bence 38a427416c Merged in bugfix/MAPG-180-unify-inline-js (pull request #161)
MAPG-180 don't parse REVISION in view linking time
2020-06-27 11:21:02 +00:00
bence 2f14e1f4c3 MAPG-180 don't parse REVISION in view linking time 2020-06-27 13:20:11 +02:00
bence 6098b7bb8d Merged in feature/MAPG-175-unify-styles (pull request #160)
MAPG-175 add theme color
2020-06-27 11:11:49 +00:00
bence 596b29f841 MAPG-175 add theme color 2020-06-27 13:09:24 +02:00
bence 34a4026ddc Merged in feature/MAPG-180-inline-js-css-automatically (pull request #159)
Feature/MAPG-180 inline js css automatically
2020-06-27 11:02:21 +00:00
bence a55ece7dac MAPG-180 don't buffer content output to variable 2020-06-27 12:56:47 +02:00
bence efdd5c54be MAPG-180 ability to inline CSS and JS assets 2020-06-27 12:51:00 +02:00
bence 825e0744ec MAPG-180 rename pagescript to pageScript 2020-06-27 12:47:13 +02:00
bence 18bf9aaade MAPG-180 declare CSS and JS files 2020-06-27 02:25:54 +02:00
bence 5e645a1313 Merged in hotfix/MAPG-184-show-runtime (pull request #158)
MAPG-184 measure runtime and show in web output
2020-06-26 23:21:02 +00:00
bence 9c9edcbbd1 MAPG-184 measure runtime and show in web output 2020-06-27 01:19:17 +02:00
bence de009637de Merged in feature/MAPG-89-create-a-better-templating-engine (pull request #156)
Feature/MAPG-89 create a better templating engine
2020-06-26 22:50:14 +00:00
bence 6fce002458 MAPG-89 unify styles 2020-06-27 00:47:14 +02:00
bence 1287fa0bc9 MAPG-89 rewrite views and view templates to the new concept 2020-06-27 00:47:14 +02:00
bence bf8e5b5f4d MAPG-89 adaptations in HtmlContent and index.php 2020-06-27 00:44:38 +02:00
bence c8a49396c2 MAPG-89 install and update sh scripts to link views in non-DEV mode 2020-06-27 00:44:38 +02:00
bence c2323c4c89 MAPG-89 add command that can link views from command line 2020-06-27 00:44:38 +02:00
bence 60cbe64718 MAPG-89 add cache folder that can hold cache files 2020-06-27 00:44:38 +02:00
bence c48bbdc3c2 MAPG-89 add classes that can link views into one PHP 2020-06-27 00:44:38 +02:00
bence ee6aa59253 Merged in bugfix/MAPG-183-tooling-fixes (pull request #157)
MAPG-183 tooling fixes
2020-06-26 22:40:14 +00:00
bence e1df28123f MAPG-183 tooling fixes 2020-06-27 00:38:19 +02:00
bence b4769b62dd Merged in feature/MAPG-177-create-unit-tests-for-all-units (pull request #155)
Feature/MAPG-177 create unit tests for all units
2020-06-25 22:33:56 +00:00
bence bee991e300 MAPG-177 add test for Model 2020-06-26 00:28:37 +02:00
bence ddc56426c2 MAPG-177 add test for GoogleOAuth 2020-06-26 00:28:37 +02:00
bence c06dd1e1d2 MAPG-177 refactor GoogleOAuth, Http\Request, Http\Response to be more testable 2020-06-26 00:28:37 +02:00
bence fd657b4244 MAPG-177 add test for JwtParser 2020-06-26 00:28:37 +02:00
bence 532f099ec9 Merged in feature/MAPG-180-unify-inline-js (pull request #154)
Feature/MAPG-180 unify inline js
2020-06-25 20:41:47 +00:00
bence ec969167e4 MAPG-180 standardize form submit with AJAX 2020-06-25 22:35:04 +02:00
bence e4a51c139f MAPG-180 improve form functionality 2020-06-25 22:34:32 +02:00
bence 27115e44b6 MAPG-180 extract all inline JS into separate JS file 2020-06-25 20:42:12 +02:00
bence 45904a98c2 Merged in feature/MAPG-156-delete-account-functionality (pull request #153)
Feature/MAPG-156 delete account functionality
2020-06-25 18:30:42 +00:00
bence b1ae7391e7 MAPG-156 implement user deletion 2020-06-25 20:26:51 +02:00
bence 8987b563dd MAPG-156 add getByUser to UserConfirmationRepository 2020-06-25 20:26:51 +02:00
bence 70d8807f38 MAPG-156 extend PDM to be able to return with Generator 2020-06-25 20:26:51 +02:00
bence 910fdddf34 MAPG-156 adapt account's JS to the new helper functions 2020-06-25 20:26:51 +02:00
bence 87c4c06aa6 MAPG-156 add some form helper functions 2020-06-25 20:26:51 +02:00
bence ad24f8ac28 MAPG-156 rename profile to account 2020-06-25 16:44:34 +02:00
bence e2067627c4 Merged in feature/MAPG-179-extend-the-readme (pull request #152)
MAPG-179 add basic stuff to the README
2020-06-25 13:33:27 +00:00
bence a8419535e1 MAPG-179 export MariaDB server port 2020-06-25 15:30:38 +02:00
bence 725d14400b MAPG-179 add basic stuff to the README 2020-06-25 15:30:21 +02:00
58 changed files with 1391 additions and 592 deletions

No files matched your search

+60 -2
View File
@@ -1,5 +1,63 @@
# MapGuesser
This is the MapGuesser Application project.
This is the MapGuesser Application project. This is a game about guessing where you are based on a street view panorama - inspired by existing applications.
License: GNU AGPL 3.0
## Installation
### Clone the Git repository
The first step is obviously cloning the repository to your machine:
```
git clone https://bitbucket.org/esoko/mapguesser.git
```
All the commands listed here should be executed from the repository root.
### (Optional) Setup Docker stack
The easiest way to build up a fully working application with web server and database is to use Docker Compose with the included `docker-compose.yml`.
All you have to do is executing the following command:
```
docker-compose up -d
```
Attach shell to the container of `mapguesser_app`. All of the following commands should be executed there.
**If you don't use the Docker stack you need to install your environment manually. Check `docker-compose.yml` and `docker/Dockerfile` to see the system requirements.**
### Initialize project
This command installes all of the Composer requirements and creates a copy of the example `.env` file.
```
composer create-project
```
### Set environment variables
The `.env` file contains several environment variables that are needed by the application to work properly. These should be configured for your environment.
**You should set here the API keys that enable playing the game. Without these API keys the application cannot work well. To get Google API keys visit this page: https://console.developers.google.com/**
One very important variable is `DEV`. This indicates that the application operates in development (staging) and not in produciton mode.
If you install the application in the Docker stack for development (staging) environment, only the variables for external dependencies (API keys, map attribution, etc.) should be adapted. All other variables (for DB connection, static root, mailing, etc.) are fine with the default value.
### Finalize installation
After you set the environment variables in the `.env` file, execute the following command:
```
scripts/install.sh
```
**And you are done!** The application is ready to use and develop.
If you installed it in the Docker stack, you can reach it on http://localhost. The mails that are sent by the application can be found on http://localhost:8080/. If needed, the database server can be directly reached on localhost:3306.
---
*License: **GNU AGPL 3.0**. Full license text can be found in file `LICENSE`.*
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+2
View File
@@ -11,6 +11,8 @@ services:
- 'mail'
mariadb:
image: mariadb:10.3
ports:
- 3306:3306
volumes:
- mysql:/var/lib/mysql
environment:
+1 -1
View File
@@ -21,7 +21,7 @@ RUN ./install-composer.sh
# Install Node.js and required packages
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt install -y nodejs
RUN npm install -g uglify-js clean-css-cli svgo
RUN npm install -g uglify-js clean-css-cli svgo yarn
EXPOSE 80
VOLUME /var/www/mapguesser
+2
View File
@@ -1,5 +1,7 @@
<?php
define('SCRIPT_STARTED', hrtime(true));
require 'vendor/autoload.php';
const ROOT = __DIR__;
+1
View File
@@ -7,5 +7,6 @@ $app = new Symfony\Component\Console\Application('MapGuesser Console', '');
$app->add(new MapGuesser\Cli\DatabaseMigration());
$app->add(new MapGuesser\Cli\AddUserCommand());
$app->add(new MapGuesser\Cli\LinkViewCommand());
$app->run();
+2 -1
View File
@@ -47,5 +47,6 @@ if ($match !== null) {
}
}
$content = new MapGuesser\Response\HtmlContent('error/404');
header('Content-Type: text/html; charset=UTF-8', true, 404);
require ROOT . '/views/error/404.php';
echo $content->render();
+7 -1
View File
@@ -17,6 +17,11 @@ button::-moz-focus-inner, input::-moz-focus-inner {
border: 0;
}
/* to be compatible with browsers that don't know <main> */
main {
display: block;
}
::selection {
background-color: #28a745;
color: #ffffff;
@@ -324,10 +329,11 @@ main {
padding: 6px 12px;
}
div.full {
main.full {
position: relative;
width: 100%;
height: calc(100% - 40px);
padding: 0;
}
footer {
-43
View File
@@ -1,43 +0,0 @@
(function () {
var form = document.getElementById('loginForm');
form.onsubmit = function (e) {
document.getElementById('loading').style.visibility = 'visible';
e.preventDefault();
var formData = new FormData(form);
MapGuesser.httpRequest('POST', form.action, function () {
if (this.response.error) {
if (this.response.error === 'user_not_found') {
window.location.replace('/signup');
return;
}
var errorText;
switch (this.response.error) {
case 'password_too_short':
errorText = 'The given password is too short. Please choose a password that is at least 6 characters long!'
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;
case 'password_not_match':
errorText = 'The given password is wrong.'
break;
}
document.getElementById('loading').style.visibility = 'hidden';
var loginFormError = document.getElementById('loginFormError');
loginFormError.style.display = 'block';
loginFormError.innerHTML = errorText;
return;
}
window.location.replace('/');
}, formData);
};
})();
+9
View File
@@ -0,0 +1,9 @@
(function () {
document.getElementById('cancelGoogleSignupButton').onclick = function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/signup/google/reset', function () {
window.location.replace('/signup');
});
};
})();
+12
View File
@@ -0,0 +1,12 @@
(function () {
var resetSignupButton = document.getElementById('resetSignupButton');
if (resetSignupButton) {
resetSignupButton.onclick = function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/signup/reset', function () {
window.location.reload();
});
};
}
})();
+89 -4
View File
@@ -15,7 +15,7 @@ var MapGuesser = {
document.head.appendChild(script);
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', GOOGLE_ANALITICS_ID);
},
@@ -66,6 +66,52 @@ var MapGuesser = {
}
},
setOnsubmitForForm: function (form) {
form.onsubmit = function (e) {
e.preventDefault();
document.getElementById('loading').style.visibility = 'visible';
var formData = new FormData(form);
var formError = form.getElementsByClassName('formError')[0];
var pageLeaveOnSuccess = form.dataset.redirectOnSuccess || form.dataset.reloadOnSuccess;
MapGuesser.httpRequest('POST', form.action, function () {
if (!pageLeaveOnSuccess) {
document.getElementById('loading').style.visibility = 'hidden';
}
if (this.response.error) {
if (pageLeaveOnSuccess) {
document.getElementById('loading').style.visibility = 'hidden';
}
formError.style.display = 'block';
formError.innerHTML = this.response.error.errorText;
return;
}
if (this.response.redirect) {
window.location.replace(this.response.redirect.target);
return;
}
if (!pageLeaveOnSuccess) {
formError.style.display = 'none';
form.reset();
} else {
if (form.dataset.redirectOnSuccess) {
window.location.replace(form.dataset.redirectOnSuccess);
} else if (form.dataset.reloadOnSuccess) {
window.location.reload();
}
}
}, formData);
}
},
showModal: function (id) {
document.getElementById(id).style.visibility = 'visible';
document.getElementById('cover').style.visibility = 'visible';
@@ -136,11 +182,35 @@ var MapGuesser = {
document.getElementById('cover').style.visibility = 'hidden';
},
toggleDisableOnChange: function (input, button) {
observeInput: function (input, buttonToToggle) {
if (input.defaultValue !== input.value) {
button.disabled = false;
buttonToToggle.disabled = false;
} else {
button.disabled = true;
buttonToToggle.disabled = true;
}
},
observeInputsInForm: function (form, observedInputs) {
for (var i = 0; i < observedInputs.length; i++) {
var input = form.elements[observedInputs[i]];
switch (input.tagName) {
case 'INPUT':
case 'TEXTAREA':
input.oninput = function () {
MapGuesser.observeInput(this, form.elements.submit);
};
break;
case 'SELECT':
input.onchange = function () {
MapGuesser.observeInput(this, form.elements.submit);
};
break;
}
}
form.onreset = function () {
form.elements.submit.disabled = true;
}
}
};
@@ -156,6 +226,21 @@ var MapGuesser = {
}
}
var forms = document.getElementsByTagName('form');
for (var i = 0; i < forms.length; i++) {
var form = forms[i];
if (form.dataset.noSubmit) {
continue;
}
MapGuesser.setOnsubmitForForm(form);
if (form.dataset.observeInputs) {
MapGuesser.observeInputsInForm(form, form.dataset.observeInputs.split(','));
}
}
document.getElementById('cover').onclick = function () {
MapGuesser.hideModal();
};
+76
View File
@@ -0,0 +1,76 @@
(function () {
var Maps = {
descriptionDivs: null,
addStaticMaps: function () {
var imgContainers = document.getElementById('mapContainer').getElementsByClassName('imgContainer');
for (var i = 0; i < imgContainers.length; i++) {
var imgContainer = imgContainers[i];
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&' +
'visible=' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng +
'&key=' + GOOGLE_MAPS_JS_API_KEY;
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
}
},
initializeDescriptionDivs: function () {
Maps.descriptionDivs = document.getElementById('mapContainer').getElementsByClassName('description');
for (var i = 0; i < Maps.descriptionDivs.length; i++) {
var description = Maps.descriptionDivs[i];
var boundingClientRect = description.getBoundingClientRect();
description.defaultHeight = boundingClientRect.height;
}
},
calculateDescriptionDivHeights: function () {
var currentY;
var rows = [];
for (var i = 0; i < Maps.descriptionDivs.length; i++) {
var description = Maps.descriptionDivs[i];
var boundingClientRect = description.getBoundingClientRect();
if (currentY !== boundingClientRect.y) {
rows.push([]);
}
rows[rows.length - 1].push(description);
currentY = boundingClientRect.y;
}
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var maxHeight = 0;
for (var j = 0; j < row.length; j++) {
var description = row[j];
if (description.defaultHeight > maxHeight) {
maxHeight = description.defaultHeight;
}
}
for (var j = 0; j < row.length; j++) {
var description = row[j];
description.style.height = maxHeight + 'px';
}
}
}
};
Maps.addStaticMaps();
Maps.initializeDescriptionDivs();
Maps.calculateDescriptionDivHeights();
window.onresize = function () {
Maps.calculateDescriptionDivHeights();
};
})();
+34
View File
@@ -0,0 +1,34 @@
(function () {
Maps = {
deleteMap: function (mapId, mapName) {
MapGuesser.showModalWithContent('Delete map', 'Are you sure you want to delete map \'' + mapName + '\'?', [{
type: 'button',
classNames: ['red'],
text: 'Delete',
onclick: function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/admin/deleteMap/' + mapId, function () {
if (this.response.error) {
document.getElementById('loading').style.visibility = 'hidden';
//TODO: handle this error
return;
}
window.location.reload();
});
}
}]);
}
};
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.onclick = function () {
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
};
}
})();
-48
View File
@@ -1,48 +0,0 @@
(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 'password_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;
return;
}
document.getElementById('profileFormError').style.display = 'none';
form.reset();
form.elements.save.disabled = true;
}, formData);
};
})();
-60
View File
@@ -1,60 +0,0 @@
(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 () {
if (this.response.error) {
if (this.response.error === 'user_found') {
window.location.replace('/');
return;
}
var errorText;
switch (this.response.error) {
case 'email_not_valid':
errorText = 'The given email address is not valid.'
break;
case 'password_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_user_not_active':
errorText = 'There is a user already registered with the given email address. Please check your email and click on the activation link!';
break;
case 'user_found_password_not_match':
errorText = 'There is a user already registered with the given email address, but the given password is wrong.'
break;
}
document.getElementById('loading').style.visibility = 'hidden';
var signupFormError = document.getElementById('signupFormError');
signupFormError.style.display = 'block';
signupFormError.innerHTML = errorText;
return;
}
window.location.replace('/signup/success');
}, formData);
};
var resetSignupButton = document.getElementById('resetSignupButton');
if (resetSignupButton) {
resetSignupButton.onclick = function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/signup/reset', function () {
window.location.reload();
});
};
}
})();
+3
View File
@@ -21,6 +21,9 @@ echo "Migrating DB..."
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
echo "Minifying JS, CSS and SVG files..."
${ROOT_DIR}/scripts/minify.sh
echo "Linking view files..."
(cd ${ROOT_DIR} && ./mapg view:link)
fi
touch ${ROOT_DIR}/installed
+8 -1
View File
@@ -5,7 +5,11 @@ ROOT_DIR=$(dirname $(readlink -f "$0"))/..
. ${ROOT_DIR}/.env
echo "Installing Composer packages..."
(cd ${ROOT_DIR} && composer install --no-dev)
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
(cd ${ROOT_DIR} && composer install --no-dev)
else
(cd ${ROOT_DIR} && composer install --dev)
fi
echo "Installing Yarn packages..."
(cd ${ROOT_DIR}/public/static && yarn install)
@@ -16,4 +20,7 @@ echo "Migrating DB..."
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
echo "Minifying JS, CSS and SVG files..."
${ROOT_DIR}/scripts/minify.sh
echo "Linking view files..."
(cd ${ROOT_DIR} && ./mapg view:link)
fi
+69
View File
@@ -0,0 +1,69 @@
<?php namespace MapGuesser\Cli;
use FilesystemIterator;
use MapGuesser\View\Linker;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
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 LinkViewCommand extends Command
{
public function configure()
{
$this->setName('view:link')
->setDescription('Linking of views.')
->addArgument('view', InputArgument::OPTIONAL, 'View file to be linked.');
}
public function execute(InputInterface $input, OutputInterface $output): int
{
$views = [];
$view = $input->getArgument('view');
if ($view !== null) {
$views[] = $view;
} else {
$folder = ROOT . '/views';
$folderLength = strlen($folder) + 1;
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
foreach ($iterator as $file) {
if ($file->isDir() || $file->getExtension() !== 'php') {
continue;
}
$view = substr($file->getPath(), $folderLength) . '/' . $file->getBasename('.php');
if (strpos($view, 'templates') === 0) {
continue;
}
$views[] = $view;
}
}
try {
foreach ($views as $view) {
$generator = new Linker($view);
$generator->generate();
}
} catch (\Exception $e) {
$output->writeln('<error>Linking view(s) failed!</error>');
$output->writeln('');
$output->writeln((string) $e);
$output->writeln('');
return 1;
}
$output->writeln('<info>View(s) successfully linked!</info>');
return 0;
}
}
+15 -14
View File
@@ -1,5 +1,6 @@
<?php namespace MapGuesser\Controller;
use MapGuesser\Http\Request;
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Interfaces\Response\IRedirect;
@@ -49,7 +50,7 @@ class LoginController
$this->request->session()->set('oauth_state', $state);
$oAuth = new GoogleOAuth();
$oAuth = new GoogleOAuth(new Request());
$url = $oAuth->getDialogUrl($state, $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
return new Redirect($url, IRedirect::TEMPORARY);
@@ -107,7 +108,7 @@ class LoginController
if ($user === null) {
if (strlen($this->request->post('password')) < 6) {
$data = ['error' => 'password_too_short'];
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
return new JsonContent($data);
}
@@ -116,17 +117,17 @@ class LoginController
$this->request->session()->set('tmp_user_data', ['email' => $this->request->post('email'), 'password_hashed' => $tmpUser->getPassword()]);
$data = ['error' => 'user_not_found'];
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('signup')->generateLink()]];
return new JsonContent($data);
}
if (!$user->getActive()) {
$data = ['error' => 'user_not_active'];
$data = ['error' => ['errorText' => 'User found with the given email address, but the account is not activated. Please check your email and click on the activation link!']];
return new JsonContent($data);
}
if (!$user->checkPassword($this->request->post('password'))) {
$data = ['error' => 'password_not_match'];
$data = ['error' => ['errorText' => 'The given password is wrong.']];
return new JsonContent($data);
}
@@ -147,7 +148,7 @@ class LoginController
return new HtmlContent('login/google_login', $data);
}
$oAuth = new GoogleOAuth();
$oAuth = new GoogleOAuth(new Request());
$tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('login-google-action')->generateLink());
if (!isset($tokenData['id_token'])) {
@@ -186,7 +187,7 @@ class LoginController
public function signup(): IContent
{
if ($this->request->user() !== null) {
$data = ['error' => 'logged_in'];
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('home')->generateLink()]];
return new JsonContent($data);
}
@@ -195,21 +196,21 @@ class LoginController
if ($user !== null) {
if ($user->getActive()) {
if (!$user->checkPassword($this->request->post('password'))) {
$data = ['error' => 'user_found_password_not_match'];
$data = ['error' => ['errorText' => 'There is a user already registered with the given email address, but the given password is wrong.']];
return new JsonContent($data);
}
$this->request->setUser($user);
$data = ['error' => 'user_found'];
$data = ['redirect' => ['target' => '/' . \Container::$routeCollection->getRoute('index')->generateLink()]];
} else {
$data = ['error' => 'user_found_user_not_active'];
$data = ['error' => ['errorText' => 'There is a user already registered with the given email address. Please check your email and click on the activation link!']];
}
return new JsonContent($data);
}
if (filter_var($this->request->post('email'), FILTER_VALIDATE_EMAIL) === false) {
$data = ['error' => 'email_not_valid'];
$data = ['error' => ['errorText' => 'The given email address is not valid.']];
return new JsonContent($data);
}
@@ -220,17 +221,17 @@ class LoginController
$tmpUser->setPassword($tmpUserData['password_hashed']);
if (!$tmpUser->checkPassword($this->request->post('password'))) {
$data = ['error' => 'passwords_not_match'];
$data = ['error' => ['errorText' => 'The given passwords do not match.']];
return new JsonContent($data);
}
} else {
if (strlen($this->request->post('password')) < 6) {
$data = ['error' => 'password_too_short'];
$data = ['error' => ['errorText' => 'The given password is too short. Please choose a password that is at least 6 characters long!']];
return new JsonContent($data);
}
if ($this->request->post('password') !== $this->request->post('password_confirm')) {
$data = ['error' => 'passwords_not_match'];
$data = ['error' => ['errorText' => 'The given passwords do not match.']];
return new JsonContent($data);
}
}
+50 -6
View File
@@ -1,10 +1,14 @@
<?php namespace MapGuesser\Controller;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserConfirmation;
use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
@@ -14,10 +18,13 @@ class UserController implements ISecured
private PersistentDataManager $pdm;
private UserConfirmationRepository $userConfirmationRepository;
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
$this->userConfirmationRepository = new UserConfirmationRepository();
}
public function authorize(): bool
@@ -27,7 +34,7 @@ class UserController implements ISecured
return $user !== null;
}
public function getProfile(): IContent
public function getAccount(): IContent
{
/**
* @var User $user
@@ -35,10 +42,21 @@ class UserController implements ISecured
$user = $this->request->user();
$data = ['user' => $user->toArray()];
return new HtmlContent('profile', $data);
return new HtmlContent('account/account', $data);
}
public function saveProfile(): IContent
public function getDeleteAccount(): IContent
{
/**
* @var User $user
*/
$user = $this->request->user();
$data = ['user' => $user->toArray()];
return new HtmlContent('account/delete', $data);
}
public function saveAccount(): IContent
{
/**
* @var User $user
@@ -46,18 +64,18 @@ class UserController implements ISecured
$user = $this->request->user();
if (!$user->checkPassword($this->request->post('password'))) {
$data = ['error' => 'password_not_match'];
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
return new JsonContent($data);
}
if (strlen($this->request->post('password_new')) > 0) {
if (strlen($this->request->post('password_new')) < 6) {
$data = ['error' => 'password_too_short'];
$data = ['error' => ['errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!']];
return new JsonContent($data);
}
if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
$data = ['error' => 'passwords_not_match'];
$data = ['error' => ['errorText' => 'The given new passwords do not match.']];
return new JsonContent($data);
}
@@ -69,4 +87,30 @@ class UserController implements ISecured
$data = ['success' => true];
return new JsonContent($data);
}
public function deleteAccount(): IContent
{
/**
* @var User $user
*/
$user = $this->request->user();
if (!$user->checkPassword($this->request->post('password'))) {
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
return new JsonContent($data);
}
\Container::$dbConnection->startTransaction();
foreach ($this->userConfirmationRepository->getByUser($user) as $userConfirmation) {
$this->pdm->deleteFromDb($userConfirmation);
}
$this->pdm->deleteFromDb($user);
\Container::$dbConnection->commit();
$data = ['success' => true];
return new JsonContent($data);
}
}
+16 -7
View File
@@ -1,11 +1,10 @@
<?php namespace MapGuesser\Http;
class Request
use MapGuesser\Interfaces\Http\IRequest;
use MapGuesser\Interfaces\Http\IResponse;
class Request implements IRequest
{
const HTTP_GET = 0;
const HTTP_POST = 1;
private string $url;
private int $method;
@@ -14,12 +13,22 @@ class Request
private array $headers = [];
public function __construct(string $url, int $method = self::HTTP_GET)
public function __construct(string $url = '', int $method = self::HTTP_GET)
{
$this->url = $url;
$this->method = $method;
}
public function setUrl(string $url): void
{
$this->url = $url;
}
public function setMethod(int $method): void
{
$this->method = $method;
}
public function setQuery($query)
{
if (is_string($query)) {
@@ -34,7 +43,7 @@ class Request
$this->headers = array_merge($this->headers, $headers);
}
public function send(): Response
public function send(): IResponse
{
$ch = curl_init();
+3 -1
View File
@@ -1,6 +1,8 @@
<?php namespace MapGuesser\Http;
class Response
use MapGuesser\Interfaces\Http\IResponse;
class Response implements IResponse
{
private string $body;
+18
View File
@@ -0,0 +1,18 @@
<?php namespace MapGuesser\Interfaces\Http;
interface IRequest
{
const HTTP_GET = 0;
const HTTP_POST = 1;
public function setUrl(string $url): void;
public function setMethod(int $method): void;
public function setQuery($query);
public function setHeaders(array $headers);
public function send(): IResponse;
}
+8
View File
@@ -0,0 +1,8 @@
<?php namespace MapGuesser\Interfaces\Http;
interface IResponse
{
public function getBody();
public function getHeaders();
}
+1 -1
View File
@@ -4,7 +4,7 @@ interface IContent
{
public function &getData(): array;
public function &render(): string;
public function render(): void;
public function getContentType(): string;
}
+12 -4
View File
@@ -1,6 +1,6 @@
<?php namespace MapGuesser\OAuth;
use MapGuesser\Http\Request;
use MapGuesser\Interfaces\Http\IRequest;
class GoogleOAuth
{
@@ -8,6 +8,13 @@ class GoogleOAuth
private static $tokenUrlBase = 'https://oauth2.googleapis.com/token';
private IRequest $request;
public function __construct(IRequest $request)
{
$this->request = $request;
}
public function getDialogUrl(string $state, string $redirectUrl): string
{
$oauthParams = [
@@ -32,9 +39,10 @@ class GoogleOAuth
'grant_type' => 'authorization_code',
];
$request = new Request(self::$tokenUrlBase, Request::HTTP_POST);
$request->setQuery($tokenParams);
$response = $request->send();
$this->request->setUrl(self::$tokenUrlBase);
$this->request->setMethod(IRequest::HTTP_POST);
$this->request->setQuery($tokenParams);
$response = $this->request->send();
return json_decode($response->getBody(), true);
}
+42 -23
View File
@@ -1,5 +1,6 @@
<?php namespace MapGuesser\PersistentData;
use Generator;
use MapGuesser\Database\Query\Modify;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet;
@@ -9,30 +10,8 @@ class PersistentDataManager
{
public function selectFromDb(Select $select, string $type, bool $withRelations = false): ?Model
{
$table = call_user_func([$type, 'getTable']);
$fields = call_user_func([$type, 'getFields']);
$select = $this->createSelect($select, $type, $withRelations);
$select->from($table);
//TODO: only with some relations?
if ($withRelations) {
$relations = call_user_func([$type, 'getRelations']);
$columns = [];
foreach ($fields as $field) {
$columns[] = [$table, $field];
}
$columns = array_merge($columns, $this->getRelationColumns($relations));
$this->leftJoinRelations($select, $table, $relations);
$select->columns($columns);
} else {
$select->columns($fields);
}
//TODO: return with array?
$data = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
if ($data === null) {
@@ -45,6 +24,18 @@ class PersistentDataManager
return $model;
}
public function selectMultipleFromDb(Select $select, string $type, bool $withRelations = false): Generator
{
$select = $this->createSelect($select, $type, $withRelations);
while ($data = $select->execute()->fetch(IResultSet::FETCH_ASSOC)) {
$model = new $type();
$this->fillWithData($data, $model);
yield $model;
}
}
public function selectFromDbById($id, string $type, bool $withRelations = false): ?Model
{
$select = new Select(\Container::$dbConnection);
@@ -136,6 +127,34 @@ class PersistentDataManager
$model->resetSnapshot();
}
private function createSelect(Select $select, string $type, bool $withRelations = false): Select
{
$table = call_user_func([$type, 'getTable']);
$fields = call_user_func([$type, 'getFields']);
$select->from($table);
//TODO: only with some relations?
if ($withRelations) {
$relations = call_user_func([$type, 'getRelations']);
$columns = [];
foreach ($fields as $field) {
$columns[] = [$table, $field];
}
$columns = array_merge($columns, $this->getRelationColumns($relations));
$this->leftJoinRelations($select, $table, $relations);
$select->columns($columns);
} else {
$select->columns($fields);
}
return $select;
}
private function getRelationColumns(array $relations): array
{
$columns = [];
@@ -1,6 +1,9 @@
<?php namespace MapGuesser\Repository;
use Generator;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserConfirmation;
use MapGuesser\PersistentData\PersistentDataManager;
@@ -25,4 +28,12 @@ class UserConfirmationRepository
return $this->pdm->selectFromDb($select, UserConfirmation::class);
}
public function getByUser(User $user): Generator
{
$select = new Select(\Container::$dbConnection);
$select->where('user_id', '=', $user->getId());
yield from $this->pdm->selectMultipleFromDb($select, UserConfirmation::class);
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ abstract class ContentBase implements IContent
return $this->data;
}
abstract public function &render(): string;
abstract public function render(): void;
abstract public function getContentType(): string;
}
+13 -9
View File
@@ -1,25 +1,29 @@
<?php namespace MapGuesser\Response;
use MapGuesser\View\Linker;
class HtmlContent extends ContentBase
{
private string $template;
private string $view;
public function __construct(string $template, array &$data = [])
public function __construct(string $view, array &$data = [])
{
$this->template = $template;
$this->view = $view;
$this->data = &$data;
}
public function &render(): string
public function render(): void
{
if (!empty($_ENV['DEV'])) {
$generator = new Linker($this->view);
$generator->generate();
}
extract($this->data);
ob_start();
require ROOT . '/views/' . $this->template . '.php';
$content = ob_get_contents();
ob_end_clean();
require ROOT . '/cache/views/' . $this->view . '.php';
return $content;
echo '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->';
}
public function getContentType(): string
+3 -3
View File
@@ -7,11 +7,11 @@ class JsonContent extends ContentBase
$this->data = &$data;
}
public function &render(): string
public function render(): void
{
$content = json_encode($this->data);
$this->data['__debug__runtime'] = round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1);
return $content;
echo json_encode($this->data);
}
public function getContentType(): string
+155
View File
@@ -0,0 +1,155 @@
<?php namespace MapGuesser\View;
class Linker
{
const INLINE_ASSET_LIMIT = 2000;
private string $view;
public function __construct(string $view)
{
$this->view = $view;
}
public function generate(): void
{
$input = ROOT . '/views/' . $this->view . '.php';
$temporaryFiles = [];
$sections = ['externalCss' => '', 'inlineCss' => '', 'externalJs' => '', 'inlineJs' => ''];
$extra = ['', ''];
do {
$parser = new Parser($input);
$fragment = $parser->parse();
$extends = $fragment->getExtends();
$this->generateAssets($fragment, $sections);
$sections = array_merge($sections, $fragment->getSections()); //TODO: detect if section defined multiple times
$extra[0] = $fragment->getExtra()[0] . $extra[0];
$extra[1] = $extra[1] . $fragment->getExtra()[1];
if ($extends === null) {
$this->writeFinal($extra, $input, ROOT . '/cache/views/' . $this->view . '.php');
break;
}
$tmpFile = tempnam(sys_get_temp_dir(), 'mapg-view-');
$temporaryFiles[] = $tmpFile;
$this->extendTemplate($sections, ROOT . '/views/' . $extends . '.php', $tmpFile);
$input = $tmpFile;
} while (true);
foreach ($temporaryFiles as $tmpFile) {
unlink($tmpFile);
}
}
private function extendTemplate(array $sections, string $file, string $output): void
{
$inputFileHandle = fopen($file, 'r');
if (!$inputFileHandle) {
throw new \Exception('Cannot open file ' . $file);
}
$outputFileHandle = fopen($output, 'w');
if (!$outputFileHandle) {
throw new \Exception('Cannot open file ' . $output . 'for writing.');
}
$lineNumber = 0;
while (($line = fgets($inputFileHandle)) !== false) {
++$lineNumber;
if (preg_match('/^\s*@yields\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
if (isset($sections[$matches[1]])) {
fwrite($outputFileHandle, $sections[$matches[1]]);
}
} else {
fwrite($outputFileHandle, $line);
}
}
fclose($inputFileHandle);
fclose($outputFileHandle);
}
private function writeFinal(array $extra, string $file, string $output): void
{
$dirname = pathinfo($output, PATHINFO_DIRNAME);
if (!is_dir($dirname)) {
mkdir($dirname, 0755, true);
}
$inputFileHandle = fopen($file, 'r');
if (!$inputFileHandle) {
throw new \Exception('Cannot open file ' . $file);
}
$outputFileHandle = fopen($output, 'w');
if (!$outputFileHandle) {
throw new \Exception('Cannot open file ' . $output . 'for writing.');
}
fwrite($outputFileHandle, $extra[0]);
while (($line = fgets($inputFileHandle)) !== false) {
fwrite($outputFileHandle, $line);
}
fwrite($outputFileHandle, $extra[1]);
fclose($inputFileHandle);
fclose($outputFileHandle);
}
private function generateAssets(ParsedFragment $fragment, array &$sections)
{
foreach ($fragment->getCss() as $cssFile) {
$asset = $this->parseAsset($cssFile, 'css');
if (isset($asset['code'])) {
$sections['inlineCss'] .= '<style>' . PHP_EOL;
$sections['inlineCss'] .= $asset['code'];
$sections['inlineCss'] .= '</style>' . PHP_EOL;
} elseif (isset($asset['file'])) {
$sections['externalCss'] .= '<link href="' . $asset['file'] . '" rel="stylesheet">' . PHP_EOL;
}
}
foreach ($fragment->getJs() as $jsFile) {
$asset = $this->parseAsset($jsFile, 'js');
if (isset($asset['code'])) {
$sections['inlineJs'] .= '<script>' . PHP_EOL;
$sections['inlineJs'] .= $asset['code'];
$sections['inlineJs'] .= '</script>' . PHP_EOL;
} elseif (isset($asset['file'])) {
$sections['externalJs'] .= '<script src="' . $asset['file'] . '"></script>' . PHP_EOL;
}
}
}
private function parseAsset(string $asset, string $type): array
{
$output = [];
eval('$asset = ' . $asset . ';');
if (
empty($_ENV['DEV']) &&
preg_match('/^' . $type . '\/.*/', $asset) &&
filesize(ROOT . '/public/static/' . $asset) < self::INLINE_ASSET_LIMIT
) {
$output['code'] = file_get_contents(ROOT . '/public/static/' . $asset);
} else {
if (!preg_match('/^http(s)?/', $asset)) {
$output['file'] = $_ENV['STATIC_ROOT'] . '/' . $asset . '?rev=<?= REVISION ?>';
} else {
$output['file'] = $asset;
}
}
return $output;
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php namespace MapGuesser\View;
class ParsedFragment
{
private ?string $extends;
private array $css;
private array $js;
private array $sections;
private array $extra;
public function __construct(?string $extends, array $css, array $js, array $sections, array $extra)
{
$this->extends = $extends;
$this->css = $css;
$this->js = $js;
$this->sections = $sections;
$this->extra = $extra;
}
public function getExtends(): ?string
{
return $this->extends;
}
public function getCss(): array
{
return $this->css;
}
public function getJs(): array
{
return $this->js;
}
public function getSections(): array
{
return $this->sections;
}
public function getExtra(): array
{
return $this->extra;
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php namespace MapGuesser\View;
class Parser
{
private string $file;
public function __construct(string $file)
{
$this->file = $file;
}
public function parse(): ParsedFragment
{
$sectionOpen = null;
$extraOpen = false;
$extends = null;
$js = [];
$css = [];
$sections = [];
$extra = ['', ''];
$fileHandle = fopen($this->file, 'r');
if (!$fileHandle) {
throw new \Exception('Cannot open file ' . $this->file);
}
$lineNumber = 0;
while (($line = fgets($fileHandle)) !== false) {
++$lineNumber;
if (($cssMatched = $this->matchCss($line)) !== null) {
$css[] = $cssMatched;
continue;
}
if (($jsMatched = $this->matchJs($line)) !== null) {
$js[] = $jsMatched;
continue;
}
if (($extendsMatched = $this->matchExtends($line)) !== null) {
if ($extends !== null) {
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - There is already an \'@extends\' declared.');
}
$extends = $extendsMatched;
continue;
}
if (($sectionMatched = $this->matchSection($line)) !== null) {
if ($extends === null) {
throw new \Exception('Error in file ' . $this->file . ' in line ' . $lineNumber . ' - \'@section\' has no meaning if view extends nothing.');
}
if ($sectionOpen !== null) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - A \'@section\' is already open (no \'@endsection\' found).');
}
$sectionOpen = $sectionMatched;
$sections[$sectionOpen] = '';
continue;
}
if ($this->matchEndSection($line)) {
if ($sectionOpen === null) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - Cannot end section until no \'@section\' is open.');
}
$sectionOpen = null;
}
if ($this->matchExtra($line)) {
if ($extraOpen) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - An \'@extra\' is already open (no \'@endextra\' found).');
}
$extraOpen = true;
continue;
}
if ($this->matchEndExtra($line)) {
if (!$extraOpen) {
throw new \Exception('Parse error in file ' . $this->file . ' in line ' . $lineNumber . ' - Cannot end extra until no \'@extra\' is open.');
}
$extraOpen = false;
}
if ($sectionOpen !== null) {
$sections[$sectionOpen] .= $line;
}
if ($extraOpen) {
$offset = $extends === null ? 0 : 1;
$extra[$offset] .= $line;
}
}
fclose($fileHandle);
return new ParsedFragment($extends, $css, $js, $sections, $extra);
}
private function matchCss(string $line): ?string
{
if (preg_match('/^\s*@css\((.*)\)\s*$/', $line, $matches)) {
return $matches[1];
}
return null;
}
private function matchJs(string $line): ?string
{
if (preg_match('/^\s*@js\((.*)\)\s*$/', $line, $matches)) {
return $matches[1];
}
return null;
}
private function matchExtends(string $line): ?string
{
if (preg_match('/^\s*@extends\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
return $matches[1];
}
return null;
}
private function matchSection(string $line): ?string
{
if (preg_match('/^\s*@section\(\'(\w+)\'\)\s*$/', $line, $matches)) {
return $matches[1];
}
return null;
}
private function matchEndSection(string $line): bool
{
return preg_match('/^\s*@endsection(?:\(\))?\s*$/', $line);
}
private function matchExtra(string $line): bool
{
return preg_match('/^\s*@extra(?:\(\))?\s*$/', $line);
}
private function matchEndExtra(string $line): bool
{
return preg_match('/^\s*@endextra(?:\(\))?\s*$/', $line);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace MapGuesser\Tests\OAuth;
use MapGuesser\Interfaces\Http\IRequest;
use MapGuesser\Interfaces\Http\IResponse;
use MapGuesser\OAuth\GoogleOAuth;
use PHPUnit\Framework\TestCase;
final class GoogleOAuthTest extends TestCase
{
public function testCanCreateDialogUrl(): void
{
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'xyz';
$state = 'random_state_string';
$redirectUrl = 'http://example.com/oauth';
$requestMock = $this->getMockBuilder(IRequest::class)
->setMethods(['setUrl', 'setMethod', 'setQuery', 'setHeaders', 'send'])
->getMock();
$googleOAuth = new GoogleOAuth($requestMock);
$dialogUrl = $googleOAuth->getDialogUrl($state, $redirectUrl);
$dialogUrlParsed = explode('?', $dialogUrl);
$this->assertEquals('https://accounts.google.com/o/oauth2/v2/auth', $dialogUrlParsed[0]);
parse_str($dialogUrlParsed[1], $dialogUrlQueryParams);
$expectedQueryParams = [
'response_type' => 'code',
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
'scope' => 'openid email',
'redirect_uri' => $redirectUrl,
'state' => $state,
'nonce' => hash('sha256', random_bytes(10) . microtime()),
];
$this->assertEquals($expectedQueryParams['response_type'], $dialogUrlQueryParams['response_type']);
$this->assertEquals($expectedQueryParams['client_id'], $dialogUrlQueryParams['client_id']);
$this->assertEquals($expectedQueryParams['scope'], $dialogUrlQueryParams['scope']);
$this->assertEquals($expectedQueryParams['redirect_uri'], $dialogUrlQueryParams['redirect_uri']);
$this->assertEquals($expectedQueryParams['state'], $dialogUrlQueryParams['state']);
$this->assertMatchesRegularExpression('/^[a-f0-9]{64}$/', $dialogUrlQueryParams['nonce']);
}
public function testCanRequestToken(): void
{
$_ENV['GOOGLE_OAUTH_CLIENT_ID'] = 'abc';
$_ENV['GOOGLE_OAUTH_CLIENT_SECRET'] = 'xxx';
$code = 'code_from_google';
$redirectUrl = 'http://example.com/oauth';
$requestMock = $this->getMockBuilder(IRequest::class)
->setMethods(['setUrl', 'setMethod', 'setQuery', 'setHeaders', 'send'])
->getMock();
$responseMock = $this->getMockBuilder(IResponse::class)
->setMethods(['getBody', 'getHeaders'])
->getMock();
$googleOAuth = new GoogleOAuth($requestMock);
$expectedQueryParams = [
'code' => $code,
'client_id' => $_ENV['GOOGLE_OAUTH_CLIENT_ID'],
'client_secret' => $_ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
'redirect_uri' => $redirectUrl,
'grant_type' => 'authorization_code',
];
$requestMock->expects($this->once())
->method('setUrl')
->with($this->equalTo('https://oauth2.googleapis.com/token'));
$requestMock->expects($this->once())
->method('setMethod')
->with($this->equalTo(IRequest::HTTP_POST));
$requestMock->expects($this->once())
->method('setQuery')
->with($this->equalTo($expectedQueryParams));
$requestMock->expects($this->once())
->method('send')
->will($this->returnValue($responseMock));
$responseMock->expects($this->once())
->method('getBody')
->will($this->returnValue('{"test":"json"}'));
$token = $googleOAuth->getToken($code, $redirectUrl);
$this->assertEquals(['test' => 'json'], $token);
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php namespace MapGuesser\Tests\Util;
use MapGuesser\PersistentData\Model\Model;
use PHPUnit\Framework\TestCase;
class DummyModel extends Model
{
protected static string $table = 'test_table';
protected static array $fields = ['name', 'valid'];
protected static array $relations = ['other_model' => OtherModel::class];
private string $name;
private bool $valid;
public function setName(string $name): void
{
$this->name = $name;
}
public function setValid(bool $valid): void
{
$this->valid = $valid;
}
public function getName(): string
{
return $this->name;
}
public function getValid(): bool
{
return $this->valid;
}
}
final class ModelTest extends TestCase
{
public function testCanReturnTable(): void
{
$this->assertEquals('test_table', DummyModel::getTable());
}
public function testCanReturnFields(): void
{
$this->assertEquals(['id', 'name', 'valid'], DummyModel::getFields());
}
public function testCanReturnRelations(): void
{
$this->assertEquals(['other_model' => OtherModel::class], DummyModel::getRelations());
}
public function testCanBeConvertedToArray(): void
{
$model = new DummyModel();
$model->setId(123);
$model->setName('John');
$model->setValid(true);
$this->assertEquals([
'id' => 123,
'name' => 'John',
'valid' => true
], $model->toArray());
}
public function testCanSaveAndResetSnapshot(): void
{
$model = new DummyModel();
$model->setId(123);
$model->setName('John');
$model->setValid(true);
$model->saveSnapshot();
$this->assertEquals([
'id' => 123,
'name' => 'John',
'valid' => true
], $model->getSnapshot());
$model->resetSnapshot();
$this->assertEquals([], $model->getSnapshot());
}
}
+51
View File
@@ -0,0 +1,51 @@
<?php namespace MapGuesser\Tests\Util;
use MapGuesser\Util\JwtParser;
use PHPUnit\Framework\TestCase;
final class JwtParserTest extends TestCase
{
private JwtParser $jwtParser;
protected function setUp(): void
{
$this->jwtParser = new JwtParser(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
);
}
public function testSettingTokenIsTheSameAsCreatingWithToken(): void
{
$jwtParser2 = new JwtParser();
$jwtParser2->setToken(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
);
$this->assertEquals($this->jwtParser, $jwtParser2);
}
public function testCanParseTokenHeader(): void
{
$this->assertEquals([
'alg' => 'HS256',
'typ' => 'JWT'
], $this->jwtParser->getHeader());
}
public function testCanParseTokenPayload(): void
{
$this->assertEquals([
'sub' => '1234567890',
'name' => 'John Doe',
'iat' => 1516239022
], $this->jwtParser->getPayload());
}
public function testCanParseTokenSignature(): void
{
$this->assertEquals(
'49f94ac7044948c78a285d904f87f0a4c7897f7e8f3a4eb2255fda750b2cc397',
bin2hex($this->jwtParser->getSignature())
);
}
}
+13 -14
View File
@@ -1,24 +1,23 @@
<?php
$jsFiles = [
'js/profile.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<h2>Profile</h2>
@extends('templates/layout_normal')
@section('main')
<h2>Account</h2>
<div class="box">
<form id="profileForm" action="/profile" method="post">
<input class="big fullWidth" type="password" name="password" placeholder="Current password" autofocus>
<form id="accountForm" action="/account" method="post" data-observe-inputs="password_new,password_new_confirm">
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
<hr>
<?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" minlength="6">
<input class="big fullWidth marginTop" type="password" name="password_new_confirm" placeholder="New password confirmation" minlength="6">
<p id="profileFormError" class="formError justify marginTop"></p>
<p id="accountFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button type="submit" name="save" disabled>Save</button>
<button type="submit" name="submit" disabled>Save</button>
</div>
<hr>
<div class="center">
<a class="button red" href="/account/delete" title="Delete account">Delete account</a>
</div>
</form>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+15
View File
@@ -0,0 +1,15 @@
@extends('templates/layout_normal')
@section('main')
<h2>Delete account</h2>
<div class="box">
<form id="deleteAccountForm" action="/account/delete" method="post" data-redirect-on-success="/">
<p class="justify">Are you sure you want to delete your account? This cannot be undone!</p>
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
<p id="deleteAccountFormError" class="formError justify marginTop"></p>
<div class="right marginTop">
<button class="red" type="submit" name="submit">Delete account</button>
</div>
</form>
</div>
@endsection
+25 -30
View File
@@ -1,27 +1,17 @@
<?php
$cssFiles = [
'node_modules/leaflet/dist/leaflet.css',
'css/map_editor.css',
'node_modules/leaflet.markercluster/dist/MarkerCluster.css',
'node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css'
];
$jsFiles = [
'node_modules/leaflet/dist/leaflet.js',
'node_modules/leaflet.markercluster/dist/leaflet.markercluster.js',
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'],
'js/map_editor.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<header class="small">
<h1>
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
--><span><?= $_ENV['APP_NAME'] ?></span>
</a>
</h1>
<p class="header">
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
@css('node_modules/leaflet/dist/leaflet.css')
@css('css/map_editor.css')
@css('node_modules/leaflet.markercluster/dist/MarkerCluster.css')
@css('node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css')
@js('node_modules/leaflet/dist/leaflet.js')
@js('node_modules/leaflet.markercluster/dist/leaflet.markercluster.js')
@js('https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'])
@js('js/map_editor.js')
@extends('templates/layout_full')
@section('subheader')
<span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
--><span><!--
<?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">
@@ -46,11 +36,12 @@ $jsFiles = [
</svg><!--
--><span id="deleted" class="bold">0</span><!--
--></span>
</p>
</header>
@endsection
@section('pagemodal')
<div id="metadata" class="modal">
<h2>Edit map data</h2>
<form id="metadataForm" class="marginTop">
<form id="metadataForm" class="marginTop" data-no-submit="true">
<input class="fullWidth" type="text" name="name" value="<?= $mapName ?>" placeholder="Name of the map">
<textarea class="fullWidth marginTop" name="description" rows="4" placeholder="Description of the map"><?= $mapDescription ?></textarea>
<div class="right">
@@ -59,7 +50,9 @@ $jsFiles = [
</div>
</form>
</div>
<div class="full">
@endsection
@section('main')
<div id="map"></div>
<div id="panorama"></div>
<div id="noPano">
@@ -73,7 +66,9 @@ $jsFiles = [
<button id="closeButton" class="gray fullWidth marginTop">Close</button>
<button id="deleteButton" class="red fullWidth marginTop">Delete</button>
</div>
</div>
@endsection
@section('pageScript')
<script>
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
@@ -81,4 +76,4 @@ $jsFiles = [
var mapBounds = <?= json_encode($bounds) ?>;
var places = <?= json_encode($places, JSON_FORCE_OBJECT) ?>;
</script>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+4 -4
View File
@@ -1,6 +1,6 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@extends('templates/layout_normal')
@section('main')
<h2>404 | Page not found</h2>
<p>The requested URL was not found on this server. <a href="/" title="<?= $_ENV['APP_NAME'] ?>">Back to start.</a></p>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+15 -23
View File
@@ -1,27 +1,17 @@
<?php
$cssFiles = [
'css/game.css'
];
$jsFiles = [
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'],
'js/game.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<header class="small">
<h1>
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
--><span><?= $_ENV['APP_NAME'] ?></span>
</a>
</h1>
<p class="header">
@css('css/game.css')
@js('https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'])
@js('js/game.js')
@extends('templates/layout_full')
@section('subheader')
<span id="mapName" class="bold"><?= $mapName ?></span><!--
--><span>Round <span id="currentRound" class="bold"></span></span><!--
--><span>Score <span id="currentScoreSum" class="bold"></span></span>
</p>
</header>
<div class="full">
@endsection
@section('main')
<div id="panoCover"></div>
<div id="panorama"></div>
<div id="showGuessButtonContainer">
@@ -56,9 +46,11 @@ $jsFiles = [
<button id="startNewGameButton" class="fullWidth">Play this map again</button>
</div>
</div>
</div>
@endsection
@section('pageScript')
<script>
var mapId = <?= $mapId ?>;
var mapBounds = <?= json_encode($bounds) ?>;
</script>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+4 -4
View File
@@ -1,8 +1,8 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@extends('templates/layout_normal')
@section('main')
<h2>Account activation</h2>
<div class="box">
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+4 -4
View File
@@ -1,5 +1,6 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@extends('templates/layout_normal')
@section('main')
<h2>Account cancellation</h2>
<div class="box">
<?php if ($success) : ?>
@@ -8,5 +9,4 @@
<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>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+4 -4
View File
@@ -1,8 +1,8 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@extends('templates/layout_normal')
@section('main')
<h2>Login up with Google</h2>
<div class="box">
<p class="error justify">Authenticating with Google failed. Please <a href="/login/google" title="Login with Google">retry</a>!</p>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+7 -28
View File
@@ -1,8 +1,11 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@js('js/login/google_signup.js')
@extends('templates/layout_normal')
@section('main')
<h2>Sign up</h2>
<div class="box">
<form id="googleSignupForm" action="/signup/google" method="post">
<form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/">
<?php if ($found): ?>
<p class="justify">Please confirm that you link your account to your Google account.</p>
<?php else: ?>
@@ -21,28 +24,4 @@
</div>
</form>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<script>
(function () {
var form = document.getElementById('googleSignupForm');
form.onsubmit = function (e) {
document.getElementById('loading').style.visibility = 'visible';
e.preventDefault();
MapGuesser.httpRequest('POST', form.action, function () {
window.location.replace('/');
});
};
document.getElementById('cancelGoogleSignupButton').onclick = function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/signup/google/reset', function () {
window.location.replace('/signup');
});
};
})();
</script>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+5 -10
View File
@@ -1,13 +1,9 @@
<?php
$jsFiles = [
'js/login.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@extends('templates/layout_normal')
@section('main')
<h2>Login</h2>
<div class="box">
<form id="loginForm" action="/login" method="post">
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
<input class="big fullWidth" type="email" name="email" placeholder="Email address" required autofocus>
<input class="big fullWidth marginTop" type="password" name="password" placeholder="Password" required minlength="6">
<p id="loginFormError" class="formError justify marginTop"></p>
@@ -20,5 +16,4 @@ $jsFiles = [
</div>
</form>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+7 -10
View File
@@ -1,13 +1,11 @@
<?php
$jsFiles = [
'js/signup.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@js('js/login/signup.js')
@extends('templates/layout_normal')
@section('main')
<h2>Sign up</h2>
<div class="box">
<form id="signupForm" action="/signup" method="post">
<form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success">
<?php if (isset($email)): ?>
<p class="justify">No user found with the given email address. Sign up with one click!</p>
<input class="big fullWidth marginTop" type="email" name="email" placeholder="Email address" value="<?= $email ?>" required>
@@ -30,5 +28,4 @@ $jsFiles = [
</div>
</form>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+4 -4
View File
@@ -1,8 +1,8 @@
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@extends('templates/layout_normal')
@section('main')
<h2>Sign up</h2>
<div class="box">
<p class="justify">Sign up was successful. Please check your email and click on the activation link to activate your account!</p>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
+9 -127
View File
@@ -1,10 +1,11 @@
<?php
$cssFiles = [
'css/maps.css'
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
@css('css/maps.css')
@js('js/maps.js')
TODO: condition!
@js('js/maps_admin.js')
@extends('templates/layout_normal')
@section('main')
<div id="mapContainer">
<?php foreach ($maps as $map): ?>
<div class="mapItem">
@@ -60,123 +61,4 @@ $cssFiles = [
<?php endfor; ?>
<?php endif; ?>
</div>
<?php require ROOT . '/views/templates/footer.php'; ?>
<script>
(function () {
const GOOGLE_MAPS_JS_API_KEY = '<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>';
var Maps = {
descriptionDivs: null,
addStaticMaps: function () {
var imgContainers = document.getElementById('mapContainer').getElementsByClassName('imgContainer');
for (var i = 0; i < imgContainers.length; i++) {
var imgContainer = imgContainers[i];
var imgSrc = 'https://maps.googleapis.com/maps/api/staticmap?size=350x175&' +
'scale=' + (window.devicePixelRatio >= 2 ? 2 : 1) + '&' +
'visible=' + imgContainer.dataset.boundSouthLat + ',' + imgContainer.dataset.boundWestLng + '|' +
imgContainer.dataset.boundNorthLat + ',' + imgContainer.dataset.boundEastLng +
'&key=' + GOOGLE_MAPS_JS_API_KEY;
imgContainer.style.backgroundImage = 'url("' + imgSrc + '")';
}
},
initializeDescriptionDivs: function () {
Maps.descriptionDivs = document.getElementById('mapContainer').getElementsByClassName('description');
for (var i = 0; i < Maps.descriptionDivs.length; i++) {
var description = Maps.descriptionDivs[i];
var boundingClientRect = description.getBoundingClientRect();
description.defaultHeight = boundingClientRect.height;
}
},
calculateDescriptionDivHeights: function () {
var currentY;
var rows = [];
for (var i = 0; i < Maps.descriptionDivs.length; i++) {
var description = Maps.descriptionDivs[i];
var boundingClientRect = description.getBoundingClientRect();
if (currentY !== boundingClientRect.y) {
rows.push([]);
}
rows[rows.length - 1].push(description);
currentY = boundingClientRect.y;
}
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var maxHeight = 0;
for (var j = 0; j < row.length; j++) {
var description = row[j];
if (description.defaultHeight > maxHeight) {
maxHeight = description.defaultHeight;
}
}
for (var j = 0; j < row.length; j++) {
var description = row[j];
description.style.height = maxHeight + 'px';
}
}
}
};
Maps.addStaticMaps();
Maps.initializeDescriptionDivs();
Maps.calculateDescriptionDivHeights();
window.onresize = function () {
Maps.calculateDescriptionDivHeights();
};
})();
</script>
<?php if ($isAdmin): ?>
<script>
(function () {
Maps = {
deleteMap: function(mapId, mapName) {
MapGuesser.showModalWithContent('Delete map', 'Are you sure you want to delete map \'' + mapName + '\'?', [{
type: 'button',
classNames: ['red'],
text: 'Delete',
onclick: function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/admin/deleteMap/' + mapId, function () {
if (this.response.error) {
document.getElementById('loading').style.visibility = 'hidden';
//TODO: handle this error
return;
}
window.location.reload();
});
}
}]);
}
};
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
button.onclick = function() {
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
};
}
})();
</script>
<?php endif; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?>
@endsection
-5
View File
@@ -1,5 +0,0 @@
</main>
<footer>
<p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!--
--><p>&copy; Pőcze Bence <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
</footer>
+18
View File
@@ -0,0 +1,18 @@
@extends('templates/mapguesser')
@section('content')
<header class="small">
<h1>
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
--><span><?= $_ENV['APP_NAME'] ?></span>
</a>
</h1>
<p class="header">
@yields('subheader')
</p>
</header>
<main class="full">
@yields('main')
</main>
@endsection
@@ -1,3 +1,5 @@
@extends('templates/mapguesser')
@section('content')
<header>
<h1>
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
@@ -7,7 +9,7 @@
</h1>
<p>
<?php if (Container::$request->user()) : ?>
<span><a href="/profile" title="Profile">
<span><a href="/account" title="Account">
<?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"/>
@@ -21,4 +23,11 @@
<?php endif; ?>
</p>
</header>
<main>
<main>
@yields('main')
</main>
<footer>
<p><span class="bold"><?= $_ENV['APP_NAME'] ?></span> <?= str_replace('Release_', '', VERSION) ?></p><!--
--><p>&copy; Pőcze Bence <?= (new DateTime(REVISION_DATE))->format('Y') ?></p>
</footer>
@endsection
-49
View File
@@ -1,49 +0,0 @@
<script>
const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
const REVISION = '<?= REVISION ?>';
var ANTI_CSRF_TOKEN = '<?= \Container::$request->session()->get('anti_csrf_token') ?>';
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
const GOOGLE_ANALITICS_ID = '<?= $_ENV['GOOGLE_ANALITICS_ID'] ?>';
<?php endif; ?>
</script>
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
<?php if (isset($jsFiles)) : ?>
<?php foreach ($jsFiles as $jsFile) : ?>
<?php
if (!preg_match('/^http(s)?/', $jsFile)) {
$jsFile = $_ENV['STATIC_ROOT'] . '/' . $jsFile . '?rev=' . REVISION;
}
?>
<script src="<?= $jsFile ?>"></script>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
<script>
(function () {
// we don't want user to agree cookies when clicking on the notice itself
document.getElementById('cookiesNotice').onclick = function (e) {
e.stopPropagation();
};
document.getElementById('agreeCookiesButton').onclick = function () {
MapGuesser.agreeCookies();
document.getElementById('cookiesNotice').style.display = 'none';
};
window.onclick = function () {
MapGuesser.agreeCookies();
};
})();
</script>
<?php else: ?>
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
<script>
(function () {
MapGuesser.initGoogleAnalitics();
})();
</script>
<?php endif; ?>
<?php endif; ?>
</body>
</html>
-41
View File
@@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= $_ENV['APP_NAME'] ?></title>
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
<?php if (isset($cssFiles)) : ?>
<?php foreach ($cssFiles as $cssFile) : ?>
<?php
if (!preg_match('/^http(s)?/', $cssFile)) {
$cssFile = $_ENV['STATIC_ROOT'] . '/' . $cssFile . '?rev=' . REVISION;
}
?>
<link href="<?= $cssFile ?>" rel="stylesheet">
<?php endforeach; ?>
<?php endif; ?>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/96x96.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
</head>
<body>
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
<div id="cookiesNotice">
<p class="small">
<?= $_ENV['APP_NAME'] ?> uses cookies to improve user experience. By using the app or clicking 'Agree', you consent to our use of cookies.
</p>
<button id="agreeCookiesButton" class="small marginTop">Agree</button>
</div>
<?php endif; ?>
<div id="loading">
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
</div>
<div id="cover"></div>
<div id="modal" class="modal">
<h2 id="modalTitle"></h2>
<p id="modalText" class="justify marginTop"></p>
<div id="modalButtons" class="right"></div>
</div>
+79
View File
@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#198231">
<title><?= $_ENV['APP_NAME'] ?></title>
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
@yields('externalCss')
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;500&display=swap" rel="stylesheet">
@yields('inlineCss')
<link rel="icon" type="image/png" sizes="192x192" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/192x192.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="96x96" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/96x96.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
</head>
<body>
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
<div id="cookiesNotice">
<p class="small">
<?= $_ENV['APP_NAME'] ?> uses cookies to improve user experience. By using the app or clicking 'Agree', you consent to our use of cookies.
</p>
<button id="agreeCookiesButton" class="small marginTop">Agree</button>
</div>
<?php endif; ?>
<div id="loading">
<img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
</div>
<div id="cover"></div>
<div id="modal" class="modal">
<h2 id="modalTitle"></h2>
<p id="modalText" class="justify marginTop"></p>
<div id="modalButtons" class="right"></div>
</div>
@yields('pagemodal')
@yields('content')
<script>
const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
const REVISION = '<?= REVISION ?>';
var ANTI_CSRF_TOKEN = '<?= \Container::$request->session()->get('anti_csrf_token') ?>';
const GOOGLE_MAPS_JS_API_KEY = '<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>';
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
const GOOGLE_ANALITICS_ID = '<?= $_ENV['GOOGLE_ANALITICS_ID'] ?>';
<?php endif; ?>
</script>
@yields('pageScript')
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
@yields('externalJs')
@yields('inlineJs')
<?php if (!isset($_COOKIE['COOKIES_CONSENT'])): ?>
<script>
(function () {
// we don't want user to agree cookies when clicking on the notice itself
document.getElementById('cookiesNotice').onclick = function (e) {
e.stopPropagation();
};
document.getElementById('agreeCookiesButton').onclick = function () {
MapGuesser.agreeCookies();
document.getElementById('cookiesNotice').style.display = 'none';
};
window.onclick = function () {
MapGuesser.agreeCookies();
};
})();
</script>
<?php else: ?>
<?php if (!empty($_ENV['GOOGLE_ANALITICS_ID'])): ?>
<script>
(function () {
MapGuesser.initGoogleAnalitics();
})();
</script>
<?php endif; ?>
<?php endif; ?>
</body>
</html>
+5 -3
View File
@@ -32,9 +32,11 @@ Container::$routeCollection->group('signup', function (MapGuesser\Routing\RouteC
$routeCollection->get('signup.cancel', 'cancel/{token}', [MapGuesser\Controller\LoginController::class, 'cancel']);
});
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
Container::$routeCollection->group('profile', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('profile', '', [MapGuesser\Controller\UserController::class, 'getProfile']);
$routeCollection->post('profile-action', '', [MapGuesser\Controller\UserController::class, 'saveProfile']);
Container::$routeCollection->group('account', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('account', '', [MapGuesser\Controller\UserController::class, 'getAccount']);
$routeCollection->post('account-action', '', [MapGuesser\Controller\UserController::class, 'saveAccount']);
$routeCollection->get('account.delete', 'delete', [MapGuesser\Controller\UserController::class, 'getDeleteAccount']);
$routeCollection->post('account.delete-action', 'delete', [MapGuesser\Controller\UserController::class, 'deleteAccount']);
});
//Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {