4 Commits

18 changed files with 65 additions and 521 deletions

View File

@@ -1,48 +0,0 @@
<?php
// ToDo's
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
include 'init.php';
$request = htmlspecialchars($_POST['request'], ENT_QUOTES);
if ($request=='buildings') {
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
try {
$pdo -> query("DELETE FROM buildings WHERE webgis_id = '$webgis_id'");
} catch (PDOException $e) {
echo "ERROR ".$e->getMessage();
}
}
if ($request == 'pipelines') {
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
try {
$pdo -> query("DELETE from pipelines where webgis_id= '$webgis_id' ");
} catch(PDOException $e) {
echo "ERROR ".$e->getMessage();
}
}
if ($request == 'valves') {
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
try {
$pdo -> query("DELETE from valves where webgis_id= '$webgis_id' ");
} catch(PDOException $e) {
echo "ERROR ".$e->getMessage();
}
}
?>

View File

@@ -1,52 +0,0 @@
<?php
// ToDo's
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
// PostgreSQL-Serververbindung
include 'init.php';
// HTTP-POST-Methode für Formulardaten
$table = htmlspecialchars($_POST['table'], ENT_QUOTES);
$field = htmlspecialchars($_POST['field'], ENT_QUOTES);
$value = htmlspecialchars($_POST['value'], ENT_QUOTES);
try {
// Datenbankabfrage
$result = $pdo -> query("SELECT *, ST_AsGeoJSON(geom) as geojson FROM $table WHERE $field = '$value'");
$features = [];
foreach($result as $row) {
// PHP-Objekt erstellen
$geometry = json_decode($row['geojson']);
// PHP-Objekt bereinigen
unset($row['geom']);
unset($row['geojson']);
// JSON-Feature hinzufügen
$feature = [
"type"=>"Feature",
"geometry"=>$geometry,
"properties"=>$row
];
array_push($features, $feature);
};
// Feature-Collection hinzufügen
$featureCollection = [
"type"=>"FeatureCollection",
"features"=>$features
];
echo json_encode($featureCollection);
// Fehlernachricht ausgeben
} catch(PDOException $e) {
echo "ERROR ".$e->getMessage();
}
?>

View File

