Compare commits

...
Author SHA1 Message Date
bence 142c91f36b Merged in bugfix/MAPG-138-fix-js-minifying (pull request #111)
MAPG-138 use traditional for loop for compatibility
2020-06-14 09:33:54 +00:00
bence 1269c876e9 MAPG-138 use traditional for loop for compatibility 2020-06-14 11:33:13 +02:00
bence 44e441fac0 Merged in bugfix/MAPG-137-fix-errors-reported-by-speed-an (pull request #110)
Bugfix/MAPG-137 fix errors reported by speed an
2020-06-14 09:25:47 +00:00
bence f85340cd00 MAPG-137 optimize png images (favicons) 2020-06-14 11:21:07 +02:00
bence ba83f73c31 MAPG-137 add width and height to images where possible 2020-06-14 11:12:18 +02:00
bence 86f05f7738 MAPG-137 place static maps into container to avoid large layout shifts 2020-06-14 11:11:37 +02:00
bence 61ce915ec0 Merged in hotfix/MAPG-125-ability-to-delete-maps (pull request #109)
MAPG-125 re-add select map name when opening modal
2020-06-14 00:53:38 +00:00
bence 2aa3cdc7f3 MAPG-125 re-add select map name when opening modal 2020-06-14 02:53:11 +02:00
bence 5760e31e29 Merged in feature/MAPG-125-ability-to-delete-maps (pull request #108)
Feature/MAPG-125 ability to delete maps
2020-06-14 00:42:31 +00:00
bence 14fd723f26 MAPG-125 add functionality to delete map button 2020-06-14 02:37:49 +02:00
bence a6d5214166 MAPG-125 add deleteMap endpoint 2020-06-14 02:36:23 +02:00
bence d1d9dede9c MAPG-125 use modal instead of custom box in map editor 2020-06-14 02:31:09 +02:00
bence 171064816c MAPG-125 add modal design 2020-06-14 02:30:47 +02:00
bence f8bb7bd092 MAPG-125 rename #cover to #guessCover in game 2020-06-14 02:07:20 +02:00
bence 466f065718 MAPG-125 switch off diff calculation in Modify for better performance
(not sure if needed - we probably won't log)
2020-06-14 02:06:27 +02:00
bence 6d68f247e6 Merged in feature/MAPG-133-csrf-protection (pull request #107)
Feature/MAPG-133 csrf protection
2020-06-13 20:41:49 +00:00
bence e4c7aadc64 MAPG-133 replace XHRs to MapGuesser.httpRequest 2020-06-13 22:39:44 +02:00
bence 25262fb5fe MAPG-133 pass CSRF token to JS
create separate mapguesser.js
2020-06-13 22:39:17 +02:00
bence 8e0c1ce08b MAPG-133 add basic CSRF protection to server side 2020-06-13 22:38:30 +02:00
bence 9e196ebf33 Merged in feature/MAPG-131-store-session-is-database (pull request #106)
Feature/MAPG-131 store session is database
2020-06-13 18:49:26 +00:00
bence 013668e3d6 MAPG-131 split web specific code from main.php to web.php
set the new session handler as session handler
configure session with longer lifetime and with stricter cookie settings
2020-06-13 20:45:36 +02:00
bence dbd16d6cbe MAPG-131 create custom session handler for DB sessions 2020-06-13 20:39:42 +02:00
bence 6a191d5c28 MAPG-131 adapt Modify to be able to handle external ID 2020-06-13 20:39:22 +02:00
bence 39e49724da MAPG-131 add sessions table to DB 2020-06-13 20:38:45 +02:00
bence 6c8a3c6a90 Merged in feature/MAPG-111-calculate-map-area-when-bounds-are-modified (pull request #105)
Feature/MAPG-111 calculate map area when bounds are modified
2020-06-12 19:57:07 +00:00
bence 7159867db0 MAPG-111 query map area from DB
modify calculations to be in square km (instead of m)
2020-06-12 21:55:04 +02:00
bence 4d7c86fd1b MAPG-111 store map area in table 'maps' 2020-06-12 21:54:05 +02:00
bence 9ca97dccb9 Merged in bugfix/MAPG-28-rewrite-missiong-static-asset-paths (pull request #104)
MAPG-28 fix static asset paths (add STATIC_ROOT and REVISION)
2020-06-12 18:16:36 +00:00
bence 2ef67d53f4 MAPG-28 fix static asset paths (add STATIC_ROOT and REVISION) 2020-06-12 20:13:43 +02:00
bence 06a0722899 Merged in feature/MAPG-28-configure-static-root-for-assets (pull request #103)
Feature/MAPG-28 configure static root for assets
2020-06-12 18:01:56 +00:00
bence 4a9cf8a217 MAPG-28 add update-version.sh and adapt deploy py script to that 2020-06-12 20:00:45 +02:00
bence b3caf1fb38 MAPG-28 adapt cssFiles and jsFiles to the new handler 2020-06-12 20:00:45 +02:00
bence 08d4aec48e MAPG-28 add static root and revision to the local JS and CSS files 2020-06-12 20:00:45 +02:00
bence 9c380076fa Merged in feature/MAPG-129-add-git-hooks (pull request #102)
MAPG-129 add prepare-commit-msg hook template
2020-06-12 17:48:37 +00:00
bence 95508ad284 MAPG-129 add prepare-commit-msg hook template 2020-06-12 19:47:58 +02:00
bence 7690714b26 MAPG-28 make it possible to configure static root (eg. for static.example.com) 2020-06-12 19:42:55 +02:00
bence f732dad1b1 MAPG-28 add version and revision info in main.php 2020-06-12 19:39:25 +02:00
bence 49b6f28a9d Merged in bugfix/MAPG-130-fix-loading-animation-position (pull request #101)
MAPG-130 loading animation should be fixed and not absolute
2020-06-11 21:53:47 +00:00
bence 33ec9d3a9c MAPG-130 loading animation should be fixed and not absolute 2020-06-11 23:53:15 +02:00
37 changed files with 635 additions and 212 deletions

No files matched your search

+1
View File
@@ -7,3 +7,4 @@ GOOGLE_MAPS_SERVER_API_KEY=your_google_maps_server_api_key
GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key GOOGLE_MAPS_JS_API_KEY=your_google_maps_js_api_key
LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url LEAFLET_TILESERVER_URL=a_leaflet_compatible_tileserver_url
LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles LEAFLET_TILESERVER_ATTRIBUTION=attribution_to_be_shown_for_tiles
STATIC_ROOT=/static
@@ -0,0 +1,24 @@
<?php
use MapGuesser\Database\Query\Modify;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet;
use MapGuesser\Util\Geo\Bounds;
$select = new Select(\Container::$dbConnection, 'maps');
$select->columns(['id', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']);
$result = $select->execute();
\Container::$dbConnection->startTransaction();
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']);
$modify = new Modify(\Container::$dbConnection, 'maps');
$modify->setId($map['id']);
$modify->set('area', $bounds->calculateApproximateArea());
$modify->save();
}
\Container::$dbConnection->commit();
@@ -0,0 +1,4 @@
ALTER TABLE
`maps`
ADD
`area` decimal(13, 4) NOT NULL DEFAULT 0.0;
@@ -0,0 +1,6 @@
CREATE TABLE `sessions` (
`id` varchar(64) NOT NULL,
`data` text NOT NULL,
`updated` timestamp NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
+4 -11
View File
@@ -3,25 +3,18 @@
require 'vendor/autoload.php'; require 'vendor/autoload.php';
const ROOT = __DIR__; const ROOT = __DIR__;
const VERSION = '';
const REVISION = '';
const REVISION_DATE = '';
$dotenv = Dotenv\Dotenv::createImmutable(ROOT); $dotenv = Dotenv\Dotenv::createImmutable(ROOT);
$dotenv->load(); $dotenv->load();
if (!empty($_ENV['DEV'])) {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
ini_set('display_errors', '0');
}
class Container class Container
{ {
static MapGuesser\Interfaces\Database\IConnection $dbConnection; static MapGuesser\Interfaces\Database\IConnection $dbConnection;
static MapGuesser\Routing\RouteCollection $routeCollection; static MapGuesser\Routing\RouteCollection $routeCollection;
static \SessionHandlerInterface $sessionHandler;
} }
Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']); Container::$dbConnection = new MapGuesser\Database\Mysql\Connection($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $_ENV['DB_NAME']);
Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
session_start();
+7 -19
View File
@@ -1,8 +1,7 @@
<?php <?php
require '../main.php'; require '../web.php';
// very basic routing
$host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST']; $host = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
$method = strtolower($_SERVER['REQUEST_METHOD']); $method = strtolower($_SERVER['REQUEST_METHOD']);
$url = substr($_SERVER['REQUEST_URI'], strlen('/')); $url = substr($_SERVER['REQUEST_URI'], strlen('/'));
@@ -11,23 +10,6 @@ if (($pos = strpos($url, '?')) !== false) {
} }
$url = rawurldecode($url); $url = rawurldecode($url);
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
$routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
$routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
$routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
});
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);
$routeCollection->post('admin.saveMap', 'saveMap/{mapId}/json', [MapGuesser\Controller\MapAdminController::class, 'saveMap']);
});
$match = Container::$routeCollection->match($method, explode('/', $url)); $match = Container::$routeCollection->match($method, explode('/', $url));
if ($match !== null) { if ($match !== null) {
@@ -44,6 +26,12 @@ if ($match !== null) {
$authorized = true; $authorized = true;
} }
if ($method === 'post' && $request->post('anti_csrf_token') !== $request->session()->get('anti_csrf_token')) {
header('Content-Type: text/html; charset=UTF-8', true, 403);
echo json_encode(['error' => 'no_valid_anti_csrf_token']);
return;
}
if ($authorized) { if ($authorized) {
$response = call_user_func([$controller, $handler[1]]); $response = call_user_func([$controller, $handler[1]]);
+1 -1
View File
@@ -4,7 +4,7 @@
z-index: 1; z-index: 1;
} }
#cover { #guessCover {
position: absolute; position: absolute;
top: 40px; top: 40px;
left: 0; left: 0;
-23
View File
@@ -1,17 +1,3 @@
#metadata {
position: absolute;
top: 50px;
left: 10px;
padding: 10px;
background-color: #eeeeee;
border: solid 1px #555555;
border-radius: 3px;
box-sizing: border-box;
opacity: 0.95;
z-index: 2;
visibility: hidden;
}
#map { #map {
width: 100%; width: 100%;
height: calc(100% - 40px); height: calc(100% - 40px);
@@ -61,9 +47,6 @@
} }
@media screen and (max-width: 999px) and (min-height: 600px) { @media screen and (max-width: 999px) and (min-height: 600px) {
#metadata {
width: calc(100% - 155px);
}
#map.selected { #map.selected {
height: calc(50% - 20px); height: calc(50% - 20px);
} }
@@ -79,12 +62,6 @@
} }
@media screen and (min-width: 1000px), (max-height: 599px) { @media screen and (min-width: 1000px), (max-height: 599px) {
#metadata {
width: calc(50% - 20px);
}
#metadata.selected {
top: 95px;
}
#map.selected { #map.selected {
width: 50%; width: 50%;
} }
+44 -1
View File
@@ -89,10 +89,18 @@ sub {
margin-top: 10px; margin-top: 10px;
} }
.marginLeft {
margin-left: 10px;
}
.marginBottom { .marginBottom {
margin-bottom: 10px; margin-bottom: 10px;
} }
.marginRight {
margin-right: 10px;
}
.right { .right {
text-align: right; text-align: right;
} }
@@ -230,6 +238,29 @@ input.big:focus, select.big:focus, textarea.big:focus {
padding: 4px; padding: 4px;
} }
div.modal {
position: fixed;
top: 100px;
background-color: #ffffff;
border-radius: 3px;
padding: 20px;
box-sizing: border-box;
z-index: 3;
visibility: hidden;
}
#cover {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: #000000;
opacity: 0.5;
z-index: 2;
visibility: hidden;
}
p.formError { p.formError {
color: #7f2929; color: #7f2929;
font-weight: 500; font-weight: 500;
@@ -285,7 +316,7 @@ div.buttonContainer>button {
} }
#loading { #loading {
position: absolute; position: fixed;
width: 64px; width: 64px;
height: 64px; height: 64px;
top: 50%; top: 50%;
@@ -313,7 +344,19 @@ div.box {
padding: 0; padding: 0;
width: 100%; width: 100%;
} }
div.modal {
left: 20px;
right: 20px;
}
div.box { div.box {
width: initial; width: initial;
} }
} }
@media screen and (min-width: 600px) {
div.modal {
width: 540px;
left: 50%;
margin-left: -270px;
}
}
+8 -1
View File
@@ -26,8 +26,15 @@ div.mapItem>div.title>p.title {
font-size: 18px; font-size: 18px;
} }
div.mapItem>img { div.mapItem>div.imgContainer {
width: 100%; width: 100%;
padding-top: 50%;
background: #cccccc;
}
div.mapItem>div.imgContainer>img {
width: 100%;
margin-top: -50%
} }
div.mapItem>div.inner { div.mapItem>div.inner {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14 -29
View File
@@ -16,7 +16,7 @@
initialize: function () { initialize: function () {
document.getElementById('loading').style.visibility = 'visible'; document.getElementById('loading').style.visibility = 'visible';
document.getElementById('cover').style.visibility = 'visible'; document.getElementById('guessCover').style.visibility = 'visible';
document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS); document.getElementById('currentRound').innerHTML = '1/' + String(Game.NUMBER_OF_ROUNDS);
document.getElementById('currentScoreSum').innerHTML = '0/0'; document.getElementById('currentScoreSum').innerHTML = '0/0';
@@ -25,11 +25,9 @@
}); });
Game.map.fitBounds(mapBounds); Game.map.fitBounds(mapBounds);
var xhr = new XMLHttpRequest(); MapGuesser.httpRequest('GET', '/game/' + mapId + '/newPlace.json', function () {
xhr.responseType = 'json';
xhr.onload = function () {
document.getElementById('loading').style.visibility = 'hidden'; document.getElementById('loading').style.visibility = 'hidden';
document.getElementById('cover').style.visibility = 'hidden'; document.getElementById('guessCover').style.visibility = 'hidden';
if (this.response.error) { if (this.response.error) {
//TODO: handle this error //TODO: handle this error
@@ -51,10 +49,7 @@
} }
Game.startNewRound(); Game.startNewRound();
}; });
xhr.open('GET', '/game/' + mapId + '/newPlace.json', true);
xhr.send();
}, },
reset: function () { reset: function () {
@@ -103,7 +98,7 @@
lastRound.line.setVisible(false); lastRound.line.setVisible(false);
} }
document.getElementById('cover').style.visibility = 'hidden'; document.getElementById('guessCover').style.visibility = 'hidden';
document.getElementById('showGuessButton').style.visibility = null; document.getElementById('showGuessButton').style.visibility = null;
document.getElementById('guess').style.visibility = null; document.getElementById('guess').style.visibility = null;
document.getElementById('guess').classList.remove('result') document.getElementById('guess').classList.remove('result')
@@ -127,16 +122,11 @@
handleErrorResponse: function (error) { handleErrorResponse: function (error) {
// for the time being we only handle the "no_session_found" error and reset the game // for the time being we only handle the "no_session_found" error and reset the game
var xhr = new XMLHttpRequest(); MapGuesser.httpRequest('GET', '/game/' + mapId + '/json', function () {
xhr.responseType = 'json';
xhr.onload = function () {
mapBounds = this.response.bounds; mapBounds = this.response.bounds;
Game.reset(); Game.reset();
}; });
xhr.open('GET', '/game/' + mapId + '/json', true);
xhr.send();
}, },
loadPano: function (panoId) { loadPano: function (panoId) {
@@ -162,15 +152,13 @@
document.getElementById('guess').classList.remove('adapt'); document.getElementById('guess').classList.remove('adapt');
} }
document.getElementById('loading').style.visibility = 'visible'; document.getElementById('loading').style.visibility = 'visible';
document.getElementById('cover').style.visibility = 'visible'; document.getElementById('guessCover').style.visibility = 'visible';
var data = new FormData(); var data = new FormData();
data.append('lat', String(guessPosition.lat)); data.append('lat', String(guessPosition.lat));
data.append('lng', String(guessPosition.lng)); data.append('lng', String(guessPosition.lng));
var xhr = new XMLHttpRequest(); MapGuesser.httpRequest('POST', '/game/' + mapId + '/guess.json', function () {
xhr.responseType = 'json';
xhr.onload = function () {
if (this.response.error) { if (this.response.error) {
Game.handleErrorResponse(this.response.error); Game.handleErrorResponse(this.response.error);
return; return;
@@ -211,10 +199,7 @@
} }
Game.panoId = this.response.panoId; Game.panoId = this.response.panoId;
}; }, data);
xhr.open('POST', '/game/' + mapId + '/guess.json', true);
xhr.send(data);
}, },
addRealGuessPair: function (position, guessPosition, hidden) { addRealGuessPair: function (position, guessPosition, hidden) {
@@ -229,7 +214,7 @@
clickable: true, clickable: true,
draggable: false, draggable: false,
icon: { icon: {
url: '/static/img/markers/marker-green.svg', url: STATIC_ROOT + '/img/markers/marker-green.svg?rev=' + REVISION,
size: new google.maps.Size(24, 32), size: new google.maps.Size(24, 32),
scaledSize: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32),
anchor: new google.maps.Point(12, 32) anchor: new google.maps.Point(12, 32)
@@ -248,7 +233,7 @@
clickable: false, clickable: false,
draggable: false, draggable: false,
icon: { icon: {
url: '/static/img/markers/marker-gray-empty.svg', url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
size: new google.maps.Size(24, 32), size: new google.maps.Size(24, 32),
scaledSize: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32),
anchor: new google.maps.Point(12, 32), anchor: new google.maps.Point(12, 32),
@@ -319,7 +304,7 @@
var round = Game.rounds[i]; var round = Game.rounds[i];
round.realMarker.setIcon({ round.realMarker.setIcon({
url: '/static/img/markers/marker-green-empty.svg', url: STATIC_ROOT + '/img/markers/marker-green-empty.svg?rev=' + REVISION,
size: new google.maps.Size(24, 32), size: new google.maps.Size(24, 32),
scaledSize: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32),
anchor: new google.maps.Point(12, 32), anchor: new google.maps.Point(12, 32),
@@ -411,7 +396,7 @@
clickable: false, clickable: false,
draggable: true, draggable: true,
icon: { icon: {
url: '/static/img/markers/marker-gray-empty.svg', url: STATIC_ROOT + '/img/markers/marker-gray-empty.svg?rev=' + REVISION,
size: new google.maps.Size(24, 32), size: new google.maps.Size(24, 32),
scaledSize: new google.maps.Size(24, 32), scaledSize: new google.maps.Size(24, 32),
anchor: new google.maps.Point(12, 32), anchor: new google.maps.Point(12, 32),
+2 -7
View File
@@ -8,9 +8,7 @@
var formData = new FormData(form); var formData = new FormData(form);
var xhr = new XMLHttpRequest(); MapGuesser.httpRequest('POST', form.action, function () {
xhr.responseType = 'json';
xhr.onload = function () {
if (this.response.error) { if (this.response.error) {
var errorText; var errorText;
switch (this.response.error) { switch (this.response.error) {
@@ -34,9 +32,6 @@
} }
window.location.replace('/'); window.location.replace('/');
}; }, formData);
xhr.open('POST', form.action, true);
xhr.send(formData);
}; };
})(); })();
+10 -28
View File
@@ -22,15 +22,13 @@
document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]'; document.getElementById('mapName').innerHTML = form.elements.name.value ? form.elements.name.value : '[unnamed map]';
document.getElementById('metadata').style.visibility = 'hidden'; MapGuesser.hideModal();
document.getElementById('saveButton').disabled = false; document.getElementById('saveButton').disabled = false;
}, },
getPlace: function (placeId, marker) { getPlace: function (placeId, marker) {
var xhr = new XMLHttpRequest(); MapGuesser.httpRequest('GET', '/admin/place.json/' + placeId, function () {
xhr.responseType = 'json';
xhr.onload = function () {
document.getElementById('loading').style.visibility = 'hidden'; document.getElementById('loading').style.visibility = 'hidden';
if (!this.response.panoId) { if (!this.response.panoId) {
@@ -46,10 +44,7 @@
places[marker.placeId].noPano = false; places[marker.placeId].noPano = false;
MapEditor.loadPano(this.response.panoId, places[marker.placeId].pov); MapEditor.loadPano(this.response.panoId, places[marker.placeId].pov);
}; });
xhr.open('GET', '/admin/place.json/' + placeId, true);
xhr.send();
}, },
loadPano: function (panoId, pov) { loadPano: function (panoId, pov) {
@@ -114,7 +109,6 @@
return; return;
} }
document.getElementById('metadata').classList.add('selected');
document.getElementById('map').classList.add('selected'); document.getElementById('map').classList.add('selected');
document.getElementById('control').classList.add('selected'); document.getElementById('control').classList.add('selected');
document.getElementById('noPano').style.visibility = 'hidden'; document.getElementById('noPano').style.visibility = 'hidden';
@@ -220,7 +214,6 @@
}, },
closePlace: function (del) { closePlace: function (del) {
document.getElementById('metadata').classList.remove('selected')
document.getElementById('map').classList.remove('selected'); document.getElementById('map').classList.remove('selected');
document.getElementById('control').classList.remove('selected'); document.getElementById('control').classList.remove('selected');
document.getElementById('noPano').style.visibility = 'hidden'; document.getElementById('noPano').style.visibility = 'hidden';
@@ -284,9 +277,7 @@
data.append('deleted[]', JSON.stringify(MapEditor.deleted[placeId])); data.append('deleted[]', JSON.stringify(MapEditor.deleted[placeId]));
} }
var xhr = new XMLHttpRequest(); MapGuesser.httpRequest('POST', '/admin/saveMap/' + mapId + '/json', function () {
xhr.responseType = 'json';
xhr.onload = function () {
document.getElementById('loading').style.visibility = 'hidden'; document.getElementById('loading').style.visibility = 'hidden';
if (this.response.error) { if (this.response.error) {
@@ -310,10 +301,7 @@
document.getElementById('deleted').innerHTML = '0'; document.getElementById('deleted').innerHTML = '0';
document.getElementById('saveButton').disabled = true; document.getElementById('saveButton').disabled = true;
}; }, data);
xhr.open('POST', '/admin/saveMap/' + mapId + '/json', true);
xhr.send(data);
}, },
replacePlaceIdsToReal: function (addedPlaces) { replacePlaceIdsToReal: function (addedPlaces) {
@@ -327,17 +315,17 @@
var IconCollection = { var IconCollection = {
iconGreen: L.icon({ iconGreen: L.icon({
iconUrl: '/static/img/markers/marker-green.svg', iconUrl: STATIC_ROOT + '/img/markers/marker-green.svg?rev' + REVISION,
iconSize: [24, 32], iconSize: [24, 32],
iconAnchor: [12, 32] iconAnchor: [12, 32]
}), }),
iconRed: L.icon({ iconRed: L.icon({
iconUrl: '/static/img/markers/marker-red.svg', iconUrl: STATIC_ROOT + '/img/markers/marker-red.svg?rev=' + REVISION,
iconSize: [24, 32], iconSize: [24, 32],
iconAnchor: [12, 32] iconAnchor: [12, 32]
}), }),
iconBlue: L.icon({ iconBlue: L.icon({
iconUrl: '/static/img/markers/marker-blue.svg', iconUrl: STATIC_ROOT + '/img/markers/marker-blue.svg?rev=' + REVISION,
iconSize: [24, 32], iconSize: [24, 32],
iconAnchor: [12, 32] iconAnchor: [12, 32]
}), }),
@@ -423,14 +411,8 @@
document.getElementById('mapName').onclick = function (e) { document.getElementById('mapName').onclick = function (e) {
e.preventDefault(); e.preventDefault();
var metadata = document.getElementById('metadata'); MapGuesser.showModal('metadata');
if (metadata.style.visibility === 'visible') {
metadata.style.visibility = 'hidden';
} else {
metadata.style.visibility = 'visible';
document.getElementById('metadataForm').elements.name.select(); document.getElementById('metadataForm').elements.name.select();
}
}; };
document.getElementById('metadataForm').onsubmit = function (e) { document.getElementById('metadataForm').onsubmit = function (e) {
@@ -440,7 +422,7 @@
}; };
document.getElementById('closeMetadataButton').onclick = function () { document.getElementById('closeMetadataButton').onclick = function () {
document.getElementById('metadata').style.visibility = 'hidden'; MapGuesser.hideModal();
}; };
document.getElementById('saveButton').onclick = function () { document.getElementById('saveButton').onclick = function () {
+108
View File
@@ -0,0 +1,108 @@
var MapGuesser = {
httpRequest: function (method, url, callback, data) {
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.onload = callback;
xhr.open(method, url, true);
if (method === 'POST') {
if (typeof data === 'undefined') {
data = new FormData();
}
data.append('anti_csrf_token', ANTI_CSRF_TOKEN);
xhr.send(data);
} else {
xhr.send();
}
},
showModal: function (id) {
document.getElementById(id).style.visibility = 'visible';
document.getElementById('cover').style.visibility = 'visible';
},
showModalWithContent: function (title, body, extraButtons) {
if (typeof extraButtons === 'undefined') {
extraButtons = [];
}
MapGuesser.showModal('modal');
document.getElementById('modalTitle').textContent = title;
if (typeof body === 'object') {
document.getElementById('modalText').appendChild(body);
} else {
document.getElementById('modalText').textContent = body;
}
var buttons = document.getElementById('modalButtons');
buttons.textContent = '';
for (var i = 0; i < extraButtons.length; i++) {
var extraButton = extraButtons[i];
var button = document.createElement(extraButton.type);
if (extraButton.type === 'a') {
button.classList.add('button');
}
for (var i = 0; i < extraButton.classNames.length; i++) {
button.classList.add(extraButton.classNames[i]);
}
button.classList.add('marginTop');
button.classList.add('marginRight');
button.textContent = extraButton.text;
if (extraButton.type === 'a') {
button.href = extraButton.href;
} else if (extraButton.type === 'button') {
button.onclick = extraButton.onclick;
}
buttons.appendChild(button);
}
var closeButton = document.createElement('button');
closeButton.classList.add('gray');
closeButton.classList.add('marginTop');
closeButton.textContent = 'Cancel';
closeButton.onclick = function () {
MapGuesser.hideModal();
};
buttons.appendChild(closeButton);
},
hideModal: function () {
var modals = document.getElementsByClassName('modal');
for (var i = 0; i < modals.length; i++) {
modals[i].style.visibility = 'hidden';
}
document.getElementById('cover').style.visibility = 'hidden';
}
};
(function () {
var anchors = document.getElementsByTagName('a');
for (var i = 0; i < anchors.length; i++) {
var a = anchors[i];
if (a.href !== 'javascript:;' && a.target !== '_blank') {
a.onclick = function () {
document.getElementById('loading').style.visibility = 'visible';
}
}
}
document.getElementById('cover').onclick = function () {
MapGuesser.hideModal();
};
})();
+1 -6
View File
@@ -74,8 +74,7 @@ def updateAppInWorktree(worktreePath):
subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath) subprocess.call([worktreePath + "/scripts/update.sh"], cwd=worktreePath)
def updateAppVersionInWorktree(worktreePath): def updateAppVersionInWorktree(worktreePath):
pass subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath)
#subprocess.call([worktreePath + "/scripts/update-version.sh"], cwd=worktreePath)
worktrees = getDataForWorktrees() worktrees = getDataForWorktrees()
@@ -111,10 +110,6 @@ if developmentWorktree.revision != developmentWorktree.newRevision:
elif developmentWorktree.version != developmentWorktree.newVersion: elif developmentWorktree.version != developmentWorktree.newVersion:
print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED") print("-> DEVELOPMENT " + developmentWorktree.path + "'s version info will be UPDATED")
checkoutWorktree(developmentWorktree.path, developmentWorktree.branch)
print(developmentWorktree.path + " is checked out to " + developmentWorktree.branch)
updateAppVersionInWorktree(developmentWorktree.path) updateAppVersionInWorktree(developmentWorktree.path)
print("MapGuesser version is updated in " + developmentWorktree.path) print("MapGuesser version is updated in " + developmentWorktree.path)
+15
View File
@@ -0,0 +1,15 @@
#!/bin/bash
BRANCH_NAME=$(git symbolic-ref --short HEAD)
BRANCH_PATTERN="^(bugfix|feature|hotfix)\/([A-Z]+-[0-9]+).*"
if [[ "${BRANCH_NAME}" =~ $BRANCH_PATTERN ]]; then
TICKET_ID=$(echo $BRANCH_NAME | sed -E "s@$BRANCH_PATTERN@\\2@")
COMMIT_MESSAGE=$(head -n 1 $1)
COMMIT_MESSAGE_REGEX="^$TICKET_ID .*"
if [[ ! "${COMMIT_MESSAGE}" =~ $COMMIT_MESSAGE_REGEX ]]; then
sed -i.bak -e "1s/^/$TICKET_ID /" $1
fi
fi
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
ROOT_DIR=$(dirname $(readlink -f "$0"))/..
. ${ROOT_DIR}/.env
cd ${ROOT_DIR}
echo "Updating version info..."
VERSION=$(git describe --tags --always --match "Release_*" HEAD)
REVISION=$(git rev-parse --short HEAD)
REVISION_DATE=$(git show -s --format=%aI HEAD)
sed -i -E "s/const VERSION = '(.*)';/const VERSION = '${VERSION}';/" main.php
sed -i -E "s/const REVISION = '(.*)';/const REVISION = '${REVISION}';/" main.php
sed -i -E "s/const REVISION_DATE = '(.*)';/const REVISION_DATE = '${REVISION_DATE}';/" main.php
+1 -1
View File
@@ -44,7 +44,7 @@ class GameController
if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) { if (!($state = $session->get('state')) || $state['mapId'] !== $mapId) {
$session->set('state', [ $session->set('state', [
'mapId' => $mapId, 'mapId' => $mapId,
'area' => $bounds->calculateApproximateArea(), 'area' => $map['area'],
'rounds' => [] 'rounds' => []
]); ]);
} }
+1 -1
View File
@@ -123,7 +123,7 @@ class GameFlowController
private function calculateScore(float $distance, float $area): int private function calculateScore(float $distance, float $area): int
{ {
$goodness = 1.0 - ($distance / sqrt($area)); $goodness = 1.0 - ($distance / (sqrt($area) * 1000));
return (int) round(pow(static::MAX_SCORE, $goodness)); return (int) round(pow(static::MAX_SCORE, $goodness));
} }
+38 -1
View File
@@ -74,6 +74,8 @@ class MapAdminController implements ISecured
{ {
$mapId = (int) $this->request->query('mapId'); $mapId = (int) $this->request->query('mapId');
\Container::$dbConnection->startTransaction();
if (!$mapId) { if (!$mapId) {
$mapId = $this->addNewMap(); $mapId = $this->addNewMap();
} }
@@ -118,7 +120,8 @@ class MapAdminController implements ISecured
'bound_south_lat' => $mapBounds->getSouthLat(), 'bound_south_lat' => $mapBounds->getSouthLat(),
'bound_west_lng' => $mapBounds->getWestLng(), 'bound_west_lng' => $mapBounds->getWestLng(),
'bound_north_lat' => $mapBounds->getNorthLat(), 'bound_north_lat' => $mapBounds->getNorthLat(),
'bound_east_lng' => $mapBounds->getEastLng() 'bound_east_lng' => $mapBounds->getEastLng(),
'area' => $mapBounds->calculateApproximateArea(),
]; ];
if (isset($_POST['name'])) { if (isset($_POST['name'])) {
@@ -130,10 +133,44 @@ class MapAdminController implements ISecured
$this->saveMapData($mapId, $map); $this->saveMapData($mapId, $map);
\Container::$dbConnection->commit();
$data = ['mapId' => $mapId, 'added' => $addedIds]; $data = ['mapId' => $mapId, 'added' => $addedIds];
return new JsonContent($data); return new JsonContent($data);
} }
public function deleteMap() {
$mapId = (int) $this->request->query('mapId');
\Container::$dbConnection->startTransaction();
$this->deletePlaces($mapId);
$modify = new Modify(\Container::$dbConnection, 'maps');
$modify->setId($mapId);
$modify->delete();
\Container::$dbConnection->commit();
$data = ['success' => true];
return new JsonContent($data);
}
private function deletePlaces(int $mapId): void
{
$select = new Select(\Container::$dbConnection, 'places');
$select->columns(['id']);
$select->where('map_id', '=', $mapId);
$result = $select->execute();
while ($place = $result->fetch(IResultSet::FETCH_ASSOC)) {
$modify = new Modify(\Container::$dbConnection, 'places');
$modify->setId($place['id']);
$modify->delete();
}
}
private function calculateMapBounds(int $mapId): Bounds private function calculateMapBounds(int $mapId): Bounds
{ {
$select = new Select(\Container::$dbConnection, 'places'); $select = new Select(\Container::$dbConnection, 'places');
+18 -20
View File
@@ -6,7 +6,6 @@ use MapGuesser\Interfaces\Authentication\IUser;
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\Util\Geo\Bounds;
use MapGuesser\Response\HtmlContent; use MapGuesser\Response\HtmlContent;
class MapsController class MapsController
@@ -29,6 +28,7 @@ class MapsController
['maps', 'bound_west_lng'], ['maps', 'bound_west_lng'],
['maps', 'bound_north_lat'], ['maps', 'bound_north_lat'],
['maps', 'bound_east_lng'], ['maps', 'bound_east_lng'],
['maps', 'area'],
new RawExpression('COUNT(places.id) AS num_places') new RawExpression('COUNT(places.id) AS num_places')
]); ]);
$select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']); $select->leftJoin('places', ['places', 'map_id'], '=', ['maps', 'id']);
@@ -39,9 +39,7 @@ class MapsController
$maps = []; $maps = [];
while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) { while ($map = $result->fetch(IResultSet::FETCH_ASSOC)) {
$bounds = Bounds::createDirectly($map['bound_south_lat'], $map['bound_west_lng'], $map['bound_north_lat'], $map['bound_east_lng']); $map['area'] = $this->formatMapAreaForHuman($map['area']);
$map['area'] = $this->formatMapAreaForHuman($bounds->calculateApproximateArea());
$maps[] = $map; $maps[] = $map;
} }
@@ -53,29 +51,29 @@ class MapsController
private function formatMapAreaForHuman(float $area): array private function formatMapAreaForHuman(float $area): array
{ {
if ($area < 100.0) { if ($area < 0.01) {
$digits = 0;
$rounded = round($area * 1000000.0, -2);
$unit = 'm';
} elseif ($area < 0.1) {
$digits = 0;
$rounded = round($area * 1000000.0, -3);
$unit = 'm';
} elseif ($area < 1.0) {
$digits = 2;
$rounded = round($area, 2);
$unit = 'km';
} elseif ($area < 100.0) {
$digits = 0; $digits = 0;
$rounded = round($area, 0); $rounded = round($area, 0);
$unit = 'm'; $unit = 'km';
} elseif ($area < 100000.0) { } elseif ($area < 10000.0) {
$digits = 0; $digits = 0;
$rounded = round($area, -2); $rounded = round($area, -2);
$unit = 'm';
} elseif ($area < 1000000.0) {
$digits = 2;
$rounded = round($area / 1000000.0, 2);
$unit = 'km';
} elseif ($area < 100000000.0) {
$digits = 0;
$rounded = round($area / 1000000.0, 0);
$unit = 'km';
} elseif ($area < 10000000000.0) {
$digits = 0;
$rounded = round($area / 1000000.0, -2);
$unit = 'km'; $unit = 'km';
} else { } else {
$digits = 0; $digits = 0;
$rounded = round($area / 1000000.0, -4); $rounded = round($area, -4);
$unit = 'km'; $unit = 'km';
} }
+17 -3
View File
@@ -16,6 +16,8 @@ class Modify
private array $original = []; private array $original = [];
private ?string $externalId = null;
private bool $autoIncrement = true; private bool $autoIncrement = true;
public function __construct(IConnection $connection, string $table) public function __construct(IConnection $connection, string $table)
@@ -31,6 +33,13 @@ class Modify
return $this; return $this;
} }
public function setExternalId($id): Modify
{
$this->externalId = $id;
return $this;
}
public function setAutoIncrement(bool $autoIncrement = true): Modify public function setAutoIncrement(bool $autoIncrement = true): Modify
{ {
$this->autoIncrement = $autoIncrement; $this->autoIncrement = $autoIncrement;
@@ -87,7 +96,9 @@ class Modify
private function insert(): void private function insert(): void
{ {
if (!$this->autoIncrement) { if ($this->externalId !== null) {
$this->attributes[$this->idName] = $this->externalId;
} elseif (!$this->autoIncrement) {
$this->attributes[$this->idName] = $this->generateKey(); $this->attributes[$this->idName] = $this->generateKey();
} }
@@ -105,11 +116,14 @@ class Modify
private function update(): void private function update(): void
{ {
$diff = $this->generateDiff(); /*$diff = $this->generateDiff();
if (count($diff) === 0) { if (count($diff) === 0) {
return; return;
} }*/
$diff = $this->attributes;
unset($diff[$this->idName]);
$set = $this->generateColumnsWithBinding(array_keys($diff)); $set = $this->generateColumnsWithBinding(array_keys($diff));
+1 -1
View File
@@ -8,7 +8,7 @@ class MapRepository
public function getById(int $mapId) public function getById(int $mapId)
{ {
$select = new Select(\Container::$dbConnection, 'maps'); $select = new Select(\Container::$dbConnection, 'maps');
$select->columns(['id', 'name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng']); $select->columns(['id', 'name', 'description', 'bound_south_lat', 'bound_west_lng', 'bound_north_lat', 'bound_east_lng', 'area']);
$select->whereId($mapId); $select->whereId($mapId);
return $select->execute()->fetch(IResultSet::FETCH_ASSOC); return $select->execute()->fetch(IResultSet::FETCH_ASSOC);
+113
View File
@@ -0,0 +1,113 @@
<?php namespace MapGuesser\Session;
use DateTime;
use MapGuesser\Database\Query\Modify;
use MapGuesser\Database\Query\Select;
use MapGuesser\Interfaces\Database\IResultSet;
use SessionHandlerInterface;
use SessionIdInterface;
use SessionUpdateTimestampHandlerInterface;
class DatabaseSessionHandler implements SessionHandlerInterface, SessionIdInterface, SessionUpdateTimestampHandlerInterface
{
private bool $exists = false;
private bool $written = false;
public function open($savePath, $sessionName): bool
{
return true;
}
public function close(): bool
{
return true;
}
public function read($id): string
{
$select = new Select(\Container::$dbConnection, 'sessions');
$select->columns(['data']);
$select->whereId($id);
$result = $select->execute()->fetch(IResultSet::FETCH_ASSOC);
if ($result === null) {
return '';
}
$this->exists = true;
return $result['data'];
}
public function write($id, $data): bool
{
$modify = new Modify(\Container::$dbConnection, 'sessions');
if ($this->exists) {
$modify->setId($id);
} else {
$modify->setExternalId($id);
}
$modify->set('data', $data);
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
$modify->save();
$written = true;
return true;
}
public function destroy($id): bool
{
$modify = new Modify(\Container::$dbConnection, 'sessions');
$modify->setId($id);
$modify->delete();
return true;
}
public function gc($maxlifetime): int
{
$select = new Select(\Container::$dbConnection, 'sessions');
$select->columns(['id']);
$select->where('updated', '<', (new DateTime('-' . $maxlifetime . ' seconds'))->format('Y-m-d H:i:s'));
$result = $select->execute();
while ($session = $result->fetch(IResultSet::FETCH_ASSOC)) {
$modify = new Modify(\Container::$dbConnection, 'sessions');
$modify->setId($session['id']);
$modify->delete();
}
return true;
}
public function create_sid(): string
{
return hash('sha256', random_bytes(10) . microtime());
}
public function validateId($id): bool
{
return preg_match('/^[a-f0-9]{64}$/', $id);
}
public function updateTimestamp($id, $data): bool
{
if ($this->written) {
return true;
}
$modify = new Modify(\Container::$dbConnection, 'sessions');
$modify->setId($id);
$modify->set('updated', (new DateTime())->format('Y-m-d H:i:s'));
$modify->save();
return true;
}
}
+4 -4
View File
@@ -2,7 +2,7 @@
class Bounds class Bounds
{ {
const ONE_DEGREE_OF_LATITUDE_IN_METER = 111132.954; const ONE_DEGREE_OF_LATITUDE_IN_KM = 111.132954;
private float $southLat = 90.0; private float $southLat = 90.0;
private float $westLng = 180.0; private float $westLng = 180.0;
@@ -84,9 +84,9 @@ class Bounds
$dLat = $this->northLat - $this->southLat; $dLat = $this->northLat - $this->southLat;
$dLng = $this->eastLng - $this->westLng; $dLng = $this->eastLng - $this->westLng;
$m = $dLat * static::ONE_DEGREE_OF_LATITUDE_IN_METER; $m = $dLat * static::ONE_DEGREE_OF_LATITUDE_IN_KM;
$a = $dLng * static::ONE_DEGREE_OF_LATITUDE_IN_METER * cos(deg2rad($this->northLat)); $a = $dLng * static::ONE_DEGREE_OF_LATITUDE_IN_KM * cos(deg2rad($this->northLat));
$c = $dLng * static::ONE_DEGREE_OF_LATITUDE_IN_METER * cos(deg2rad($this->southLat)); $c = $dLng * static::ONE_DEGREE_OF_LATITUDE_IN_KM * cos(deg2rad($this->southLat));
return $m * ($a + $c) / 2; return $m * ($a + $c) / 2;
} }
+25 -15
View File
@@ -1,18 +1,31 @@
<?php $cssFiles = ['/static/node_modules/leaflet/dist/leaflet.css', '/static/css/map_editor.css', '/static/node_modules/leaflet.markercluster/dist/MarkerCluster.css', '/static/node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css']; ?> <?php
$cssFiles = [
'node_modules/leaflet/dist/leaflet.css',
'css/map_editor.css',
'node_modules/leaflet.markercluster/dist/MarkerCluster.css',
'node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css'
];
$jsFiles = [
'node_modules/leaflet/dist/leaflet.js',
'node_modules/leaflet.markercluster/dist/leaflet.markercluster.js',
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'],
'js/map_editor.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?> <?php require ROOT . '/views/templates/main_header.php'; ?>
<div class="header small"> <div class="header small">
<div class="grid"> <div class="grid">
<h1> <h1>
<a href="/maps" title="Back to playable maps"> <a href="/maps" title="Back to playable maps">
<img class="inline" src="/static/img/icon.svg"> <img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
<span>MapGuesser</span> <span>MapGuesser</span>
</a> </a>
</h1> </h1>
<p> <p>
<span class="bold"><a href="javascript:;" id="mapName"><?= $mapName ?></a></span><!-- <span class="bold"><a href="javascript:;" id="mapName" title="Edit map data"><?= $mapName ?></a></span><!--
--><span><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> --><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/> <path fill-rule="evenodd" d="M8 3.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5H4a.5.5 0 0 1 0-1h3.5V4a.5.5 0 0 1 .5-.5z"/>
<path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/> <path fill-rule="evenodd" d="M7.5 8a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H8.5V12a.5.5 0 0 1-1 0V8z"/>
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
@@ -21,14 +34,14 @@
--></span><!-- --></span><!--
--><span><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> --><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path d="M15.502 1.94a.5.5 0 0 1 0 .706a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456l-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
</svg> </svg>
<span id="edited" class="bold">0</span><!-- <span id="edited" class="bold">0</span><!--
--></span><!-- --></span><!--
--><span><!-- --><span><!--
<?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?> <?php /* Copyright (c) 2019 The Bootstrap Authors. License can be found in 'USED_SOFTWARE' in section 'Bootstrap Icons'. */ ?>
--><svg class="inline" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> --><svg class="inline" width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/> <path fill-rule="evenodd" d="M3.5 8a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.5-.5z"/>
<path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> <path fill-rule="evenodd" d="M14 1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/>
</svg> </svg>
@@ -37,13 +50,14 @@
</p> </p>
</div> </div>
</div> </div>
<div id="metadata"> <div id="metadata" class="modal">
<form id="metadataForm"> <h2>Edit map data</h2>
<form id="metadataForm" class="marginTop">
<input class="fullWidth" type="text" name="name" value="<?= $mapName ?>" placeholder="Name of the map"> <input class="fullWidth" type="text" name="name" value="<?= $mapName ?>" placeholder="Name of the map">
<textarea class="fullWidth marginTop" name="description" rows="4" placeholder="Description of the map"><?= $mapDescription ?></textarea> <textarea class="fullWidth marginTop" name="description" rows="4" placeholder="Description of the map"><?= $mapDescription ?></textarea>
<div style="text-align: right;"> <div class="right">
<button id="closeMetadataButton" class="gray marginTop" type="button">Close</button> <button class="marginTop marginRight" type="submit">Apply</button><!--
<button class="marginTop" type="submit">Apply</button> --><button id="closeMetadataButton" class="gray marginTop" type="button">Close</button>
</div> </div>
</form> </form>
</div> </div>
@@ -67,8 +81,4 @@
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>
<script src="/static/node_modules/leaflet/dist/leaflet.js"></script>
<script src="/static/node_modules/leaflet.markercluster/dist/leaflet.markercluster.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
<script src="/static/js/map_editor.js"></script>
<?php require ROOT . '/views/templates/main_footer.php'; ?> <?php require ROOT . '/views/templates/main_footer.php'; ?>
+11 -5
View File
@@ -1,10 +1,18 @@
<?php $cssFiles = ['/static/css/game.css']; ?> <?php
$cssFiles = [
'css/game.css'
];
$jsFiles = [
'https://maps.googleapis.com/maps/api/js?key=' . $_ENV['GOOGLE_MAPS_JS_API_KEY'],
'js/game.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?> <?php require ROOT . '/views/templates/main_header.php'; ?>
<div class="header small"> <div class="header small">
<div class="grid"> <div class="grid">
<h1> <h1>
<a href="/maps" title="Back to playable maps"> <a href="/maps" title="Back to playable maps">
<img class="inline" src="/static/img/icon.svg"> <img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
<span>MapGuesser</span> <span>MapGuesser</span>
</a> </a>
</h1> </h1>
@@ -15,7 +23,7 @@
</p> </p>
</div> </div>
</div> </div>
<div id="cover"></div> <div id="guessCover"></div>
<div id="panorama"></div> <div id="panorama"></div>
<div id="showGuessButtonContainer"> <div id="showGuessButtonContainer">
<button id="showGuessButton" class="fullWidth">Show guess map</button> <button id="showGuessButton" class="fullWidth">Show guess map</button>
@@ -53,6 +61,4 @@
var mapId = <?= $mapId ?>; var mapId = <?= $mapId ?>;
var mapBounds = <?= json_encode($bounds) ?>; var mapBounds = <?= json_encode($bounds) ?>;
</script> </script>
<script src="https://maps.googleapis.com/maps/api/js?key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"></script>
<script src="/static/js/game.js"></script>
<?php require ROOT . '/views/templates/main_footer.php'; ?> <?php require ROOT . '/views/templates/main_footer.php'; ?>
+5 -1
View File
@@ -1,3 +1,8 @@
<?php
$jsFiles = [
'js/login.js',
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?> <?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?> <?php require ROOT . '/views/templates/header.php'; ?>
<div class="main"> <div class="main">
@@ -13,5 +18,4 @@
</form> </form>
</div> </div>
</div> </div>
<script src="/static/js/login.js"></script>
<?php require ROOT . '/views/templates/main_footer.php'; ?> <?php require ROOT . '/views/templates/main_footer.php'; ?>
+50 -8
View File
@@ -1,15 +1,21 @@
<?php $cssFiles = ['/static/css/maps.css']; ?> <?php
$cssFiles = [
'css/maps.css'
];
?>
<?php require ROOT . '/views/templates/main_header.php'; ?> <?php require ROOT . '/views/templates/main_header.php'; ?>
<?php require ROOT . '/views/templates/header.php'; ?> <?php require ROOT . '/views/templates/header.php'; ?>
<div class="main"> <div class="main">
<h2>Playable maps</h2> <h2>Playable maps</h2>
<div id="mapContainer"> <div id="mapContainer">
<?php foreach ($maps as $map) : ?> <?php foreach ($maps as $map): ?>
<div class="mapItem"> <div class="mapItem">
<div class="title"> <div class="title">
<p class="title"><?= $map['name'] ?></p> <p class="title"><?= $map['name'] ?></p>
</div> </div>
<img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>"> <div class="imgContainer">
<img src="https://maps.googleapis.com/maps/api/staticmap?size=350x175&visible=<?= $map['bound_south_lat'] . ',' . $map['bound_west_lng'] . '|' . $map['bound_north_lat'] . ',' . $map['bound_east_lng'] ?>&key=<?= $_ENV['GOOGLE_MAPS_JS_API_KEY'] ?>" alt="Map area">
</div>
<div class="inner"> <div class="inner">
<div class="info"> <div class="info">
<p> <p>
@@ -32,18 +38,18 @@
</div> </div>
<?php if ($isAdmin): ?> <?php if ($isAdmin): ?>
<div class="buttonContainer"> <div class="buttonContainer">
<a class="button fullWidth noRightRadius" href="game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a> <a class="button fullWidth noRightRadius" href="/game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<a class="button yellow fullWidth noLeftRadius noRightRadius" href="admin/mapEditor/<?= $map['id']; ?>" title="Edit map '<?= $map['name'] ?>'">Edit</a> <a class="button yellow fullWidth noLeftRadius noRightRadius" href="/admin/mapEditor/<?= $map['id']; ?>" title="Edit map '<?= $map['name'] ?>'">Edit</a>
<button class="button red fullWidth noLeftRadius" title="Delete map '<?= $map['name'] ?>'">Delete</button> <button class="button red fullWidth noLeftRadius deleteButton" data-map-id="<?= $map['id'] ?>" data-map-name="<?= htmlspecialchars($map['name']) ?>" title="Delete map '<?= $map['name'] ?>'">Delete</button>
</div> </div>
<?php else: ?> <?php else: ?>
<a class="button fullWidth" href="game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a> <a class="button fullWidth" href="/game/<?= $map['id']; ?>" title="Play map '<?= $map['name'] ?>'">Play this map</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php if ($isAdmin): ?> <?php if ($isAdmin): ?>
<div class="mapItem new"> <div class="mapItem new">
<a class="button green fullWidth" href="admin/mapEditor" title="Add new map"> <a class="button green fullWidth" href="/admin/mapEditor" title="Add new map">
Add new map Add new map
</a> </a>
</div> </div>
@@ -57,4 +63,40 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
<?php if ($isAdmin): ?>
<script>
(function() {
Maps = {
deleteMap: function(mapId, mapName) {
MapGuesser.showModalWithContent('Delete map', 'Are you sure you want to delete map \'' + mapName + '\'?', [{
type: 'button',
classNames: ['red'],
text: 'Delete',
onclick: function () {
document.getElementById('loading').style.visibility = 'visible';
MapGuesser.httpRequest('POST', '/admin/deleteMap/' + mapId, function () {
if (this.response.error) {
document.getElementById('loading').style.visibility = 'hidden';
//TODO: handle this error
return;
}
window.location.reload();
});
}
}]);
}
};
var buttons = document.getElementById('mapContainer').getElementsByClassName('deleteButton');
for (var button of buttons) {
button.onclick = function() {
Maps.deleteMap(this.dataset.mapId, this.dataset.mapName);
};
}
})();
</script>
<?php endif; ?>
<?php require ROOT . '/views/templates/main_footer.php'; ?> <?php require ROOT . '/views/templates/main_footer.php'; ?>
+1 -1
View File
@@ -1,7 +1,7 @@
<div class="header"> <div class="header">
<h1> <h1>
<a href="/" title="MapGuesser"> <a href="/" title="MapGuesser">
<img class="inline" src="/static/img/icon.svg"> <img class="inline" width="1em" height="1em" src="<?= $_ENV['STATIC_ROOT'] ?>/img/icon.svg?rev=<?= REVISION ?>">
MapGuesser MapGuesser
</a> </a>
</h1> </h1>
+14 -11
View File
@@ -1,15 +1,18 @@
<script> <script>
(function () { const STATIC_ROOT = '<?= $_ENV['STATIC_ROOT'] ?>';
var anchors = document.getElementsByTagName('a'); const REVISION = '<?= REVISION ?>';
for (var i = 0; i < anchors.length; i++) { const ANTI_CSRF_TOKEN = '<?= $_SESSION['anti_csrf_token'] ?>';
var a = anchors[i];
if (a.href !== 'javascript:;' && a.target !== '_blank') {
a.onclick = function () {
document.getElementById('loading').style.visibility = 'visible';
}
}
}
})();
</script> </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; ?>
</body> </body>
</html> </html>
+17 -6
View File
@@ -4,19 +4,30 @@
<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">
<title>MapGuesser</title> <title>MapGuesser</title>
<link href="/static/css/mapguesser.css" rel="stylesheet"> <link href="<?= $_ENV['STATIC_ROOT'] ?>/css/mapguesser.css?rev=<?= REVISION ?>" rel="stylesheet">
<?php if (isset($cssFiles)) : ?> <?php if (isset($cssFiles)) : ?>
<?php foreach ($cssFiles as $cssFile) : ?> <?php foreach ($cssFiles as $cssFile) : ?>
<?php
if (!preg_match('/^http(s)?/', $cssFile)) {
$cssFile = $_ENV['STATIC_ROOT'] . '/' . $cssFile . '?rev=' . REVISION;
}
?>
<link href="<?= $cssFile ?>" rel="stylesheet"> <link href="<?= $cssFile ?>" rel="stylesheet">
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?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">
<link rel="icon" type="image/png" sizes="192x192" href="/static/img/favicon/192x192.png"> <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="/static/img/favicon/96x96.png"> <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="/static/img/favicon/32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/32x32.png?rev=<?= REVISION ?>">
<link rel="icon" type="image/png" sizes="16x16" href="/static/img/favicon/16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="<?= $_ENV['STATIC_ROOT'] ?>/img/favicon/16x16.png?rev=<?= REVISION ?>">
</head> </head>
<body> <body>
<div id="loading"> <div id="loading">
<img src="/static/img/loading.svg"> <img src="<?= $_ENV['STATIC_ROOT'] ?>/img/loading.svg?rev=<?= REVISION ?>" width="64" height="64">
</div>
<div id="cover"></div>
<div id="modal" class="modal">
<h2 id="modalTitle"></h2>
<p id="modalText" class="marginTop"></p>
<div id="modalButtons" class="right"></div>
</div> </div>
+45
View File
@@ -0,0 +1,45 @@
<?php
require 'main.php';
if (!empty($_ENV['DEV'])) {
error_reporting(E_ALL);
ini_set('display_errors', '1');
} else {
ini_set('display_errors', '0');
}
Container::$routeCollection = new MapGuesser\Routing\RouteCollection();
Container::$routeCollection->get('index', '', [MapGuesser\Controller\HomeController::class, 'getIndex']);
Container::$routeCollection->get('login', 'login', [MapGuesser\Controller\LoginController::class, 'getLoginForm']);
Container::$routeCollection->post('login-action', 'login', [MapGuesser\Controller\LoginController::class, 'login']);
Container::$routeCollection->get('logout', 'logout', [MapGuesser\Controller\LoginController::class, 'logout']);
Container::$routeCollection->get('maps', 'maps', [MapGuesser\Controller\MapsController::class, 'getMaps']);
Container::$routeCollection->group('game', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('game', '{mapId}', [MapGuesser\Controller\GameController::class, 'getGame']);
$routeCollection->get('game-json', '{mapId}/json', [MapGuesser\Controller\GameController::class, 'getGameJson']);
$routeCollection->get('newPlace-json', '{mapId}/newPlace.json', [MapGuesser\Controller\GameFlowController::class, 'getNewPlace']);
$routeCollection->post('guess-json', '{mapId}/guess.json', [MapGuesser\Controller\GameFlowController::class, 'evaluateGuess']);
});
Container::$routeCollection->group('admin', function (MapGuesser\Routing\RouteCollection $routeCollection) {
$routeCollection->get('admin.mapEditor', 'mapEditor/{mapId?}', [MapGuesser\Controller\MapAdminController::class, 'getMapEditor']);
$routeCollection->get('admin.place', 'place.json/{placeId}', [MapGuesser\Controller\MapAdminController::class, 'getPlace']);
$routeCollection->post('admin.saveMap', 'saveMap/{mapId}/json', [MapGuesser\Controller\MapAdminController::class, 'saveMap']);
$routeCollection->post('admin.deleteMap', 'deleteMap/{mapId}', [MapGuesser\Controller\MapAdminController::class, 'deleteMap']);
});
Container::$sessionHandler = new MapGuesser\Session\DatabaseSessionHandler();
session_set_save_handler(Container::$sessionHandler, true);
session_start([
'gc_maxlifetime' => 604800,
'cookie_lifetime' => 604800,
'cookie_httponly' => true,
'cookie_samesite' => 'Lax'
]);
if (!isset($_SESSION['anti_csrf_token'])) {
$_SESSION['anti_csrf_token'] = hash('sha256', random_bytes(10) . microtime());
}