csad
All checks were successful
Deploy / deploy (push) Successful in 58s

This commit is contained in:
2026-07-31 20:40:25 +02:00
parent f6cb211859
commit db9b02449d
8 changed files with 136 additions and 8 deletions

View File

@@ -16,6 +16,7 @@ Aktueller Projektstand
- Kinder koennen im Mitgliederbereich zusaetzlich nachtraeglich bearbeitet und geloescht werden. - Kinder koennen im Mitgliederbereich zusaetzlich nachtraeglich bearbeitet und geloescht werden.
- Wenn bei einem Kind ein Geburtsdatum hinterlegt ist, wird das Alter automatisch daraus berechnet und spaeter bei Bedarf jaehrlich aktualisiert; ohne Geburtsdatum bleibt das manuell gepflegte Alter massgeblich. - Wenn bei einem Kind ein Geburtsdatum hinterlegt ist, wird das Alter automatisch daraus berechnet und spaeter bei Bedarf jaehrlich aktualisiert; ohne Geburtsdatum bleibt das manuell gepflegte Alter massgeblich.
- Profilbezogene sensible Angaben wie Vorname, Nachname, Telefonnummer, Beruf, Sprachen und Kurzvorstellung werden app-seitig verschluesselt gespeichert; vorhandene Klartextwerte werden bei der Nutzung schrittweise nachmigriert. - Profilbezogene sensible Angaben wie Vorname, Nachname, Telefonnummer, Beruf, Sprachen und Kurzvorstellung werden app-seitig verschluesselt gespeichert; vorhandene Klartextwerte werden bei der Nutzung schrittweise nachmigriert.
- Das Profil besitzt jetzt zusaetzlich eine vollstaendige Adresse mit verschluesselter Strasse/Hausnummer sowie eine optionale Adress-Lokalisierung ueber die vorhandenen externen Karten-/Geocoding-Dienste.
- Alte statische Avatar-Presets und frühere Layer-Assets sind entfernt. - Alte statische Avatar-Presets und frühere Layer-Assets sind entfernt.
- Änderungen an Funktionen mit Cookies, LocalStorage, SessionStorage, Geolocation, Tracking oder Drittanbietern müssen immer auch in Datenschutz-/Cookie-Hinweisen und im Consent-Flow berücksichtigt werden. - Änderungen an Funktionen mit Cookies, LocalStorage, SessionStorage, Geolocation, Tracking oder Drittanbietern müssen immer auch in Datenschutz-/Cookie-Hinweisen und im Consent-Flow berücksichtigt werden.

View File

@@ -36,6 +36,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
- Kinder koennen im Mitgliederbereich angelegt, nachtraeglich bearbeitet und wieder geloescht werden. - Kinder koennen im Mitgliederbereich angelegt, nachtraeglich bearbeitet und wieder geloescht werden.
- Wenn ein Geburtsdatum gesetzt ist, wird das Alter im Mitgliederbereich automatisch berechnet und spaeter bei Bedarf anhand des Datums aktualisiert; ohne Geburtsdatum gilt der manuelle Alterswert. - Wenn ein Geburtsdatum gesetzt ist, wird das Alter im Mitgliederbereich automatisch berechnet und spaeter bei Bedarf anhand des Datums aktualisiert; ohne Geburtsdatum gilt der manuelle Alterswert.
- Sensible Profildaten wie Vorname, Nachname, Telefonnummer, Beruf, Sprachen und Kurzvorstellung werden app-seitig verschluesselt gespeichert; bestehende Klartextwerte werden bei Nutzung schrittweise in den verschluesselten Zustand ueberfuehrt. - Sensible Profildaten wie Vorname, Nachname, Telefonnummer, Beruf, Sprachen und Kurzvorstellung werden app-seitig verschluesselt gespeichert; bestehende Klartextwerte werden bei Nutzung schrittweise in den verschluesselten Zustand ueberfuehrt.
- Im Profil koennen jetzt auch Strasse/Hausnummer, PLZ, Ort und Region gepflegt werden; fuer die Adress-Lokalisierung werden die bereits vorhandenen externen Karten-/Geocoding-Dienste wiederverwendet.
- Im Profil kann der Nutzer aktuell Komponenten wie Augen, Augenbrauen, Mund, Brille, Haare, Bart und Ohrringe direkt per Vorschaubild auswählen. - Im Profil kann der Nutzer aktuell Komponenten wie Augen, Augenbrauen, Mund, Brille, Haare, Bart und Ohrringe direkt per Vorschaubild auswählen.
- Der Button `Random Profilbild` setzt Seed und Varianten des aktiven Styles zufaellig neu und aktualisiert die Vorschau sofort. - Der Button `Random Profilbild` setzt Seed und Varianten des aktiven Styles zufaellig neu und aktualisiert die Vorschau sofort.
- Beim Oeffnen des Avatar-Generators wird nur der aktive Style-/Komponenten-Tab geladen; weitere Varianten werden erst beim Wechsel nachgeladen. - Beim Oeffnen des Avatar-Generators wird nur der aktive Style-/Komponenten-Tab geladen; weitere Varianten werden erst beim Wechsel nachgeladen.