@@ -1,73 +0,0 @@
<?php
// ToDo's
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
// PostgreSQL-Serververbindung
include 'init.php';
$request = htmlspecialchars($_POST['request'], ENT_QUOTES);
if ($request == 'valves') {
$valve_id = htmlspecialchars($_POST['valve_id'], ENT_QUOTES);
$valve_type = htmlspecialchars($_POST['valve_type'], ENT_QUOTES);
$valve_dma_id = htmlspecialchars($_POST['valve_dma_id'], ENT_QUOTES);
$valve_diameter = htmlspecialchars($_POST['valve_diameter'], ENT_QUOTES);
$valve_visibility = htmlspecialchars($_POST['valve_visibility'], ENT_QUOTES);
$valve_location = htmlspecialchars($_POST['valve_location'], ENT_QUOTES);
$valve_geometry = $_POST['valve_geometry'];
$result = $pdo -> query("SELECT * FROM valves WHERE valve_id = '$valve_id'");
if ($result->rowCount()>0) {
echo "ERROR: Valve ID already exists. Please type in another ID!";
} else {
// Datenbankabfrage
$result = $pdo -> query("INSERT INTO valves(valve_id, valve_type, valve_dma_id, valve_diameter, valve_location, valve_visibility, geom) VALUES ('$valve_id', '$valve_type', '$valve_dma_id', '$valve_diameter', '$valve_location', '$valve_visibility', ST_SetSRID(ST_GeomFromGeoJSON('$valve_geometry'), 4326))");
}
}
if ($request == 'pipelines') {
$pipeline_id = htmlspecialchars($_POST['pipeline_id'], ENT_QUOTES);
$pipeline_category = htmlspecialchars($_POST['pipeline_category'], ENT_QUOTES);
$pipeline_dma_id = htmlspecialchars($_POST['pipeline_dma_id'], ENT_QUOTES);
$pipeline_diameter = htmlspecialchars($_POST['pipeline_diameter'], ENT_QUOTES);
$pipeline_method = htmlspecialchars($_POST['pipeline_method'], ENT_QUOTES);
$pipeline_location = htmlspecialchars($_POST['pipeline_location'], ENT_QUOTES);
$pipeline_geometry = $_POST['pipeline_geometry'];
$result = $pdo -> query("SELECT * FROM pipelines WHERE pipeline_id = '$pipeline_id'");
if ($result->rowCount()>0) {
echo "ERROR: Pipeline ID already exists. Please type in another ID!";
} else {
// Datenbankabfrage
$result = $pdo -> query("INSERT INTO pipelines(pipeline_id, pipeline_category, pipeline_dma_id, pipeline_diameter, pipeline_method, pipeline_location, geom) VALUES ('$pipeline_id', '$pipeline_category', '$pipeline_dma_id', '$pipeline_diameter', '$pipeline_method', '$pipeline_location', ST_SetSRID(ST_GeomFromGeoJSON('$pipeline_geometry'), 4326))");
}
}
if ($request == 'buildings') {
$account_no = htmlspecialchars($_POST['account_no'], ENT_QUOTES);
$building_category = htmlspecialchars($_POST['building_category'], ENT_QUOTES);
$building_dma_id = htmlspecialchars($_POST['building_dma_id'], ENT_QUOTES);
$building_storey = htmlspecialchars($_POST['building_storey'], ENT_QUOTES);
$building_population = htmlspecialchars($_POST['building_population'], ENT_QUOTES);
$building_location = htmlspecialchars($_POST['building_location'], ENT_QUOTES);
$building_geometry = $_POST['building_geometry'];
$result = $pdo -> query("SELECT *from buildings where account_no= '$account_no'");
if ($result->rowCount()>0) {
echo "ERROR: Building ID already exists. Please type in another ID!";
} else {
$sql = $pdo -> query("INSERT INTO buildings(account_no, building_category, building_dma_id, building_storey, building_population, building_location, geom) VALUES ('$account_no', '$building_category', '$building_dma_id', '$building_storey', '$building_population', '$building_location', ST_Force3DZ(ST_SetSRID(ST_GeomFromGeoJSON('$building_geometry'), 4326)))");
}
}
?>

View File

@@ -1,63 +0,0 @@
<?php
// ToDo's
// Whitelists oder Prepared Statements gegen SQL-Injection hinzufügen
// PostgreSQL-Serververbindung
include 'init.php';
// HTTP-POST-Methode für Formulardaten
$table = htmlspecialchars($_POST['table'], ENT_QUOTES);
$dma_id = htmlspecialchars($_POST['dma_id'], ENT_QUOTES);
if($table == 'valves') {
$dma_id_field = "valve_dma_id";
}
if($table == 'buildings') {
$dma_id_field = "building_dma_id";
}
if($table == 'pipelines') {
$dma_id_field = "pipeline_dma_id";
}
try {
// Datenbankabfrage
$result = $pdo -> query("SELECT *, ST_AsGeoJSON(geom) as geojson FROM $table WHERE $dma_id_field = '$dma_id'");
$features = [];
foreach($result as $row) {
// PHP-Objekt erstellen
$geometry = json_decode($row['geojson']);
// PHP-Objekt bereinigen
unset($row['geom']);
unset($row['geojson']);
// JSON-Feature hinzufügen
$feature = [
"type"=>"Feature",
"geometry"=>$geometry,
"properties"=>$row
];
array_push($features, $feature);
};
// Feature-Collection hinzufügen
$featureCollection = [
"type"=>"FeatureCollection",
"features"=>$features
];
echo json_encode($featureCollection);
// Fehlernachricht ausgeben
} catch(PDOException $e) {
echo "ERROR ".$e->getMessage();
}
?>

