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
|
||||
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
|
||||
ALTER TABLE votes
|
||||
|
||||
@@ -126,6 +126,23 @@ function handle_read($input) {
|
||||
'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);
|
||||
}
|
||||
|
||||
@@ -162,10 +179,10 @@ function handle_create($input) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
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
|
||||
(:mid, ST_SetSRID(ST_GeomFromGeoJSON(:geom), 4326), :geom_type,
|
||||
:category, :title, :description, :author_name)
|
||||
:category, :title, :description, :author_name, :browser_id)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
@@ -175,7 +192,8 @@ function handle_create($input) {
|
||||
':category' => $input['category'],
|
||||
':title' => $input['title'],
|
||||
':description' => $input['description'] ?? '',
|
||||
':author_name' => $input['author_name']
|
||||
':author_name' => $input['author_name'],
|
||||
':browser_id' => $input['browser_id'] ?? null
|
||||
]);
|
||||
|
||||
json_response([
|
||||
@@ -320,11 +338,16 @@ function handle_vote($input) {
|
||||
// Prepared SQL Statement
|
||||
try {
|
||||
// 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("
|
||||
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();
|
||||
|
||||
if ($existing) {
|
||||
@@ -339,27 +362,29 @@ function handle_vote($input) {
|
||||
$stmt->execute([':vid' => $existing['vote_id']]);
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO votes (contribution_id, voter_name, vote_type)
|
||||
VALUES (:cid, :voter, :vtype)
|
||||
INSERT INTO votes (contribution_id, voter_name, vote_type, browser_id)
|
||||
VALUES (:cid, :voter, :vtype, :bid)
|
||||
");
|
||||
$stmt->execute([
|
||||
':cid' => $input['contribution_id'],
|
||||
':voter' => $input['voter_name'],
|
||||
':vtype' => $input['vote_type']
|
||||
':vtype' => $input['vote_type'],
|
||||
':bid' => $browser_id
|
||||
]);
|
||||
json_response(['message' => 'Vote changed.', 'action' => 'changed'], 200);
|
||||
}
|
||||
} else {
|
||||
// No existing Vote — Inserts Vote
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO votes (contribution_id, voter_name, vote_type)
|
||||
VALUES (:cid, :voter, :vtype)
|
||||
");
|
||||
$stmt->execute([
|
||||
':cid' => $input['contribution_id'],
|
||||
':voter' => $input['voter_name'],
|
||||
':vtype' => $input['vote_type']
|
||||
]);
|
||||
INSERT INTO votes (contribution_id, voter_name, vote_type, browser_id)
|
||||
VALUES (:cid, :voter, :vtype, :bid)
|
||||
");
|
||||
$stmt->execute([
|
||||
':cid' => $input['contribution_id'],
|
||||
':voter' => $input['voter_name'],
|
||||
':vtype' => $input['vote_type'],
|
||||
':bid' => $browser_id
|
||||
]);
|
||||
json_response(['message' => 'Vote recorded.', 'action' => 'created'], 201);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,18 +5,8 @@
|
||||
// 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/auth.php';
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Loads Municipality Configuration
|
||||
@@ -122,6 +112,8 @@ $news_items = $stmt->fetchAll();
|
||||
|
||||
<!-- Mobile Hamburger Menu -->
|
||||
<button class="header-menu-toggle" onclick="toggleMobileNav()">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -370,6 +362,9 @@ $news_items = $stmt->fetchAll();
|
||||
|
||||
// Category Definitions from Database
|
||||
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>
|
||||
|
||||
<!-- Application Logic -->
|
||||
|
||||
@@ -16,9 +16,26 @@
|
||||
// API Endpoint as relative Path
|
||||
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') || '';
|
||||
|
||||
// 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
|
||||
let map; // Leaflet Map Instance
|
||||
let sidebar; // Sidebar Instance
|
||||
@@ -290,9 +307,15 @@ function apiCall(data, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Loads all Contributions from API and displays Contributions on Map
|
||||
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) {
|
||||
console.error('Load Error:', data.error);
|
||||
return;
|
||||
@@ -300,6 +323,14 @@ function loadContributions() {
|
||||
|
||||
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
|
||||
if (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>' +
|
||||
'</button>' +
|
||||
'</div>' +
|
||||
(currentUser === props.author_name ?
|
||||
(props.browser_id === browserId || (typeof IS_ADMIN !== 'undefined' && IS_ADMIN) ?
|
||||
'<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-danger" onclick="deleteContribution(' + props.contribution_id + ')"><i class="fa-solid fa-trash"></i> Löschen</button>' +
|
||||
@@ -441,7 +472,8 @@ function submitCreate() {
|
||||
description: description,
|
||||
geom: geom,
|
||||
geom_type: geomType,
|
||||
author_name: currentUser
|
||||
author_name: currentUser,
|
||||
browser_id: browserId
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
Swal.fire('Fehler', response.error, 'error');
|
||||
@@ -570,7 +602,8 @@ function voteContribution(contributionId, voteType) {
|
||||
action: 'vote',
|
||||
contribution_id: contributionId,
|
||||
voter_name: currentUser,
|
||||
vote_type: voteType
|
||||
vote_type: voteType,
|
||||
browser_id: browserId
|
||||
}, function (response) {
|
||||
if (response.error) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user