Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8adc9f730 | ||
|
|
149003250c
|
||
|
|
171b4278a6 | ||
|
|
091018d22f
|
||
|
|
34f0c92eb5 | ||
|
|
8ac5d5c6e2
|
||
|
|
e883d184b7
|
||
|
|
71b1e28aa9 | ||
|
|
3555be9b63
|
||
|
|
d03a3934c6 | ||
|
|
bd5b49b09b
|
||
|
|
4333240acc
|
||
|
|
4099f1d962
|
||
|
|
2fac828098
|
||
|
|
f3225ae275
|
||
|
|
48eba75cf4
|
||
|
|
a97a6f8977 | ||
|
|
2a3f891aa0
|
No files matched your search
+2
-2
@@ -36,7 +36,7 @@ if ($match !== null) {
|
||||
|
||||
if ($response instanceof MapGuesser\Interfaces\Response\IContent) {
|
||||
header('Content-Type: ' . $response->getContentType() . '; charset=UTF-8');
|
||||
echo $response->render();
|
||||
$response->render();
|
||||
|
||||
return;
|
||||
} elseif ($response instanceof MapGuesser\Interfaces\Response\IRedirect) {
|
||||
@@ -49,4 +49,4 @@ if ($match !== null) {
|
||||
|
||||
$content = new MapGuesser\Response\HtmlContent('error/404');
|
||||
header('Content-Type: text/html; charset=UTF-8', true, 404);
|
||||
echo $content->render();
|
||||
$content->render();
|
||||
@@ -153,13 +153,27 @@ button, a.button {
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
button.small {
|
||||
button.small, div.inputWithButton>button {
|
||||
font-size: 14px;
|
||||
padding: 0 12px;
|
||||
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 {
|
||||
background-color: #29457f;
|
||||
outline: none;
|
||||
@@ -191,7 +205,7 @@ button.gray, a.button.gray {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -199,7 +213,7 @@ button.red, a.button.red {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -207,7 +221,7 @@ button.yellow, a.button.yellow {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -215,7 +229,7 @@ button.green, a.button.green {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -223,22 +237,36 @@ input, select, textarea {
|
||||
background-color: #f9fafb;
|
||||
border: solid #c8d2e1 1px;
|
||||
border-radius: 2px;
|
||||
padding: 4px;
|
||||
box-sizing: border-box;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
input, select {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-size: 13px;
|
||||
padding: 5px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
input.big, select.big, textarea.big {
|
||||
padding: 5px;
|
||||
input.big, select.big, textarea.big, div.inputWithButton>input {
|
||||
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 {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -253,14 +281,30 @@ input:disabled, select:disabled, textarea:disabled {
|
||||
input:focus, select:focus, textarea:focus {
|
||||
background-color: #ffffff;
|
||||
border: solid #29457f 2px;
|
||||
padding: 3px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input.big:focus, select.big:focus, textarea.big:focus {
|
||||
input:focus, select:focus {
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
input.big:focus, select.big:focus {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
div.inputWithButton>input {
|
||||
width: 100%;
|
||||
padding: 0 83px 0 5px;
|
||||
}
|
||||
|
||||
textarea.big:focus {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.modal {
|
||||
position: fixed;
|
||||
background-color: #ffffff;
|
||||
|
||||
@@ -18,7 +18,16 @@ div.mapItem>div.title {
|
||||
color: white;
|
||||
border-top-left-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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -244,4 +244,23 @@ var MapGuesser = {
|
||||
document.getElementById('cover').onclick = function () {
|
||||
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();
|
||||
};
|
||||
}
|
||||
})();
|
||||
@@ -23,6 +23,7 @@ class AddUserCommand extends Command
|
||||
$user = new User();
|
||||
$user->setEmail($input->getArgument('email'));
|
||||
$user->setPlainPassword($input->getArgument('password'));
|
||||
$user->setActive(true);
|
||||
|
||||
if ($input->hasArgument('type')) {
|
||||
$user->setType($input->getArgument('type'));
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
<?php namespace MapGuesser\Controller;
|
||||
|
||||
use DateTime;
|
||||
use MapGuesser\Database\Query\Select;
|
||||
use MapGuesser\Http\Request;
|
||||
use MapGuesser\Interfaces\Authorization\ISecured;
|
||||
use MapGuesser\Interfaces\Database\IResultSet;
|
||||
use MapGuesser\Interfaces\Request\IRequest;
|
||||
use MapGuesser\Interfaces\Response\IContent;
|
||||
use MapGuesser\Interfaces\Response\IRedirect;
|
||||
use MapGuesser\OAuth\GoogleOAuth;
|
||||
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;
|
||||
use MapGuesser\Response\Redirect;
|
||||
use MapGuesser\Util\JwtParser;
|
||||
|
||||
class UserController implements ISecured
|
||||
{
|
||||
@@ -45,6 +51,63 @@ class UserController implements ISecured
|
||||
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
|
||||
{
|
||||
/**
|
||||
@@ -63,8 +126,13 @@ class UserController implements ISecured
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
if (!$user->checkPassword($this->request->post('password'))) {
|
||||
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
|
||||
if (!$this->confirmUserIdentity(
|
||||
$user,
|
||||
$this->request->session()->get('authenticated_with_google_until'),
|
||||
$this->request->post('password'),
|
||||
$error
|
||||
)) {
|
||||
$data = ['error' => ['errorText' => $error]];
|
||||
return new JsonContent($data);
|
||||
}
|
||||
|
||||
@@ -84,6 +152,8 @@ class UserController implements ISecured
|
||||
|
||||
$this->pdm->saveToDb($user);
|
||||
|
||||
$this->request->session()->delete('authenticated_with_google_until');
|
||||
|
||||
$data = ['success' => true];
|
||||
return new JsonContent($data);
|
||||
}
|
||||
@@ -95,8 +165,13 @@ class UserController implements ISecured
|
||||
*/
|
||||
$user = $this->request->user();
|
||||
|
||||
if (!$user->checkPassword($this->request->post('password'))) {
|
||||
$data = ['error' => ['errorText' => 'The given current password is wrong.']];
|
||||
if (!$this->confirmUserIdentity(
|
||||
$user,
|
||||
$this->request->session()->get('authenticated_with_google_until'),
|
||||
$this->request->post('password'),
|
||||
$error
|
||||
)) {
|
||||
$data = ['error' => ['errorText' => $error]];
|
||||
return new JsonContent($data);
|
||||
}
|
||||
|
||||
@@ -110,7 +185,28 @@ class UserController implements ISecured
|
||||
|
||||
\Container::$dbConnection->commit();
|
||||
|
||||
$this->request->session()->delete('authenticated_with_google_until');
|
||||
|
||||
$data = ['success' => true];
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ class GoogleOAuth
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function getDialogUrl(string $state, string $redirectUrl): string
|
||||
public function getDialogUrl(string $state, string $redirectUrl, ?string $loginHint = null): string
|
||||
{
|
||||
$oauthParams = [
|
||||
'response_type' => 'code',
|
||||
@@ -26,6 +26,10 @@ class GoogleOAuth
|
||||
'nonce' => hash('sha256', random_bytes(10) . microtime()),
|
||||
];
|
||||
|
||||
if ($loginHint !== null) {
|
||||
$oauthParams['login_hint'] = $loginHint;
|
||||
}
|
||||
|
||||
return self::$dialogUrlBase . '?' . http_build_query($oauthParams);
|
||||
}
|
||||
|
||||
|
||||
+12
-15
@@ -108,7 +108,7 @@ class Linker
|
||||
private function generateAssets(ParsedFragment $fragment, array &$sections)
|
||||
{
|
||||
foreach ($fragment->getCss() as $cssFile) {
|
||||
$asset = $this->parseAsset($cssFile, 'css');
|
||||
$asset = $this->parseAsset($cssFile);
|
||||
if (isset($asset['code'])) {
|
||||
$sections['inlineCss'] .= '<style>' . PHP_EOL;
|
||||
$sections['inlineCss'] .= $asset['code'];
|
||||
@@ -119,7 +119,7 @@ class Linker
|
||||
}
|
||||
|
||||
foreach ($fragment->getJs() as $jsFile) {
|
||||
$asset = $this->parseAsset($jsFile, 'js');
|
||||
$asset = $this->parseAsset($jsFile);
|
||||
if (isset($asset['code'])) {
|
||||
$sections['inlineJs'] .= '<script>' . PHP_EOL;
|
||||
$sections['inlineJs'] .= $asset['code'];
|
||||
@@ -130,24 +130,21 @@ class Linker
|
||||
}
|
||||
}
|
||||
|
||||
private function parseAsset(string $asset, string $type): array
|
||||
private function parseAsset(string $asset): 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 ?>';
|
||||
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'] = $asset;
|
||||
$output['file'] = '<?= $_ENV[\'STATIC_ROOT\'] ?>/' . $asset . '?rev=<?= REVISION ?>';
|
||||
}
|
||||
} else {
|
||||
$output['file'] = $asset;
|
||||
}
|
||||
|
||||
return $output;
|
||||
|
||||
+2
-2
@@ -126,7 +126,7 @@ class Parser
|
||||
|
||||
private function matchExtends(string $line): ?string
|
||||
{
|
||||
if (preg_match('/^\s*@extends\(\'([\w\/]+)\'\)\s*$/', $line, $matches)) {
|
||||
if (preg_match('/^\s*@extends\(([\w\/]+)\)\s*$/', $line, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class Parser
|
||||
|
||||
private function matchSection(string $line): ?string
|
||||
{
|
||||
if (preg_match('/^\s*@section\(\'(\w+)\'\)\s*$/', $line, $matches)) {
|
||||
if (preg_match('/^\s*@section\((\w+)\)\s*$/', $line, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,4 @@
|
||||
@css(test.css)
|
||||
@js(test.js)
|
||||
@js(test_<?= $some['expression'] ?>)
|
||||
<div>Test HTML with @css and @js</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
@extends(parent)
|
||||
|
||||
@section(section1)
|
||||
<div>Test HTML with @extends</div>
|
||||
@endsection
|
||||
@@ -0,0 +1 @@
|
||||
<div>Test HTML without @extends</div>
|
||||
@@ -1,10 +1,27 @@
|
||||
@extends('templates/layout_normal')
|
||||
@js(js/account/account.js)
|
||||
|
||||
@section('main')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section(main)
|
||||
<h2>Account</h2>
|
||||
<div class="box">
|
||||
<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>
|
||||
<?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>
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
@extends('templates/layout_normal')
|
||||
@js(js/account/account.js)
|
||||
|
||||
@section('main')
|
||||
@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 class="justify marginBottom">Are you sure you want to delete your account? This cannot be undone!</p>
|
||||
<?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>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
+13
-13
@@ -1,16 +1,16 @@
|
||||
@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')
|
||||
@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')
|
||||
@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')
|
||||
@extends(templates/layout_full)
|
||||
|
||||
@section('subheader')
|
||||
@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'. */ ?>
|
||||
@@ -38,7 +38,7 @@
|
||||
--></span>
|
||||
@endsection
|
||||
|
||||
@section('pagemodal')
|
||||
@section(pagemodal)
|
||||
<div id="metadata" class="modal">
|
||||
<h2>Edit map data</h2>
|
||||
<form id="metadataForm" class="marginTop" data-no-submit="true">
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<div id="map"></div>
|
||||
<div id="panorama"></div>
|
||||
<div id="noPano">
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('pageScript')
|
||||
@section(pageScript)
|
||||
<script>
|
||||
var tileUrl = '<?= $_ENV['LEAFLET_TILESERVER_URL'] ?>';
|
||||
var tileAttribution = '<?= $_ENV['LEAFLET_TILESERVER_ATTRIBUTION'] ?>';
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@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>
|
||||
@endsection
|
||||
+7
-7
@@ -1,17 +1,17 @@
|
||||
@css('css/game.css')
|
||||
@css(css/game.css)
|
||||
|
||||
@js('https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'])
|
||||
@js('js/game.js')
|
||||
@js(https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>)
|
||||
@js(js/game.js)
|
||||
|
||||
@extends('templates/layout_full')
|
||||
@extends(templates/layout_full)
|
||||
|
||||
@section('subheader')
|
||||
@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>
|
||||
@endsection
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<div id="panoCover"></div>
|
||||
<div id="panorama"></div>
|
||||
<div id="showGuessButtonContainer">
|
||||
@@ -48,7 +48,7 @@
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('pageScript')
|
||||
@section(pageScript)
|
||||
<script>
|
||||
var mapId = <?= $mapId ?>;
|
||||
var mapBounds = <?= json_encode($bounds) ?>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<h2>Account cancellation</h2>
|
||||
<div class="box">
|
||||
<?php if ($success) : ?>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@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>
|
||||
<p class="error justify">Authentication with Google failed. Please <a href="/login/google" title="Login with Google">try again</a>!</p>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,8 +1,8 @@
|
||||
@js('js/login/google_signup.js')
|
||||
@js(js/login/google_signup.js)
|
||||
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<h2>Sign up</h2>
|
||||
<div class="box">
|
||||
<form id="googleSignupForm" action="/signup/google" method="post" data-redirect-on-success="/">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<h2>Login</h2>
|
||||
<div class="box">
|
||||
<form id="loginForm" action="/login" method="post" data-redirect-on-success="/">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@js('js/login/signup.js')
|
||||
@js(js/login/signup.js)
|
||||
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<h2>Sign up</h2>
|
||||
<div class="box">
|
||||
<form id="signupForm" action="/signup" method="post" data-redirect-on-success="/signup/success">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@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>
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
@css('css/maps.css')
|
||||
@js('js/maps.js')
|
||||
@css(css/maps.css)
|
||||
@js(js/maps.js)
|
||||
TODO: condition!
|
||||
@js('js/maps_admin.js')
|
||||
@js(js/maps_admin.js)
|
||||
|
||||
@extends('templates/layout_normal')
|
||||
@extends(templates/layout_normal)
|
||||
|
||||
@section('main')
|
||||
@section(main)
|
||||
<div id="mapContainer">
|
||||
<?php foreach ($maps as $map): ?>
|
||||
<div class="mapItem">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@extends('templates/mapguesser')
|
||||
@extends(templates/mapguesser)
|
||||
|
||||
@section('content')
|
||||
@section(content)
|
||||
<header class="small">
|
||||
<h1>
|
||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
@extends('templates/mapguesser')
|
||||
@section('content')
|
||||
@extends(templates/mapguesser)
|
||||
|
||||
@section(content)
|
||||
<header>
|
||||
<h1>
|
||||
<a href="/" title="<?= $_ENV['APP_NAME'] ?>">
|
||||
|
||||
@@ -42,38 +42,11 @@
|
||||
<?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')
|
||||
<?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>
|
||||
@@ -37,6 +37,8 @@ Container::$routeCollection->group('account', function (MapGuesser\Routing\Route
|
||||
$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']);
|
||||
$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->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
|
||||
|
||||
Reference in new issue
Block a user