View File

@@ -1,97 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- jQuery UI -->
<link rel="stylesheet" href="source/jquery-ui.min.css">
<script src="source/jquery-ui.min.js"></script>
<!-- Bootstrap Stylesheet & Skript -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="styles.css">
<!-- Sidebar Plugin -->
<link rel="stylesheet" href="plugins/sidebar/leaflet-sidebar.css">
<script src="plugins/sidebar/leaflet-sidebar.js"></script>
<!-- Button Plugin -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet-easybutton@2/src/easy-button.js"></script>
<!-- Font Plugin -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css">
<!-- PolylineMeasure Plugin -->
<link rel="stylesheet" href="https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.css">
<script src="https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.js"></script>
<!-- MousePosition Plugin -->
<link rel="stylesheet" href="plugins/mouseposition/L.Control.MousePosition.css">
<script src="plugins/mouseposition/L.Control.MousePosition.js"></script>
<!-- Geoman Plugin -->
<link rel="stylesheet" href="https://unpkg.com/@geoman-io/leaflet-geoman-free@latest/dist/leaflet-geoman.css">
<script src="https://unpkg.com/@geoman-io/leaflet-geoman-free@latest/dist/leaflet-geoman.js"></script>
<!-- Minimap Plugin -->
<link rel="stylesheet" href="plugins/minimap/Control.MiniMap.min.css">
<script src="plugins/minimap/Control.MiniMap.min.js"></script>
<!-- ajax Plugin -->
<script src="plugins/ajax/leaflet.ajax.js"></script>
</head>
<body>
<div class="popup-container">
<input type="hidden" name="building_database_id" class="updateBuilding" value="something">
<input type="hidden" name="account_no_old" class="updateBuilding" value="something">
<div class="popup-form-group">
<label class="control-label popup-label">Building ID</label>
<input type="text" class="form-control popup-input text-center updateBuilding" value="something" name="account_no">
</div>
<div class="popup-form-group">
<label class="control-label popup-label">Category</label>
<input type="text" class="form-control popup-input text-center updateBuilding" value="something" name="building_category">
</div>
<div class="popup-form-group">
<label class="control-label popup-label">Storey</label>
<input type="number" class="form-control popup-input text-center updateBuilding" value="something" name="building_storey">
</div>
<div class="popup-form-group">
<label class="control-label popup-label">Population</label>
<input type="number" class="form-control popup-input text-center updateBuilding" value="something" name="building_population">
</div>
<div class="popup-form-group">
<label class="control-label popup-label">Location</label>
<input type="text" class="form-control popup-input text-center updateBuilding" value="something" name="building_locationn">
</div>
<div class="popup-button-group">
<button type="submit" class="btn btn-success popup-button">Update</button>
<button type="submit" class="btn btn-danger popup-button">Delete</button>
</div>
</div>
</body>
</html>

View File

