implemented anonymous user authentification with browser identification number from cookies
This commit is contained in:
@@ -19,7 +19,7 @@ CREATE INDEX idx_votes_browser ON votes(browser_id);
|
|||||||
|
|
||||||
-- Drops old Constraint voter_name based
|
-- Drops old Constraint voter_name based
|
||||||
ALTER TABLE votes
|
ALTER TABLE votes
|
||||||
DROP CONSTRAINT IF EXISTS votes_contribution_id_voter_name_key;
|
DROP CONSTRAINT IF EXISTS votes_unique_per_voter;
|
||||||
|
|
||||||
-- Creates new Constraint browser_id based
|
-- Creates new Constraint browser_id based
|
||||||
ALTER TABLE votes
|
ALTER TABLE votes
|
||||||
|
|||||||
@@ -126,6 +126,23 @@ function handle_read($input) {
|
|||||||
'features' => $features
|
'features' => $features
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Includes User's Votes for persistent Vote Display
|
||||||
|
// Returns which Contributions the current Browser has voted on
|
||||||
|
$browser_id = $input['browser_id'] ?? '';
|
||||||
|
if ($browser_id !== '') {
|
||||||
|
$stmt = $pdo->prepare("
|
||||||
|
SELECT contribution_id, vote_type
|
||||||
|
FROM votes
|
||||||
|
WHERE browser_id = :bid
|
||||||
|
");
|
||||||
|
$stmt->execute([':bid' => $browser_id]);
|
||||||
|
$user_votes = [];
|
||||||
|
foreach ($stmt->fetchAll() as $v) {
|
||||||
|
$user_votes[$v['contribution_id']] = $v['vote_type'];
|
||||||
|
}
|
||||||
|
$featureCollection['user_votes'] = $user_votes;
|
||||||
|
}
|
||||||
|
|
||||||
json_response($featureCollection);
|
json_response($featureCollection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,10 +179,10 @@ function handle_create($input) {
|
|||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO contributions
|
INSERT INTO contributions
|
||||||
(municipality_id, geom, geom_type, category, title, description, author_name)
|
(municipality_id, geom, geom_type, category, title, description, author_name, browser_id)
|
||||||
VALUES
|
VALUES
|
||||||
(:mid, ST_SetSRID(ST_GeomFromGeoJSON(:geom), 4326), :geom_type,
|
(:mid, ST_SetSRID(ST_GeomFromGeoJSON(:geom), 4326), :geom_type,
|
||||||
:category, :title, :description, :author_name)
|
:category, :title, :description, :author_name, :browser_id)
|
||||||
");
|
");
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
@@ -175,7 +192,8 @@ function handle_create($input) {
|
|||||||
':category' => $input['category'],
|
':category' => $input['category'],
|
||||||
':title' => $input['title'],
|
':title' => $input['title'],
|
||||||
':description' => $input['description'] ?? '',
|
':description' => $input['description'] ?? '',
|
||||||
':author_name' => $input['author_name']
|
':author_name' => $input['author_name'],
|
||||||
|
':browser_id' => $input['browser_id'] ?? null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
json_response([
|
json_response([
|
||||||
@@ -320,11 +338,16 @@ function handle_vote($input) {
|
|||||||
// Prepared SQL Statement
|
// Prepared SQL Statement
|
||||||
try {
|
try {
|
||||||
// Checks if Voter already voted on this Contribution
|
// Checks if Voter already voted on this Contribution
|
||||||
|
$browser_id = $input['browser_id'] ?? '';
|
||||||
|
if (empty($browser_id)) {
|
||||||
|
error_response('Browser ID required for Voting.');
|
||||||
|
}
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
SELECT vote_id, vote_type FROM votes
|
SELECT vote_id, vote_type FROM votes
|
||||||
WHERE contribution_id = :cid AND voter_name = :voter
|
WHERE contribution_id = :cid AND browser_id = :bid
|
||||||
");
|
");
|
||||||
$stmt->execute([':cid' => $input['contribution_id'], ':voter' => $input['voter_name']]);
|
$stmt->execute([':cid' => $input['contribution_id'], ':bid' => $browser_id]);
|
||||||
$existing = $stmt->fetch();
|
$existing = $stmt->fetch();
|
||||||
|
|
||||||
if ($existing) {
|
if ($existing) {
|
||||||
@@ -339,27 +362,29 @@ function handle_vote($input) {
|
|||||||
$stmt->execute([':vid' => $existing['vote_id']]);
|
$stmt->execute([':vid' => $existing['vote_id']]);
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO votes (contribution_id, voter_name, vote_type)
|
INSERT INTO votes (contribution_id, voter_name, vote_type, browser_id)
|
||||||
VALUES (:cid, :voter, :vtype)
|
VALUES (:cid, :voter, :vtype, :bid)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
':cid' => $input['contribution_id'],
|
':cid' => $input['contribution_id'],
|
||||||
':voter' => $input['voter_name'],
|
':voter' => $input['voter_name'],
|
||||||
':vtype' => $input['vote_type']
|
':vtype' => $input['vote_type'],
|
||||||
|
':bid' => $browser_id
|
||||||
]);
|
]);
|
||||||
json_response(['message' => 'Vote changed.', 'action' => 'changed'], 200);
|
json_response(['message' => 'Vote changed.', 'action' => 'changed'], 200);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No existing Vote — Inserts Vote
|
// No existing Vote — Inserts Vote
|
||||||
$stmt = $pdo->prepare("
|
$stmt = $pdo->prepare("
|
||||||
INSERT INTO votes (contribution_id, voter_name, vote_type)
|
INSERT INTO votes (contribution_id, voter_name, vote_type, browser_id)
|
||||||
VALUES (:cid, :voter, :vtype)
|
VALUES (:cid, :voter, :vtype, :bid)
|
||||||
");
|
");
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
':cid' => $input['contribution_id'],
|
':cid' => $input['contribution_id'],
|
||||||
':voter' => $input['voter_name'],
|
':voter' => $input['voter_name'],
|
||||||
':vtype' => $input['vote_type']
|
':vtype' => $input['vote_type'],
|
||||||
]);
|
':bid' => $browser_id
|
||||||
|
]);
|
||||||
json_response(['message' => 'Vote recorded.', 'action' => 'created'], 201);
|
json_response(['message' => 'Vote recorded.', 'action' => 'created'], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,8 @@
|
|||||||
// Renders Leaflet Map Interface including Leaflet Plugins
|
// Renders Leaflet Map Interface including Leaflet Plugins
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
// Reads Environment Configfile
|
|
||||||
$envFile = __DIR__ . '/../../.env';
|
|
||||||
if (file_exists($envFile)) {
|
|
||||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
if (strpos(trim($line), '#') === 0) continue;
|
|
||||||
list($key, $value) = array_map('trim', explode('=', $line, 2));
|
|
||||||
putenv("$key=$value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/api/db.php';
|
require_once __DIR__ . '/api/db.php';
|
||||||
|
require_once __DIR__ . '/api/auth.php';
|
||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
// Loads Municipality Configuration
|
// Loads Municipality Configuration
|
||||||
@@ -122,6 +112,8 @@ $news_items = $stmt->fetchAll();
|
|||||||
|
|
||||||
<!-- Mobile Hamburger Menu -->
|
<!-- Mobile Hamburger Menu -->
|
||||||
<button class="header-menu-toggle" onclick="toggleMobileNav()">
|
<button class="header-menu-toggle" onclick="toggleMobileNav()">
|
||||||
|
<i class="fa-solid fa-bars"></i>
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
||||||
@@ -370,6 +362,9 @@ $news_items = $stmt->fetchAll();
|
|||||||
|
|
||||||
// Category Definitions from Database
|
// Category Definitions from Database
|
||||||
const CATEGORIES = <?= json_encode(get_categories(), JSON_UNESCAPED_UNICODE) ?>;
|
const CATEGORIES = <?= json_encode(get_categories(), JSON_UNESCAPED_UNICODE) ?>;
|
||||||
|
|
||||||
|
// Admin Status from PHP Session
|
||||||
|
const IS_ADMIN = <?= (function_exists('is_admin') && is_admin()) ? 'true' : 'false' ?>;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Application Logic -->
|
<!-- Application Logic -->
|
||||||
|
|||||||
@@ -16,9 +16,26 @@
|
|||||||
// API Endpoint as relative Path
|
// API Endpoint as relative Path
|
||||||
const API_URL = 'api/contributions.php';
|
const API_URL = 'api/contributions.php';
|
||||||
|
|
||||||
// Current User Name, set via Login Modal, stored in sessionStorage
|
// Username set via Login Modal stored in sessionStorage
|
||||||
let currentUser = sessionStorage.getItem('webgis_user') || '';
|
let currentUser = sessionStorage.getItem('webgis_user') || '';
|
||||||
|
|
||||||
|
// Browser Identification Number for anonymous User Identification stored as Cookie
|
||||||
|
let browserId = getBrowserId();
|
||||||
|
|
||||||
|
function getBrowserId() {
|
||||||
|
let id = document.cookie.replace(/(?:(?:^|.*;\s*)webgis_browser_id\s*=\s*([^;]*).*$)|^.*$/, '$1');
|
||||||
|
if (!id) {
|
||||||
|
id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
const r = Math.random() * 16 | 0;
|
||||||
|
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||||
|
});
|
||||||
|
// Cookie Expiration in one Year
|
||||||
|
document.cookie = 'webgis_browser_id=' + id + ';path=/;max-age=31536000;SameSite=Lax';
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Application State
|
// Application State
|
||||||
let map; // Leaflet Map Instance
|
let map; // Leaflet Map Instance
|
||||||
let sidebar; // Sidebar Instance
|
let sidebar; // Sidebar Instance
|
||||||
@@ -290,9 +307,15 @@ function apiCall(data, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Loads all Contributions from API and displays Contributions on Map
|
// Loads all Contributions from API and displays Contributions on Map
|
||||||
function loadContributions() {
|
function loadContributions() {
|
||||||
apiCall({ action: 'read', municipality_id: MUNICIPALITY.id }, function (data) {
|
const readParams = { action: 'read', municipality_id: MUNICIPALITY.id };
|
||||||
|
|
||||||
|
// Sends Browser ID for persistent Vote Display
|
||||||
|
readParams.browser_id = browserId;
|
||||||
|
|
||||||
|
apiCall(readParams, function (data) {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.error('Load Error:', data.error);
|
console.error('Load Error:', data.error);
|
||||||
return;
|
return;
|
||||||
@@ -300,6 +323,14 @@ function loadContributions() {
|
|||||||
|
|
||||||
contributionsData = data.features || [];
|
contributionsData = data.features || [];
|
||||||
|
|
||||||
|
// Restores Vote Highlights from API Response
|
||||||
|
if (data.user_votes) {
|
||||||
|
userVotes = {};
|
||||||
|
for (const key in data.user_votes) {
|
||||||
|
userVotes[key] = data.user_votes[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Removes existing Layer if present
|
// Removes existing Layer if present
|
||||||
if (contributionsLayer) {
|
if (contributionsLayer) {
|
||||||
map.removeLayer(contributionsLayer);
|
map.removeLayer(contributionsLayer);
|
||||||
@@ -384,7 +415,7 @@ function buildPopupHtml(feature) {
|
|||||||
'<i class="fa-solid fa-thumbs-down"></i> <span id="dislikes-' + props.contribution_id + '">' + props.dislikes_count + '</span>' +
|
'<i class="fa-solid fa-thumbs-down"></i> <span id="dislikes-' + props.contribution_id + '">' + props.dislikes_count + '</span>' +
|
||||||
'</button>' +
|
'</button>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
(currentUser === props.author_name ?
|
(props.browser_id === browserId || (typeof IS_ADMIN !== 'undefined' && IS_ADMIN) ?
|
||||||
'<div class="popup-detail-actions">' +
|
'<div class="popup-detail-actions">' +
|
||||||
'<button class="btn btn-primary" onclick="editContribution(' + props.contribution_id + ')"><i class="fa-solid fa-pen"></i> Bearbeiten</button>' +
|
'<button class="btn btn-primary" onclick="editContribution(' + props.contribution_id + ')"><i class="fa-solid fa-pen"></i> Bearbeiten</button>' +
|
||||||
'<button class="btn btn-danger" onclick="deleteContribution(' + props.contribution_id + ')"><i class="fa-solid fa-trash"></i> Löschen</button>' +
|
'<button class="btn btn-danger" onclick="deleteContribution(' + props.contribution_id + ')"><i class="fa-solid fa-trash"></i> Löschen</button>' +
|
||||||
@@ -441,7 +472,8 @@ function submitCreate() {
|
|||||||
description: description,
|
description: description,
|
||||||
geom: geom,
|
geom: geom,
|
||||||
geom_type: geomType,
|
geom_type: geomType,
|
||||||
author_name: currentUser
|
author_name: currentUser,
|
||||||
|
browser_id: browserId
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
Swal.fire('Fehler', response.error, 'error');
|
Swal.fire('Fehler', response.error, 'error');
|
||||||
@@ -570,7 +602,8 @@ function voteContribution(contributionId, voteType) {
|
|||||||
action: 'vote',
|
action: 'vote',
|
||||||
contribution_id: contributionId,
|
contribution_id: contributionId,
|
||||||
voter_name: currentUser,
|
voter_name: currentUser,
|
||||||
vote_type: voteType
|
vote_type: voteType,
|
||||||
|
browser_id: browserId
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user