Files
Zefix_search/index.php
T

559 lines
23 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/app.php';
app_send_security_headers();
include "zefixAPI.php";
$turnstileSiteKey = app_turnstile_is_configured() ? app_turnstile_site_key() : '';
$formStartedToken = app_create_form_started_token();
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/bootstrap.css">
<link rel="stylesheet" href="css/custom.css?v=2.2">
<?php if ($turnstileSiteKey !== ''): ?>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<?php endif; ?>
<title>Silias Zefix Export</title>
<style>
/* Nice input for the search box */
#sitzInput {
box-sizing: border-box;
font-size: 16px;
border: 1px solid #dee2e6;
border-radius: .5rem;
padding: .6rem .8rem;
width: 100%;
}
.scrollWindow {
max-height: 320px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: .75rem;
background: #fff;
padding: .75rem;
}
.button-small {
font-size: 0.8rem;
padding: .35rem .6rem;
border-radius: .5rem;
}
.gemeindeeintrag { margin-bottom: .25rem; }
.bot-trap {
position: absolute !important;
left: -10000px !important;
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-light site-header border-bottom py-2">
<div class="container">
<a class="brand-lockup" href="index.php" aria-label="Silias Zefix Export Startseite">
<span class="brand-logo-crop" aria-hidden="true">
<img src="img/silias-logo.png" alt="">
</span>
<span class="brand-divider" aria-hidden="true"></span>
<span class="brand-product">Zefix Export<small>Ein kostenloses Silias-Tool</small></span>
</a>
</div>
</nav>
<main class="container py-4 py-md-5">
<div class="row justify-content-center">
<div class="col-lg-10 col-xl-9">
<div class="mb-4 mb-md-5">
<div class="hero-eyebrow mb-2">Zentraler Firmenindex</div>
<h1 class="display-6 fw-bold hero-title mb-3">Firmendaten gezielt auswählen und als CSV exportieren</h1>
<p class="lead hero-copy mb-0">
Filtern Sie Unternehmen nach Firmenname, Ort und Rechtsform. Sobald der Export bereit ist,
erhalten Sie einen sicheren Download-Link per E-Mail.
</p>
</div>
<div class="card export-shell shadow-sm">
<div class="card-body p-4 p-md-5">
<form action="submit.php" method="post" class="needs-validation" novalidate>
<input type="hidden" name="form_started" value="<?php echo htmlspecialchars($formStartedToken, ENT_QUOTES); ?>">
<div class="bot-trap" aria-hidden="true" hidden>
<label for="website">Website</label>
<input type="text" id="website" name="website" value="" tabindex="-1" autocomplete="off">
</div>
<div class="form-section mb-3">
<div class="section-heading">
<span class="section-number" aria-hidden="true">1</span>
<div>
<h2>Firmenname</h2>
<p>Optional: Schränken Sie den Export auf passende Firmennamen ein.</p>
</div>
</div>
<label for="firma" class="form-label">Firmenname oder Suchmuster</label>
<input type="text" id="firma" name="firma" class="form-control" placeholder="Zum Beispiel *solar*" maxlength="200">
<div class="form-text mt-2">Das Sternchen (*) kann als Platzhalter verwendet werden.</div>
</div>
<div class="form-section mb-3">
<div class="section-heading">
<span class="section-number" aria-hidden="true">2</span>
<div>
<h2>Ort auswählen</h2>
<p>Filtern Sie zuerst nach Kanton oder Ortsname und übernehmen Sie danach die gewünschten Orte.</p>
</div>
</div>
<div class="alert alert-primary border-0 py-2 mb-3" role="note">
<strong>Hinweis:</strong> Die Kantone filtern nur die Ortsliste. Klicken Sie danach auf
<strong>«Gefilterte Orte auswählen»</strong>, damit die Orte in den Export übernommen werden.
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label mb-0">Kanton</label>
<span class="selection-meta" id="cantonCount">0 ausgewählt</span>
</div>
<div id="kantonauswahl" class="scrollWindow" onclick="filterFunction()"></div>
</div>
<div>
<div class="d-flex justify-content-between align-items-center mb-2">
<label for="sitzInput" class="form-label mb-0">Sitz (Ort)</label>
<span class="selection-meta" id="seatCount">0 ausgewählt</span>
</div>
<div class="card border-0 bg-white">
<div class="card-body p-0">
<div class="d-flex flex-column flex-lg-row gap-2 mb-2">
<div class="flex-grow-1">
<input type="text"
placeholder="Ort suchen"
id="sitzInput"
onkeyup="filterFunction()"
onchange="filterFunction()"
name="sitzfilter"
autocomplete="off">
</div>
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-outline-primary button-small" onclick="gefundeneGemeindenAuswählen()">
Gefilterte Orte auswählen
</button>
<button type="button" class="btn btn-outline-secondary button-small" onclick="ausgewählteGemeindenLöschen()">
Auswahl löschen
</button>
<button type="button" class="btn btn-outline-secondary button-small" onclick="alleGemeindenAnzeigen()">
Alle anzeigen
</button>
<button type="button" class="btn btn-outline-secondary button-small" onclick="nurAusgewählteGemeindenAnzeigen()">
Nur ausgewählte anzeigen
</button>
</div>
</div>
<div id="sitzauswahl" class="scrollWindow"></div>
</div>
</div>
</div>
</div>
<div class="form-section mb-3">
<div class="section-heading">
<span class="section-number" aria-hidden="true">3</span>
<div>
<h2>Rechtsform auswählen</h2>
<p>Wählen Sie mindestens eine Rechtsform für den Export.</p>
</div>
</div>
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
<div>
<button type="button" class="btn btn-outline-primary button-small" onclick="alleRechtsformenAuswählen()">
Alle auswählen
</button>
<button type="button" class="btn btn-outline-secondary button-small" onclick="rechtsformenLöschen()">
Auswahl löschen
</button>
</div>
<span class="selection-meta" id="legalFormCount">0 ausgewählt</span>
</div>
<div id="rechtsformenauswahl" class="scrollWindow"></div>
</div>
<div class="form-section mb-3">
<div class="section-heading">
<span class="section-number" aria-hidden="true">4</span>
<div>
<h2>Export anfordern</h2>
<p>Geben Sie die Zieladresse an und starten Sie den geschützten Export.</p>
</div>
</div>
<div class="mb-4">
<div class="form-label mb-2">Weitere Optionen</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="exakteSuche" name="exakteSuche" disabled>
<label class="form-check-label" for="exakteSuche">Exakte Suche (noch in Entwicklung)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="geloeschteRechtseinheiten" name="geloeschteRechtseinheiten">
<label class="form-check-label" for="geloeschteRechtseinheiten">Gelöschte Rechtseinheiten suchen</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="inBisherigenFirmen" name="inBisherigenFirmen" disabled>
<label class="form-check-label" for="inBisherigenFirmen">In bisherigen Firmen/Namen suchen (noch in Entwicklung)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="phonetischeSuche" name="phonetischeSuche" disabled>
<label class="form-check-label" for="phonetischeSuche">Phonetische Suche (noch in Entwicklung)</label>
</div>
</div>
<div class="mb-4">
<label for="email" class="form-label">Ihre E-Mail-Adresse</label>
<div class="form-text mb-2">
Die Aufbereitung kann einige Zeit dauern. Sie erhalten den Download-Link per E-Mail, sobald die Daten bereit sind.
</div>
<input type="email" id="email" name="email" class="form-control" required placeholder="name@firma.ch">
<div class="invalid-feedback">Bitte geben Sie eine gültige E-Mail-Adresse ein.</div>
</div>
<?php if ($turnstileSiteKey !== ''): ?>
<div class="privacy-note mb-3">
<div class="cf-turnstile"
data-sitekey="<?php echo htmlspecialchars($turnstileSiteKey, ENT_QUOTES); ?>"
data-action="turnstile-spin-v2"
data-theme="auto"
data-language="de"
data-feedback-enabled="false"></div>
<div class="form-text mt-2">
Der Missbrauchsschutz verarbeitet technisch notwendige Browser- und Verbindungsdaten.
Details finden Sie in der <a href="datenschutz.php">Datenschutzerklärung</a>.
</div>
</div>
<?php else: ?>
<div class="alert alert-warning" role="alert">
Der Export ist vorübergehend deaktiviert, da der Missbrauchsschutz noch nicht konfiguriert ist.
</div>
<?php endif; ?>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg"<?php echo $turnstileSiteKey === '' ? ' disabled' : ''; ?>>Export starten</button>
</div>
<div id="selectionValidationMessage" class="text-danger mt-2" style="display:none;">
Wählen Sie mindestens einen Ort und eine Rechtsform aus.
</div>
</div>
</form>
<script>
// Bootstrap validation (optional)
(function () {
'use strict'
const forms = document.querySelectorAll('.needs-validation')
Array.prototype.slice.call(forms).forEach(function (form) {
form.addEventListener('submit', function (event) {
const hasSelectedSitz = document.querySelector('input[name="sitze[]"]:checked') !== null
const hasSelectedRechtsform = document.querySelector('input[name="rechtsformen[]"]:checked') !== null
const selectionValidationMessage = document.getElementById('selectionValidationMessage')
if (selectionValidationMessage) {
selectionValidationMessage.style.display = hasSelectedSitz && hasSelectedRechtsform ? 'none' : 'block'
}
if (!form.checkValidity() || !hasSelectedSitz || !hasSelectedRechtsform) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})();
</script>
<script>
// CSV data injected by PHP
let communityCsvData;
let legalFormsCsvData;
<?php
include 'gemeinden.php';
include 'legalForms.php';
?>
// --- Helpers to create nicer checkbox rows (Bootstrap form-check) ---
function addCheckboxRow(container, checkboxName, checkboxValue, labelText) {
const wrapper = document.createElement('div');
wrapper.className = "form-check";
const input = document.createElement('input');
input.className = "form-check-input";
input.type = "checkbox";
input.name = checkboxName;
input.value = checkboxValue;
input.id = checkboxName.replace(/[^a-z0-9]/gi, '-') + '-' + String(checkboxValue).replace(/[^a-z0-9]/gi, '-');
const label = document.createElement('label');
label.className = "form-check-label";
label.htmlFor = input.id;
label.textContent = labelText;
wrapper.appendChild(input);
wrapper.appendChild(label);
container.appendChild(wrapper);
return input;
}
// --- Legal forms ---
const legalFormslines = legalFormsCsvData.trim().split('\n');
const legalFormsheaders = legalFormslines[0].split(',');
const legalformIdIndex = legalFormsheaders.indexOf('id');
const legalformNameIndex = legalFormsheaders.indexOf('name');
const rechtsformenauswahl = document.getElementById("rechtsformenauswahl");
for (let i = 1; i < legalFormslines.length; i++) {
const cells = legalFormslines[i].split(',');
const id = (cells[legalformIdIndex] || '').trim();
const name = (cells[legalformNameIndex] || '').trim();
if (!id || !name) continue;
addCheckboxRow(rechtsformenauswahl, "rechtsformen[]", id, " " + name);
}
// --- Communities + Cantons ---
const lines = communityCsvData.trim().split('\n');
const headers = lines[0].split(',');
const gemeindeNameIndex = headers.indexOf('Gemeindename');
const bfsIdIndex = headers.indexOf('id');
const kantonIndex = headers.indexOf('Kanton');
const registryOfCommerceIdIndex = headers.indexOf('registryOfCommerceId');
const sitze = [];
const kantone = [];
const kantonauswahl = document.getElementById("kantonauswahl");
const sitzauswahl = document.getElementById("sitzauswahl");
for (let i = 1; i < lines.length; i++) {
const cells = lines[i].split(',');
if (!cells || cells.length < 4) continue;
const bfsId = (cells[bfsIdIndex] || '').trim();
const gemeindeName = (cells[gemeindeNameIndex] || '').trim();
const kanton = (cells[kantonIndex] || '').trim();
const registryOfCommerceId = (cells[registryOfCommerceIdIndex] || '').trim();
if (!bfsId || !gemeindeName) continue;
// Add canton if new
if (kanton && !kantone.includes(kanton)) {
kantone.push(kanton);
addCheckboxRow(kantonauswahl, "kantone[]", kanton, " " + kanton);
}
// Store row for filtering logic
sitze.push(cells);
// Create nicer entry
const gemeindeEintrag = document.createElement("div");
gemeindeEintrag.classList.add("gemeindeeintrag");
const wrapper = document.createElement('div');
wrapper.className = "form-check";
const input = document.createElement('input');
input.className = "form-check-input";
input.type = "checkbox";
input.name = "sitze[]";
input.value = bfsId;
input.id = "sitz-" + bfsId;
const label = document.createElement('label');
label.className = "form-check-label";
label.htmlFor = input.id;
label.textContent = " " + gemeindeName;
wrapper.appendChild(input);
wrapper.appendChild(label);
gemeindeEintrag.appendChild(wrapper);
sitzauswahl.appendChild(gemeindeEintrag);
}
const sitzInput = document.getElementById('sitzInput');
function updateSelectionCounts() {
const cantonTotal = document.querySelectorAll('input[name="kantone[]"]:checked').length;
const seatTotal = document.querySelectorAll('input[name="sitze[]"]:checked').length;
const legalFormTotal = document.querySelectorAll('input[name="rechtsformen[]"]:checked').length;
document.getElementById('cantonCount').textContent = cantonTotal + ' ausgewählt';
document.getElementById('seatCount').textContent = seatTotal + ' ausgewählt';
document.getElementById('legalFormCount').textContent = legalFormTotal + ' ausgewählt';
}
document.addEventListener('change', function(event) {
if (event.target && event.target.matches('input[type="checkbox"]')) {
updateSelectionCounts();
}
});
function kantoneAusgewählt() {
const checkboxes = document.querySelectorAll('input[name="kantone[]"]');
let countChecked = 0;
for (let i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].checked) countChecked++;
}
return countChecked;
}
function filterFunction() {
const numKantoneAusgewählt = kantoneAusgewählt();
const filter = (sitzInput.value || "").toUpperCase();
const entries = sitzauswahl.getElementsByClassName("gemeindeeintrag");
for (let i = 0; i < entries.length; i++) {
const txtValue = entries[i].textContent || entries[i].innerText;
const selectedCheckbox = entries[i].querySelector('input[type="checkbox"]');
const isSelected = selectedCheckbox ? selectedCheckbox.checked : false;
// map entry->sitz row index: still aligned because we push sitze once per entry
const row = sitze[i];
const rowKanton = row ? row[kantonIndex] : "";
let cantonOk = true;
if (numKantoneAusgewählt > 0 && rowKanton) {
const cantonBox = document.querySelector('input[name="kantone[]"][value="' + rowKanton + '"]');
cantonOk = cantonBox ? cantonBox.checked : false;
}
const matchesText = txtValue.toUpperCase().indexOf(filter) > -1;
if ((matchesText && (cantonOk || numKantoneAusgewählt === 0)) || isSelected) {
entries[i].style.display = "";
} else {
entries[i].style.display = "none";
}
}
}
function alleGemeindenAnzeigen() {
sitzInput.value = "";
filterFunction();
}
function nurAusgewählteGemeindenAnzeigen() {
sitzInput.value = "(nur ausgewählte)";
filterFunction();
}
function ausgewählteGemeindenLöschen() {
sitzauswahl.querySelectorAll('input[type="checkbox"]').forEach(function(cb) {
cb.checked = false;
});
filterFunction();
updateSelectionCounts();
}
function gefundeneGemeindenAuswählen() {
sitzauswahl.querySelectorAll('input[type="checkbox"]').forEach(function(cb) {
// show/hide is on the gemeindeeintrag wrapper
const entry = cb.closest('.gemeindeeintrag');
if (entry && entry.style.display !== "none") cb.checked = true;
});
filterFunction();
updateSelectionCounts();
}
function alleRechtsformenAuswählen() {
document.querySelectorAll('input[name="rechtsformen[]"]').forEach(function(cb) {
cb.checked = true;
});
updateSelectionCounts();
}
function rechtsformenLöschen() {
document.querySelectorAll('input[name="rechtsformen[]"]').forEach(function(cb) {
cb.checked = false;
});
updateSelectionCounts();
}
updateSelectionCounts();
</script>
</div>
</div>
</div>
</div>
</main>
<footer class="site-footer border-top">
<div class="container py-4">
<div class="row g-4">
<div class="col-md-4">
<h2 class="h6 mb-2">Impressum</h2>
<div class="text-muted" style="font-size:.95rem;">
Silias KLG<br>
Toggenburgstrasse 31<br>
8245 Feuerthalen<br>
<a href="https://www.silias.ch" target="_blank" rel="noopener">www.silias.ch</a><br>
<a href="mailto:info@silias.ch">info@silias.ch</a><br>
<a href="https://gitea.silias.ch/Silias-Public/Zefix_search">Projekt-Repository</a>
</div>
</div>
<div class="col-md-4">
<h2 class="h6 mb-2">Datenquelle</h2>
<div class="text-muted" style="font-size:.95rem;">
Zentraler Firmenindex<br>
Eidgenössisches Justiz- und Polizeidepartement (EJPD)<br>
Bundesamt für Justiz (BJ)<br>
Eidgenössisches Amt für das Handelsregister (EHRA)<br>
Bundesrain 20, 3003 Bern<br>
<a href="https://www.zefix.admin.ch" target="_blank" rel="noopener">www.zefix.admin.ch</a><br>
<a href="tel:+41584624197">+41 (0) 58 462 41 97</a><br>
<a href="mailto:zefix@bj.admin.ch">zefix@bj.admin.ch</a>
</div>
</div>
<div class="col-md-4">
<h2 class="h6 mb-2">Datenschutz</h2>
<div class="text-muted" style="font-size:.95rem;">
Wir verwenden Ihre E-Mail-Adresse ausschliesslich zur Bereitstellung des angeforderten Exports.
<br><a href="datenschutz.php">Datenschutzerklärung</a>
</div>
</div>
</div>
<div class="text-center text-muted mt-4" style="font-size:.9rem;">
© <?php echo date('Y'); ?> Silias KLG
</div>
</div>
</footer>
</body>
</html>