View File

@@ -30,6 +30,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
- im Bereich `Kinder` koennen vorhandene Eintraege jetzt auch nachtraeglich bearbeitet und geloescht werden - im Bereich `Kinder` koennen vorhandene Eintraege jetzt auch nachtraeglich bearbeitet und geloescht werden
- bei Kindern mit Geburtsdatum wird das Alter automatisch berechnet und bei Aufruf des Mitgliederbereichs bei Bedarf jaehrlich nachgezogen; ohne Geburtsdatum gilt das manuell gepflegte Alter - bei Kindern mit Geburtsdatum wird das Alter automatisch berechnet und bei Aufruf des Mitgliederbereichs bei Bedarf jaehrlich nachgezogen; ohne Geburtsdatum gilt das manuell gepflegte Alter
- sensible Profildaten werden im Mitgliederbereich jetzt konsequent app-seitig verschluesselt gespeichert; bestehende Klartextwerte werden beim Lesen schrittweise in den verschluesselten Zustand ueberfuehrt - sensible Profildaten werden im Mitgliederbereich jetzt konsequent app-seitig verschluesselt gespeichert; bestehende Klartextwerte werden beim Lesen schrittweise in den verschluesselten Zustand ueberfuehrt
- das Profil unterstuetzt jetzt eine vollstaendige Adresse mit verschluesselter Strasse/Hausnummer sowie optionaler Adress-Lokalisierung ueber die bereits vorhandenen Karten-/Geocoding-Dienste
- Debug-Floating-Button nur für `site_admin` im Debug-Modus - Debug-Floating-Button nur für `site_admin` im Debug-Modus
## Technik ## Technik

View File