@@ -1,98 +0,0 @@
<?php
include 'init.php';
$request = htmlspecialchars($_POST['request'], ENT_QUOTES);
if ($request=='buildings') {
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
$account_no_old = htmlspecialchars($_POST['account_no_old'], ENT_QUOTES);
$account_no = htmlspecialchars($_POST['account_no'], ENT_QUOTES);
$building_category = htmlspecialchars($_POST['building_category'], ENT_QUOTES);
$building_storey = htmlspecialchars($_POST['building_storey'], ENT_QUOTES);
$building_population = htmlspecialchars($_POST['building_population'], ENT_QUOTES);
$building_location = htmlspecialchars($_POST['building_location'], ENT_QUOTES);
$building_dma_id = htmlspecialchars($_POST['building_dma_id'], ENT_QUOTES);
try {
if ($account_no_old != $account_no) {
$result = $pdo -> query("SELECT * FROM buildings WHERE account_no = '$account_no'");
if ($result -> rowCount()>0) {
echo "ERROR: Account Number already exists. Pleas type in another Account Number!";
} else {
$pdo -> query("UPDATE buildings SET account_no = '$account_no', building_category = '$building_category', building_storey = '$building_storey', building_population = '$building_population', building_location = '$building_location', building_dma_id = '$building_dma_id' WHERE webgis_id = '$webgis_id'");
}
} else { $pdo -> query("UPDATE buildings SET account_no = '$account_no', building_category = '$building_category', building_storey = '$building_storey', building_population = '$building_population', building_location = '$building_location', building_dma_id = '$building_dma_id' WHERE webgis_id = '$webgis_id'");
}
} catch (PDOException $e) {
echo "ERROR ".$e->getMessage();
}
}
if ($request == 'pipelines') {
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
$pipeline_id_old = htmlspecialchars($_POST['pipeline_id_old'], ENT_QUOTES);
$pipeline_id = htmlspecialchars($_POST['pipeline_id'], ENT_QUOTES);
$pipeline_dma_id = htmlspecialchars($_POST['pipeline_dma_id'], ENT_QUOTES);
$pipeline_diameter = htmlspecialchars($_POST['pipeline_diameter'], ENT_QUOTES);
$pipeline_location = htmlspecialchars($_POST['pipeline_location'], ENT_QUOTES);
$pipeline_category = htmlspecialchars($_POST['pipeline_category'], ENT_QUOTES);
$pipeline_length = htmlspecialchars($_POST['pipeline_length'], ENT_QUOTES);
try {
if ($pipeline_id_old != $pipeline_id) {
$result = $pdo -> query("SELECT *from pipelines where pipeline_id = '$pipeline_id' ");
if ($result -> rowCount()>0) {
echo "ERROR: Pipeline ID already exists. Please choose a new ID";
} else {
$pdo -> query("UPDATE pipelines set pipeline_id = '$pipeline_id', pipeline_dma_id = '$pipeline_dma_id', pipeline_diameter = '$pipeline_diameter', pipeline_location = '$pipeline_location', pipeline_category='$pipeline_category', pipeline_length='$pipeline_length' where webgis_id = '$webgis_id'");
}
} else {
$pdo -> query("UPDATE pipelines set pipeline_id = '$pipeline_id', pipeline_dma_id = '$pipeline_dma_id', pipeline_diameter = '$pipeline_diameter', pipeline_location = '$pipeline_location', pipeline_category='$pipeline_category', pipeline_length='$pipeline_length' where webgis_id = '$webgis_id'");
}
} catch(PDOException $e) {
echo "ERROR ".$e->getMessage();
}
}
if ($request == 'valves') {
$webgis_id = htmlspecialchars($_POST['webgis_id'], ENT_QUOTES);
$valve_id_old = htmlspecialchars($_POST['valve_id_old'], ENT_QUOTES);
$valve_id = htmlspecialchars($_POST['valve_id'], ENT_QUOTES);
$valve_dma_id = htmlspecialchars($_POST['valve_dma_id'], ENT_QUOTES);
$valve_type = htmlspecialchars($_POST['valve_type'], ENT_QUOTES);
$valve_diameter = htmlspecialchars($_POST['valve_diameter'], ENT_QUOTES);
$valve_location = htmlspecialchars($_POST['valve_location'], ENT_QUOTES);
$valve_visibility = htmlspecialchars($_POST['valve_visibility'], ENT_QUOTES);
try {
if ($valve_id_old != $valve_id) {
$result = $pdo -> query("SELECT *from valves where valve_id = '$valve_id' ");
if ($result -> rowCount()>0) {
echo "ERROR: Valve ID already exists. Please choose a new ID";
} else {
$pdo -> query("UPDATE valves set valve_id = '$valve_id', valve_dma_id = '$valve_dma_id', valve_type = '$valve_type', valve_diameter = '$valve_diameter', valve_location = '$valve_location', valve_visibility = '$valve_visibility' where webgis_id = '$webgis_id' ");
}
} else {
$pdo -> query("UPDATE valves set valve_id = '$valve_id', valve_dma_id = '$valve_dma_id', valve_type = '$valve_type', valve_diameter = '$valve_diameter', valve_location = '$valve_location', valve_visibility = '$valve_visibility' where webgis_id = '$webgis_id' ");
}
} catch(PDOException $e) {
echo "ERROR ".$e->getMessage();
}
}
?>

