Compare commits

..
Author SHA1 Message Date
bence a73e60ae5a Merged in bugfix/MAPG-189-fix-inputwithbuttons-input-padding (pull request #168)
MAPG-189 fix div.inputWithButton>input's padding when it is focused
2020-07-03 21:51:38 +00:00
bence 5383da81b3 MAPG-189 fix div.inputWithButton>input's padding when it is focused 2020-07-03 23:50:22 +02:00
bence e8adc9f730 Merged in feature/MAPG-177-create-unit-tests-for-all-units (pull request #167)
MAPG-177 add tests for View\Parser
2020-06-28 21:13:32 +00:00
bence 149003250c MAPG-177 add tests for View\Parser 2020-06-28 22:55:15 +02:00
bence 171b4278a6 Merged in feature/MAPG-175-unify-styles (pull request #166)
MAPG-175 make map title one liner
2020-06-28 18:16:23 +00:00
bence 091018d22f MAPG-175 make map title one liner 2020-06-28 20:15:11 +02:00
bence 34f0c92eb5 Merged in bugfix/MAPG-187-dont-evaluate-asset-paths (pull request #165)
Bugfix/MAPG-187 dont evaluate asset paths
2020-06-28 17:38:03 +00:00
bence 8ac5d5c6e2 MAPG-187 adapt views to the new parser 2020-06-28 19:35:14 +02:00
bence e883d184b7 MAPG-187 refactor view parsing and linking 2020-06-28 19:35:14 +02:00
bence 71b1e28aa9 Merged in feature/MAPG-188-pagespeed-improvements (pull request #164)
MAPG-188 get rid of inline js
2020-06-28 16:13:16 +00:00
bence 3555be9b63 MAPG-188 get rid of inline js 2020-06-28 17:56:47 +02:00
bence d03a3934c6 Merged in feature/MAPG-181-handle-the-case-when-user-has-no-password (pull request #163)
Feature/MAPG-181 handle the case when user has no password
2020-06-28 08:42:10 +00:00
bence bd5b49b09b MAPG-181 set user active when added from CLI 2020-06-28 03:19:07 +02:00
bence 4333240acc MAPG-181 add stuff to authenticate without password 2020-06-28 03:18:51 +02:00
bence 4099f1d962 MAPG-181 add new layout type (minimal) 2020-06-28 03:16:48 +02:00
bence 2fac828098 MAPG-181 text fixes 2020-06-28 03:16:28 +02:00
bence f3225ae275 MAPG-181 ability to add login_hint to dialog url 2020-06-28 03:15:20 +02:00
bence 48eba75cf4 MAPG-181 fix and unify styles for buttons and inputs 2020-06-28 03:12:16 +02:00
bence a97a6f8977 Merged in bugfix/MAPG-180-dont-echo-void (pull request #162)
MAPG-180 don't echo void
2020-06-27 12:26:57 +00:00
bence 2a3f891aa0 MAPG-180 don't echo void 2020-06-27 14:26:19 +02:00
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 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
52 changed files with 1161 additions and 226 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+1 -1
View File
@@ -21,7 +21,7 @@ RUN ./install-composer.sh
# Install Node.js and required packages # Install Node.js and required packages
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN apt install -y nodejs 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 EXPOSE 80
VOLUME /var/www/mapguesser VOLUME /var/www/mapguesser
+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\DatabaseMigration());
$app->add(new MapGuesser\Cli\AddUserCommand()); $app->add(new MapGuesser\Cli\AddUserCommand());
$app->add(new MapGuesser\Cli\LinkViewCommand());
$app->run(); $app->run();
+3 -2
View File
@@ -36,7 +36,7 @@ if ($match !== null) {
if ($response instanceof MapGuesser\Interfaces\Response\IContent) { if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8'); header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
echo $response->render(); $response->render();
return; return;
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) { } elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
@@ -47,5 +47,6 @@ if ($match !== null) {
} }
} }
$content = new MapGuesser\Response\HtmlContent('error/404');
header('Content-Type: text/html; charset=UTF-8', true, 404); header('Content-Type: text/html; charset=UTF-8', true, 404);
require ROOT . '/views/error/404.php'; $content->render();
+66 -12
View File
@@ -17,6 +17,11 @@ button::-moz-focus-inner, input::-moz-focus-inner {
border: 0; border: 0;
} }
/* to be compatible with browsers that don't know <main> */
main {
display: block;
}
::selection { ::selection {
background-color: #28a745; background-color: #28a745;
color: #ffffff; color: #ffffff;
@@ -148,13 +153,27 @@ button, a.button {
line-height: 35px; line-height: 35px;
} }
button.small { button.small, div.inputWithButton>button {
font-size: 14px; font-size: 14px;
padding: 0 12px; padding: 0 12px;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
} }
button.small {
height: 32px;
line-height: 32px;
}
div.inputWithButton>button {
border-radius: 2px;
height: 27px;
line-height: 27px;
width: 75px;
margin-left: -79px;
vertical-align: 2px;
}
button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus { button:enabled:hover, button:enabled:focus, a.button:hover, a.button:focus {
background-color: #29457f; background-color: #29457f;
outline: none; outline: none;
@@ -186,7 +205,7 @@ button.gray, a.button.gray {
background-color: #808080; background-color: #808080;
} }
button.gray:hover, button.gray:focus, a.button.gray:hover, a.button.gray:focus { button.gray:enabled:hover, button.gray:enabled:focus, a.button.gray:hover, a.button.gray:focus {
background-color: #555555; background-color: #555555;
} }
@@ -194,7 +213,7 @@ button.red, a.button.red {
background-color: #aa5e5e; background-color: #aa5e5e;
} }
button.red:hover, button.red:focus, a.button.red:hover, a.button.red:focus { button.red:enabled:hover, button.red:enabled:focus, a.button.red:hover, a.button.red:focus {
background-color: #7f2929; background-color: #7f2929;
} }
@@ -202,7 +221,7 @@ button.yellow, a.button.yellow {
background-color: #e8a349; background-color: #e8a349;
} }
button.yellow:hover, button.yellow:focus, a.button.yellow:hover, a.button.yellow:focus { button.yellow:enabled:hover, button.yellow:enabled:focus, a.button.yellow:hover, a.button.yellow:focus {
background-color: #c37713; background-color: #c37713;
} }
@@ -210,7 +229,7 @@ button.green, a.button.green {
background-color: #28a745; background-color: #28a745;
} }
button.green:hover, button.green:focus, a.button.green:hover, a.button.green:focus { button.green:enabled:hover, button.green:enabled:focus, a.button.green:hover, a.button.green:focus {
background-color: #1b7d31; background-color: #1b7d31;
} }
@@ -218,22 +237,36 @@ input, select, textarea {
background-color: #f9fafb; background-color: #f9fafb;
border: solid #c8d2e1 1px; border: solid #c8d2e1 1px;
border-radius: 2px; border-radius: 2px;
padding: 4px;
box-sizing: border-box; box-sizing: border-box;
font-size: 15px; font-size: 15px;
font-weight: 300; font-weight: 300;
} }
input, select {
height: 30px;
line-height: 30px;
padding: 0 5px;
}
textarea { textarea {
font-size: 13px; padding: 5px;
resize: none; resize: none;
} }
input.big, select.big, textarea.big { input.big, select.big, textarea.big, div.inputWithButton>input {
padding: 5px;
font-size: 18px; font-size: 18px;
} }
input.big, select.big, div.inputWithButton>input {
height: 35px;
line-height: 35px;
padding: 0 6px;
}
textarea.big {
padding: 6px;
}
input.fullWidth, select.fullWidth, textarea.fullWidth { input.fullWidth, select.fullWidth, textarea.fullWidth {
display: block; display: block;
width: 100%; width: 100%;
@@ -248,14 +281,34 @@ input:disabled, select:disabled, textarea:disabled {
input:focus, select:focus, textarea:focus { input:focus, select:focus, textarea:focus {
background-color: #ffffff; background-color: #ffffff;
border: solid #29457f 2px; border: solid #29457f 2px;
padding: 3px;
outline: none; outline: none;
} }
input.big:focus, select.big:focus, textarea.big:focus { input:focus, select:focus {
padding: 0 4px;
}
textarea:focus {
padding: 4px; padding: 4px;
} }
input.big:focus, select.big:focus {
padding: 0 5px;
}
div.inputWithButton>input {
width: 100%;
padding: 0 83px 0 6px;
}
div.inputWithButton>input:focus {
padding: 0 82px 0 5px;
}
textarea.big:focus {
padding: 5px;
}
div.modal { div.modal {
position: fixed; position: fixed;
background-color: #ffffff; background-color: #ffffff;
@@ -324,10 +377,11 @@ main {
padding: 6px 12px; padding: 6px 12px;
} }
div.full { main.full {
position: relative; position: relative;
width: 100%; width: 100%;
height: calc(100% - 40px); height: calc(100% - 40px);
padding: 0;
} }
footer { footer {
+10 -1
View File
@@ -18,7 +18,16 @@ div.mapItem>div.title {
color: white; color: white;
border-top-left-radius: 3px; border-top-left-radius: 3px;
border-top-right-radius: 3px; border-top-right-radius: 3px;
padding: 4px 8px; padding: 0 8px;
height: 35px;
line-height: 35px;
}
div.mapItem>div.title>p {
line-height: inherit;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
div.mapItem>div.title>p.title { div.mapItem>div.title>p.title {
+69
View File
@@ -0,0 +1,69 @@
var Account = {
original: null,
countdown: null,
openGoogleAuthenticate: function () {
window.open('/account/googleAuthenticate', 'googleAuthenticate', 'height=600,width=600')
},
authenticatedWithGoogleCallback: function (authenticatedWithGoogleUntil) {
var password = document.getElementsByTagName('form')[0].elements.password;
var button = document.getElementById('authenticateWithGoogleButton');
Account.original = {
type: password.type,
placeholder: password.placeholder,
required: password.required,
disabled: password.disabled
};
password.type = 'text';
password.placeholder = 'Authenticated!'
password.value = '';
password.required = false;
password.disabled = true;
button.disabled = true;
Account.countdown = setInterval(function () {
var timeLeft = Math.ceil((authenticatedWithGoogleUntil.getTime() - new Date().getTime()) / 1000);
if (timeLeft > 30) {
return;
}
if (timeLeft <= 0) {
Account.resetGoogleAuthentication();
return;
}
password.placeholder = 'Authenticated! ' + timeLeft + ' seconds left...';
}, 1000);
},
resetGoogleAuthentication: function () {
if (Account.countdown !== null) {
clearInterval(Account.countdown);
}
var password = document.getElementsByTagName('form')[0].elements.password;
var button = document.getElementById('authenticateWithGoogleButton');
password.type = Account.original.type;
password.placeholder = Account.original.placeholder
password.required = Account.original.required;
password.disabled = Account.original.disabled;
button.disabled = false;
}
};
(function () {
document.getElementById('authenticateWithGoogleButton').onclick = function () {
Account.openGoogleAuthenticate();
};
document.getElementsByTagName('form')[0].onreset = function () {
Account.resetGoogleAuthentication();
};
})();
@@ -0,0 +1,10 @@
(function () {
if (success) {
window.opener.Account.authenticatedWithGoogleCallback(authenticatedWithGoogleUntil);
window.close();
} else {
document.getElementById('closeWindowButton').onclick = function () {
window.close();
}
}
})();
+19
View File
@@ -244,4 +244,23 @@ var MapGuesser = {
document.getElementById('cover').onclick = function () { document.getElementById('cover').onclick = function () {
MapGuesser.hideModal(); MapGuesser.hideModal();
}; };
if (COOKIES_CONSENT) {
MapGuesser.initGoogleAnalitics();
} else {
// 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();
};
}
})(); })();
+3
View File
@@ -21,6 +21,9 @@ echo "Migrating DB..."
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
echo "Minifying JS, CSS and SVG files..." echo "Minifying JS, CSS and SVG files..."
${ROOT_DIR}/scripts/minify.sh ${ROOT_DIR}/scripts/minify.sh
echo "Linking view files..."
(cd ${ROOT_DIR} && ./mapg view:link)
fi fi
touch ${ROOT_DIR}/installed touch ${ROOT_DIR}/installed
+8 -1
View File
@@ -5,7 +5,11 @@ ROOT_DIR=$(dirname $(readlink -f "$0"))/..
. ${ROOT_DIR}/.env . ${ROOT_DIR}/.env
echo "Installing Composer packages..." 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..." echo "Installing Yarn packages..."
(cd ${ROOT_DIR}/public/static && yarn install) (cd ${ROOT_DIR}/public/static && yarn install)
@@ -16,4 +20,7 @@ echo "Migrating DB..."
if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then if [ -z "${DEV}" ] || [ "${DEV}" -eq "0" ]; then
echo "Minifying JS, CSS and SVG files..." echo "Minifying JS, CSS and SVG files..."
${ROOT_DIR}/scripts/minify.sh ${ROOT_DIR}/scripts/minify.sh
echo "Linking view files..."
(cd ${ROOT_DIR} && ./mapg view:link)
fi fi
+1
View File
@@ -23,6 +23,7 @@ class AddUserCommand extends Command
$user = new User(); $user = new User();
$user->setEmail($input->getArgument('email')); $user->setEmail($input->getArgument('email'));
$user->setPlainPassword($input->getArgument('password')); $user->setPlainPassword($input->getArgument('password'));
$user->setActive(true);
if ($input->hasArgument('type')) { if ($input->hasArgument('type')) {
$user->setType($input->getArgument('type')); $user->setType($input->getArgument('type'));
+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;
}
}
+100 -4
View File
@@ -1,16 +1,22 @@
<?php namespace MapGuesser\Controller; <?php namespace MapGuesser\Controller;
use DateTime;
use MapGuesser\Database\Query\Select; use MapGuesser\Database\Query\Select;
use MapGuesser\Http\Request;
use MapGuesser\Interfaces\Authorization\ISecured; use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Database\IResultSet; use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\Interfaces\Request\IRequest; use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent; use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\Interfaces\Response\IRedirect;
use MapGuesser\OAuth\GoogleOAuth;
use MapGuesser\PersistentData\PersistentDataManager; use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\PersistentData\Model\User; use MapGuesser\PersistentData\Model\User;
use MapGuesser\PersistentData\Model\UserConfirmation; use MapGuesser\PersistentData\Model\UserConfirmation;
use MapGuesser\Repository\UserConfirmationRepository; use MapGuesser\Repository\UserConfirmationRepository;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent; use MapGuesser\Response\JsonContent;
use MapGuesser\Response\Redirect;
use MapGuesser\Util\JwtParser;
class UserController implements ISecured class UserController implements ISecured
{ {
@@ -45,6 +51,63 @@ class UserController implements ISecured
return new HtmlContent('account/account', $data); return new HtmlContent('account/account', $data);
} }
public function getGoogleAuthenticateRedirect(): IRedirect
{
/**
* @var User $user
*/
$user = $this->request->user();
$state = bin2hex(random_bytes(16));
$this->request->session()->set('oauth_state', $state);
$oAuth = new GoogleOAuth(new Request());
$url = $oAuth->getDialogUrl(
$state,
$this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink(),
$user->getEmail()
);
return new Redirect($url, IRedirect::TEMPORARY);
}
public function authenticateWithGoogle(): IContent
{
/**
* @var User $user
*/
$user = $this->request->user();
if ($this->request->query('state') !== $this->request->session()->get('oauth_state')) {
$data = ['success' => false];
return new HtmlContent('account/google_authenticate', $data);
}
$oAuth = new GoogleOAuth(new Request());
$tokenData = $oAuth->getToken($this->request->query('code'), $this->request->getBase() . '/' . \Container::$routeCollection->getRoute('account.googleAuthenticate-action')->generateLink());
if (!isset($tokenData['id_token'])) {
$data = ['success' => false];
return new HtmlContent('account/google_authenticate', $data);
}
$jwtParser = new JwtParser($tokenData['id_token']);
$userData = $jwtParser->getPayload();
if ($userData['sub'] !== $user->getGoogleSub()) {
$data = ['success' => false, 'errorText' => 'This Google account is not linked to your account.'];
return new HtmlContent('account/google_authenticate', $data);
}
$authenticatedWithGoogleUntil = new DateTime('+45 seconds');
$this->request->session()->set('authenticated_with_google_until', $authenticatedWithGoogleUntil);
$data = ['success' => true, 'authenticatedWithGoogleUntil' => $authenticatedWithGoogleUntil];
return new HtmlContent('account/google_authenticate', $data);
}
public function getDeleteAccount(): IContent public function getDeleteAccount(): IContent
{ {
/** /**
@@ -63,8 +126,13 @@ class UserController implements ISecured
*/ */
$user = $this->request->user(); $user = $this->request->user();
if (!$user->checkPassword($this->request->post('password'))) { if (!$this->confirmUserIdentity(
$data = ['error' => ['errorText' => 'The given current password is wrong.']]; $user,
$this->request->session()->get('authenticated_with_google_until'),
$this->request->post('password'),
$error
)) {
$data = ['error' => ['errorText' => $error]];
return new JsonContent($data); return new JsonContent($data);
} }
@@ -84,6 +152,8 @@ class UserController implements ISecured
$this->pdm->saveToDb($user); $this->pdm->saveToDb($user);
$this->request->session()->delete('authenticated_with_google_until');
$data = ['success' => true]; $data = ['success' => true];
return new JsonContent($data); return new JsonContent($data);
} }
@@ -95,8 +165,13 @@ class UserController implements ISecured
*/ */
$user = $this->request->user(); $user = $this->request->user();
if (!$user->checkPassword($this->request->post('password'))) { if (!$this->confirmUserIdentity(
$data = ['error' => ['errorText' => 'The given current password is wrong.']]; $user,
$this->request->session()->get('authenticated_with_google_until'),
$this->request->post('password'),
$error
)) {
$data = ['error' => ['errorText' => $error]];
return new JsonContent($data); return new JsonContent($data);
} }
@@ -110,7 +185,28 @@ class UserController implements ISecured
\Container::$dbConnection->commit(); \Container::$dbConnection->commit();
$this->request->session()->delete('authenticated_with_google_until');
$data = ['success' => true]; $data = ['success' => true];
return new JsonContent($data); return new JsonContent($data);
} }
private function confirmUserIdentity(User $user, ?DateTime $authenticatedWithGoogleUntil, ?string $password, &$error): bool
{
if ($authenticatedWithGoogleUntil !== null && $authenticatedWithGoogleUntil > new DateTime()) {
return true;
}
if ($password !== null) {
if ($user->checkPassword($password)) {
return true;
}
$error = 'The given current password is wrong.';
return false;
}
$error = 'Could not confirm your identity. Please try again!';
return false;
}
} }
+1 -1
View File
@@ -4,7 +4,7 @@ interface IContent
{ {
public function &getData(): array; public function &getData(): array;
public function &render(): string; public function render(): void;
public function getContentType(): string; public function getContentType(): string;
} }
+5 -1
View File
@@ -15,7 +15,7 @@ class GoogleOAuth
$this->request = $request; $this->request = $request;
} }
public function getDialogUrl(string $state, string $redirectUrl): string public function getDialogUrl(string $state, string $redirectUrl, ?string $loginHint = null): string
{ {
$oauthParams = [ $oauthParams = [
'response_type' => 'code', 'response_type' => 'code',
@@ -26,6 +26,10 @@ class GoogleOAuth
'nonce' => hash('sha256', random_bytes(10) . microtime()), 'nonce' => hash('sha256', random_bytes(10) . microtime()),
]; ];
if ($loginHint !== null) {
$oauthParams['login_hint'] = $loginHint;
}
return self::$dialogUrlBase . '?' . http_build_query($oauthParams); return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
} }
+1 -1
View File
@@ -11,7 +11,7 @@ abstract class ContentBase implements IContent
return $this->data; return $this->data;
} }
abstract public function &render(): string; abstract public function render(): void;
abstract public function getContentType(): string; abstract public function getContentType(): string;
} }
+13 -11
View File
@@ -1,27 +1,29 @@
<?php namespace MapGuesser\Response; <?php namespace MapGuesser\Response;
use MapGuesser\View\Linker;
class HtmlContent extends ContentBase 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; $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); extract($this->data);
ob_start(); require ROOT . '/cache/views/' . $this->view . '.php';
require ROOT . '/views/' . $this->template . '.php';
$content = ob_get_contents();
ob_end_clean();
$content .= '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->'; echo '<!-- __debug__runtime: ' . round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1) . ' -->';
return $content;
} }
public function getContentType(): string public function getContentType(): string
+2 -4
View File
@@ -7,13 +7,11 @@ class JsonContent extends ContentBase
$this->data = &$data; $this->data = &$data;
} }
public function &render(): string public function render(): void
{ {
$this->data['__debug__runtime'] = round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1); $this->data['__debug__runtime'] = round((hrtime(true) - SCRIPT_STARTED) / 1e+6, 1);
$content = json_encode($this->data); echo json_encode($this->data);
return $content;
} }
public function getContentType(): string public function getContentType(): string
+152
View File
@@ -0,0 +1,152 @@
<?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);
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);
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): array
{
$output = [];
if (preg_match('/^[\w\/\.]+$/', $asset)) {
if (
empty($_ENV['DEV']) &&
filesize(ROOT . '/public/static/' . $asset) < self::INLINE_ASSET_LIMIT
) {
$output['code'] = file_get_contents(ROOT . '/public/static/' . $asset);
} else {
$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);
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php namespace MapGuesser\Tests\View\Parser;
use MapGuesser\View\ParsedFragment;
use MapGuesser\View\Parser;
use PHPUnit\Framework\TestCase;
final class ParserTest extends TestCase
{
const TEST_VIEWS_PATH = __DIR__ . '/../assets/views';
public function testCanParseViewWithoutExtends(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_without_extends.php');
$parser = new Parser($file);
$fragment = $parser->parse();
$expected = new ParsedFragment(
null,
[],
[],
[],
['', '']
);
$this->assertEquals($expected, $fragment);
}
public function testCanParseViewWithAssets(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_with_assets.php');
$parser = new Parser($file);
$fragment = $parser->parse();
$expected = new ParsedFragment(
null,
[
'test.css'
],
[
'test.js',
'test_<?= $some[\'expression\'] ?>'
],
[],
['', '']
);
$this->assertEquals($expected, $fragment);
}
public function testCanParseViewWithExtends(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_with_extends.php');
$parser = new Parser($file);
$fragment = $parser->parse();
$expected = new ParsedFragment(
'parent',
[],
[],
[
'section1' => '<div>Test HTML with @extends</div>' . "\n"
],
['', '']
);
$this->assertEquals($expected, $fragment);
}
public function testCanParseComplexView(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_complex.php');
$parser = new Parser($file);
$fragment = $parser->parse();
$expected = new ParsedFragment(
'parent',
[
'test1.css'
],
[
'test1.js',
'test2_<?= $some[\'expression\'] ?>'
],
[
'section1' => '<div>Test HTML with @extends - section 1</div>' . "\n",
'section2' => '<div>Test HTML with @extends - section 2</div>' . "\n"
],
[
'<?php phpinfo() ?>' . "\n",
'<?php $a = \'test_string\' ?>' . "\n" . 'EXTRA' . "\n",
]
);
$this->assertEquals($expected, $fragment);
}
public function testFailsIfMultipleExtendsGiven(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_multiple_extends.php');
$parser = new Parser($file);
$this->expectExceptionMessage('Error in file ' . $file . ' in line 3 - There is already an \'@extends\' declared.');
$parser->parse();
}
public function testFailsIfSectionWithoutExtendsGiven(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_section_without_extends.php');
$parser = new Parser($file);
$this->expectExceptionMessage('Error in file ' . $file . ' in line 1 - \'@section\' has no meaning if view extends nothing.');
$parser->parse();
}
public function testFailsIfOpeningSectionBeforePreviousClosed(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_multiple_sections_open.php');
$parser = new Parser($file);
$this->expectExceptionMessage('Parse error in file ' . $file . ' in line 4 - A \'@section\' is already open (no \'@endsection\' found).');
$parser->parse();
}
public function testFailsIfClosingSectionWhenNoSectionIsOpen(): void
{
$file = realpath(self::TEST_VIEWS_PATH . '/view_invalid_section_not_open.php');
$parser = new Parser($file);
$this->expectExceptionMessage('Parse error in file ' . $file . ' in line 4 - Cannot end section until no \'@section\' is open.');
$parser->parse();
}
}
+22
View File
@@ -0,0 +1,22 @@
@css(test1.css)
@js(test1.js)
@js(test2_<?= $some['expression'] ?>)
@extra
<?php phpinfo() ?>
@endextra
@extends(parent)
@section(section1)
<div>Test HTML with @extends - section 1</div>
@endsection
@section(section2)
<div>Test HTML with @extends - section 2</div>
@endsection
@extra
<?php $a = 'test_string' ?>
EXTRA
@endextra
@@ -0,0 +1,5 @@
@extends(parent1)
@extends(parent2)
<div>Test HTML with multiple @extends</div>
@@ -0,0 +1,6 @@
@extends(parent)
@section(section1)
@section(section2)
<div>Test HTML with opening @section before previous closed</div>
@@ -0,0 +1,4 @@
@extends(parent)
<div>Test HTML with @endsection when no section is open</div>
@endsection
@@ -0,0 +1,3 @@
@section(section1)
<div>Test HTML with @section but without @extends</div>
+4
View File
@@ -0,0 +1,4 @@
@css(test.css)
@js(test.js)
@js(test_<?= $some['expression'] ?>)
<div>Test HTML with @css and @js</div>
+5
View File
@@ -0,0 +1,5 @@
@extends(parent)
@section(section1)
<div>Test HTML with @extends</div>
@endsection
@@ -0,0 +1 @@
<div>Test HTML without @extends</div>
+22 -5
View File
@@ -1,9 +1,27 @@
<?php require ROOT . '/views/templates/main_header.php'; ?> @js(js/account/account.js)
<?php require ROOT . '/views/templates/header.php'; ?>
@extends(templates/layout_normal)
@section(main)
<h2>Account</h2> <h2>Account</h2>
<div class="box"> <div class="box">
<form id="accountForm" action="/account" method="post" data-observe-inputs="password_new,password_new_confirm"> <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> <?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
<p class="justify small">Please confirm your identity with your password or with Google to modify your account.</p>
<div class="inputWithButton">
<input type="password" name="password" placeholder="Current password" required minlength="6" autofocus><!--
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
</div>
<?php elseif ($user['password'] !== null): ?>
<p class="justify small">Please confirm your identity with your password to modify your account.</p>
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
<?php elseif ($user['google_sub'] !== null): ?>
<p class="justify small">Please confirm your identity with Google to modify your account.</p>
<div class="inputWithButton">
<input type="text" name="password" placeholder="Authenticate with Google..." disabled><!--
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
</div>
<?php endif; ?>
<hr> <hr>
<?php /* TODO: disabled for the time being, email modification should be implemented */ ?> <?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" type="email" name="email" placeholder="Email address" value="<?= $user['email'] ?>" disabled>
@@ -19,5 +37,4 @@
</div> </div>
</form> </form>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+25 -7
View File
@@ -1,15 +1,33 @@
<?php require ROOT . '/views/templates/main_header.php'; ?> @js(js/account/account.js)
<?php require ROOT . '/views/templates/header.php'; ?>
@extends(templates/layout_normal)
@section(main)
<h2>Delete account</h2> <h2>Delete account</h2>
<div class="box"> <div class="box">
<form id="deleteAccountForm" action="/account/delete" method="post" data-redirect-on-success="/"> <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> <p class="justify marginBottom">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> <?php if ($user['password'] !== null && $user['google_sub'] !== null): ?>
<p class="justify small">Please confirm your identity with your password or with Google to delete your account.</p>
<div class="inputWithButton">
<input type="password" name="password" placeholder="Current password" required minlength="6" autofocus><!--
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
</div>
<?php elseif ($user['password'] !== null): ?>
<p class="justify small">Please confirm your identity with your password to delete your account.</p>
<input class="big fullWidth" type="password" name="password" placeholder="Current password" required minlength="6" autofocus>
<?php elseif ($user['google_sub'] !== null): ?>
<p class="justify small">Please confirm your identity with Google to delete your account.</p>
<div class="inputWithButton">
<input type="text" name="password" placeholder="Authenticate with Google..." disabled><!--
--><button id="authenticateWithGoogleButton" class="yellow" type="button">Google</button>
</div>
<?php endif; ?>
<p id="deleteAccountFormError" class="formError justify marginTop"></p> <p id="deleteAccountFormError" class="formError justify marginTop"></p>
<div class="right marginTop"> <div class="right marginTop">
<button class="red" type="submit" name="submit">Delete account</button> <button class="red marginRight" type="submit" name="submit">Delete account</button><!--
--><a class="button gray marginTop" href="/account" title="Back to account">Cancel</a>
</div> </div>
</form> </form>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+28
View File
@@ -0,0 +1,28 @@
@js(js/account/google_authenticate.js)
@extends(templates/layout_minimal)
@section(main)
<h2>Authenticate with Google</h2>
<?php if (!$success): ?>
<div class="box">
<p class="error justify">
<?php if (isset($errorText)): ?>
<?= $errorText ?>
<?php else: ?>
Authentication with Google failed.
<?php endif; ?>
Please <a id="closeWindowButton" href="javascript:;" title="Close">close this window/tab</a> and try again!
</p>
</div>
<?php endif; ?>
@endsection
@section(pageScript)
<script>
var success = <?= $success ? 'true' : 'false' ?>;
<?php if (isset($authenticatedWithGoogleUntil)): ?>
var authenticatedWithGoogleUntil = new Date('<?= $authenticatedWithGoogleUntil->format('c') ?>');
<?php endif; ?>
</script>
@endsection
+24 -29
View File
@@ -1,27 +1,17 @@
<?php @css(node_modules/leaflet/dist/leaflet.css)
$cssFiles = [ @css(css/map_editor.css)
'node_modules/leaflet/dist/leaflet.css', @css(node_modules/leaflet.markercluster/dist/MarkerCluster.css)
'css/map_editor.css', @css(node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css)
'node_modules/leaflet.markercluster/dist/MarkerCluster.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)
$jsFiles = [ @js(https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>)
'node_modules/leaflet/dist/leaflet.js', @js(js/map_editor.js)
'node_modules/leaflet.markercluster/dist/leaflet.markercluster.js',
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'], @extends(templates/layout_full)
'js/map_editor.js',
]; @section(subheader)
?> <span><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
<?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><!--
--><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" width="1em" height="1em" 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">
@@ -46,8 +36,9 @@ $jsFiles = [
</svg><!-- </svg><!--
--><span id="deleted" class="bold">0</span><!-- --><span id="deleted" class="bold">0</span><!--
--></span> --></span>
</p> @endsection
</header>
@section(pagemodal)
<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" data-no-submit="true"> <form id="metadataForm" class="marginTop" data-no-submit="true">
@@ -59,7 +50,9 @@ $jsFiles = [
</div> </div>
</form> </form>
</div> </div>
<div class="full"> @endsection
@section(main)
<div id="map"></div> <div id="map"></div>
<div id="panorama"></div> <div id="panorama"></div>
<div id="noPano"> <div id="noPano">
@@ -73,7 +66,9 @@ $jsFiles = [
<button id="closeButton" class="gray fullWidth marginTop">Close</button> <button id="closeButton" class="gray fullWidth marginTop">Close</button>
<button id="deleteButton" class="red fullWidth marginTop">Delete</button> <button id="deleteButton" class="red fullWidth marginTop">Delete</button>
</div> </div>
</div> @endsection
@section(pageScript)
<script> <script>
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>'; var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>'; var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
@@ -81,4 +76,4 @@ $jsFiles = [
var mapBounds = <?= json_encode($bounds) ?>; var mapBounds = <?= json_encode($bounds) ?>;
var places = <?= json_encode($places, JSON_FORCE_OBJECT) ?>; var places = <?= json_encode($places, JSON_FORCE_OBJECT) ?>;
</script> </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'; ?> @extends(templates/layout_normal)
<?php require ROOT . '/views/templates/header.php'; ?>
@section(main)
<h2>404 | Page not found</h2> <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> <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'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+15 -23
View File
@@ -1,27 +1,17 @@
<?php @css(css/game.css)
$cssFiles = [
'css/game.css' @js(https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>)
]; @js(js/game.js)
$jsFiles = [
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'], @extends(templates/layout_full)
'js/game.js',
]; @section(subheader)
?>
<?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 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> @endsection
</header>
<div class="full"> @section(main)
<div id="panoCover"></div> <div id="panoCover"></div>
<div id="panorama"></div> <div id="panorama"></div>
<div id="showGuessButtonContainer"> <div id="showGuessButtonContainer">
@@ -56,9 +46,11 @@ $jsFiles = [
<button id="startNewGameButton" class="fullWidth">Play this map again</button> <button id="startNewGameButton" class="fullWidth">Play this map again</button>
</div> </div>
</div> </div>
</div> @endsection
@section(pageScript)
<script> <script>
var mapId = <?= $mapId ?>; var mapId = <?= $mapId ?>;
var mapBounds = <?= json_encode($bounds) ?>; var mapBounds = <?= json_encode($bounds) ?>;
</script> </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'; ?> @extends(templates/layout_normal)
<?php require ROOT . '/views/templates/header.php'; ?>
@section(main)
<h2>Account activation</h2> <h2>Account activation</h2>
<div class="box"> <div class="box">
<p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p> <p class="error justify">Activation failed. Please check the link you entered or retry <a href="/signup" title="Sign up">sign up</a>!</p>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+4 -4
View File
@@ -1,5 +1,6 @@
<?php require ROOT . '/views/templates/main_header.php'; ?> @extends(templates/layout_normal)
<?php require ROOT . '/views/templates/header.php'; ?>
@section(main)
<h2>Account cancellation</h2> <h2>Account cancellation</h2>
<div class="box"> <div class="box">
<?php if ($success) : ?> <?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> <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; ?> <?php endif; ?>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+5 -5
View File
@@ -1,8 +1,8 @@
<?php require ROOT . '/views/templates/main_header.php'; ?> @extends(templates/layout_normal)
<?php require ROOT . '/views/templates/header.php'; ?>
@section(main)
<h2>Login up with Google</h2> <h2>Login up with Google</h2>
<div class="box"> <div class="box">
<p class="error justify">Authenticating with Google failed. Please <a href="/login/google" title="Login with Google">retry</a>!</p> <p class="error justify">Authentication with Google failed. Please <a href="/login/google" title="Login with Google">try again</a>!</p>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+6 -9
View File
@@ -1,10 +1,8 @@
<?php @js(js/login/google_signup.js)
$jsFiles = [
'js/login/google_signup.js', @extends(templates/layout_normal)
];
?> @section(main)
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<h2>Sign up</h2> <h2>Sign up</h2>
<div class="box"> <div class="box">
<form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/"> <form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/">
@@ -26,5 +24,4 @@ $jsFiles = [
</div> </div>
</form> </form>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+4 -4
View File
@@ -1,5 +1,6 @@
<?php require ROOT . '/views/templates/main_header.php'; ?> @extends(templates/layout_normal)
<?php require ROOT . '/views/templates/header.php'; ?>
@section(main)
<h2>Login</h2> <h2>Login</h2>
<div class="box"> <div class="box">
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/"> <form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
@@ -15,5 +16,4 @@
</div> </div>
</form> </form>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+6 -9
View File
@@ -1,10 +1,8 @@
<?php @js(js/login/signup.js)
$jsFiles = [
'js/login/signup.js', @extends(templates/layout_normal)
];
?> @section(main)
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<h2>Sign up</h2> <h2>Sign up</h2>
<div class="box"> <div class="box">
<form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success"> <form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success">
@@ -30,5 +28,4 @@ $jsFiles = [
</div> </div>
</form> </form>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+4 -4
View File
@@ -1,8 +1,8 @@
<?php require ROOT . '/views/templates/main_header.php'; ?> @extends(templates/layout_normal)
<?php require ROOT . '/views/templates/header.php'; ?>
@section(main)
<h2>Sign up</h2> <h2>Sign up</h2>
<div class="box"> <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> <p class="justify">Sign up was successful. Please check your email and click on the activation link to activate your account!</p>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
+9 -15
View File
@@ -1,16 +1,11 @@
<?php @css(css/maps.css)
$cssFiles = [ @js(js/maps.js)
'css/maps.css' TODO: condition!
]; @js(js/maps_admin.js)
$jsFiles = [
'js/maps.js', @extends(templates/layout_normal)
];
if ($isAdmin) { @section(main)
$jsFiles[] = 'js/maps_admin.js';
}
?>
<?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?>
<div id="mapContainer"> <div id="mapContainer">
<?php foreach ($maps as $map): ?> <?php foreach ($maps as $map): ?>
<div class="mapItem"> <div class="mapItem">
@@ -66,5 +61,4 @@ if ($isAdmin) {
<?php endfor; ?> <?php endfor; ?>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php require ROOT . '/views/templates/footer.php'; ?> @endsection
<?php require ROOT . '/views/templates/main_footer.php'; ?>
-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
+13
View File
@@ -0,0 +1,13 @@
@extends(templates/mapguesser)
@section(content)
<header>
<h1>
<img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>"><!--
--><?= $_ENV['APP_NAME'] ?>
</h1>
</header>
<main>
@yields('main')
</main>
@endsection
@@ -1,3 +1,6 @@
@extends(templates/mapguesser)
@section(content)
<header> <header>
<h1> <h1>
<a href="/" title="<?= $_ENV['APP_NAME'] ?>"> <a href="/" title="<?= $_ENV['APP_NAME'] ?>">
@@ -22,3 +25,10 @@
</p> </p>
</header> </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
-50
View File
@@ -1,50 +0,0 @@
<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>
<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>
@@ -3,19 +3,12 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#198231">
<title><?= $_ENV['APP_NAME'] ?></title> <title><?= $_ENV['APP_NAME'] ?></title>
<link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet"> <link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
<?php if (isset($cssFiles)) : ?> @yields('externalCss')
<?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 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="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="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="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
@@ -39,3 +32,21 @@
<p id="modalText" class="justify marginTop"></p> <p id="modalText" class="justify marginTop"></p>
<div id="modalButtons" class="right"></div> <div id="modalButtons" class="right"></div>
</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; ?>
const COOKIES_CONSENT = <?= isset($_COOKIE['COOKIES_CONSENT']) ? 'true' : 'false' ?>;
</script>
@yields('pageScript')
<script src="<?= $_ENV['STATIC_ROOT'] ?>/js/mapguesser.js?rev=<?= REVISION ?>"></script>
@yields('externalJs')
@yields('inlineJs')
</body>
</html>
+2
View File
@@ -37,6 +37,8 @@ Container::$routeCollection->group('account', function (MapGuesser\Routing\Route
$routeCollection->post('account-action', '', [MapGuesser\Controller\UserController::class, 'saveAccount']); $routeCollection->post('account-action', '', [MapGuesser\Controller\UserController::class, 'saveAccount']);
$routeCollection->get('account.delete', 'delete', [MapGuesser\Controller\UserController::class, 'getDeleteAccount']); $routeCollection->get('account.delete', 'delete', [MapGuesser\Controller\UserController::class, 'getDeleteAccount']);
$routeCollection->post('account.delete-action', 'delete', [MapGuesser\Controller\UserController::class, 'deleteAccount']); $routeCollection->post('account.delete-action', 'delete', [MapGuesser\Controller\UserController::class, 'deleteAccount']);
$routeCollection->get('account.googleAuthenticate', 'googleAuthenticate', [MapGuesser\Controller\UserController::class, 'getGoogleAuthenticateRedirect']);
$routeCollection->get('account.googleAuthenticate-action', 'googleAuthenticate/code', [MapGuesser\Controller\UserController::class, 'authenticateWithGoogle']);
}); });
//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) {