@@ -81,7 +81,7 @@ $sectionLinks = [
<dl class="account-kv"> <dl class="account-kv">
<div><dt>Name</dt><dd><?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></dd></div> <div><dt>Name</dt><dd><?= htmlspecialchars(trim($profile['first_name'] . ' ' . $profile['last_name']), ENT_QUOTES) ?></dd></div>
<div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></dd></div> <div><dt>Anzeigename</dt><dd><?= htmlspecialchars($profile['display_name'], ENT_QUOTES) ?></dd></div>
<div><dt>Ort</dt><dd><?= htmlspecialchars(trim($profile['city'] . ' ' . $profile['zip']), ENT_QUOTES) ?></dd></div> <div><dt>Adresse</dt><dd><?= htmlspecialchars(trim(implode(', ', array_filter([(string)($profile['street'] ?? ''), trim((string)$profile['zip'] . ' ' . (string)$profile['city']), (string)($profile['region'] ?? '')]))), ENT_QUOTES) ?></dd></div>
<div><dt>E-Mail</dt><dd><?= htmlspecialchars($profile['email'], ENT_QUOTES) ?></dd></div> <div><dt>E-Mail</dt><dd><?= htmlspecialchars($profile['email'], ENT_QUOTES) ?></dd></div>
<div><dt>Telefon</dt><dd><?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?></dd></div> <div><dt>Telefon</dt><dd><?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?></dd></div>
<div><dt>Beruf</dt><dd><?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?></dd></div> <div><dt>Beruf</dt><dd><?= htmlspecialchars($profile['profession'], ENT_QUOTES) ?></dd></div>
@@ -486,6 +486,9 @@ $sectionLinks = [
</div> </div>
<form method="post" class="stack gap-12"> <form method="post" class="stack gap-12">
<input type="hidden" name="action" value="profile"> <input type="hidden" name="action" value="profile">
<input type="hidden" id="pRegion" name="region" value="<?= htmlspecialchars((string)($profile['region'] ?? ''), ENT_QUOTES) ?>">
<input type="hidden" id="pLat" name="lat" value="<?= htmlspecialchars((string)($profile['lat'] ?? ''), ENT_QUOTES) ?>">
<input type="hidden" id="pLng" name="lng" value="<?= htmlspecialchars((string)($profile['lng'] ?? ''), ENT_QUOTES) ?>">
<div class="form-grid"> <div class="form-grid">
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label" for="pName">Anzeigename</label> <label class="label" for="pName">Anzeigename</label>
@@ -498,12 +501,12 @@ $sectionLinks = [
</div> </div>
<div class="form-grid"> <div class="form-grid">
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label" for="pLast">Nachname</label> <label class="label" for="pStreet">Straße / Hausnummer</label>
<input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>"> <input id="pStreet" name="street" class="input" value="<?= htmlspecialchars((string)($profile['street'] ?? ''), ENT_QUOTES) ?>" placeholder="z. B. Musterstraße 12">
</div> </div>
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label" for="pCity">Ort</label> <label class="label" for="pLast">Nachname</label>
<input id="pCity" name="city" class="input" value="<?= htmlspecialchars($profile['city'], ENT_QUOTES) ?>"> <input id="pLast" name="last_name" class="input" value="<?= htmlspecialchars($profile['last_name'], ENT_QUOTES) ?>">
</div> </div>
</div> </div>
<div class="form-grid"> <div class="form-grid">
@@ -511,11 +514,25 @@ $sectionLinks = [
<label class="label" for="pZip">PLZ</label> <label class="label" for="pZip">PLZ</label>
<input id="pZip" name="zip" class="input" value="<?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?>"> <input id="pZip" name="zip" class="input" value="<?= htmlspecialchars($profile['zip'], ENT_QUOTES) ?>">
</div> </div>
<div class="stack gap-6">
<label class="label" for="pCity">Ort</label>
<input id="pCity" name="city" class="input" value="<?= htmlspecialchars($profile['city'], ENT_QUOTES) ?>">
</div>
</div>
<div class="form-grid">
<div class="stack gap-6">
<label class="label" for="pRegionView">Region / Bezirk</label>
<input id="pRegionView" class="input" value="<?= htmlspecialchars((string)($profile['region'] ?? ''), ENT_QUOTES) ?>" placeholder="wird bei Lokalisierung ergänzt">
</div>
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label" for="pPhone">Telefon (mobil)</label> <label class="label" for="pPhone">Telefon (mobil)</label>
<input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>"> <input id="pPhone" name="contact_phone" class="input" value="<?= htmlspecialchars($profile['contact_phone'], ENT_QUOTES) ?>">
</div> </div>
</div> </div>
<div class="flex gap-12" style="flex-wrap:wrap;">
<button class="btn ghost" type="button" id="btnProfileLocate">Adresse lokalisieren</button>
<p class="muted small" style="margin:0;">Verwendet dieselben Karten-/Adressdienste wie bei Events und nur nach Einwilligung in externe Dienste.</p>
</div>
<div class="stack gap-6"> <div class="stack gap-6">
<label class="label">Sprachen (Mehrfachauswahl)</label> <label class="label">Sprachen (Mehrfachauswahl)</label>
<div class="chips" style="flex-wrap: wrap;"> <div class="chips" style="flex-wrap: wrap;">
@@ -728,6 +745,14 @@ $sectionLinks = [
const zipInput = document.getElementById('evZip'); const zipInput = document.getElementById('evZip');
const cityInput = document.getElementById('evCity'); const cityInput = document.getElementById('evCity');
const regionInput = document.getElementById('evRegion'); const regionInput = document.getElementById('evRegion');
const profileLocateButton = document.getElementById('btnProfileLocate');
const profileStreetInput = document.getElementById('pStreet');
const profileZipInput = document.getElementById('pZip');
const profileCityInput = document.getElementById('pCity');
const profileRegionInput = document.getElementById('pRegion');
const profileRegionViewInput = document.getElementById('pRegionView');
const profileLatInput = document.getElementById('pLat');
const profileLngInput = document.getElementById('pLng');
let map, marker; let map, marker;
function ensureLeaflet(callback) { function ensureLeaflet(callback) {
@@ -818,6 +843,28 @@ $sectionLinks = [
.catch(() => {}); .catch(() => {});
} }
function geocodeAddressData(query, onSuccess) {
if (!window.PKTConsent || !window.PKTConsent.has('external_services')) {
alert('Für Karten und Adresssuche bitte zuerst die externen Dienste in den Cookie-Einstellungen erlauben.');
window.PKTConsent?.openPreferences?.();
return;
}
fetch('https://nominatim.openstreetmap.org/search?format=jsonv2&limit=1&addressdetails=1&q=' + encodeURIComponent(query), {
headers: { 'Accept-Language': 'de', 'User-Agent': 'papa-kind-treff/1.0' },
})
.then(r => r.json())
.then(data => {
if (Array.isArray(data) && data[0]) {
onSuccess?.(data[0]);
return;
}
alert('Adresse konnte nicht eindeutig lokalisiert werden.');
})
.catch(() => {
alert('Adresssuche aktuell nicht erreichbar.');
});
}
function initMap() { function initMap() {
if (map) { map.invalidateSize(); return; } if (map) { map.invalidateSize(); return; }
map = L.map(mapContainer).setView([51.1657, 10.4515], 6); map = L.map(mapContainer).setView([51.1657, 10.4515], 6);
@@ -863,6 +910,62 @@ $sectionLinks = [
geocodeAndPlace(parts.join(', ')); geocodeAndPlace(parts.join(', '));
}); });
(function(){
const syncProfileRegionView = () => {
if (profileRegionViewInput && profileRegionInput) {
profileRegionInput.value = profileRegionViewInput.value.trim();
}
};
const clearProfileCoordinates = () => {
if (profileLatInput) profileLatInput.value = '';
if (profileLngInput) profileLngInput.value = '';
};
[profileStreetInput, profileZipInput, profileCityInput].forEach((input) => {
input?.addEventListener('input', clearProfileCoordinates);
});
profileRegionViewInput?.addEventListener('input', () => {
syncProfileRegionView();
clearProfileCoordinates();
});
profileLocateButton?.addEventListener('click', () => {
const parts = [
profileStreetInput?.value || '',
profileZipInput?.value || '',
profileCityInput?.value || '',
profileRegionViewInput?.value || '',
].map(v => v.trim()).filter(Boolean);
if (!parts.length) {
alert('Bitte zuerst mindestens einen Teil der Adresse eingeben.');
return;
}
geocodeAddressData(parts.join(', '), (result) => {
const address = result.address || {};
const street = [address.road || '', address.house_number || ''].filter(Boolean).join(' ').trim();
const region = address.suburb || address.state || address.county || '';
const city = address.city || address.town || address.village || '';
if (profileStreetInput && street) profileStreetInput.value = street;
if (profileZipInput && address.postcode) profileZipInput.value = address.postcode;
if (profileCityInput && city) profileCityInput.value = city;
if (profileRegionViewInput) profileRegionViewInput.value = region;
if (profileRegionInput) profileRegionInput.value = region;
const lat = parseFloat(result.lat);
const lng = parseFloat(result.lon);
if (profileLatInput) profileLatInput.value = Number.isNaN(lat) ? '' : lat.toFixed(7);
if (profileLngInput) profileLngInput.value = Number.isNaN(lng) ? '' : lng.toFixed(7);
});
});
syncProfileRegionView();
})();
<?php if ($editing): ?> <?php if ($editing): ?>
(function(){ (function(){
const modal = document.getElementById('modalEvent'); const modal = document.getElementById('modalEvent');

View File

@@ -48,7 +48,7 @@ $clientCookie = $config->cookiePrefix() . 'client';
<p> <p>
Wenn du ein Konto anlegst oder den Mitgliederbereich nutzt, verarbeiten wir die dafür notwendigen Daten, Wenn du ein Konto anlegst oder den Mitgliederbereich nutzt, verarbeiten wir die dafür notwendigen Daten,
insbesondere E-Mail-Adresse, Passwort-Hash, Verifikationsstatus sowie die von dir gepflegten Profilangaben. insbesondere E-Mail-Adresse, Passwort-Hash, Verifikationsstatus sowie die von dir gepflegten Profilangaben.
Dazu können Anzeigename, Name, Ort, Sprachen, Kurzbeschreibung, optionale Kinderangaben, Dazu können Anzeigename, Name, Adresse, Ort, Sprachen, Kurzbeschreibung, optionale Kinderangaben,
eigene Events, Event-Teilnahmen und Community-Inhalte gehören. eigene Events, Event-Teilnahmen und Community-Inhalte gehören.
</p> </p>
<p> <p>
@@ -137,6 +137,11 @@ $clientCookie = $config->cookiePrefix() . 'client';
Je nach Auswahl wird dieser nur für den aktuellen Besuch oder darüber hinaus lokal gespeichert, Je nach Auswahl wird dieser nur für den aktuellen Besuch oder darüber hinaus lokal gespeichert,
damit dir passende Treffen in deiner Nähe angezeigt werden können. damit dir passende Treffen in deiner Nähe angezeigt werden können.
</p> </p>
<p>
Zusätzlich kannst du im Mitgliederbereich deine Profiladresse über die gleichen externen Adressdienste
lokalisieren lassen, die auch für Event-Adressen genutzt werden. Dabei werden die von dir eingegebenen
Adressbestandteile an den jeweiligen Geocoding-Dienst übermittelt.
</p>
<p> <p>
Für Karten- und Adressfunktionen werden derzeit externe Dienste genutzt: Für Karten- und Adressfunktionen werden derzeit externe Dienste genutzt:
</p> </p>

View File

@@ -22,6 +22,7 @@ CREATE TABLE user_profiles (
display_name VARCHAR(120) NOT NULL, display_name VARCHAR(120) NOT NULL,
first_name VARBINARY(512) NULL, first_name VARBINARY(512) NULL,
last_name VARBINARY(512) NULL, last_name VARBINARY(512) NULL,
street VARBINARY(512) NULL,
share_level ENUM('basic','papa','papa_contact') NOT NULL DEFAULT 'basic', share_level ENUM('basic','papa','papa_contact') NOT NULL DEFAULT 'basic',
children_visibility ENUM('hidden','age_only','details') NOT NULL DEFAULT 'hidden', children_visibility ENUM('hidden','age_only','details') NOT NULL DEFAULT 'hidden',
zip CHAR(5) NULL, zip CHAR(5) NULL,

View File

@@ -11,6 +11,7 @@ final class AccountPages
private const ENCRYPTED_PROFILE_FIELDS = [ private const ENCRYPTED_PROFILE_FIELDS = [
'first_name', 'first_name',
'last_name', 'last_name',
'street',
'contact_phone', 'contact_phone',
'profession', 'profession',
'languages', 'languages',
@@ -185,6 +186,7 @@ final class AccountPages
} }
$firstNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['first_name'])); $firstNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['first_name']));
$lastNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['last_name'])); $lastNameEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['last_name']));
$streetEnc = self::encryptOptionalProfileField($crypto, trim((string)($_POST['street'] ?? '')));
$phoneEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['contact_phone'])); $phoneEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['contact_phone']));
$professionEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['profession'])); $professionEnc = self::encryptOptionalProfileField($crypto, trim((string)$_POST['profession']));
$languagesEnc = self::encryptOptionalProfileField($crypto, trim((string)$languages)); $languagesEnc = self::encryptOptionalProfileField($crypto, trim((string)$languages));
@@ -193,13 +195,17 @@ final class AccountPages
if ($profileSettings) { if ($profileSettings) {
$profileSettings->ensureSchema(); $profileSettings->ensureSchema();
} }
$stmt = $pdo?->prepare('UPDATE user_profiles SET display_name=:name, first_name=:fname, last_name=:lname, zip=:zip, city=:city, profession=:prof, languages=:langs, about=:about, contact_phone=:phone, location_tracking_preference=:locationPref, updated_at=NOW() WHERE user_id=:id'); $stmt = $pdo?->prepare('UPDATE user_profiles SET display_name=:name, first_name=:fname, last_name=:lname, street=:street, zip=:zip, city=:city, region=:region, lat=:lat, lng=:lng, profession=:prof, languages=:langs, about=:about, contact_phone=:phone, location_tracking_preference=:locationPref, updated_at=NOW() WHERE user_id=:id');
$stmt?->execute([ $stmt?->execute([
'name' => trim((string)$_POST['display_name']), 'name' => trim((string)$_POST['display_name']),
'fname' => $firstNameEnc, 'fname' => $firstNameEnc,
'lname' => $lastNameEnc, 'lname' => $lastNameEnc,
'street' => $streetEnc,
'zip' => trim((string)$_POST['zip']), 'zip' => trim((string)$_POST['zip']),
'city' => trim((string)$_POST['city']), 'city' => trim((string)$_POST['city']),
'region' => trim((string)($_POST['region'] ?? '')),
'lat' => isset($_POST['lat']) && $_POST['lat'] !== '' ? (float)$_POST['lat'] : null,
'lng' => isset($_POST['lng']) && $_POST['lng'] !== '' ? (float)$_POST['lng'] : null,
'prof' => $professionEnc, 'prof' => $professionEnc,
'langs' => $languagesEnc, 'langs' => $languagesEnc,
'about' => $aboutEnc, 'about' => $aboutEnc,
@@ -402,6 +408,10 @@ final class AccountPages
'last_name' => '', 'last_name' => '',
'zip' => '', 'zip' => '',
'city' => '', 'city' => '',
'region' => '',
'lat' => null,
'lng' => null,
'street' => '',
'profession' => '', 'profession' => '',
'languages' => '', 'languages' => '',
'about' => '', 'about' => '',
@@ -427,7 +437,7 @@ final class AccountPages
static fn(string $column): string => 'p.' . $column, static fn(string $column): string => 'p.' . $column,
AvatarManager::allProfileColumns() AvatarManager::allProfileColumns()
)); ));
$stmt = $pdo?->prepare("SELECT u.email, u.status, p.display_name, p.first_name, p.last_name, p.zip, p.city, p.profession, p.languages, p.about, p.contact_phone, p.location_tracking_preference, $avatarColumns FROM users u LEFT JOIN user_profiles p ON p.user_id = u.id WHERE u.id = :id LIMIT 1"); $stmt = $pdo?->prepare("SELECT u.email, u.status, p.display_name, p.first_name, p.last_name, p.street, p.zip, p.city, p.region, p.lat, p.lng, p.profession, p.languages, p.about, p.contact_phone, p.location_tracking_preference, $avatarColumns FROM users u LEFT JOIN user_profiles p ON p.user_id = u.id WHERE u.id = :id LIMIT 1");
$stmt?->execute(['id' => $userId]); $stmt?->execute(['id' => $userId]);
$row = $stmt?->fetch(\PDO::FETCH_ASSOC); $row = $stmt?->fetch(\PDO::FETCH_ASSOC);
if ($row) { if ($row) {

View File

@@ -61,6 +61,7 @@ final class ProfileSettings
$columnDefinitions = [ $columnDefinitions = [
'first_name' => 'VARBINARY(512) NULL', 'first_name' => 'VARBINARY(512) NULL',
'last_name' => 'VARBINARY(512) NULL', 'last_name' => 'VARBINARY(512) NULL',
'street' => 'VARBINARY(512) NULL',
'contact_phone' => 'VARBINARY(512) NULL', 'contact_phone' => 'VARBINARY(512) NULL',
'contact_email' => 'VARBINARY(512) NULL', 'contact_email' => 'VARBINARY(512) NULL',
'profession' => 'VARBINARY(512) NULL', 'profession' => 'VARBINARY(512) NULL',
@@ -70,6 +71,11 @@ final class ProfileSettings
foreach ($columnDefinitions as $column => $definition) { foreach ($columnDefinitions as $column => $definition) {
if (!$this->hasColumn('user_profiles', $column)) { if (!$this->hasColumn('user_profiles', $column)) {
if ($column === 'street') {
$this->pdo->exec('ALTER TABLE user_profiles ADD COLUMN street VARBINARY(512) NULL AFTER last_name');
$this->columnCache['user_profiles.street'] = true;
$this->columnTypeCache['user_profiles.street'] = 'varbinary';
}
continue; continue;
} }