View File

@@ -131,7 +131,7 @@ $counts['total'] = count($all_contributions);
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Moderation — <?= htmlspecialchars($municipality['name']) ?></title> <title>Moderation — <?= htmlspecialchars($municipality['name']) ?></title>
<link rel="icon" href="<?= htmlspecialchars($municipality['logo_path'] ?? 'assets/icon-municipality.png') ?>" type="image/png"> <link rel="icon" href="assets/shield-halved-solid-off-black.png" type="image/png">
<!-- Loads CSS Dependencies --> <!-- Loads CSS Dependencies -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -11,7 +11,7 @@ $municipality = $stmt->fetch();
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Impressum — <?= htmlspecialchars($municipality['name']) ?></title> <title>Impressum — <?= htmlspecialchars($municipality['name']) ?></title>
<link rel="icon" href="<?= htmlspecialchars($municipality['logo_path'] ?? 'assets/icon-municipality.png') ?>" type="image/png"> <link rel="icon" href="assets/scale-balanced-solid-off-black.png" type="image/png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style> <style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>

View File

@@ -34,7 +34,7 @@ $news_items = $stmt->fetchAll();
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></title> <title>Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></title>
<link rel="icon" href="<?= htmlspecialchars($municipality['logo_path'] ?? 'assets/icon-municipality.png') ?>" type="image/png"> <link rel="icon" href="assets/user-group-solid-off-black.png" type="image/png">
<meta name="description" content="Bürgerbeteiligungsportal. Hinweise und Vorschläge auf der Karte eintragen."> <meta name="description" content="Bürgerbeteiligungsportal. Hinweise und Vorschläge auf der Karte eintragen.">
@@ -89,7 +89,7 @@ $news_items = $stmt->fetchAll();
<header id="app-header"> <header id="app-header">
<div class="header-left"> <div class="header-left">
<?php if (!empty($municipality['logo_path'])): ?> <?php if (!empty($municipality['logo_path'])): ?>
<img src="<?= htmlspecialchars($municipality['logo_path']) ?>" alt="<?= htmlspecialchars($municipality['name']) ?>" class="header-logo" onerror="this.style.display='none'"> <img src="assets/user-group-solid-off-white.png" alt="user-group-solid-off-white" class="header-logo" onerror="this.style.display='none'">
<?php endif; ?> <?php endif; ?>
<h1 class="header-title">Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></h1> <h1 class="header-title">Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></h1>
</div> </div>
@@ -131,9 +131,9 @@ $news_items = $stmt->fetchAll();
<div class="leaflet-sidebar-tabs"> <div class="leaflet-sidebar-tabs">
<ul role="tablist"> <ul role="tablist">
<li><a href="#tab-home" role="tab"><i class="fa-solid fa-house"></i></a></li> <li><a href="#tab-home" role="tab"><i class="fa-solid fa-house"></i></a></li>
<li><a href="#tab-help" role="tab"><i class="fa-solid fa-circle-question"></i></a></li>
<li><a href="#tab-list" role="tab"><i class="fa-solid fa-list"></i></a></li> <li><a href="#tab-list" role="tab"><i class="fa-solid fa-list"></i></a></li>
<li><a href="#tab-news" role="tab"><i class="fa-solid fa-newspaper"></i></a></li> <li><a href="#tab-news" role="tab"><i class="fa-solid fa-newspaper"></i></a></li>
<li><a href="#tab-help" role="tab"><i class="fa-solid fa-circle-question"></i></a></li>
</ul> </ul>
</div> </div>
@@ -148,17 +148,12 @@ $news_items = $stmt->fetchAll();
</h2> </h2>
<div class="sidebar-body"> <div class="sidebar-body">
<p>Willkommen beim Bürgerbeteiligungsportal <strong><?= htmlspecialchars($municipality['name']) ?></strong>.</p> <p>Willkommen beim Bürgerbeteiligungsportal <strong><?= htmlspecialchars($municipality['name']) ?></strong>.</p>
<p>Verwenden Sie die Karte, um Hinweise und Aufgaben für die Stadtverwaltung hinzuzufügen oder bestehende Beiträge der Bürgerschaft zu betrachten.</p> <p>Verwenden Sie die Karte, um Hinweise für die Stadtverwaltung hinzuzufügen oder bestehende Beiträge zu betrachten, zu bewerten und zu kommentieren.</p>
<h3>Kategorien</h3> <h3>Kategorien</h3>
<div id="category-filter"> <div id="category-filter">
<!-- Category Filter Checkboxes — populated by app.js --> <!-- Category Filter Checkboxes — populated by app.js -->
</div> </div>
<h3>Statistik</h3>
<div id="stats-container">
<!-- Contribution Statistics — populated by app.js -->
</div>
</div> </div>
</div> </div>
@@ -176,9 +171,44 @@ $news_items = $stmt->fetchAll();
<!-- Contribution Cards — populated by app.js --> <!-- Contribution Cards — populated by app.js -->
</div> </div>
</div> </div>
</div>
<!-- News Tab -->
<div class="leaflet-sidebar-pane" id="tab-news">
<h2 class="leaflet-sidebar-header">
Neuigkeiten
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
</h2>
<div class="sidebar-body">
<!-- News Search -->
<div class="list-search">
<input type="text" id="news-search-input" placeholder="Neuigkeiten durchsuchen..." class="form-input" oninput="filterNews()">
</div>
<!-- News Items Container -->
<div id="news-list">
<?php if (empty($news_items)): ?>
<p class="empty-state">Noch keine Neuigkeiten veröffentlicht.</p>
<?php else: ?>
<?php foreach ($news_items as $news): ?>
<div class="news-item"
data-title="<?= htmlspecialchars(strtolower($news['title'])) ?>"
data-content="<?= htmlspecialchars(strtolower($news['content'])) ?>"
data-author="<?= htmlspecialchars(strtolower($news['author_name'])) ?>">
<h3><?= htmlspecialchars($news['title']) ?></h3>
<p><?= nl2br(htmlspecialchars($news['content'])) ?></p>
<span class="news-date">
<?= htmlspecialchars($news['author_name']) ?>
· <?= date('d.m.Y', strtotime($news['published_at'])) ?>
</span>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div> </div>
<!-- Help Tab --> <!-- Help Tab -->
<div class="leaflet-sidebar-pane" id="tab-help"> <div class="leaflet-sidebar-pane" id="tab-help">
<h2 class="leaflet-sidebar-header"> <h2 class="leaflet-sidebar-header">
Hilfe Hilfe
@@ -206,44 +236,7 @@ $news_items = $stmt->fetchAll();
<p>Gerne können Sie Ihre Meinung zu bestehenden Beiträgen auch durch die Kommentarfunktion äußern.</p> <p>Gerne können Sie Ihre Meinung zu bestehenden Beiträgen auch durch die Kommentarfunktion äußern.</p>
<h3><i class="fa-solid fa-magnifying-glass"></i> Suchen</h3> <h3><i class="fa-solid fa-magnifying-glass"></i> Suchen</h3>
<p>Verwenden Sie die Adresssuche rechts, um schnell den richtigen Ort auf der Mitmachkarte zu finden.</p> <p>Verwenden Sie die Adresssuche rechts, um schnell den richtigen Ort auf der Mitmachkarte zu finden.</p>
</div>
</div>
<!-- News Tab -->
<div class="leaflet-sidebar-pane" id="tab-news">
<h2 class="leaflet-sidebar-header">
Neuigkeiten
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
</h2>
<div class="sidebar-body">
<!-- News Search -->
<div class="list-search">
<input type="text" id="news-search-input" placeholder="Neuigkeiten durchsuchen..." class="form-input" oninput="filterNews()">
</div>
<!-- News Items Container -->
<div id="news-list">
<?php if (empty($news_items)): ?>
<p style="text-align:center;color:#999;padding:20px;">Noch keine Neuigkeiten veröffentlicht.</p>
<?php else: ?>
<?php foreach ($news_items as $news): ?>
<div class="news-item"
data-title="<?= htmlspecialchars(strtolower($news['title'])) ?>"
data-content="<?= htmlspecialchars(strtolower($news['content'])) ?>"
data-author="<?= htmlspecialchars(strtolower($news['author_name'])) ?>">
<h3><?= htmlspecialchars($news['title']) ?></h3>
<p><?= nl2br(htmlspecialchars($news['content'])) ?></p>
<span class="news-date">
<?= htmlspecialchars($news['author_name']) ?>
· <?= date('d.m.Y', strtotime($news['published_at'])) ?>
</span>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div> </div>
</div> </div>

