From 2993a443a74b92b63d6f332b7ccf942d5462d9d5 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Thu, 23 Apr 2026 14:59:03 +0200 Subject: [PATCH 01/25] removed dublicate pdo call --- public/api/init.php | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/public/api/init.php b/public/api/init.php index 37f2952..5dd04d2 100644 --- a/public/api/init.php +++ b/public/api/init.php @@ -28,24 +28,16 @@ session_start(); // Initializes Database Connection try { - $opt = [ + $dsn = "pgsql:host=$host;dbname=$db;port=$port"; + $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false - ]; - $dsn = "pgsql:host=$host;dbname=$db;port=$port;sslmode=disable"; - $pdo = new PDO($dsn, $user, $pass, $opt); + ]); - - $pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]); - - - - - // Creates Error Message -} catch(PDOException $e) { - echo "Error: ".$e->getMessage(); +} catch (PDOException $e) { + echo "Error: " . $e->getMessage(); } ?> \ No newline at end of file -- 2.49.1 From ade9ca212807f13dd394ceda64ebfa96249db7aa Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Thu, 23 Apr 2026 15:13:49 +0200 Subject: [PATCH 02/25] styling and fond sweetalert font override --- public/admin.css | 18 ++++++++++++++++++ public/admin.php | 10 +++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/public/admin.css b/public/admin.css index adfb977..d9b8948 100644 --- a/public/admin.css +++ b/public/admin.css @@ -331,6 +331,14 @@ body { flex-shrink: 0; } +.badge-category { + background: #e0e0e0; + color: #5a5a7a; + display: inline-flex; + align-items: center; + gap: 4px; +} + .badge-pending { background: #fff3cd; color: #856404; } .badge-approved { background: #d4edda; color: #155724; } .badge-rejected { background: #f8d7da; color: #721c24; } @@ -486,6 +494,16 @@ body { .back-link a { color: #5a5a7a; } +/* ----------------------------------------------------------------- + SweetAlert2 Font Override + ----------------------------------------------------------------- */ +.swal2-input, +.swal2-textarea, +.swal2-select { + font-family: 'Segoe UI', system-ui, sans-serif !important; +} + + /* ----------------------------------------------------------------- Mobile Responsive ----------------------------------------------------------------- */ diff --git a/public/admin.php b/public/admin.php index 144d407..03730fb 100644 --- a/public/admin.php +++ b/public/admin.php @@ -234,6 +234,10 @@ $counts['total'] = count($all_contributions);
+ + + +
@@ -256,10 +260,6 @@ $counts['total'] = count($all_contributions);
- - - - Uhr @@ -670,7 +670,7 @@ function show_login_page($municipality, $error = null) {

Moderationsportal

Bitte geben Sie das Moderationspasswort ein.

- +
-- 2.49.1 From 8d67c0c0b9b1485cbdb23468c4c5c65fceac81fc Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Thu, 23 Apr 2026 15:27:33 +0200 Subject: [PATCH 03/25] title and description above text fields for contribution edit --- public/admin.php | 12 ++++++++---- public/js/app.js | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/public/admin.php b/public/admin.php index 03730fb..d83a97e 100644 --- a/public/admin.php +++ b/public/admin.php @@ -577,10 +577,14 @@ $counts['total'] = count($all_contributions); title: 'Beitrag bearbeiten', html: '
' + - '' + - '' + - '' + - '' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '' + + '
' + '
', showCancelButton: true, confirmButtonText: 'Speichern', diff --git a/public/js/app.js b/public/js/app.js index 795d384..e8a2abd 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -484,10 +484,14 @@ function editContribution(contributionId) { title: 'Beitrag bearbeiten', html: '
' + - '' + - '' + - '' + - '' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '' + + '
' + '
', showCancelButton: true, confirmButtonText: 'Speichern', -- 2.49.1 From ec4c9fa8a98a65d4c23f43cf8893421bbf941c9e Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Thu, 23 Apr 2026 15:30:41 +0200 Subject: [PATCH 04/25] changed edit button colour to primary --- public/admin.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/admin.css b/public/admin.css index d9b8948..3ed0c02 100644 --- a/public/admin.css +++ b/public/admin.css @@ -312,7 +312,7 @@ body { .btn-approve { background: #2e7d32; color: white; } .btn-reject { background: #c62828; color: white; } -.btn-edit { background: #1565C0; color: white; } +.btn-edit { background: var(--color-primary); color: white; } .btn-delete { background: #424242; color: white; } .btn-map { background: #546E7A; color: white; } -- 2.49.1 From 8179498333ff3e48eb5962f80cb89dc1697b17f4 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 15:38:18 +0200 Subject: [PATCH 05/25] bootstrap button colours --- public/admin.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/admin.css b/public/admin.css index 3ed0c02..a8347e2 100644 --- a/public/admin.css +++ b/public/admin.css @@ -310,9 +310,9 @@ body { .btn:hover { filter: brightness(1.1); } -.btn-approve { background: #2e7d32; color: white; } -.btn-reject { background: #c62828; color: white; } -.btn-edit { background: var(--color-primary); color: white; } +.btn-approve { background: #28a745 ; color: white; } +.btn-reject { background: #DC3545; color: white; } +.btn-edit { background: #ffc107; color: white; } .btn-delete { background: #424242; color: white; } .btn-map { background: #546E7A; color: white; } -- 2.49.1 From 6a721fde7c99bfd937e657f42fbf3c1d8ee91931 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 15:45:27 +0200 Subject: [PATCH 06/25] fixed point layer opacity bug, changed point layer styling --- public/js/app.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index e8a2abd..500fb6d 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -331,10 +331,11 @@ function stylePoint(feature, latlng) { return L.circleMarker(latlng, { radius: 8, - color: '#ffffff', - weight: 2, + color: cat.color, + weight: 3, fillColor: cat.color, - fillOpacity: 0.9 + fillOpacity: 0.25, + opacity: 0.8 }); } @@ -755,7 +756,14 @@ function toggleCategoryFilter(checkbox) { if (layer.feature) { const cat = layer.feature.properties.category; if (activeFilters.indexOf(cat) !== -1) { - layer.setStyle({ opacity: 0.8, fillOpacity: layer.feature.geometry.type === 'Point' ? 0.25 : 0.25 }); + const catDef = CATEGORIES[cat] || CATEGORIES.other; + layer.setStyle({ + color: catDef.color, + weight: 3, + opacity: 0.8, + fillColor: catDef.color, + fillOpacity: 0.25 + }); if (layer.setRadius) layer.setRadius(8); layer.options.interactive = true; } else { -- 2.49.1 From 076e82213dc7f5b46d743d249d0c369d1e16aacf Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 16:06:26 +0200 Subject: [PATCH 07/25] added privacy and imprint pages to meet german DSGVO criteria --- public/index.php | 8 ++++---- public/js/app.js | 24 ------------------------ 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/public/index.php b/public/index.php index 7423017..994143f 100644 --- a/public/index.php +++ b/public/index.php @@ -100,14 +100,14 @@ if (!$municipality) { Informationen - - + diff --git a/public/js/app.js b/public/js/app.js index 500fb6d..357119d 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -870,30 +870,6 @@ function showInfoModal() { }); } -// Privacy Modal -function showPrivacyModal() { - Swal.fire({ - title: 'Datenschutz', - html: '

Das Bürgerbeteiligungsportal speichert die von Ihnen ' + - 'hinterlegten Daten zur Durchführung der Bürgerbeteiligung.

' + - '

Ihre Daten werden nicht an Dritte weitergegeben. ' + - 'Details entnehmen Sie bitte der vollständigen Datenschutzerklärung von ' + - MUNICIPALITY.name + '.

', - confirmButtonColor: MUNICIPALITY.primaryColor - }); -} - -// Imprint Modal -function showImprintModal() { - Swal.fire({ - title: 'Impressum', - html: '

Stadt ' + MUNICIPALITY.name + '

' + - '

Die vollständigen Angaben ' + - 'werden hier hinzugefügt, sobald das Portal in den Produktivbetrieb geht.

', - confirmButtonColor: MUNICIPALITY.primaryColor - }); -} - // ===================================================================== // Block 14: Mobile Navigation -- 2.49.1 From 9c8e641557f342c570ca7ea7d19c5162790eb188 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 16:09:53 +0200 Subject: [PATCH 08/25] added privacy and imprint pages to meet german DSGVO criteria --- public/imprint.php | 55 +++++++++++++++++++++++++++++++++++++++ public/privacy.php | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 public/imprint.php create mode 100644 public/privacy.php diff --git a/public/imprint.php b/public/imprint.php new file mode 100644 index 0000000..81f56cc --- /dev/null +++ b/public/imprint.php @@ -0,0 +1,55 @@ +prepare("SELECT * FROM municipalities WHERE slug = :slug"); +$stmt->execute([':slug' => 'lohne']); +$municipality = $stmt->fetch(); +?> + + + + + + Impressum — <?= htmlspecialchars($municipality['name']) ?> + + + + + + +
+

Angaben gemäß § 5 TMG

+

Stadt

+

Die vollständigen Angaben (Adresse, Telefon, E-Mail, Vertretungsberechtigte) werden hier ergänzt, sobald das Portal in den Produktivbetrieb geht.

+ +

Technische Umsetzung

+

endex GmbH

+ +

Haftungsausschluss

+

Die Inhalte der Bürgerbeiträge geben die Meinung der jeweiligen Verfasser wieder. Die Stadt übernimmt keine Gewähr für deren Richtigkeit.

+
+ + \ No newline at end of file diff --git a/public/privacy.php b/public/privacy.php new file mode 100644 index 0000000..0c9f6a3 --- /dev/null +++ b/public/privacy.php @@ -0,0 +1,64 @@ +prepare("SELECT * FROM municipalities WHERE slug = :slug"); +$stmt->execute([':slug' => 'lohne']); +$municipality = $stmt->fetch(); +?> + + + + + + Datenschutz — <?= htmlspecialchars($municipality['name']) ?> + + + + + + +
+

Verantwortliche Stelle

+

Stadt

+

Die vollständigen Kontaktdaten werden hier ergänzt, sobald das Portal in den Produktivbetrieb geht.

+ +

Erhobene Daten

+

Das Bürgerbeteiligungsportal speichert die von Ihnen eingegebenen Beiträge (Titel, Beschreibung, Geometrie, Name) sowie Ihre Abstimmungen zur Durchführung der Bürgerbeteiligung.

+ +

Zweck der Verarbeitung

+

Die Daten werden ausschließlich zur Durchführung und Auswertung der Bürgerbeteiligung verwendet.

+ +

Weitergabe an Dritte

+

Ihre Daten werden nicht an Dritte weitergegeben.

+ +

Ihre Rechte

+

Sie haben das Recht auf Auskunft, Berichtigung und Löschung Ihrer Daten. Kontaktieren Sie hierzu die verantwortliche Stelle.

+ +

Cookies und Sitzungsdaten

+

Das Portal verwendet Sitzungscookies zur Speicherung Ihres Namens während der Nutzung. Diese werden beim Schließen des Browsers gelöscht.

+
+ + \ No newline at end of file -- 2.49.1 From c9040b2f4e5d1fba0385c0f76f4248869f426039 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 16:13:45 +0200 Subject: [PATCH 09/25] reads municipality logo from database --- public/index.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/index.php b/public/index.php index 994143f..900a11d 100644 --- a/public/index.php +++ b/public/index.php @@ -91,8 +91,10 @@ if (!$municipality) {
- -

Bürgerbeteiligung

+ + + +

Mitmachkarte

-- 2.49.1 From 04f96b7ababb87b8e842f70cb0456d6ef53e4062 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 16:50:27 +0200 Subject: [PATCH 11/25] commented migration for news table --- migrations/003_news_table.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/003_news_table.sql b/migrations/003_news_table.sql index dbbc3ad..cd68291 100644 --- a/migrations/003_news_table.sql +++ b/migrations/003_news_table.sql @@ -30,13 +30,13 @@ CREATE TRIGGER set_news_updated_at -- --------------------------------------------------------------------- --- Block 4: Indexes for fast Queries +-- Block 3 Indexes for fast Queries -- --------------------------------------------------------------------- CREATE INDEX idx_news_municipality ON news(municipality_id); -- --------------------------------------------------------------------- --- Block 8: Seed Data — Initial News Article +-- Block 4: Seed Data — Initial News Article -- --------------------------------------------------------------------- INSERT INTO news (municipality_id, title, content) SELECT municipality_id, 'Mitmachkarte gestartet', -- 2.49.1 From 9ca215c36d1f658e4d66d975801998d92ba45804 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 16:55:49 +0200 Subject: [PATCH 12/25] added migration for reverse geocoding --- migrations/004_reverse_geocoding.sql | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 migrations/004_reverse_geocoding.sql diff --git a/migrations/004_reverse_geocoding.sql b/migrations/004_reverse_geocoding.sql new file mode 100644 index 0000000..662caa8 --- /dev/null +++ b/migrations/004_reverse_geocoding.sql @@ -0,0 +1,8 @@ +-- ===================================================================== +-- Migration 004: Adds Address Column for Reverse Geocoding +-- ===================================================================== + +ALTER TABLE contributions + ADD COLUMN address VARCHAR(255) DEFAULT NULL; + +COMMENT ON COLUMN contributions.address IS 'Reverse geocoded Address, stored automatically on Creation.'; \ No newline at end of file -- 2.49.1 From 5cadc5c1b4386a7e10dd44e41d481555f934263f Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 17:00:55 +0200 Subject: [PATCH 13/25] reverse geocoding for contributions --- public/api/contributions.php | 2 +- public/js/app.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/public/api/contributions.php b/public/api/contributions.php index 4a833aa..f969d40 100644 --- a/public/api/contributions.php +++ b/public/api/contributions.php @@ -205,7 +205,7 @@ function handle_update($input) { } // Builds dynamic SQL Query to only update sent Fields - $updatable_fields = ['category', 'title', 'description', 'status']; + $updatable_fields = ['category', 'title', 'description', 'status', 'address']; $set_clauses = []; $params = [':id' => $contribution_id]; diff --git a/public/js/app.js b/public/js/app.js index 357119d..eb2278d 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -448,6 +448,14 @@ function submitCreate() { return; } + // Triggers Reverse Geocoding in Background + if (response.contribution_id && drawnGeometry) { + const coords = drawnGeomType === 'point' ? drawnGeometry.coordinates : + drawnGeomType === 'line' ? drawnGeometry.coordinates[0] : + drawnGeometry.coordinates[0][0]; + reverseGeocode(response.contribution_id, coords[1], coords[0]); + } + Swal.fire('Eingereicht!', 'Ihr Beitrag wurde erfolgreich eingereicht und wird nach Prüfung durch das Moderationsteam veröffentlicht.', 'success'); closeCreateModal(); loadContributions(); @@ -917,6 +925,31 @@ function categoryIcon(cat) { return ''; } +// Reverse Geocodes Coordinates and saves Address to Contribution via API +function reverseGeocode(contributionId, lat, lng) { + fetch('https://nominatim.openstreetmap.org/reverse?format=json&lat=' + lat + '&lon=' + lng + '&zoom=18&addressdetails=1', { + headers: { 'Accept-Language': 'de' } + }) + .then(function (r) { return r.json(); }) + .then(function (data) { + if (data.display_name) { + const addr = data.address || {}; + const parts = []; + if (addr.road) parts.push(addr.road + (addr.house_number ? ' ' + addr.house_number : '')); + if (addr.city || addr.town || addr.village) parts.push(addr.city || addr.town || addr.village); + const shortAddress = parts.length > 0 ? parts.join(', ') : data.display_name.split(',').slice(0, 2).join(','); + + // Saves Address to Database via API + apiCall({ + action: 'update', + contribution_id: contributionId, + address: shortAddress + }, function () {}); + } + }) + .catch(function () {}); +} + // ===================================================================== // Block 16: Application Startup -- 2.49.1 From 62ae9f18b0352698fffed6c8cfceac36ed2d958b Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 17:08:32 +0200 Subject: [PATCH 14/25] added date and author to news in sidebar --- public/index.php | 5 ++++- public/styles.css | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index b2a3d2d..8b99032 100644 --- a/public/index.php +++ b/public/index.php @@ -217,9 +217,12 @@ $news_items = $stmt->fetchAll();
-

+ + + · +
diff --git a/public/styles.css b/public/styles.css index 8ea0159..9da66e5 100644 --- a/public/styles.css +++ b/public/styles.css @@ -368,6 +368,7 @@ html, body { News Items (Sidebar Tab) ----------------------------------------------------------------- */ .news-item { + font-size: 0.8rem; padding: var(--space-md); margin-bottom: var(--space-sm); background: var(--color-surface); @@ -376,10 +377,8 @@ html, body { } .news-date { - font-size: 0.75rem; + font-size: 0.8rem; color: var(--color-text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; } .news-item h3 { -- 2.49.1 From 25cf797294a13808bbad6b49ab0166b4f1d968a3 Mon Sep 17 00:00:00 2001 From: patrickzerhusen Date: Fri, 24 Apr 2026 17:18:56 +0200 Subject: [PATCH 15/25] added news CRUD functionality in moderation portal --- public/admin.php | 197 ++++++++++++++++++++++++++++++++++- public/api/contributions.php | 92 ++++++++++++++++ 2 files changed, 285 insertions(+), 4 deletions(-) diff --git a/public/admin.php b/public/admin.php index d83a97e..4347d82 100644 --- a/public/admin.php +++ b/public/admin.php @@ -57,6 +57,16 @@ $stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug"); $stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]); $municipality = $stmt->fetch(); +// Loads News for Moderation +$stmt = $pdo->prepare(" + SELECT news_id, title, content, author_name, published_at, created_at + FROM news + WHERE municipality_id = :mid + ORDER BY published_at DESC +"); +$stmt->execute([':mid' => $municipality['municipality_id']]); +$news_items = $stmt->fetchAll(); + // Shows Login Page if not authenticated if ($page === 'login' || !is_admin()) { show_login_page($municipality, $login_error ?? null); @@ -312,15 +322,56 @@ $counts['total'] = count($all_contributions); - +