View File

@@ -346,7 +346,7 @@ function loadContributions() {
layerControl.addOverlay(contributionsLayer, '<i class="fa-solid fa-map-pin" style="color:#C00000;"></i> Beiträge'); layerControl.addOverlay(contributionsLayer, '<i class="fa-solid fa-map-pin" style="color:#C00000;"></i> Beiträge');
// Update Sidebar List and Statistics // Update Sidebar List and Statistics
updateContributionsList(); updateContributionsList();
updateStatistics(); buildCategoryFilter();
}); });
} }
@@ -766,7 +766,7 @@ function updateContributionsList() {
// Builds HTML // Builds HTML
if (filtered.length === 0) { if (filtered.length === 0) {
container.innerHTML = '<p style="text-align:center;color:#999;padding:20px;">Keine Beiträge gefunden.</p>'; container.innerHTML = '<p class="empty-state">Keine Beiträge gefunden.</p>';
return; return;
} }
@@ -830,22 +830,33 @@ document.getElementById('list-search-input').addEventListener('input', function
// Block 12: Sidebar Category Filter and Statistics // Block 12: Sidebar Category Filter and Statistics
// ===================================================================== // =====================================================================
// Builds Category Filter Checkboxes // Builds Category Filter Checkboxes with Counts
function buildCategoryFilter() { function buildCategoryFilter() {
const container = document.getElementById('category-filter'); const container = document.getElementById('category-filter');
const counts = {};
contributionsData.forEach(function (f) {
const cat = f.properties.category;
counts[cat] = (counts[cat] || 0) + 1;
});
const total = contributionsData.length;
let html = ''; let html = '';
for (const key in CATEGORIES) { for (const key in CATEGORIES) {
const cat = CATEGORIES[key]; const cat = CATEGORIES[key];
const checked = activeFilters.indexOf(key) !== -1 ? 'checked' : ''; const checked = activeFilters.indexOf(key) !== -1 ? 'checked' : '';
const count = counts[key] || 0;
html += '' + html += '<label style="display:flex;align-items:center;gap:8px;margin-bottom:6px;cursor:pointer;font-size:0.85rem;color:var(--color-text-secondary)">' +
'<label style="display:flex;align-items:center;gap:8px;margin-bottom:6px;cursor:pointer;">' + '<input type="checkbox" value="' + key + '" ' + checked + ' onchange="toggleCategoryFilter(this)">' +
'<input type="checkbox" value="' + key + '" ' + checked + ' onchange="toggleCategoryFilter(this)">' + categoryIcon(cat) +
'<span>' + categoryIcon(cat) + ' ' + cat.label + '</span>' + '<span>' + cat.label + ' (' + count + ')</span>' +
'</label>'; '</label>';
} }
html += '<p style="margin-top:10px;font-size:0.85rem;color:var(--color-text-secondary)"><strong>' + total + '</strong> Beiträge insgesamt</p>';
container.innerHTML = html; container.innerHTML = html;
} }
@@ -892,34 +903,6 @@ function toggleCategoryFilter(checkbox) {
updateContributionsList(); updateContributionsList();
} }
// Updates Statistics in Home Tab
function updateStatistics() {
const container = document.getElementById('stats-container');
const total = contributionsData.length;
// Counts per Category
const counts = {};
contributionsData.forEach(function (f) {
const cat = f.properties.category;
counts[cat] = (counts[cat] || 0) + 1;
});
let html = '<p style="font-size:0.9rem;"><strong>' + total + '</strong> Beiträge insgesamt</p>';
for (const key in CATEGORIES) {
const cat = CATEGORIES[key];
const count = counts[key] || 0;
if (count > 0) {
html += '<div style="display:flex;align-items:center;gap:8px;margin:4px 0;font-size:0.85rem;">' +
categoryIcon(cat) + ' ' +
cat.label + ': ' + count +
'</div>';
}
}
container.innerHTML = html;
}
// ===================================================================== // =====================================================================
// Block 13: Modals — Welcome, Login, Info, Privacy, Imprint // Block 13: Modals — Welcome, Login, Info, Privacy, Imprint
@@ -1034,7 +1017,7 @@ function escapeHtml(text) {
// Returns a colored Font Awesome Icon HTML String for a Category // Returns a colored Font Awesome Icon HTML String for a Category
function categoryIcon(cat) { function categoryIcon(cat) {
return '<i class="fa-solid ' + cat.faIcon + '" style="color:' + cat.color + ';"></i>'; return '<i class="fa-solid ' + cat.faIcon + ' fa-fw" style="color:' + cat.color + ';"></i>';
} }
// Reverse Geocodes Coordinates and saves Address to Contribution via API // Reverse Geocodes Coordinates and saves Address to Contribution via API

View File

@@ -11,7 +11,7 @@ $municipality = $stmt->fetch();
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Datenschutz — <?= htmlspecialchars($municipality['name']) ?></title> <title>Datenschutz — <?= htmlspecialchars($municipality['name']) ?></title>
<link rel="icon" href="<?= htmlspecialchars($municipality['logo_path'] ?? 'assets/icon-municipality.png') ?>" type="image/png"> <link rel="icon" href="assets/lock-solid-off-black.png" type="image/png">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="styles.css"> <link rel="stylesheet" href="styles.css">
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style> <style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>

View File

@@ -464,6 +464,7 @@ select.form-input { cursor: pointer; }
margin-bottom: var(--space-sm); margin-bottom: var(--space-sm);
line-height: 1.5; line-height: 1.5;
color: var(--color-text-secondary); color: var(--color-text-secondary);
font-size: 0.85rem;
} }

View File

@@ -5,10 +5,8 @@ Citizen Participation Portal for Lohne (Oldenburg).
## Project Structure ## Project Structure
- `migrations/` — versioned SQL Schema Migrations - `migrations/` — versioned SQL Schema Migrations
- `api/` — Backend (PHP)
- `public/` — Frontend (HTML, CSS, JS) - `public/` — Frontend (HTML, CSS, JS)
- `scripts/` — Maintenance Scripts (backup, deployment) - `scripts/` — Maintenance Scripts (backup, deployment)
- `legacy/` — Reference Code from Prototype
## Local Setup ## Local Setup