prepare intranet for first Dokploy deployment

This commit is contained in:
2026-08-26 12:59:51 +02:00
parent 5cfe688bdc
commit 29dfb85fef
1321 changed files with 227234 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
.git
.gitignore
.env
*.dump
backup.sql
*.zip
**/__pycache__/
**/*.pyc
worker/ckal_upload_client/xlsx/*.xlsx
worker/pdf_python/terminuebersicht.pdf
+28
View File
@@ -0,0 +1,28 @@
# Environment / Secrets
.env
# Datenbank-Backups
*.dump
backup.sql
# Archive
*.zip
# CKalender Runtime-Dateien
worker/ckal_upload_client/xlsx/*.xlsx
worker/pdf_python/terminuebersicht.pdf
# Benutzer-Profilbilder
# default.jpg ist ein statisches Fallback-Asset und bleibt in Git
web/intranet_repo/general_files/profile_pictures/*
!web/intranet_repo/general_files/profile_pictures/default.jpg
# Contentpanel / News
# Hochgeladene PDFs sowie daraus erzeugte PNGs sind Produktivdaten
web/intranet_repo/projects/contentpanel/insertion/news/*
# Generierte Dashboard-Terminbilder
web/intranet_repo/projects/dashboard/month/*
# Lokale Backup-Dateien
*.bak
+55
View File
@@ -0,0 +1,55 @@
FROM php:8.3.33-apache-bookworm
ENV TZ=Europe/Berlin \
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
PATH=/opt/intranet-venv/bin:$PATH
RUN apt-get update && apt-get install -y --no-install-recommends \
locales tzdata \
python3 python3-venv \
openssh-client ghostscript poppler-utils pngquant \
libpq-dev libzip-dev \
libpng-dev libjpeg62-turbo-dev libfreetype6-dev \
&& echo 'de_DE.UTF-8 UTF-8' > /etc/locale.gen \
&& locale-gen \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j"$(nproc)" pdo_pgsql pgsql zip gd \
&& php -r '$r=["pdo_pgsql","pgsql","curl","mbstring","dom","SimpleXML","xml","xmlreader","xmlwriter","zip","gd","Zend OPcache"]; foreach($r as $e){if(!extension_loaded($e)){fwrite(STDERR,"missing PHP ext: $e\n"); exit(1);}}' \
&& python3 -m venv /opt/intranet-venv \
&& /opt/intranet-venv/bin/pip install --no-cache-dir --upgrade pip \
&& /opt/intranet-venv/bin/pip install --no-cache-dir \
beautifulsoup4==4.13.4 fpdf==1.7.2 requests==2.32.4 playwright==1.62.0 \
&& /opt/intranet-venv/bin/python -m playwright install --with-deps chromium \
&& chmod -R a+rX /ms-playwright \
&& rm -rf /var/lib/apt/lists/*
ENV LANG=de_DE.UTF-8 \
LC_ALL=de_DE.UTF-8
COPY docker/intranet.ini /usr/local/etc/php/conf.d/99-intranet.ini
COPY web/ /var/www/html/
COPY worker/ /opt/intranet/worker/
COPY docker/scheduler.py /opt/intranet/docker/scheduler.py
RUN mkdir -p \
/data/ckal-xlsx /data/pdf /data/state \
/var/www/html/intranet_repo/general_files/profile_pictures \
/var/www/html/intranet_repo/projects/contentpanel/insertion/news \
/var/www/html/intranet_repo/projects/developerpanel/work \
/var/www/html/intranet_repo/projects/seelsorge/data \
/var/www/html/intranet_repo/projects/dashboard/month \
&& chown -R www-data:www-data \
/data \
/var/www/html/intranet_repo/general_files/profile_pictures \
/var/www/html/intranet_repo/projects/contentpanel/insertion/news \
/var/www/html/intranet_repo/projects/developerpanel/work \
/var/www/html/intranet_repo/projects/seelsorge/data \
/var/www/html/intranet_repo/projects/dashboard/month \
&& chmod +x /opt/intranet/docker/scheduler.py /opt/intranet/worker/pdf_python/pdf_to_png.sh
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -fsS http://127.0.0.1/ >/dev/null || exit 1
CMD ["apache2-foreground"]
+100
View File
@@ -0,0 +1,100 @@
services:
web:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
expose:
- "80"
environment:
TZ: Europe/Berlin
INTRANET_FQDN: ${INTRANET_FQDN:?set INTRANET_FQDN}
DB_HOST: ${DB_HOST:?set DB_HOST}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:?set DB_NAME}
DB_USER: ${DB_USER:?set DB_USER}
DB_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD}
SMTP_HOST: ${SMTP_HOST:-smtp.strato.de}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:?set SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD:?set SMTP_PASSWORD}
ADMIN_EMAIL: ${ADMIN_EMAIL:-technik@nak-reinickendorf.de}
SMS_GATEWAY_URL: ${SMS_GATEWAY_URL:?set SMS_GATEWAY_URL}
HA_BASE_URL: ${HA_BASE_URL:?set HA_BASE_URL}
HA_TOKEN: ${HA_TOKEN:?set HA_TOKEN}
OPENWEATHER_API_KEY: ${OPENWEATHER_API_KEY:?set OPENWEATHER_API_KEY}
CKAL_XLSX_DIR: /data/ckal-xlsx
volumes:
- profile_pictures:/var/www/html/intranet_repo/general_files/profile_pictures
- content_news:/var/www/html/intranet_repo/projects/contentpanel/insertion/news
- developer_work:/var/www/html/intranet_repo/projects/developerpanel/work
- seelsorge_data:/var/www/html/intranet_repo/projects/seelsorge/data
- dashboard_month:/var/www/html/intranet_repo/projects/dashboard/month
- ckal_xlsx:/data/ckal-xlsx
networks:
- app
- dokploy-network
scheduler:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
init: true
user: "33:33"
command: ["/opt/intranet/docker/scheduler.py"]
shm_size: "512m"
environment:
TZ: Europe/Berlin
HOME: /tmp/intranet-worker
XDG_CACHE_HOME: /tmp/intranet-worker-cache
PLAYWRIGHT_BROWSERS_PATH: /ms-playwright
INTRANET_FQDN: ${INTRANET_FQDN:?set INTRANET_FQDN}
DB_HOST: ${DB_HOST:?set DB_HOST}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:?set DB_NAME}
DB_USER: ${DB_USER:?set DB_USER}
DB_PASSWORD: ${DB_PASSWORD:?set DB_PASSWORD}
SMTP_HOST: ${SMTP_HOST:-smtp.strato.de}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:?set SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD:?set SMTP_PASSWORD}
ADMIN_EMAIL: ${ADMIN_EMAIL:-technik@nak-reinickendorf.de}
CKAL_USERNAME: ${CKAL_USERNAME:?set CKAL_USERNAME}
CKAL_PASSWORD: ${CKAL_PASSWORD:?set CKAL_PASSWORD}
CKAL_CALENDAR_URL: ${CKAL_CALENDAR_URL:?set CKAL_CALENDAR_URL}
CKAL_XLSX_DIR: /data/ckal-xlsx
PDF_OUTPUT: /data/pdf/terminuebersicht.pdf
DASHBOARD_MONTH_DIR: /var/www/html/intranet_repo/projects/dashboard/month
CALENDAR_INTERVAL_SECONDS: ${CALENDAR_INTERVAL_SECONDS:-60}
STRATO_SSH_HOST: ${STRATO_SSH_HOST:-ssh.strato.de}
STRATO_SSH_PORT: ${STRATO_SSH_PORT:-22}
STRATO_SSH_USER: ${STRATO_SSH_USER:?set STRATO_SSH_USER}
STRATO_REMOTE_PATH: ${STRATO_REMOTE_PATH:?set STRATO_REMOTE_PATH}
STRATO_SSH_KEY_FILE: /run/secrets/strato_ed25519
STRATO_KNOWN_HOSTS_FILE: /run/secrets/known_hosts
volumes:
- dashboard_month:/var/www/html/intranet_repo/projects/dashboard/month
- ckal_xlsx:/data/ckal-xlsx
- calendar_pdf:/data/pdf
- scheduler_state:/data/state
- ../files/strato_ed25519:/run/secrets/strato_ed25519:ro
- ../files/known_hosts:/run/secrets/known_hosts:ro
networks:
- app
- dokploy-network
volumes:
profile_pictures:
content_news:
developer_work:
seelsorge_data:
dashboard_month:
ckal_xlsx:
calendar_pdf:
scheduler_state:
networks:
app:
dokploy-network:
external: true
+11
View File
@@ -0,0 +1,11 @@
date.timezone = Europe/Berlin
upload_max_filesize = 64M
post_max_size = 70M
memory_limit = 512M
max_execution_time = 180
display_errors = Off
log_errors = On
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.cookie_secure = 1
expose_php = Off
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import fcntl
import os
import shutil
import subprocess
import time
from datetime import datetime
from pathlib import Path
PYTHON = '/opt/intranet-venv/bin/python'
ROOT = Path('/opt/intranet/worker')
STATE_DIR = Path('/data/state')
PDF_OUTPUT = Path(os.getenv('PDF_OUTPUT', '/data/pdf/terminuebersicht.pdf'))
INTERVAL = int(os.getenv('CALENDAR_INTERVAL_SECONDS', '60'))
def required(name: str) -> str:
value = os.getenv(name, '').strip()
if not value:
raise RuntimeError(f'Missing environment variable: {name}')
return value
def run(cmd, cwd=None):
print('+', ' '.join(map(str, cmd)), flush=True)
subprocess.run(cmd, cwd=cwd, check=True, env=os.environ.copy())
def prepare_ssh_key() -> Path:
source = Path(os.getenv('STRATO_SSH_KEY_FILE', '/run/secrets/strato_ed25519'))
if not source.is_file():
raise RuntimeError(f'SSH key file missing: {source}')
target = Path('/tmp/strato_ed25519')
shutil.copyfile(source, target)
target.chmod(0o600)
return target
def calendar_sync():
run([PYTHON, str(ROOT / 'ckal_upload_client/client.py')], cwd=ROOT / 'ckal_upload_client')
run([PYTHON, str(ROOT / 'pdf_python/create_pdf.py')], cwd=ROOT / 'pdf_python')
run(['/bin/bash', str(ROOT / 'pdf_python/pdf_to_png.sh')], cwd=ROOT / 'pdf_python')
required('STRATO_SSH_USER')
required('STRATO_REMOTE_PATH')
host = os.getenv('STRATO_SSH_HOST', 'ssh.strato.de')
port = os.getenv('STRATO_SSH_PORT', '22')
known_hosts = Path(os.getenv('STRATO_KNOWN_HOSTS_FILE', '/run/secrets/known_hosts'))
if not known_hosts.is_file():
raise RuntimeError(f'known_hosts missing: {known_hosts}')
key = prepare_ssh_key()
if not PDF_OUTPUT.is_file() or PDF_OUTPUT.stat().st_size == 0:
raise RuntimeError(f'PDF missing/empty: {PDF_OUTPUT}')
run([
'/usr/bin/scp', '-O', '-q', '-P', port,
'-i', str(key),
'-o', 'IdentitiesOnly=yes',
'-o', 'BatchMode=yes',
'-o', 'StrictHostKeyChecking=yes',
'-o', f'UserKnownHostsFile={known_hosts}',
str(PDF_OUTPUT),
f"{required('STRATO_SSH_USER')}@{host}:{required('STRATO_REMOTE_PATH')}",
])
print('Calendar Sync erfolgreich', flush=True)
def brandschutz_once_per_month():
now = datetime.now()
if now.day != 1 or now.hour < 8:
return
marker = STATE_DIR / 'brandschutz-last-attempt'
month = now.strftime('%Y-%m')
if marker.exists() and marker.read_text().strip() == month:
return
# At-most-once: erst markieren, dann ausführen. Bei Fehler manuell prüfen und Marker löschen.
marker.write_text(month + '\n')
print(f'Brandschutz-Lauf {month}', flush=True)
run(['/usr/local/bin/php', '/var/www/html/intranet_repo/projects/brandschutz/brandschutz_cron.php'])
def main():
STATE_DIR.mkdir(parents=True, exist_ok=True)
lock_handle = (STATE_DIR / 'scheduler.lock').open('w')
try:
fcntl.flock(lock_handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise RuntimeError('Ein anderer Scheduler läuft bereits.') from exc
if INTERVAL < 30:
raise RuntimeError('CALENDAR_INTERVAL_SECONDS muss >= 30 sein.')
for name in ('CKAL_USERNAME', 'CKAL_PASSWORD', 'CKAL_CALENDAR_URL'):
required(name)
PDF_OUTPUT.parent.mkdir(parents=True, exist_ok=True)
next_run = 0.0
while True:
try:
brandschutz_once_per_month()
except Exception as exc:
print(f'FEHLER Brandschutz: {exc}', flush=True)
now_mono = time.monotonic()
if now_mono >= next_run:
try:
calendar_sync()
except Exception as exc:
print(f'FEHLER Calendar Sync: {exc}', flush=True)
next_run = time.monotonic() + INTERVAL
(STATE_DIR / 'heartbeat').write_text(str(int(time.time())) + '\n')
time.sleep(1)
if __name__ == '__main__':
main()
+3
View File
@@ -0,0 +1,3 @@
<?php session_start();
header("Location: /intranet_repo/projects/dashboard/dashboard.php");
exit;
+3
View File
@@ -0,0 +1,3 @@
<?php session_start();
header("Location: /intranet_repo/projects/intranet/index.php");
exit;
+2
View File
@@ -0,0 +1,2 @@
# pool_nak
Entwicklungsumgebung für das Seelsorge-Interface.
@@ -0,0 +1,66 @@
<?php
/*
Name: content/kalender.php
Beschreibung: Übersicht: Kalenderblatt
Cookie-Setting: calendarDate = {$datum} -> Format: "Y-m-d"
*/
require_once('util/connector_feiertage.php');
$connector_feiertage = lib_Feiertage_Connector::getInstance();
require_once('util/connector_kalendertage.php');
$connector_kalendertage = lib_Kalendertage_Connector::getInstance();
$pdo = require($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
?>
<?php
if (isset($_GET['action']) && $_GET['action'] === 'edit') {
include $_SERVER['DOCUMENT_ROOT']."/intranet_repo/projects/seelsorge/content/kalender_edit.php";
} else {
include $_SERVER['DOCUMENT_ROOT']."/intranet_repo/projects/seelsorge/content/kalender_view.php";
}
?>
<script>
function toggleView() {
var currentUrl = window.location.href;
var baseUrl = currentUrl.split('?')[0];
var newUrl;
if (currentUrl.includes('action=edit')) {
newUrl = baseUrl;
} else {
newUrl = baseUrl + '?action=edit';
}
window.location.href = newUrl;
}
</script>
<?php
if (isset($_GET['action']) && $_GET['action'] === 'edit') { ?>
<button onclick="toggleView()">View</button>
<?php } else { ?>
<button onclick="toggleView()"><svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g stroke="#a649da" stroke-linecap="round" stroke-width="2"><path d="m20 20h-16"></path><path clip-rule="evenodd" d="m14.5858 4.41422c.781-.78105 2.0474-.78105 2.8284 0 .7811.78105.7811 2.04738 0 2.82843l-8.28322 8.28325-3.03046.202.20203-3.0304z" fill-rule="evenodd"></path></g></svg>
<span class="lable">Edit</span></button>
<?php } ?>
<script>
function toggleAvailability(date, clickedElement) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
console.log(this.responseText);
clickedElement.classList.toggle("normalday");
clickedElement.classList.toggle("availableday");
}
};
xhttp.open("POST", "toggle_availability.php", true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send("date=" + date);
}
</script>
+756
View File
@@ -0,0 +1,756 @@
/*
Name: kalli.css
Beschreibung: Spezielles CSS für KALLI
TODO: LÖSCHEN - WIRD NICHT MEHR EINGESETZT
*/
a {
color: black;
text-decoration: none;
}
a:hover{
color: white;
}
.header_menu {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 7%;
width: 100vw;
background-color: #5793c9;
border-bottom: 4px solid #c3e2ff;
z-index: 1000; /* Z-Index erhöhen, um sicherzustellen, dass der Header über anderen Elementen liegt */
position: fixed;
}
.header_menu a {
height: 100%;
}
.header_menu img {
height: 100%;
}
.content {
position: absolute;
width: 100vw;;
top: 8%;
}
.profile {
display: flex;
flex-direction: row;
}
.center {
height: 100%;
}
.center a {
padding: 0 10px 0 0;
display: flex;
justify-content: center;
align-items: center;
}
.center img {
height: 80%;
max-height: 50px;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
aspect-ratio: 1/1;
}
h1, h2 {
text-align: center;
}
.alert{
background-color: #f44336 !important;
}
.spacer{
height: 6em;
}
.content-spacer {
min-height: calc( 100vh - 67px);
}
.legal {
margin: auto;
max-width: 63em;
min-width: 0em;
text-align: justify;
}
.legal a {
color: #5793c9;
}
.legal a:hover {
color: #1c5282;
}
.pdf {
margin: auto;
width: 70vw;
height: 50em;
min-width: 600px;
}
#pdf {
display: none;
text-align: center;
font-size: 20px;
margin: 3em;
}
#pdf:hover{
color: #1c5282;
}
@media screen and (max-width:650px) {
.pdf{
display: none;
}
#pdf{
display: block;
}
}
.event-box {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 10px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box0 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 10px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
min-width: 20em;
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
}
.event-box1 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box2 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box3 {
max-width: 60vw;
margin: auto;
background-color: #ffffff;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box4 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 10px 1em;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box5 {
max-width: 60vw;
margin: auto;
background-color: #1c5282;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box6 {
overflow-x: scroll; /*ACHTUNG!*/
max-width: 80vw;
background-color: #c3e2ff;
margin: auto;
padding: 5px 1em;
}
.group-column {
flex-grow: 10; /* default 0 */
padding: 10px 1em;
}
.event {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.event1 {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.event-box a {
margin: 0 1em;
}
.svg1 {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 15px 0 20px;
}
.news {
width: fit-content;
padding: 1em;
background-color: #5793c9;
border-radius: 5px;
margin: 3em auto;
}
.news1 {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.news2 {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.news3 {
width: fit-content;
padding: 0.5em;
background-color: #5793c9;
border-radius: 5px;
margin: auto;
}
.news2:hover {
background-color: #1c5282;
}
.news3:hover {
background-color: #1c5282;
}
.null {
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
}
.news a{
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-content: flex-end;
}
.svg-news {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0 0 10px;
}
.form-div {
margin: auto;
max-width: 70vw;
width: 30em;
padding: 1em;
background-color: #c3e2ff;
border-radius: 5px;
}
.form-div1 {
margin: auto;
max-width: 70vw;
width: 30em;
padding: 1em;
text-align: center;
}
input[type=text], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
textarea, select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=number], select {
width: 100%;
padding: 5px 1px 5px 5px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=submit] {
width: 100%;
background-color: #5793c9;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #2a6193;
}
footer {
height: 3em;
border-top: #5793c9 3px solid;
margin-top: 1em;
display: flex;
flex-direction: column;
width: 100vw;
}
footer a:hover{
color: #5793c9;
}
.legal-links {
width: fit-content;
margin: auto;
}
.legal-links a {
margin: 0 1em;
font-size: 16px;
}
#mySidenav a {
position: absolute; /* Position them relative to the browser window */
position: fixed;
right: -80px; /* Position them outside of the screen */
transition: 0.3s; /* Add transition on hover */
padding: 15px; /* 15px padding */
width: 100px; /* Set a specific width */
text-decoration: none; /* Remove underline */
font-size: 20px; /* Increase font size */
color: white; /* White text color */
border-radius: 5px 0 0 5px; /* Rounded corners on the top right and bottom right side */
text-align: end;
}
#mySidenav a:hover {
right: 0; /* On mouse-over, make the elements appear as they should */
}
/* The about link: 20px from the top with a green background */
#admin {
top: 15px;
background-color: #5793c9;
}
/* Style the Image Used to Trigger the Modal */
#myImg {
border-radius: 5px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
cursor: pointer;
}
/* On mouse-over, add a deeper shadow */
#myImg:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.box2 {
width: 100%;
padding: 5px 0;
max-width: 600px;
margin: auto;
}
.box2:hover {
max-width: 800px;
font-size: 20pt;
transition: 1s;
}
.box3 {
max-width: 100%;
}
/* Add some padding inside the card container */
.container {
padding: 2px 16px;
text-align: center;
}
.container_flex {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
max-width: 550px;
min-width: 120px;
background-color: #35374b;
margin: 0.5em auto;
padding: 0px;
color: #fff;
border-radius: 7px
}
.container_flex_row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
width: 90%;
margin: 0.75em auto;
padding: 7px;
border-radius: 7px
}
.container_flex_row_2 {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
padding: 0;
margin: auto;
}
.container_flex_row_bg {
background-color: #c3e2ff;
border-radius: 7px;
margin: 1em auto 1em auto;
width: 90%;
padding: 3px 3px 3px 3px;
}
.time_group {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
width: 300px;
}
.container_today {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
max-width: 550px;
min-width: 90px;
background-color: #35374b;
margin: 0.75em auto;
padding: 7px;
color: #fff;
border-radius: 7px
}
.container_flex_title {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
max-width: 550px;
background-color: #3478a2;
margin: 0.75em auto;
color: #fff;
border-radius: 7px
}
.container_flex_casual {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
max-width: 650px;
background-color: #35374b;
padding: 0 1em 0 1em;
color: #fff;
border-radius: 7px;
margin: 0 0.75em 0 0.75em;
}
.container_flex_casual_2 {
background-color: #35374b;
padding: 1em 1em 1em 1em;
color: #fff;
border-radius: 7px;
max-height: 100px;
justify-content: center;
}
.container_flex_casual_2 img {
display: block;
justify-content: center;
margin: auto;
}
.container_flex_time {
justify-content: center;
margin: auto;
background-color: #35374b;
margin: 5px;
font-size: 25;
padding: 12px 20px 12px 20px;
color: #fff;
border-radius: 7px;
}
.container_flex_time p {
margin: 0;
}
.container_flex_topbar {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 10px 0 10px;
border-radius: 7px;
}
.vertical_spacer {
height: 100%;
margin: auto 0 auto 0;
}
.container_flex_2 {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 0px 10px 0px;
border-radius: 7px;
margin: auto;
}
.container_flex_weather {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 10px 10px 10px;
border-radius: 7px;
}
.container_flex_3 {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
background-color: #3b3c4e;
width: 593px;
padding: 10px 10px 10px 10px;
color: #fff;
border-radius: 7px;
}
.container_flex_bg {
background-color: #c3e2ff;
border-radius: 7px;
padding: 3px 10px 3px 10px;
}
.item_flex_2 {
margin: 0.2em;
}
.item_flex_oben {
margin: 0.75em auto auto auto;
}
.item_flex_rechts {
margin: auto 0.2em auto auto;
}
.item_flex_links {
margin: auto auto auto 0.2em;
}
.opnv-icon {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0 0 20px;
}
.temp-icon {
display: flex;
flex-direction: column;
justify-content: center;
}
.temp-icon-house {
display: flex;
flex-direction: column;
justify-content: center;
margin: 7px 0 7px 5px;
}
.temp-icon-today img {
display: block;
justify-content: center;
padding: 5px 5px 5px 5px;
margin: auto;
}
.temp-icon-house img {
display: block;
justify-content: center;
margin: auto 2em auto auto;
}
.temp-line {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 10 0 0px;
}
.item_flex.line {
max-width: 300px;
padding: 0px 10px 0px 10px;
}
.h2_test {
font-size:25px;
padding: 15px 0 0 0;
}
.item_flex.line p{
text-align: center;
}
.item_flex.time-remain{
text-align: center;
margin: 0 20px 0 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.item_flex.time-remain *{
margin: 0;
}
table {
border-collapse: separate;
border-spacing: 0.2em;
}
th, td {
padding: .2em .5em;
border-radius: .1em;
}
td {
font-style: italic;
text-align: right;
box-shadow: inset 1px 3px 5px -3px rgb(0 0 0 / .5);
}
td:empty{
box-shadow: none;
}
tbody tr:nth-child(even) {
background-color: #e4ebf2;
color: #000;
}
.rechtsbuendig {
text-align: right;
}
.bosskoddes {
max-width: 100%;
height: auto;
display: none;
}
+789
View File
@@ -0,0 +1,789 @@
body, html {
margin: 0;
padding: 0;
min-height: 100vh;
position: relative;
max-width: 100vw;
overflow-x: hidden;
}
*{
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
a {
color: black;
text-decoration: none;
}
a:hover{
color: white;
}
.header_menu {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 7%;
width: 100vw;
background-color: #5793c9;
border-bottom: 4px solid #c3e2ff;
z-index: 1000; /* Z-Index erhöhen, um sicherzustellen, dass der Header über anderen Elementen liegt */
position: fixed;
}
.header_menu a {
height: 100%;
}
.header_menu img {
height: 100%;
}
.content {
position: absolute;
width: 100vw;;
top: 8%;
}
.profile {
display: flex;
flex-direction: row;
}
.center {
height: 100%;
}
.center a {
padding: 0 10px 0 0;
display: flex;
justify-content: center;
align-items: center;
}
.center img {
height: 80%;
max-height: 50px;
border-radius: 50%;
overflow: hidden;
object-fit: cover;
aspect-ratio: 1/1;
}
h1, h2 {
text-align: center;
}
.error-box {
background-color: #04AA6D;
color: #fefefe;
margin: auto;
height: 3em;
text-align: center;
display: flex;
justify-content: center;
border-radius: 2px;
min-width: fit-content;
padding: 0 1em;
}
.error-box1 {
background-color: #f44336;
color: #fefefe;
margin: auto;
height: 3em;
text-align: center;
display: flex;
justify-content: center;
border-radius: 2px;
min-width: fit-content;
padding: 0 1em;
}
.alert{
background-color: #f44336 !important;
}
.spacer{
height: 6em;
}
.content-spacer {
min-height: calc( 100vh - 67px);
}
.legal {
margin: auto;
max-width: 63em;
min-width: 0em;
text-align: justify;
}
.legal a {
color: #5793c9;
}
.legal a:hover {
color: #1c5282;
}
.pdf {
margin: auto;
width: 70vw;
height: 50em;
min-width: 600px;
}
#pdf {
display: none;
text-align: center;
font-size: 20px;
margin: 3em;
}
#pdf:hover{
color: #1c5282;
}
@media screen and (max-width:650px) {
.pdf{
display: none;
}
#pdf{
display: block;
}
}
.event-box {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 10px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box0 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 10px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
min-width: 20em;
justify-content: center; /* Center horizontally */
align-items: center; /* Center vertically */
}
.event-box1 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box2 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box3 {
max-width: 60vw;
margin: auto;
background-color: #ffffff;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box4 {
max-width: 60vw;
margin: auto;
background-color: #c3e2ff;
padding: 10px 1em;
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box5 {
max-width: 60vw;
margin: auto;
background-color: #1c5282;
padding: 0px 1em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
min-width: 20em;
}
.event-box6 {
overflow-x: scroll; /*ACHTUNG!*/
max-width: 80vw;
background-color: #c3e2ff;
margin: auto;
padding: 5px 1em;
}
.group-column {
flex-grow: 10; /* default 0 */
padding: 10px 1em;
}
.event {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.event1 {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.event-box a {
margin: 0 1em;
}
.svg1 {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 15px 0 20px;
}
.news {
width: fit-content;
padding: 1em;
background-color: #5793c9;
border-radius: 5px;
margin: 3em auto;
}
.news1 {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.news2 {
background-color: #5793c9;
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
display: flex;
flex-direction: row;
}
.news3 {
width: fit-content;
padding: 0.5em;
background-color: #5793c9;
border-radius: 5px;
margin: auto;
}
.news2:hover {
background-color: #1c5282;
}
.news3:hover {
background-color: #1c5282;
}
.null {
margin: 15px 0;
padding-left: 10px;
padding-right: 10px;
}
.news a{
display: flex;
flex-direction: row;
justify-content: space-evenly;
align-content: flex-end;
}
.svg-news {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0 0 10px;
}
.form-div {
margin: auto;
max-width: 70vw;
width: 30em;
padding: 1em;
background-color: #c3e2ff;
border-radius: 5px;
}
.form-div1 {
margin: auto;
max-width: 70vw;
width: 30em;
padding: 1em;
text-align: center;
}
input[type=text], select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
textarea, select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=number], select {
width: 100%;
padding: 5px 1px 5px 5px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
input[type=submit] {
width: 100%;
background-color: #5793c9;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type=submit]:hover {
background-color: #2a6193;
}
footer {
height: 3em;
border-top: #5793c9 3px solid;
margin-top: 1em;
display: flex;
flex-direction: column;
width: 100vw;
}
footer a:hover{
color: #5793c9;
}
.legal-links {
width: fit-content;
margin: auto;
}
.legal-links a {
margin: 0 1em;
font-size: 16px;
}
#mySidenav a {
position: absolute; /* Position them relative to the browser window */
position: fixed;
right: -80px; /* Position them outside of the screen */
transition: 0.3s; /* Add transition on hover */
padding: 15px; /* 15px padding */
width: 100px; /* Set a specific width */
text-decoration: none; /* Remove underline */
font-size: 20px; /* Increase font size */
color: white; /* White text color */
border-radius: 5px 0 0 5px; /* Rounded corners on the top right and bottom right side */
text-align: end;
}
#mySidenav a:hover {
right: 0; /* On mouse-over, make the elements appear as they should */
}
/* The about link: 20px from the top with a green background */
#admin {
top: 15px;
background-color: #5793c9;
}
/* Style the Image Used to Trigger the Modal */
#myImg {
border-radius: 5px;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
cursor: pointer;
}
/* On mouse-over, add a deeper shadow */
#myImg:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
}
.box2 {
width: 100%;
padding: 5px 0;
max-width: 600px;
margin: auto;
}
.box2:hover {
max-width: 800px;
font-size: 20pt;
transition: 1s;
}
.box3 {
max-width: 100%;
}
/* Add some padding inside the card container */
.container {
padding: 2px 16px;
text-align: center;
}
.container_flex {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
max-width: 550px;
min-width: 120px;
background-color: #35374b;
margin: 0.5em auto;
padding: 0px;
color: #fff;
border-radius: 7px
}
.container_flex_row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
width: 90%;
margin: 0.75em auto;
padding: 7px;
border-radius: 7px
}
.container_flex_row_2 {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
padding: 0;
margin: auto;
}
.container_flex_row_bg {
background-color: #c3e2ff;
border-radius: 7px;
margin: 1em auto 1em auto;
width: 90%;
padding: 3px 3px 3px 3px;
}
.time_group {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
width: 300px;
}
.container_today {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
max-width: 550px;
min-width: 90px;
background-color: #35374b;
margin: 0.75em auto;
padding: 7px;
color: #fff;
border-radius: 7px
}
.container_flex_title {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
max-width: 550px;
background-color: #3478a2;
margin: 0.75em auto;
color: #fff;
border-radius: 7px
}
.container_flex_casual {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
max-width: 650px;
background-color: #35374b;
padding: 0 1em 0 1em;
color: #fff;
border-radius: 7px;
margin: 0 0.75em 0 0.75em;
}
.container_flex_casual_2 {
background-color: #35374b;
padding: 1em 1em 1em 1em;
color: #fff;
border-radius: 7px;
max-height: 100px;
justify-content: center;
}
.container_flex_casual_2 img {
display: block;
justify-content: center;
margin: auto;
}
.container_flex_time {
justify-content: center;
margin: auto;
background-color: #35374b;
margin: 5px;
font-size: 25;
padding: 12px 20px 12px 20px;
color: #fff;
border-radius: 7px;
}
.container_flex_time p {
margin: 0;
}
.container_flex_topbar {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 10px 0 10px;
border-radius: 7px;
}
.vertical_spacer {
height: 100%;
margin: auto 0 auto 0;
}
.container_flex_2 {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 0px 10px 0px;
border-radius: 7px;
margin: auto;
}
.container_flex_weather {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
padding: 10px 10px 10px 10px;
border-radius: 7px;
}
.container_flex_3 {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
background-color: #3b3c4e;
width: 593px;
padding: 10px 10px 10px 10px;
color: #fff;
border-radius: 7px;
}
.container_flex_bg {
background-color: #c3e2ff;
border-radius: 7px;
padding: 3px 10px 3px 10px;
}
.item_flex_2 {
margin: 0.2em;
}
.item_flex_oben {
margin: 0.75em auto auto auto;
}
.item_flex_rechts {
margin: auto 0.2em auto auto;
}
.item_flex_links {
margin: auto auto auto 0.2em;
}
.opnv-icon {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0 0 20px;
}
.temp-icon {
display: flex;
flex-direction: column;
justify-content: center;
}
.temp-icon-house {
display: flex;
flex-direction: column;
justify-content: center;
margin: 7px 0 7px 5px;
}
.temp-icon-today img {
display: block;
justify-content: center;
padding: 5px 5px 5px 5px;
margin: auto;
}
.temp-icon-house img {
display: block;
justify-content: center;
margin: auto 2em auto auto;
}
.temp-line {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 10 0 0px;
}
.item_flex.line {
max-width: 300px;
padding: 0px 10px 0px 10px;
}
.h2_test {
font-size:25px;
padding: 15px 0 0 0;
}
.item_flex.line p{
text-align: center;
}
.item_flex.time-remain{
text-align: center;
margin: 0 20px 0 0;
display: flex;
flex-direction: column;
justify-content: center;
}
.item_flex.time-remain *{
margin: 0;
}
table {
border-collapse: separate;
border-spacing: 0.2em;
}
th, td {
padding: .2em .5em;
border-radius: .1em;
}
td {
font-style: italic;
text-align: right;
box-shadow: inset 1px 3px 5px -3px rgb(0 0 0 / .5);
}
td:empty{
box-shadow: none;
}
tbody tr:nth-child(even) {
background-color: #e4ebf2;
color: #000;
}
.rechtsbuendig {
text-align: right;
}
.bosskoddes {
max-width: 100%;
height: auto;
display: none;
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,774 @@
/* Name: seelsorge.css
Beschreibung: Spezielles CSS für Seelsorge Planer */
#seelsorge.menue {
position:relative;
top: 0px;
display: flex;
margin: 0px 0px 0px 0px;
height: fit-content;
width: 100%;
justify-content: space-evenly;
align-items: center;
background-color: transparent;
transition: top 0.9s ease-in-out;
}
#seelsorge.menue .list {
display: flex;
flex-flow: row nowrap;
justify-content: left;
align-items: center;
margin: 0 1.8vw 0 1.8vw;
padding: 0px;
width: 100%;
}
#seelsorge.menue .menu-item:link {color: hsl(210, 60%, 50%);}
#seelsorge.menue .menu-item:hover {color: #FEFEFE; opacity: 1.0; transition: top 0.6s ease-in-out;}
#seelsorge.menue .active {
/*background-color: hsl(210, 80%, 35%) !important;*/
color: #FEFEFE !important;
font-weight: 400 !important;
/*box-shadow: 0 0 2px 1px rgba(255, 255, 255, 0.4),
0 1px 12px 0 rgba(0, 0, 0, 0.5) !important;*/
opacity: 1.0 !important;
}
#seelsorge.menue .menu-item {
padding: 0px;
margin: 0px 0px 0px 30px;
display: block;
width: 175px;
max-width: 24vw;
height: 100%;
text-align: center;
/*border-radius: 9999px;*/
color: #DDDDDD;
font-size: 100%;
font-weight: 100;
cursor: pointer;
opacity: 0.9;
transition: background-color 0.5s ease;
/*box-shadow: 0 1px 1px 0 rgba(255, 255, 255, 0.3),
0 1px 6px 0 rgba(0, 0, 0, 0.2);*/
}
#seelsorge.menue .indicator {
position: absolute;
left: 0px;
bottom: -3px;
width: 0px;
border-bottom: 2px solid #FEFEFE;
transition: width 0.9s ease-in-out, left 0.6s ease-in-out;
}
.content_seelsorge {
position: relative;
display: none; /* Ausblenden, wird per Javascript gesteuert */
justify-content: center;
justify-self: center;
padding: 5px;
margin-top: 0px;
width: calc(100% - 20px - 2px); /* Abzug von Padding+Border */
min-height: 100%;
border: 1px solid #DDDDDD;
border-radius: 5px;
background-color: hsl( 60, 33%, 100%);
}
.content .active {
display: flex;
}
p {font-size: 10pt;}
.long {text-align: justify;}
.up {text-transform: uppercase;}
.low {text-transform: lowercase;}
.capital {text-transform: capitalize;}
table, div.table {
display: table;
padding: 0px;
margin: 0px;
align-items: center;
border: 0px solid #000000;
}
/* Spezielle Styles */
div.title {
display: table;
-ms-flex: 100%;
flex: 100%;
margin: 5px;
background-color: transparent;
border-radius: 6px;
text-align: center;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.3);
background: linear-gradient(to top left,
hsl(60, 33%, 70%) 0%,
hsl(195, 33%, 70%) 100%);
}
p.title {
margin: 0px;
background-color: hsl(297, 50%, 20%);
-webkit-background-clip: text;
-moz-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: rgba(255,255,255,0.5) 0px 3px 3px;
font-size: 31pt;
font-weight: bold;
}
/* Wiederholbare Styles*/
.hidden {
display: none;
}
.left {
text-align: left;
}
.right {
text-align : right;
}
img.header {
padding: 0px;
margin: 0px;
height: 100px;
width: auto;
max-height: 100px;
border: 0px solid rgba(0, 0, 0, 0.2);
border-radius: 5px;
}
/* div.menue {
-ms-flex: 80px;
flex: 80px;
flex-flow: column wrap;
padding: 15px 5px 5px 5px;
margin: 5px;
height: auto;
align-items: center;
border: 6px solid hsl(60, 33%, 90%) ;
border-radius: 6px;
background-color: hsl(60, 33%, 75%);
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2),
0 6px 20px 0 rgba(0, 0, 0, 0.3);
text-align: left;
font-family: Bahnschrift, Arial;
font-size: 12pt;
font-weight: bold;
} */
/* div.menue a:link {color: hsl(195, 60%, 50%);}
div.menue a:visited {color: hsl(195, 60%, 50%);}
div.menue a:hover {color: hsl(195, 100%, 100%);}
div.menue a:active {color: hsl(195, 20%, 50%);}
div.menue a.currentPage {background-color: hsl(208, 50%, 70%); color: hsl(195, 50%, 20%);}
div.menue ul {
display: flex;
flex-flow: column wrap;
list-style-type: none;
width: 100%;
padding: 0px;
margin: 0px;
} */
/* div.menue ul a {
padding: 10px;
padding-left: 20px;
margin: 5px;
margin-left: 0px;
display: block;
width: auto;
height: auto;
border-radius: 20px;
text-decoration: none;
background-color: hsl(60, 33%, 100%);
border: 0px solid hsl(195, 0%, 80%);
transition: background-color 0.5s ease;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 6px 0 rgba(0, 0, 0, 0.2);
} */
/* div.menue ul a:hover {
border: 0px solid hsl(60, 33%, 100%);
opacity: 1;
color: hsl(60, 50%, 80%);
background-color: hsl(208, 60%, 35%);
box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.4), 0 1px 12px 0 rgba(0, 0, 0, 0.4);
}
img.menue_bg {
width: 90px;
padding-top: 15px;
opacity: 0.7;
} */
/* Content-Styles: Output und Panels */
/* div.content {
display: flex;
-ms-flex: 70%;
flex: 70%;
flex-flow: row wrap;
padding: 5px;
margin: 5px;
width: auto;
height: auto;
max-height: calc(100vh - 37px - 140px);
overflow: auto;
background-color: hsl(60, 33%, 90%);
border-radius: 6px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.3);
} */
div.content_panel form {
padding: 0px;
padding-top: 0px;
margin: 0px;
margin-top: 10px;
/*overflow: auto;*/
width: calc(100% - 0px); /* Abzug von Abständen */
height: calc(100vh - 37px - 140px - 100px + 2px); /* Abzug von Abständen */
/*background-color: hsl( 60, 33%, 95%);*/
}
div.content_panel input {
/* margin-top: 5px; */
margin-left: 0px;
padding: 10px;
padding-left: 20px;
padding-right: 20px;
width: 120px;
color: hsl(60, 33%, 75%);
background-color: hsl(208, 50%, 30%);
border: 2px solid #AAA;
border-radius: 5px;
font-weight: bold;
}
div.content_output {
padding: 15px;
padding-top: 0px;
padding-bottom: 0px;
margin: 0px;
margin-top: 10px;
overflow: auto;
width: calc(100% - 31px); /* Abzug von Abständen */
max-height: calc(100vh - 37px - 140px - 100px); /* Abzug von Abständen */
background-color: hsl( 60, 33%, 95%);
border: 1px solid #DDDDDD;
border-radius: 4px;
}
div.content_output ul {
margin-left: 0px;
padding-left: 0px;
}
div.content_output li {
margin-left: 20px;
padding-left: 0px;
}
/*Pofilgalerie*/
ul.galerie {
display: block;
padding:0px;
margin:10px;
width:auto;
list-style: none;
}
li.galerie {
position: relative;
float: left;
padding: 0px;
margin: 6px;
width: 122px;
height: auto;
border: 1px solid hsl(60, 33%, 50%);
border-radius: 6px;
/*background-color: #000;*/
opacity: 0.9;
background: linear-gradient(to top left,
hsl(297, 70%, 10%) 0%,
hsl(297, 60%, 40%) 30%,
hsl(297, 40%, 30%) 60%,
hsl(297, 70%, 30%) 100%
);
}
li.galerie:hover {
border: 1px solid hsl(60, 33%, 30%);
opacity: 1;
background: linear-gradient(to top left,
hsl(297, 70%, 20%) 0%,
hsl(297, 60%, 50%) 30%,
hsl(297, 40%, 40%) 60%,
hsl(297, 70%, 40%) 100%
);
z-index: 0;
}
div.galerie_green {
position: absolute;
left: 50%;
top: 66px;
transform: translate(-50%, -50%);
width: 100%;
height: 70%;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-left-radius: 50% 17%;
border-bottom-right-radius: 50% 17%;
opacity: 1;
background: radial-gradient(closest-corner at 30% 50%, hsl(146,70%,50%), hsl(146,70%,20%));
box-shadow: 0 0 2px 1px hsl(146,70%,50%), 0 3px 3px 1px hsl(146,70%,50%,0.7), 0 4px 6px hsl(146,70%,50%,0.5);
z-index: -3;
}
div.galerie_red {
position: absolute;
left: 50%;
top: 66px;
transform: translate(-50%, -50%);
width: 100%;
height: 70%;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-left-radius: 50% 17%;
border-bottom-right-radius: 50% 17%;
opacity: 1;
background: radial-gradient(closest-corner at 10% 50%, hsl(360,70%,50%), hsl(360,70%,20%));
box-shadow: 0 0 2px 1px hsl(360,70%,50%), 0 3px 3px 1px hsl(360,70%,50%,0.7), 0 4px 6px hsl(360,70%,50%,0.5);
z-index: -3;
}
img.galerie_image {
padding: 0px;
margin: 0px;
width: 100%;
height: auto;
border: 0px solid hsl(60, 33%, 60%);
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-left-radius: 50% 17%;
border-bottom-right-radius: 50% 17%;
z-index: -2;
}
div.galerie_name {
display: table;
padding: 0px;
margin: 0px;
width: 100%;
height: 60px;
text-align: center;
font-size: 11pt;
font-weight: bold;
z-index: -1;
}
/* Profildetails
--------------------------------------------------------------------------------
*/
div.profil_overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
background-color: hsl(195, 60%, 10%,0.9);
z-index: 7;
}
div.profil_container {
display: none;
flex-flow: row wrap;
justify-content: stretch;
align-items: stretch;
align-content: center;
position: absolute;
padding: 10px;
margin: 10px;
max-width: 60%;
border: 0px solid #DDDDDD;
background-color: transparent;
border-radius: 20px;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 12px 20px 0 rgba(0, 0, 0, 0.5);
z-index: -1;
}
div.profil_container a:link {color: hsl(297, 33%, 33%);}
div.profil_container a:visited {color: hsl(297, 33%, 33%);}
div.profil_container a:hover {color: hsl(297, 50%, 60%);}
div.profil_container a:active {color: hsl(297, 50%, 60%);}
div.profil_container_bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.4);
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
border-radius: 20px;
z-index: -2;
}
.profil_name {
margin: 10px;
width: 100%;
text-align: left;
font-size: 16pt;
font-weight: bold;
color: hsl(195, 30%, 15%);
text-shadow: 1px 1px 2px #888888;
}
.profil_name_sub {
width: 100%;
text-align: left;
font-size: 12pt;
font-weight: bold;
color: hsl(297, 50%, 40%);
}
.profil_exit {
display: table;
position: absolute;
top: 15px;
right: 15px;
width: 30px;
height: 30px;
text-align: center;
font-size: 16pt;
background-color: #FFFFFF;
border: 0px solid #DDDDDD;
border-radius: 50%;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.2), 0 1px 3px 0 rgba(0, 0, 0, 0.4);
}
.profil_exit:hover {
cursor: pointer;
background-color: hsl(297, 70%, 40%, 0.7);
}
div.profil_content_small {
padding: 5px;
margin: 5px;
width: 250px;
text-align: center;
background-color: #FFFFFF;
border: 5px solid hsl(60, 33%, 80%);
border-radius: 10px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.3), 0 3px 6px 0 rgba(0, 0, 0, 0.4);
}
div.profil_content_small img {
padding: 0px;
margin: 0px;
width: 100%;
border-radius: 3px;
}
div.profil_data {
-ms-flex: 50%;
flex: 50%;
padding: 30px;
margin: 5px;
border: 0px solid #DDDDDD;
background-color: hsl(60, 33%, 80%);
border-radius: 10px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.3), 0 3px 6px 0 rgba(0, 0, 0, 0.4);
}
div.profil_data td {
padding-top: 2px;
padding-left: 5px;
padding-right: 5px;
padding-bottom: 2px;
margin: 0px;
width: auto;
border-spacing: 0px;
border-bottom: 1px solid #AAAAAA;
}
div.profil_calendar {
-ms-flex: 50%;
flex: 50%;
width: auto;
height: auto;
min-width: 600px;
padding: 5px;
margin: 5px;
background-color: #FFFFFF;
border: 1px solid #DDDDDD;
border-radius: 10px 10px 10px 10px;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.3), 0 3px 6px 0 rgba(0, 0, 0, 0.4);
}
/* Kalenderblatt*/
.page_calendar {
position: relative;
display: none; /* Ausblenden, wird per Javascript gesteuert */
height: calc(100vh - 20px - 2px);
max-height: 750px;
max-width: 525px;
padding: 0px;
margin: 5px;
background-color: #FEFEFE;
border: 1px solid hsl(210, 0%, 90%);
border-radius: 10px;
box-shadow: 0px 1px 1px 0 rgba(0, 0, 0, 0.3), 0 3px 6px 0 rgba(0, 0, 0, 0.4);
aspect-ratio: 7/10;
/* Bild */
/*background-image: url('img/bg_sun.jpg');*/
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.page_calendar .active { display: flex; }
.page_calendar button {
position: absolute;
right: 5px;
top: 5px;
height: 30px;
border-radius: 5px;
border: 1px solid transparent;
background-color: hsl(297, 10%, 95%);
z-index: 10;
}
.page_calendar button:hover {
border: 1px solid hsl(297, 40%, 90%);
background-color: hsl(297, 80%, 95%);
}
.calendar_header {
position: relative;
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
height: 34px;
margin: 0px 0px 0px 0px;
padding: 5px 5px 5px 5px;
background: linear-gradient(to bottom,
hsl(210, 57%, 80%) 0%,
hsl(210, 57%, 97%) 90%);
border: 0px solid #DDDDDD;
border-bottom: 1px solid #DDDDDD;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
}
.calendar_title {
position: relative;
height: fit-content;
width: 150px;
font-size: 14.0pt;
text-align: center;
}
.arrow_item {
padding: 5px;
border-radius: 5px;
border: 1px solid transparent;
background-color: transparent;
}
.arrow_item:hover {
border: 1px solid hsla(297, 0%, 50%, 0.5);
background-color: hsl(297, 0%, 90%, 0.5);
}
.arrow_item:hover svg path { fill: hsl(210, 0%, 100%); }
.arrow_light svg path { fill: hsl(210, 30%, 70%); }
.arrow_dark svg path { fill: hsl(210, 100%, 30%); }
.calendar_content {
position: relative;
display: flex;
flex-flow: column nowrap;
justify-content: flex-start;
padding: 0px;
margin: 0px;
height: 100%;
width: 100%;
overflow: hidden;
font-size: 1.0em;
font-weight: 600;
border-spacing: 0px;
border: 0px solid #DDDDDD;
border-radius: 10px;
}
.calendar_content table {
padding: 0px;
margin: 0px;
width: 100%;
height: 100%;
/* border-spacing: 9px; */
}
.calendar_content thead {
width: 100%;
height: 100%;
/* border-spacing: 9px; */
}
.calendar_content table td {
text-align: center;
vertical-align: middle;
}
td.calendar_head {
padding-bottom: 5px;
border-bottom: 2px solid hsl(195, 40%, 70%);
}
td.calendar_head_sat {
padding-bottom: 10px;
border-bottom: 2px solid hsl(297, 10%, 70%);
}
td.calendar_head_sun {
padding-bottom: 10px;
border-bottom: 2px solid hsl(297, 80%, 70%);
}
td.calendar_day_area {
align-content: baseline;
padding-top: 2px;
width: 100%;
}
td.calendar_day_area:hover {
cursor: default;
}
td.calendar_day_area a {
text-decoration: none;
}
#calendar_day_item:hover, #calendar_day_item *:hover {
color: hsl(195, 0%, 90%);
background-color: hsl(195, 80%, 60%);
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.3);
}
.calendar_day_item {
width: auto;
height: 100%;
aspect-ratio: 1/1;
vertical-align: middle;
border-radius: 100%;
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.3);
}
/*Kombi mit -> calendar_day_item*/
.availableday {
color: #FFFFFF;
background-color: hsl(132, 100%, 27%);
}
.normalday {
background-color: hsl(297, 20%, 100%);
}
.today {
color: #FFFFFF;
background-color: hsl(195, 100%, 30%);
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.6);
}
/*td.today:hover {
color: hsl(195, 0%, 90%);
background-color: hsl(195, 80%, 60%);
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.3);
} */
.saturday {
color: #FFFFFF;
background-color: #555555;
}
.sunday { /* auch für gesetzliche Feiertage in Berlin */
color: #FFFFFF;
background-color: hsl(297, 80%, 70%);
}
.localholiday { /* sonstige regionale Feiertage */
background-color: hsl(297, 20%, 70%);
}
div.calendar_day {
display: flex;
-ms-flex-direction: column;
flex-direction: column;
justify-content: stretch;
align-items: center;
align-content: center;
height: 60px;
border: 0px solid #000;
}
div.feiertagsname {
position: absolute;
bottom: 0px;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
justify-content: stretch;
padding: 0px;
margin-top: 0px;
font-size: 7.5pt;
text-align: center;
width: 100%;
color: hsl(297, 30%, 50%);
border: 1px solid hsl(297, 30%, 90%);
z-index:5;
}
/* Verfügbarkeit: Kalendertag */
td.greenbox {
box-shadow: 4px 4px 4px hsl(146,50%,50%),
4px -4px 4px hsl(146,70%,30%),
-4px 4px 4px hsl(146,30%,70%),
-4px -4px 4px hsl(146,50%,30%);
}
@media (max-width: 750px) {
#seelsorge.menue .list {
justify-content: space-evenly;
}
#seelsorge.menue .menu-item {
padding: 5px 2vw 5px 2vw;
margin: 1vw;
max-width: 24vw;
font-size: 80%;
}
.content_seelsorge {
padding: 0px;
margin-top: 0px;
width: calc(100% - 0px - 0px); /* Abzug von Padding+Border */
border: 0px solid #DDDDDD;
border-radius: 5px;
background-color: transparent;
}
.page_calendar {
width: calc(100vw - 30px - 2px);
max-width: 700px;
padding: 0px;
margin: 0px;
transform: scale(0.9);
}
}
@media (max-width: 650px) {
.page_calendar {
width: calc(100vw - 10px - 2px);
max-width: 600px;
transform: scale(0.8);
}
}
@media (max-width: 550px) {
.page_calendar {
max-height: 550px;
max-width: 550px;
transform: scale(0.9);
}
}
@media (max-width: 475px) {
.page_calendar {
max-height: 475px;
max-width: 475px;
transform: scale(0.6);
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php /* Name: template.php
Beschreibung: Grundstruktur einer PHP-Seite für das Intranet */
// Initialisierung
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
</head>
<body>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<div class="header_title" id="header_title">
<span class="header_title_text">DUMMY-TITEL FÜR INTRANET-SEITE</span>
</div>
<div class="content" id="base"> <!-- alternativ: id="fullwith" -->
<div class="content_panel">
<?php
if (isset($_SESSION["user_id"])):
?>
<form class="display_within" style="border:none"method="post" action="">
</form>
<?php else:
header("Location: $g_httpLoginPath");
exit();
endif; ?>
</div> <!-- EOD: content_panel -->
</div> <!-- EOD: content -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,59 @@
<?php /* Name: template_admin.php
Beschreibung: Grundstruktur einer PHP-Seite für Administratoren */
// Initialisierung
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
</head>
<body>
<?php // Besondere Admin-Rechte checken
// Fetch user details
$sql = "SELECT * FROM user_permissions up INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id WHERE up.user_id = {$_SESSION["user_id"]}";
$result = $pdo->query($sql);
$isAdmin = $result->fetch(PDO::FETCH_ASSOC);
// Check if the user has admin access (Admin = 1)
if ($isAdmin["Admin"] == 1) {
$sql_groups = "SELECT * FROM user_permissions";
$stmt = $pdo->prepare($sql_groups);
$stmt->execute();
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
header("Location: {$g_httpHomePath}");
exit();
}
?>
<div class="header_title" id="header_title">
<span class="header_title_text">DUMMY-TITEL FÜR ADMIN-SEITE</span>
</div>
<div class="content" id="base">
<div class="content_panel">
<?php
if (isset($_SESSION["user_id"])):
?>
<form class="display_within" style="border:none"method="post" action="">
</form>
<?php else:
header("Location: $g_httpLoginPath");
exit();
endif; ?>
</div> <!-- EOD: content_panel -->
</div> <!-- EOD: content -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
+130
View File
@@ -0,0 +1,130 @@
# NAK Intranet – Design-Richtlinien
## Basierend auf dem CD-Manual NAK 2026
---
## 1. Farben (Digital-Palette)
| Farbe | HEX-Wert | Verwendung |
|-----------------|-----------|---------------------------------------|
| NAK Blau | `#2786cf` | Primärfarbe, Buttons, Links, Header |
| Schwarz | `#000000` | Texte, Icons |
| Sonnengelb | `#ffdc0f` | Akzente, Warnungen |
| Orange | `#f08200` | Akzente |
| Rot | `#d21400` | Fehlermeldungen, Warnstufen |
| Himbeere | `#d73282` | Akzente |
| Lila | `#731e7d` | Seelsorge-Bereich |
| Dunkelblau | `#1243a0` | Akzente |
| Türkisblau | `#00a091` | Akzente |
| Dunkelgrün | `#006e14` | Entwickler-Bereich, Erfolg |
| Gelbgrün | `#82be23` | Akzente |
### Neutrale Farben (Graustufen)
- Hintergrund: `#f8f9fa` (Gray-50)
- Karten/Panels: `#ffffff`
- Trennlinien: `#e9ecef` (Gray-200)
- Sekundärer Text: `#6c757d` (Gray-600)
- Primärer Text: `#212529` (Gray-900)
---
## 2. Typografie
- **Hausschrift**: Suisse Int'l (als woff2 eingebunden)
- Light (300) – Fließtext
- Medium (500) – Überschriften, Labels
- Semibold (600) – Buttons, Hervorhebungen
- **Fallback**: Arial, sans-serif
### Schriftgrößen
| Token | Größe |
|-------------|-----------|
| xs | 0.75rem |
| sm | 0.875rem |
| base | 1rem |
| md | 1.125rem |
| lg | 1.25rem |
| xl | 1.5rem |
| 2xl | 2rem |
| 3xl | 2.5rem |
---
## 3. Formsprache: Eckiges Design
Das CD-Manual schreibt eine **eckige Formsprache** vor:
- **Keine abgerundeten Ecken** (border-radius: 0) für UI-Elemente
- Ausnahme: Profilbilder (kreisförmig, border-radius: 50%)
- Ausnahme: Pillentags/Badges (border-radius: 9999px)
- Klare, geradlinige Kanten
- Scharfe Konturen bei Karten, Buttons, Eingabefeldern, Modals
---
## 4. Logo – Wort-Bild-Marke
- Das offizielle NAK-Logo wird immer zusammen mit dem Schriftzug verwendet
- Anordnung: Logo-Symbol links, daneben:
- Zeile 1: **Neuapostolische Kirche** (Medium, 500)
- Zeile 2: *Berlin-Reinickendorf* (Light, 300)
- Mindestgröße: Logo-Symbol 44px (Desktop), 36px (Mobile)
- Das Logo darf nicht verzerrt, eingefärbt oder manipuliert werden
---
## 5. Header
- Weißer Hintergrund (`#ffffff`)
- 3px NAK-Blau-Linie am unteren Rand
- Logo + Wordmark links
- Profilbild rechts (kreisrund)
- **Desktop**: Hover → Name + Abmelden ausfahren
- **Mobile**: Klick → Name + Abmelden ausfahren
- Sticky mit Hide-on-scroll-down, Show-on-scroll-up
---
## 6. Footer
- Weißer Hintergrund
- 2px NAK-Blau-Linie oben
- Zentrierte Links: Impressum | Admin kontaktieren | Datenschutz
- Kein Release-Marker (entfernt)
---
## 7. Hauptmenü (Intranet Startseite)
- **Desktop**: Volle Bildschirmbreite, Grid-Layout
- **Mobile**: Zentriert, angepasstes Grid (2 Spalten)
- Buttons: Eckig, mit Schatten, Hover-Effekt (leichte Vergrößerung)
- Farbige Buttons gemäß Bereichszugehörigkeit (Blau, Lila, Rot, Schwarz, Grün)
---
## 8. Authorization-Seiten (Login, Registrierung, Passwort-Reset)
- Zentriertes Card-Layout
- NAK-Logo mit Wordmark oben
- Eckige Eingabefelder
- Eckige Buttons (Pillform für primäre Actions beibehalten als Ausnahme)
- Hintergrund: Leichter Gradient (Gray-50 → NAK-Blau-Light)
---
## 9. Profil-Seiten
- Zentriertes Layout, max. 600px Breite
- Großes, kreisrundes Profilbild mit Hover-Effekt
- Eckige Eingabefelder und Karten
- Klare Gruppenübersicht
---
## 10. Allgemeine Regeln
- **Schatten**: Dezent, mehrstufig (sm, md, lg, xl)
- **Übergänge**: 150ms (schnell), 250ms (standard), 400ms (langsam)
- **Abstände**: Konsistentes Spacing-System (0.25rem bis 4rem)
- **Responsive**: Mobile-first, Breakpoints bei 480px und 768px
- **Barrierefreiheit**: Focus-Styles beibehalten, ausreichender Kontrast
@@ -0,0 +1,27 @@
<?php
require_once __DIR__ . '/runtime_env.php';
$host = intranet_env('DB_HOST');
$port = intranet_env('DB_PORT', '5432');
$dbname = intranet_env('DB_NAME');
$username = intranet_env('DB_USER');
$password = intranet_env('DB_PASSWORD');
try {
$pdo = new PDO(
"pgsql:host={$host};port={$port};dbname={$dbname}",
$username,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
} catch (PDOException $e) {
error_log('Database connection failed: ' . $e->getMessage());
http_response_code(500);
exit('Database connection failed.');
}
return $pdo;
@@ -0,0 +1,124 @@
<?php /* Name: functions.php
Beschreibung: Generelle Funktionen
- Verwaltung von Schaltflächen
- Info-Panels (Boxen und Tabellen)
- Markdown-Formatierung
- Eigene Prüffunktionen */
// Schaltfläche: Ausgabe eines beliebigen Buttons
function display_button($p_key,$p_text,$align) {
global $p_buttons;
if (array_key_exists($p_key, $p_buttons)) {
$p_file = $p_buttons[$p_key];
echo "<div class='button_picto' style='justify-content:" . $align . ";'>";
echo file_get_contents($p_file);
if ($p_text) echo $p_text;
echo "</div>";
}
}
/** Infopanel mit einer zweispaltigen Tabelle
* @param string $title Überschrift des Panels
* @param array $data_array Strukturdaten mit Schlüssel und Wert
* @return string Die formatierte Zeichenkette.
*/
function infopanel_table($title, $data_array) {
// Ausgabe des Panels mit ZWEI Spalten
echo '<div class="info_panel_table">';
echo ' <table>';
echo ' <thead>';
echo ' <tr>';
echo ' <th colspan="2">';
echo ' <label>' . $title . '</label>';
echo ' </th>';
echo ' </tr>';
echo '</thead>';
echo ' <tbody id="tableBody">';
foreach ($data_array as $key => $value) {
echo ' <tr>';
echo ' <td>' . $key . '</td>';
echo ' <td>' . nvl($value, '---') . '</td>';
echo ' </tr>';
}
echo ' </tbody>';
echo ' </table>';
echo '</div>';
}
/** Markdown-Formatierung für Schnelleingaben
* @param string $text Die zu formatierende Zeichenkette.
* @return string Die formatierte Zeichenkette.
*/
function markdownFormat($text) {
$text = nl2br(rtrim($text, "\n")); // Umbrüche formatieren (<BR>)
$pattern = "/(\::::)( .+)/";
$replacement = "<li style='margin-left:40px; list-style: none;'>$2</li>"; // :::: {Einrücken 2. Ebene}
$text = preg_replace($pattern, $replacement, $text);
$pattern = "/(\::)( .+)/";
$replacement = "<li style='margin-left:20px; list-style: none;'>$2</li>"; // :: {Einrücken 1. Ebene}
$text = preg_replace($pattern, $replacement, $text);
$pattern = "/(\-\-)( .+)/";
$replacement = "<li style='margin-left:40px;'>$2</li>"; // -- {Listenelement 2. Ebene}
$text = preg_replace($pattern, $replacement, $text);
$pattern = "/(\-)( .+)/";
$replacement = "<li>$2</li>"; // - {Listenelement 1. Ebene}
$text = preg_replace($pattern, $replacement, $text);
$text = preg_replace("/\*\*(.*?)\*\*/s", "<b>$1</b>", $text); // **{Fett}**
$text = preg_replace("/\*(.*?)\*/s", "<i>$1</i>", $text); // *{Kursiv}*
$text = "<ul>" . $text . "</ul>"; // Listenumschluss
return $text;
}
/////////////////////////////////////////
// EIGENE PRÜFFUNKTIONEN //
/////////////////////////////////////////
/** Checken einer Zeichenkette auf das Vorhandensein.
* @param string|null $value Die zu überprüfende Zeichenkette.
* @param mixed $default Der Ersatzwert, der zurückgegeben wird, wenn die Zeichenkette leer ist.
* @return mixed Der ursprüngliche Wert oder der Ersatzwert.
*/
function nvl(string $value = null, $default) {
return isset($value) && $value !== '' ? $value : $default;
}
/** Gegenderte Anzeige eines Rollennamens (Datenbankwert → Anzeige).
* @param string $role Der Rollenname aus der Datenbank
* @return string Der gegenderte Anzeigename
*/
function genderRole(string $role): string {
static $map = [
'Admin' => 'Admin',
'Technik' => 'Technik',
'Entwickler' => 'Entwickler*in',
'Amtsgeschwister' => 'Amtsgeschwister',
'Diakon' => 'Diakon*in',
'DiakonIn' => 'Diakon*in',
'Priester' => 'Priester*in',
'PriesterIn' => 'Priester*in',
'Brandschutz' => 'Brandschutz',
'Brandschutzbeauftragter' => 'Brandschutz­beauftrage*r',
];
return $map[$role] ?? $role;
}
?>
@@ -0,0 +1,80 @@
<?php /* Name: parameters.php
Beschreibung: Generelle Parameter
- inkl. Pfade und Standardbuttons */
require_once __DIR__ . '/runtime_env.php';
$publicHost = $_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN');
$p_version = "v0.6.0"; // {Release}.{Update}.{Hotfix/Commit}
$p_nac_base = "bln-rd"; // Gemeinde
$p_project = "Intranet"; // Systemname des Projekts
$p_status = "#dev"; // Entwicklungsstatus: #dev #test #live
// Lokalisierung
date_default_timezone_set('Europe/Berlin'); //Mitteleuropäische Zeitzone
$g_locale = setlocale(LC_TIME, 'de_DE', 'de_DE.UTF-8'); //Deutsche UTF-8 Kodierung
// Zusammenfassungen von Informationen für Administratoren und Entwickler
$array_admin = [
'Projekt' => $p_project,
'Gemeinde' => $p_nac_base,
'Version' => $p_version,
'Status' => $p_status,
];
$array_dev = [
'Host' => $_SERVER['HTTP_HOST'],
'IPv4-Adresse' => $_SERVER['SERVER_ADDR'],
'PHP-Version' => phpversion(),
'Server-Software' => $_SERVER['SERVER_SOFTWARE'],
'Betriebssystem' => php_uname('s'),
'Zeitzone' => date_default_timezone_get(),
'Locale-Einstellung' => $g_locale,
'Aktivierte PHP-Erweiterungen' => implode(', ', get_loaded_extensions()),
'Maximale Upload-Dateigröße' => ini_get('upload_max_filesize'),
'Speicherlimit für PHP-Skripte' => ini_get('memory_limit'),
];
// Prefixe
$isHttps = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on')
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https');
$g_httpPrefix = ($isHttps ? 'https' : 'http') . '://';
// Absolute Pfade
$g_generalFilePath = $_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/";
$g_projectsPath = $_SERVER['DOCUMENT_ROOT']."/intranet_repo/projects/";
$g_httpCurrentUrl = $g_httpPrefix.$publicHost.$_SERVER['REQUEST_URI'];
$g_httpCurrentCleanUrl = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$g_httpCurrentUrl = $g_httpPrefix.$publicHost.$_SERVER['REQUEST_URI'];
$g_httpHomePath = $g_httpPrefix.$publicHost."/intranet_repo/projects/intranet/index.php";
$g_httpLoginPath = $g_httpPrefix.$publicHost."/intranet_repo/projects/authorization/login.php";
$g_httpLogoutPath = $g_httpPrefix.$publicHost."/intranet_repo/projects/authorization/logout.php";
$g_imagePath = $g_projectsPath."_central/img/";
$g_imageButtonPath = $g_projectsPath."_central/img/picto/";
$g_phpSnippetsPath = $g_projectsPath."_central/php/snippets/";
$g_profilePicturePath = $g_generalFilePath."profile_pictures/";
// Relative Pfade
$g_imageRelativePath = "/intranet_repo/projects/_central/img/";
$g_imageButtonRelativePath = "/intranet_repo/projects/_central/img/picto/";
$g_profilePictureRelativePath = "/intranet_repo/general_files/profile_pictures/";
// Buttons verwalten (SVG-Grafiken)
$p_buttons = array(
'add' => $g_imageButtonPath.'add.svg',
'arrow_left' => $g_imageButtonPath.'arrow_left.svg',
'arrow_right' => $g_imageButtonPath.'arrow_right.svg',
'arrow_up' => $g_imageButtonPath.'arrow_up.svg',
'cancel' => $g_imageButtonPath.'cancel.svg',
'edit' => $g_imageButtonPath.'edit.svg',
'calendar' => $g_imageButtonPath.'calendar.svg'
);
?>
@@ -0,0 +1,14 @@
<?php
if (!function_exists('intranet_env')) {
function intranet_env(string $name, ?string $default = null): string
{
$value = getenv($name);
if ($value === false || $value === '') {
if ($default !== null) {
return $default;
}
throw new RuntimeException("Missing required environment variable: {$name}");
}
return $value;
}
}
@@ -0,0 +1,323 @@
<?php
/* Name: send_mail.php
Beschreibung: Zentrale Mail-Funktion für das Intranet
Sendet direkt über SMTP (smtp.strato.de:587 mit STARTTLS)
HTML-Template für moderne Darstellung
*/
require_once __DIR__ . '/runtime_env.php';
define('SMTP_HOST', intranet_env('SMTP_HOST', 'smtp.strato.de'));
define('SMTP_PORT', (int) intranet_env('SMTP_PORT', '587'));
define('SMTP_USER', intranet_env('SMTP_USER'));
define('SMTP_PASS', intranet_env('SMTP_PASSWORD'));
define('ADMIN_EMAIL', intranet_env('ADMIN_EMAIL', 'technik@nak-reinickendorf.de'));
/**
* Erzeugt das HTML-Template für E-Mails im NAK-Design.
*/
function build_html_mail(string $betreff, string $inhalt, ?string $buttonText = null, ?string $buttonUrl = null): string {
$buttonHtml = '';
if ($buttonText && $buttonUrl) {
$buttonHtml = '
<tr><td style="padding:24px 0 8px 0;" align="center">
<a href="' . htmlspecialchars($buttonUrl) . '" style="display:inline-block;padding:14px 32px;background:#2786cf;color:#ffffff;text-decoration:none;font-weight:700;font-size:15px;letter-spacing:0.02em;">
' . htmlspecialchars($buttonText) . '
</a>
</td></tr>';
}
// Inhalt: Zeilenumbrüche in <br> umwandeln, aber vorhandenes HTML beibehalten
$inhaltHtml = nl2br(htmlspecialchars($inhalt));
$datum = date('d.m.Y, H:i') . ' Uhr';
// Logo-URL dynamisch ermitteln
$host = $_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN');
$logoUrl = 'https://' . $host . '/intranet_repo/projects/_central/img/nak-logo.svg';
return '<!DOCTYPE html>
<html lang="de">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"></head>
<body style="margin:0;padding:0;background-color:#f0f2f5;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',Roboto,Helvetica,Arial,sans-serif;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f0f2f5;padding:32px 16px;">
<tr><td align="center">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:560px;overflow:hidden;">
<!-- Header with Logo + Wordmark -->
<tr><td style="background:#ffffff;padding:28px 32px;border-bottom:3px solid #2786cf;" align="left">
<table role="presentation" cellpadding="0" cellspacing="0"><tr>
<td style="vertical-align:bottom;padding-right:14px;">
<img src="' . htmlspecialchars($logoUrl) . '" alt="NAK" width="48" height="48" style="display:block;width:48px;height:48px;">
</td>
<td style="vertical-align:bottom;line-height:1.2;">
<div style="font-size:15px;font-weight:500;color:#1a1a1a;white-space:nowrap;">Neuapostolische Kirche</div>
<div style="font-size:12px;font-weight:300;color:#6b7280;white-space:nowrap;">Gemeinde Reinickendorf</div>
</td>
</tr></table>
</td></tr>
<!-- Blue title bar -->
<tr><td style="background:linear-gradient(135deg,#2786cf,#1e6fad);padding:20px 32px;">
<div style="font-size:20px;font-weight:700;color:#ffffff;letter-spacing:-0.01em;">' . htmlspecialchars($betreff) . '</div>
</td></tr>
<!-- Content -->
<tr><td style="background:#ffffff;padding:28px 32px 12px 32px;">
<div style="font-size:15px;line-height:1.7;color:#374151;">' . $inhaltHtml . '</div>
</td></tr>
' . $buttonHtml . '
<!-- Footer -->
<tr><td style="background:#ffffff;padding:20px 32px 24px 32px;border-top:1px solid #e5e7eb;">
<div style="font-size:12px;color:#9ca3af;text-align:center;line-height:1.5;">
Gesendet am ' . $datum . '<br>
Intranet NAK Reinickendorf &middot; Automatische Benachrichtigung
</div>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>';
}
/**
* Sendet eine HTML-E-Mail über SMTP.
*
* @param string $empfaenger Empfänger
* @param string $betreff Betreff
* @param string $nachricht Klartext-Nachricht (wird ins HTML-Template eingebettet)
* @param string|null $cc CC-Adresse
* @param string $absender Absender
* @param string|null $buttonText Optionaler Button-Text
* @param string|null $buttonUrl Optionale Button-URL
* @return bool
*/
function send_intranet_mail(
string $empfaenger,
string $betreff,
string $nachricht,
?string $cc = null,
string $absender = "intranet@nak-reinickendorf.de",
?string $buttonText = null,
?string $buttonUrl = null
): bool {
$htmlBody = build_html_mail($betreff, $nachricht, $buttonText, $buttonUrl);
// MIME-Header
$boundary = 'boundary_' . bin2hex(random_bytes(16));
$msgId = '<' . bin2hex(random_bytes(16)) . '@nak-reinickendorf.de>';
$headers = "From: Intranet NAK Reinickendorf <{$absender}>\r\n";
$headers .= "Reply-To: {$absender}\r\n";
$headers .= "To: {$empfaenger}\r\n";
if (!empty($cc)) {
$headers .= "Cc: {$cc}\r\n";
}
$headers .= "Subject: =?UTF-8?B?" . base64_encode($betreff) . "?=\r\n";
$headers .= "Message-ID: {$msgId}\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n";
$headers .= "X-Mailer: Intranet-NAK-Reinickendorf/1.0\r\n";
$headers .= "Date: " . date('r') . "\r\n";
// Plaintext-Fallback
$plainBody = strip_tags($nachricht);
// Multipart Body
$body = "--{$boundary}\r\n";
$body .= "Content-Type: text/plain; charset=utf-8\r\n";
$body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
$body .= $plainBody . "\r\n\r\n";
$body .= "--{$boundary}\r\n";
$body .= "Content-Type: text/html; charset=utf-8\r\n";
$body .= "Content-Transfer-Encoding: 8bit\r\n\r\n";
$body .= $htmlBody . "\r\n\r\n";
$body .= "--{$boundary}--\r\n";
$message = $headers . "\r\n" . $body;
$recipients = [$empfaenger];
if (!empty($cc)) {
$recipients[] = $cc;
}
return smtp_send(SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, $absender, $recipients, $message);
}
/**
* Direkter SMTP-Versand mit STARTTLS und AUTH LOGIN.
*/
function smtp_send(string $host, int $port, string $user, string $pass, string $from, array $to, string $data): bool {
$errno = 0;
$errstr = '';
$socket = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errstr, 30);
if (!$socket) {
error_log("SMTP connect failed: {$errstr} ({$errno})");
return false;
}
stream_set_timeout($socket, 30);
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '220') { fclose($socket); return false; }
smtp_write($socket, "EHLO " . gethostname());
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '250') { fclose($socket); return false; }
smtp_write($socket, "STARTTLS");
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '220') { fclose($socket); return false; }
$crypto = stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT);
if (!$crypto) {
error_log("SMTP STARTTLS failed");
fclose($socket);
return false;
}
smtp_write($socket, "EHLO " . gethostname());
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '250') { fclose($socket); return false; }
smtp_write($socket, "AUTH LOGIN");
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '334') { fclose($socket); return false; }
smtp_write($socket, base64_encode($user));
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '334') { fclose($socket); return false; }
smtp_write($socket, base64_encode($pass));
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '235') {
error_log("SMTP auth failed: {$response}");
fclose($socket);
return false;
}
smtp_write($socket, "MAIL FROM:<{$from}>");
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '250') { fclose($socket); return false; }
foreach ($to as $recipient) {
smtp_write($socket, "RCPT TO:<{$recipient}>");
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '250') { fclose($socket); return false; }
}
smtp_write($socket, "DATA");
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '354') { fclose($socket); return false; }
$data = str_replace("\r\n.", "\r\n..", $data);
fwrite($socket, $data . "\r\n.\r\n");
$response = smtp_read($socket);
if (substr($response, 0, 3) !== '250') { fclose($socket); return false; }
smtp_write($socket, "QUIT");
smtp_read($socket);
fclose($socket);
return true;
}
function smtp_write($socket, string $cmd): void {
fwrite($socket, $cmd . "\r\n");
}
function smtp_read($socket): string {
$response = '';
while ($line = fgets($socket, 512)) {
$response .= $line;
if (isset($line[3]) && $line[3] === ' ') break;
if (strlen($line) < 4) break;
}
return $response;
}
/**
* Ermittelt alle E-Mail-Adressen von Nutzern, die BEIDE angegebenen Rollen besitzen.
* Benötigt eine bestehende $pdo-Verbindung.
*
* @param PDO $pdo Datenbankverbindung
* @param array $roles Array von Rollennamen, die alle erfüllt sein müssen
* @return array Liste der E-Mail-Adressen
*/
function get_emails_by_roles(PDO $pdo, array $roles): array {
if (empty($roles)) return [];
$conditions = [];
foreach ($roles as $role) {
$conditions[] = '"' . str_replace('"', '""', $role) . '" = 1';
}
$where = implode(' AND ', $conditions);
$sql = "SELECT ugi.mail FROM user_permissions up
INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id
WHERE {$where} AND ugi.mail IS NOT NULL AND ugi.mail != ''";
$result = $pdo->query($sql);
$emails = [];
if ($result) {
while ($row = $result->fetch()) {
$emails[] = $row['mail'];
}
}
return $emails;
}
/**
* Sendet eine Mail an alle Nutzer mit den Rollen Admin UND Technik (Sammelmail).
*
* @param PDO $pdo Datenbankverbindung
* @param string $betreff Betreff
* @param string $nachricht Nachricht
* @param string|null $buttonText Optionaler Button-Text
* @param string|null $buttonUrl Optionale Button-URL
* @return bool True wenn mindestens eine Mail gesendet wurde
*/
function send_admin_team_mail(
PDO $pdo,
string $betreff,
string $nachricht,
?string $buttonText = null,
?string $buttonUrl = null
): bool {
$emails = get_emails_by_roles($pdo, ['Admin', 'Technik']);
if (empty($emails)) {
// Fallback auf ADMIN_EMAIL
return send_intranet_mail(ADMIN_EMAIL, $betreff, $nachricht, null, "intranet@nak-reinickendorf.de", $buttonText, $buttonUrl);
}
$success = false;
foreach ($emails as $email) {
if (send_intranet_mail($email, $betreff, $nachricht, null, "intranet@nak-reinickendorf.de", $buttonText, $buttonUrl)) {
$success = true;
}
}
return $success;
}
/**
* Sendet eine Mail an alle Nutzer mit der Rolle Brandschutzbeauftragter.
*
* @param PDO $pdo Datenbankverbindung
* @param string $betreff Betreff
* @param string $nachricht Nachricht
* @param string|null $buttonText Optionaler Button-Text
* @param string|null $buttonUrl Optionale Button-URL
* @return bool True wenn mindestens eine Mail gesendet wurde
*/
function send_brandschutzbeauftragter_mail(
PDO $pdo,
string $betreff,
string $nachricht,
?string $buttonText = null,
?string $buttonUrl = null
): bool {
$emails = get_emails_by_roles($pdo, ['Brandschutzbeauftragter']);
if (empty($emails)) return false;
$success = false;
foreach ($emails as $email) {
if (send_intranet_mail($email, $betreff, $nachricht, null, "intranet@nak-reinickendorf.de", $buttonText, $buttonUrl)) {
$success = true;
}
}
return $success;
}
?>
@@ -0,0 +1,42 @@
<?php /* Name: initialise.php
Beschreibung: Initialisierung der grundlegenden Verarbeitungen
für die Seiten des Intranets (1. Aufruf)
(Ausgenommen davon sind die An-/Abmeldung und Registrierung)
Ladung: Session, Parameter, Funktionen,
Datenbankverbindung und Authentifizierung des Nutzers */
// Session
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Parameter und Funktionen
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
// Datenbankverbindung
if (isset($_SESSION["user_id"])) {
// Include database connection file
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
$stmt = $pdo->prepare("SELECT * FROM user_general_information WHERE user_id = ?");
$stmt->execute([$_SESSION["user_id"]]);
$user = $stmt->fetch();
// Authentication
if (isset($user["mail"])) {
$stmt_groups = $pdo->prepare("SELECT * FROM user_permissions WHERE user_id = ?");
$stmt_groups->execute([$_SESSION["user_id"]]);
$groups = $stmt_groups->fetch();
}
} else {
header("Location: {$g_httpLoginPath}");
exit();
}
?>
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
id="svg"
x="0px"
y="0px"
width="5058.3882"
viewBox="0 0 5058.3882 766.70679"
enable-background="new 0 0 5200 800"
xml:space="preserve"
sodipodi:docname="intranet.svg"
height="766.70679"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs1" />
<sodipodi:namedview
id="namedview25"
pagecolor="#f1f2f4"
bordercolor="#000000"
borderopacity="1"
showgrid="false"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.052980769"
inkscape:cx="-764.42831"
inkscape:cy="-934.30127"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="1358"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg" />
<style
id="style1">path { fill: #f1f2f4; opacity=&quot;1&quot; stroke=&quot;none&quot; style=&quot;display:inline&quot;}</style>
<path
id="1_i"
d="m 55.846921,280.65323 c -0.785523,160.7849 1.212471,321.0364 6.316604,482.0026 -10.618347,0 -20.714839,0 -30.811332,0 -9.993119,0 -19.986239,0 -31.226396,0 C 11.316134,508.41933 15.377435,254.38003 0,0 21.078011,0 41.14803,0 60.747296,0 59.11366,93.408 57.480641,186.7806 55.846921,280.65323 Z" />
<path
id="2_n"
d="m 263.94938,455.03133 c -0.094,102.7806 -2.31018,205.0743 2.11413,307.6643 -17.28564,0 -34.04425,0 -51.74792,0 11.72375,-253.883 14.63104,-507.8318 -0.0664,-762.2574 1.96695,-0.1725 3.37747,-0.4015 4.78839,-0.4039 15.4993,-0.026 30.99961,-0.095 46.49674,0.09 1.79605,0.021 4.29101,1.0033 5.25723,2.3834 29.33283,41.8961 58.50794,83.9026 87.72083,125.8828 88.01974,126.4882 176.68716,252.5439 260.47421,381.9064 30.3267,46.8228 58.979,94.7306 88.3378,142.1788 2.9187,4.7172 5.4344,9.6837 8.1371,14.5346 0.4771,-0.084 0.9542,-0.1676 1.4313,-0.2513 0,-1.8538 0.051,-3.7093 -0.01,-5.5612 -1.3987,-43.9697 -3.1534,-87.9316 -4.1195,-131.9107 -0.9443,-42.9836 -1.3427,-85.9837 -1.5278,-128.9785 -0.4152,-96.4939 -0.363,-192.99 -0.8986,-289.483 -0.1895,-34.1557 -1.5371,-68.305 -2.3427,-102.4575 -0.059,-2.4917 -0.01,-4.9859 -0.01,-7.9146 16.9793,0 33.8638,0 50.7475,0 -8.1759,253.8841 -9.1391,507.8516 0.09,762.4987 -10.439,0 -21.2036,0.1765 -31.9487,-0.2031 -1.4963,-0.053 -3.172,-2.6637 -4.2991,-4.3938 -21.73,-33.3585 -43.6975,-66.5681 -65.0128,-100.1909 -84.3111,-132.9982 -173.89784,-262.4539 -262.97652,-392.2553 -39.96744,-58.2388 -79.65716,-116.6683 -119.39981,-175.0609 -3.8371,-5.6378 -7.14658,-11.634 -10.72168,-17.4512 -0.73645,-1.1983 -1.56519,-2.3399 -3.27585,-3.3485 3.66208,128.2398 4.84033,256.3529 2.76215,384.9832 z" />
<path
id="3_t"
d="m 1383.2471,45.7185 c -50.642,-0.8569 -100.7924,-1.7344 -150.9444,-2.5139 -9.1775,-0.1426 -8.562,-1.4584 -8.9499,8.4 -4.6272,117.5814 -6.1254,235.21883 -5.1343,352.87233 0.8647,102.6436 2.7607,205.2785 4.1234,307.9183 0.2186,16.473 0.031,32.9515 0.031,49.8292 -18.988,0 -39.025,0 -58.7251,0 0,-9.6737 -0.2366,-19.2759 0.037,-28.8634 2.1898,-76.6167 5.2717,-153.2178 6.513,-229.8489 1.2874,-79.468 1.6011,-158.9717 0.8603,-238.4459 -0.5403,-57.96713 -3.4699,-115.91223 -5.3622,-173.86623 -0.4622,-14.1552 -1.1482,-28.3041 -1.4823,-42.4618 -0.098,-4.1707 -1.2757,-5.7989 -5.7866,-5.6855 -43.9765,1.1051 -87.9588,1.9644 -131.9378,2.9721 -37.31584,0.855 -74.62925,1.8169 -111.94445,2.7077 -1.1154,0.027 -2.2374,-0.2138 -3.6374,-0.3589 0,-16.1464 0,-32.0675 0,-48.3736 188.52605,0 376.89005,0 565.92395,0 -0.7413,15.745 -1.4809,31.4546 -2.2509,47.8118 -30.601,-0.7008 -60.7217,-1.3905 -91.3332,-2.0933 z" />
<path
id="4_r"
d="m 1693.066,762.82672 c -0.7055,1.5411 -1.4462,3.6166 -2.205,3.6232 -20.2795,0.1767 -40.561,0.1407 -61.944,0.1407 15.0303,-254.4632 17.6845,-508.3694 2.6879,-762.98801 2.3573,0 4.1279,0.028 5.8975,0 65.3069,-1.1859 130.6113,-2.5171 195.921,-3.5202 45.6857,-0.7017 90.89,2.9055 134.6044,17.4884 30.7028,10.2424 58.0708,25.8477 79.4777,50.6849 19.125,22.1897 30.1187,48.1807 34.6248,76.878 7.3323,46.6937 3.1809,92.11211 -17.7588,135.03271 -19.3518,39.6654 -49.6182,69.4095 -87.0791,92.004 -11.0918,6.6899 -22.9963,12.0273 -34.4651,18.1044 -1.2696,0.6729 -2.1201,2.1372 -3.1651,3.2344 0.9088,0.7843 1.6939,1.9721 2.7445,2.2944 36.1333,11.0854 63.6491,34.041 86.894,62.9085 19.6672,24.4246 34.4917,51.813 48.4719,79.6813 35.5154,70.7976 64.3426,144.4186 91.9143,218.5624 1.0901,2.9319 2.1233,5.8851 3.481,9.6554 -4.3962,0 -8.1399,0 -11.8838,0 -11.9995,0 -24.0019,-0.1566 -35.9963,0.092 -3.7735,0.078 -5.4329,-1.1069 -6.7889,-4.6875 -23.8288,-62.9311 -49.8635,-124.9229 -79.7014,-185.2701 -15.07,-30.4794 -31.0022,-60.4575 -50.9167,-88.1612 -17.3465,-24.1305 -40.2879,-41.3755 -66.3296,-54.9509 -16.0514,-8.3675 -33.5768,-10.739 -51.2111,-11.9236 -60.4316,-4.0596 -120.9238,-2.3967 -181.4035,-1.2929 -0.818,0.015 -1.6336,0.18 -3.2119,0.3637 0,1.7754 -0.019,3.5351 0,5.2943 1.0211,82.6516 1.7776,165.3077 3.2138,247.9521 0.5122,29.4727 2.6728,58.9167 4.1247,88.8036 m -8.2547,-715.70671 c -0.3651,2.015 -1.081,4.0366 -1.0428,6.0439 0.5898,30.9884 1.8343,61.975 1.8316,92.9618 -0.01,73.99691 -0.5904,147.99381 -0.9398,221.99061 -0.01,2.0834 0,4.1669 0,7.4907 8.4679,0 16.1119,0 23.7561,7e-4 31.4994,0.02 63.0038,0.3861 94.4965,-0.065 18.6283,-0.2666 37.338,-0.928 55.8349,-2.9912 34.8654,-3.8887 67.622,-14.6499 97.1828,-33.9769 31.875,-20.84 55.9978,-48.1322 68.4575,-84.6419 10.281,-30.1245 11.4106,-61.01651 6.5383,-92.26621 -7.3345,-47.0424 -32.3579,-80.0179 -77.2124,-97.3932 -32.8736,-12.7342 -67.2872,-17.6183 -102.1238,-19.4808 -55.3125,-2.9572 -110.6172,-1.0149 -166.7777,2.3271 z" />
<path
id="5_a"
d="m 2327.7505,752.85213 c 19.2041,-45.4364 37.4165,-90.8864 57.5408,-135.4733 54.7898,-121.3885 110.5935,-242.3189 165.5835,-363.6176 30.3787,-67.01033 60.198,-134.28313 86.1362,-203.18563 3.1062,-8.2519 6.0257,-16.5901 8.6145,-25.0164 2.5359,-8.2529 4.5389,-16.6696 6.9214,-25.5592 21.1829,0 43.064,0 64.2639,0 17.3108,61.5161 43.5442,119.3058 69.3809,177.1244 53.384,119.46383 108.0369,238.35983 161.8311,357.64143 28.3308,62.8204 56.5305,125.719 80.7556,190.2853 3.8511,10.2639 6.7737,20.8787 10.0288,31.3614 0.53,1.7069 0.5525,3.5715 0.8721,5.8006 -20.2281,0 -40.1001,0 -59.3702,0 -13.7429,-62.2083 -39.8989,-119.74 -63.768,-178.0492 -13.052,-31.8849 -27.1612,-63.336 -40.6372,-95.0493 -1.4331,-3.3725 -3.0796,-4.7511 -6.9056,-4.7478 -124.3234,0.1053 -248.6472,0.09 -372.9707,0.07 -2.7417,-5e-4 -4.9179,-0.01 -6.3181,3.3023 -29.9043,70.669 -60.5156,141.0623 -85.6504,213.6276 -5.9753,17.2508 -10.1294,35.1353 -15.0029,52.761 -0.6965,2.5188 -0.7229,5.2229 -1.0989,8.147 -20.7207,0 -41.302,0 -62.7038,0 0.8281,-3.1485 1.603,-6.0952 2.497,-9.4221 m 276.5979,-525.8677 c -32.832,72.7061 -65.664,145.4123 -98.6044,218.3583 116.7929,0 232.8803,0 349.3957,0 -0.6238,-1.7314 -1.0427,-3.139 -1.6248,-4.4756 -39.3452,-90.357 -78.6225,-180.7436 -118.0979,-271.04363 -17.6528,-40.3805 -37.3188,-79.9036 -51.6972,-121.8717 -2.7461,-1.0229 -4.2273,0.01 -5.0967,3.1074 -1.3469,4.7975 -2.8616,9.592 -4.821,14.1669 -23.0066,53.7071 -46.1097,107.3729 -69.4537,161.75833 z" />
<path
id="6_n"
d="m 3241.2241,675.02045 c 0.6673,29.4491 1.3304,58.3981 2.0013,87.6769 -16.9424,0 -33.701,0 -51.4034,0 11.7352,-253.8879 14.6209,-507.8365 -0.057,-762.26152 1.9609,-0.1712 3.372,-0.3992 4.7834,-0.4015 15.4993,-0.026 30.9998,-0.095 46.4968,0.09 1.7954,0.021 4.2896,1.005 5.2561,2.3853 29.333,41.896 58.5081,83.9025 87.7207,125.8825 88.0191,126.48822 176.6868,252.54332 260.4734,381.90562 30.3267,46.8226 58.979,94.7303 88.3377,142.1783 2.9184,4.717 5.4338,9.6833 8.1365,14.5339 0.477,-0.084 0.9543,-0.16751 1.4313,-0.2513 0,-1.8539 0.052,-3.7095 -0.01,-5.5616 -1.3989,-43.9696 -3.1499,-87.9314 -4.1208,-131.9104 -0.9419,-42.6501 -1.3448,-85.3168 -1.5264,-127.9782 -0.4116,-96.6604 -0.3533,-193.32302 -0.8872,-289.98242 -0.1897,-34.3223 -1.5447,-68.6381 -2.354,-102.9572 -0.059,-2.4919 -0.01,-4.9864 -0.01,-7.9151 16.9785,0 33.863,0 50.7471,0 -8.1768,253.88292 -9.1368,507.84992 0.089,762.49881 -10.436,0 -21.2007,0.17651 -31.9455,-0.203 -1.4964,-0.053 -3.1731,-2.6624 -4.2999,-4.3922 -21.7307,-33.35829 -43.698,-66.56799 -65.0134,-100.19069 -84.3152,-132.9974 -173.8948,-262.4575 -262.9819,-392.2526 -39.5003,-57.54982 -78.7505,-115.27102 -118.032,-172.97042 -4.3061,-6.3253 -8.0596,-13.0268 -12.0869,-19.5428 -0.7402,-1.1976 -1.5667,-2.342 -3.5991,-3.2771 -0.089,1.8293 -0.2928,3.6614 -0.2495,5.4876 1.0673,44.9812 2.7861,89.9574 3.1115,134.94372 0.5664,78.3216 0.3504,156.6495 0.325,234.9747 -0.025,76.3304 -0.2212,152.6607 -0.3382,229.49099 z" />
<path
id="7_e"
d="m 4126.4746,718.67753 c 71.4199,-2.2664 142.3417,-4.5288 214.061,-6.8168 -0.7451,16.4174 -1.5098,33.2761 -2.2896,50.4655 -150.0732,0 -299.7773,0 -449.3708,0 0,-9.0219 -0.2398,-17.8086 0.043,-26.5785 1.0991,-34.1419 2.4472,-68.2758 3.5473,-102.4177 1.9158,-59.4573 4.5193,-118.9072 5.3506,-178.381 0.9199,-65.8076 0.7197,-131.6446 -0.034,-197.457 -0.5263,-45.9768 -2.6347,-91.93993 -4.3493,-137.89753 -1.4288,-38.2954 -3.3152,-76.5737 -4.9873,-114.8601 -0.064,-1.4648 -0.01,-2.9348 -0.01,-4.7344 138.3159,0 276.373,0 415.1914,0 -0.7642,15.7183 -1.521,31.2789 -2.314,47.5817 -2.6108,0 -5.0278,0.027 -7.4439,0 -114.8071,-1.4635 -229.6137,-2.946 -344.4211,-4.3742 -9.5456,-0.1187 -8.282,-0.8195 -8.2141,8.2124 0.7046,93.8221 1.4219,187.64413 2.1331,281.46623 0.018,2.3219 0,4.6441 0,7.8306 96.0235,-0.3875 191.7266,-1.1139 287.7359,-7.079 0,17.5543 0,34.787 0,51.832 -95.5908,-1.947 -191.2661,-3.8957 -287.7454,-5.8608 0.2673,114.2965 -1.0813,227.1922 2.9629,341.1996 60.1511,-0.7109 119.9011,-1.4171 180.1502,-2.1271 z" />
<path
id="8_t"
d="m 4643.8493,0.005 c 138.2856,0 276.0713,0 414.5386,0 -0.7608,16.1367 -1.501,31.8451 -2.2613,47.9687 -2.6269,0 -4.7666,0.035 -6.9052,-0.01 -78.6382,-1.466 -157.2764,-2.9459 -235.9146,-4.4006 -8.1318,-0.1504 -8.1011,-0.082 -8.4204,7.9567 -5.8862,148.0727 -6.2427,296.21243 -4.646,444.35493 0.9448,87.642 3.2334,175.2695 4.9151,262.9035 0.022,1.1504 0,2.3017 0,3.8394 -20.2554,0 -40.292,0 -59.9736,0 0,-9.7037 -0.2476,-19.3086 0.039,-28.8974 1.8487,-61.7897 3.9976,-123.5708 5.6973,-185.3643 2.5986,-94.4601 3.6372,-188.9431 1.8257,-283.4266 -0.9419,-49.1413 -2.9234,-98.26313 -4.52,-147.39093 -0.7364,-22.648 -1.7842,-45.2863 -2.4356,-67.9363 -0.1338,-4.647 -1.436,-6.2972 -6.3467,-6.1719 -57.6347,1.4701 -115.2749,2.7291 -172.9135,4.0528 -23.4888,0.5394 -46.9776,1.1033 -70.4664,1.6419 -0.9853,0.023 -1.9736,-0.099 -3.4404,-0.1796 0,-16.1404 0,-32.1629 0,-48.9453 50.2334,0 100.4795,0 151.2256,0 z" />
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,865 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:i="http://ns.adobe.com/AdobeIllustrator/10.0/" version="1.1" viewBox="0 0 566.929 566.929">
<!-- Generator: Adobe Illustrator 29.8.2, SVG Export Plug-In . SVG Version: 2.1.1 Build 3) -->
<rect x="0" y="-.006" width="566.93" height="566.94" fill="#2786cf"/>
<path d="M198.546,432.37c-.147,0-.308-.03-.469-.049l-71.276-10.782c-1.709-.253-2.867-1.855-2.618-3.554.254-1.68,1.783-2.832,3.55-2.608l71.281,10.791c1.7.254,2.862,1.846,2.608,3.545-.235,1.534-1.568,2.657-3.076,2.657h0ZM372.855,432.155c-1.523,0-2.851-1.114-3.066-2.657-.254-1.699.918-3.281,2.597-3.535l71.306-10.576c1.758-.215,3.281.928,3.516,2.617.253,1.7-.918,3.291-2.598,3.545l-71.306,10.567c-.156.029-.293.039-.449.039h0ZM202.804,405.339c-.459,0-.928-.107-1.373-.322l-84.855-41.609c-1.538-.742-2.178-2.617-1.421-4.16.742-1.543,2.632-2.188,4.16-1.426l84.856,41.609c1.538.752,2.182,2.608,1.421,4.17-.528,1.094-1.646,1.738-2.788,1.738h0ZM368.637,405.134c-1.162,0-2.256-.644-2.802-1.738-.743-1.563-.098-3.418,1.445-4.17l84.841-41.404c1.543-.742,3.398-.108,4.17,1.426.722,1.543.097,3.418-1.455,4.16l-84.831,41.404c-.43.215-.899.322-1.368.322h0ZM215.05,380.995c-.747,0-1.504-.264-2.1-.811l-84.894-77.8c-1.255-1.162-1.348-3.134-.186-4.394,1.167-1.26,3.135-1.338,4.395-.196l84.884,77.79c1.27,1.172,1.358,3.155.196,4.405-.611.674-1.456,1.006-2.295,1.006h0ZM356.372,380.926c-.84,0-1.68-.332-2.285-1.006-1.172-1.269-1.094-3.222.195-4.394l84.899-77.722c1.25-1.152,3.223-1.074,4.375.196,1.172,1.259,1.094,3.232-.195,4.394l-84.899,77.722c-.586.547-1.348.81-2.09.81h0ZM234.15,361.748c-1.045,0-2.075-.547-2.661-1.495l-68.63-112.583c-.888-1.485-.429-3.399,1.026-4.278,1.469-.898,3.398-.439,4.287,1.035l68.634,112.565c.899,1.465.43,3.398-1.035,4.297-.508.293-1.064.459-1.621.459h0ZM337.174,361.601c-.567,0-1.094-.156-1.622-.459-1.464-.889-1.914-2.812-1.035-4.287l68.767-112.418c.898-1.474,2.832-1.924,4.277-1.025,1.465.879,1.934,2.802,1.035,4.267l-68.766,112.428c-.586.967-1.621,1.494-2.656,1.494h0ZM313.083,349.111c-.274,0-.547-.029-.84-.117-1.641-.469-2.617-2.178-2.149-3.819l38.299-137.314c.469-1.646,2.158-2.627,3.877-2.153,1.621.464,2.598,2.173,2.129,3.828l-38.299,137.299c-.39,1.368-1.64,2.276-3.017,2.276h0ZM258.514,349.101c-1.357,0-2.607-.898-2.998-2.266l-38.504-137.309c-.458-1.655.503-3.364,2.159-3.828,1.689-.484,3.369.503,3.838,2.158l38.503,137.3c.459,1.65-.507,3.359-2.158,3.828-.283.088-.576.117-.84.117h0Z" fill="#fff"/>
<path d="M297.624,445.094h-23.837c-2.344,0-4.248-1.904-4.248-4.248V138.959h-73.664c-2.334,0-4.238-1.924-4.238-4.283v-24.085c0-2.344,1.904-4.253,4.238-4.253h73.644l.01-55.14c0-2.344,1.904-4.253,4.258-4.253h23.856c2.364,0,4.239,1.909,4.239,4.253v55.14h73.649c2.353,0,4.267,1.909,4.267,4.253v24.085c0,2.359-1.914,4.283-4.267,4.283l-73.649-.015v301.902c0,2.344-1.895,4.248-4.258,4.248h0ZM275.75,438.893h19.93V136.971c0-2.343,1.924-4.248,4.327-4.248l73.571.015v-20.18h-73.63c-2.363,0-4.268-1.904-4.268-4.248v-55.144h-19.94l-.01,55.144c0,2.344-1.894,4.248-4.219,4.248h-73.668v20.18h73.649c2.363,0,4.258,1.89,4.258,4.219v301.936h0Z" fill="#fff"/>
<path d="M396.009,475.873l-41.003.127c-1.875,2.754-3.946,5.381-6.163,7.911h-126.206c-2.275-2.618-4.438-5.352-6.264-8.047l-40.706.009c-3.936,0-7.124,3.184-7.124,7.12s3.188,7.119,7.124,7.119l36.395-.049c.142.019.288.049.43.049h183.518c3.926,0,7.139-3.184,7.139-7.1,0-3.955-3.213-7.139-7.139-7.139Z" fill="#fff"/>
<path d="M249.921,504.603h-29.926c-3.936,0-7.144,3.164-7.144,7.109,0,1.905.738,3.692,2.08,5.039,1.353,1.338,3.145,2.11,5.064,2.11h131.513c1.915,0,3.711-.772,5.059-2.11,1.328-1.347,2.07-3.134,2.07-5.039,0-3.945-3.183-7.109-7.129-7.109h-29.959c-10.85,5.137-22.988,8.008-35.82,8.008s-24.94-2.871-35.809-8.008Z" fill="#fff"/>
<path d="M420.501,447.106h-53.045c-.606,2.676-1.348,5.371-2.188,8.027h-158.978c-.933-2.676-1.758-5.4-2.344-8.027h-52.908c-3.931,0-7.119,3.203-7.119,7.129s3.188,7.129,7.119,7.129l54.651-.02h157.314v.01l57.498.01c3.935,0,7.138-3.194,7.138-7.129s-3.203-7.129-7.138-7.129Z" fill="#fff"/>
<metadata>
<i:aipgfRef id="adobe_illustrator_pgf"/>
<i:aipgf id="adobe_illustrator_pgf" i:pgfEncoding="zstd/base64" i:pgfVersion="24">
<![CDATA[
KLUv/QBYjDcDKlznqTkARWTOAxxOcPp/OP3///8/BAghfluMttAQlbR4WuP1DVdcbcT27RYWRsV4
lSSlBJI+OERx6X//LyHqCXkK8Qr6MnH8PHt35bAvOsFQ/uywa4LNb7tyuosVYRvbV0eE6YvFNMMC
tsE8TABsYw6YxgYwfREgwvRFirrmNPbxsO/ubU5jXt/gF27wA7XmIqcxD+yi54Ha6+I3hh+nsQ1y
cfQ26IWcxnj3dN+89729UAw5fXUwfa0iA0x7N2a1nu6CYJ61FnSYg4H9K/uus2ehcUmwVwD4Jegu
2bsEzJ75kyPX2+OYxnbjcjQuB/uiHObovh/Qzm0Ba7wSYJ3je/YbYN+n1nfdYP3rvHsfpzFR/HTo
bU6DfH+be6E47k/snMa6t3e4x721p7kYctd1Xej+rpGTJqr77rtzr3v9C7vYyV728kyv7W2Pd88D
RdL0Tv1tD7xE0kTTT3OQe+AHiuAIjiBpgiZ4XimoQx3uHvYvDMNQJEPSRLW4uSd+54Wm+9Kb886v
7l0fGHIwLL+yl7tMyRNMR1QUw22G5IeC5y4/kp9e6ZEf2s9edtLr/dI83afYTfDi53iSowiO3h73
ZYomv8oQBNMvDa9we2lHzT72sYe765GDHLzQfZZb3KKnd2pqUo8dJL9xDK+SHEmzJEmxHMmQvEAy
JEFSBEnv25zU5CbRMR3P0TzHchzJSwTHcPy6HveoS9EcxbCL3qVFtAzNkAxDMQR7yHeYgugJlqMI
guD3gR30QA5+++TfRZ7kaaYnqlGOcu9DwWtESXREyREV0VEMURAFr8/rfKMa1Wh6ajRFT/Q8zdM8
y9MsyXM8x1M8L/EUz/AMwfM7v9M7vbPzc59bn6mZouZpmqVZmqRJjqbIMY6xL3qWl1xufcklSbs8
JUnDtN6Y/jAQ2xjHwHBvb+Rf56L4aTHU4Lj16H087HtzGgP5KHog16P3jXyLnMY2B3Xn++vi10cO
chr7xi8cv607/8TQAzmNhXrUO/xE8Pu6F3L6Wrh+SdIw76pc5Tb3JfaxXxq8RPHS5bnRnW7NN+e8
c49/HOQhF/nISV5yk58c5SnXfXfee/f618EedrGP/SJ72c1+drSnXXvb4173PO/zQC/0RG/0SK/0
TO/0UC/19Lc//vXP+77v+8Av/MRv/Miv/Mzv/NAv/TR4bZCDHfTADwTBEBTBESTBEjTBE0TBFNTh
DnnYQy/8QjAMQzEcQzIsQzM8QzRMQy1ukYtd9MRPBMVQFMVrFEmxFE3xFFExFfW4Rz720Ru/ERzD
URzHkRzL0RzPER3TUZOb5GQnPfIjQTIkRXIkSbIkTfIkUTIldXntkpe99MqvBMuwFMuxJMuyNMuz
RMu01OY2udlNz/xM0ATN0BTN0STN0jTN00TN1NTnPvnZT+/8TvAMz0s8x5M8y9M8zxM901OjG+Vo
Rz30Q0E0REV0REm0RE30RFE0RXW6U5721Eu/FEzDVEzHlEzL1EzPFE3TNDVR8zRNkzRFMzQ/s5vb
TMuzLMHSK3mpS5Q0SZIcP7KTm9TkhY7mSH5jH/eYiqdoiqQoiqDopWQohiEPdZiOHtjJb/S+7nlX
T/e3vevUptbkKIYa1ODndb71tXU66vEqz/AsQ1IMLzHsIL9I/ZGfd3le98Brhx/ppXu89ha3qMU0
TNHwBDkKnqAJlp4G0/O7yo/8xm/8RPD7Pu/rohd64CX2sPee8pSjXOTgpbnm6UY3eOqT1GSoP165
0PIszdIsy5Isx3IsxTIswfIrv9Ire9lLXu5Sl7pMSZREyZM8SZM0yZIkyYskR1IkRTIkQRIkP9Ij
O9lJTnJyk5rUZDqiIzqe4zmaYzmWIzmO4yiO4RiO4PiN3+iN3tjHPvLx2qMe9ZiKqYiKqHiKpmiK
pUiKpDiKoiiKoRiKoAiKn+iJXewiF7m4RS1qMQ3R8AzN0AzLkAzHcAzFMAyvMATDL/TCHvaQhzvU
YQqmIAqaYAmSIAmOoAiGIAh+4Ad6YAc5uMENajD90A/9zs/8zK/8xk/8wi/8vu/yPu/rH//2p7/U
Sz3UOz3TKz3SG73RCz3Q+zzP6x73tqc97aUd7Wc3e9nJPnaxhx3sX/c677vrnvIL5Sc3eclJPnKR
h/zjHu+c8801P7e5y03ucYs73N/2dt987613qlFt6lKPWtSg/rSnu74EroWLLTGOaexyoFqr+30I
+35+3/X1phfUwCTz61t2/epbYjwS+TyYg/XhFPNzAbOXfer1LNYqtxT83LY82Pfs26JU88oRzf5d
XU4Dc7zeXVnYgHZerd/Pbwb767u6fkCrSb0t1an1buolZfb1bompBrTzycfy975FEZxaVbz+VOu6
q8m6bTl/8vtZbm1U88p5DT48tyyKvRzZ1GoamAN74Rb1ZjDVaV4F8CkWxVwY/rheftvYXdsCfOzC
8AdTya1fYt/P83rys0yAx7undv4rhyW3LmxiEeBpYA63xcKsAuZVbilEXu+6YecjDOvDKH82MAfm
/+qmjrhduQbmYN22xGQ/+1d1an0BPqU6rQUNzDGfvIPJAGN9Ae2H55MPNS4Y9WbiFvzVt021XA5J
LLhSbzu/7ee8cAEpbs3ek4Jd+HM07ZLn17NYsMm+qKZdng2n9cYk8dfP8+qm3Nvit8FwHPmo+bi3
FjkR4yHbfpUU6g1yoAjuCDaOYefAcYxI3jbVfgOfWjlaKIBvQx/rSeyLCjBmSO3y8/p+P7skKdwZ
Uru2+GGguLEe8o1x7nHs2x7fG9zj9jhxcxEDRT1+IAh6Iid6XMQ0BoZ67N/3cbFzrbm9uQ2KWA/H
3kG9Q+/7uK+DEUzi2xD8eX1sQNIb87hkTpEGkFAtaX79/MIm6W1TviSF+3sCVg2rVSDOLReWfWf2
qb9+9uqm8ydJn+tZeDN6dz15fOqvGpLHM6R2bUwiz+0L+t5tdbT2PM/8Sf/CBZhEAN8O8B7IaR1q
HWKh2L/OcTGCeaHnedz2ttt6W2/R4yModpALR46FXINgH0X+eSOnuefn1Y1Z/uONXX8iOO5Ph/sb
Ofh1D+SbjyMHNahHsHuccy16nLz6KyuGv4Ph9au/sr6X5ZWz0NCYpFQ7mnZpTCKn3naevcenVlaG
FGZI7cIkPrXKIFLPkNrVMcn3JlargHXbVkniO0NubF5ZH06926akL0Nq1zq/PrVusHclwMQk8odd
2JRlf4I/Z349q3WCT027eA+3t0FQ1HtzW/ywPnriqL8+jtrjPB1iYiiGWod97DrcnLcxUPx6B7sH
jqHHyb7TYjTt4uKHfTrEvI2FfWOSAeAy9bbs24JLTbu62MGNSXwBrSyvA97V1LTL+yR8Yx/n9OY0
xyR+Xs8Xbh0oNuZMqU+CU7AmQkAWNDCJd+f2o2mXo4E53ndlff++XoN9j2rf1w80MMmHU027OCdy
I8ckL9zK34dIYobUtEtzHsccncaH5/XPcst1Mxgm+fC8+m9q2oVprke8CWbJiIeTaQFdI0vYeoZh
4+iOsSBH5GOXDjP7tIvB4FQMHGb2sRqLTcfjdboD8WTvwGFmn6ezcOo0K5kcqGV5cCIVVqnCqcJc
aaItNnFqV6gpEoKx3I0ESqe9hDKSfRxUkExHxEimXSGLJ5Jxmm1klQd1ywYaClu2lqyp7gQIMcA5
CA+Uw6MP2RhdQHaTYto2pvMVdAWXAY6DFhCUa1fYOLj0rcdyjx84wcvPplSiRgyn7uBNjCkBAogh
TcbcXeaHw++9gKPAHw0CqNauLYVQ0iYEBDgt8IkxLfgcLUNNQTWEFUA1JP6LbYkzChOERvs7zyio
3kALp07mvHH7JKQKp+dl+GhBCcSQiIblfwyFwuArmFLZslHTOe0aWFW2TDQORDKTO20ZwfZaJmJ0
kWxrKGwZmXJIZjp4IxlL5v/LmlnUdA6BjHjueEhUO5QgOO14RNgbBp0M9AAjIzviIeLpEK+J0tr1
EE1k7xiLsnuyDMGJoNlsaKv8/kw8wMomlr7KZhkoYfqssmwHii57mUkEUrmGhVMXSQmgugPhBFdg
G5bTYFFQKkeciYDyEQZBAPPJHaddseerbI4ESqfbcnZk7qp1ZB9LgmRoQwiSiaiSI/Nwpy0TI2Ik
yzSEIJn3vyNTgR3KxiUHAh7fbJ1eaGLTbiwglHhIwKAl4QXFyd9MqtOpj/C0tSujULDopUCJ7K3N
8V0aVBrKBpa+ysbqjhDCaSkioMACFUCxi2V3UGDGysWzYHWlZGDxVudX2dLVCN2MzdbpzhSybGHp
q2zaBcE8C5/8+YmZjOkhO0yCXfMA/rN3hpzApxdVQ54B/EcNeeYXTt/vCeC51Z7856ghDyrFUUOe
LVJDHtabWOj5ObUa8szyqlljNeRRQ555fuFUDXnWk8e7rQohXl9PixEAVr0ej/qe4/oBWo7mJLZ1
1xDp+1B6crwbt7Pb2vgBq8ec05PTwCZtvJqpt51bFt189dfN92/rfh8TgPHsfhFaVDu/ns1o9oXN
zIeADtzOa2d+4XT2sqnl0XteTfA/kdu77YeA9asfb2fZnxx+4Y2XelvYRG7OPQv+qhP8whvPW31Y
gPmTLPinBKxf/XgZxFtX5vDdrq+J1VJ9/Lx+/3p1twSvAPC5tg5euCVg/erecbxyb7Pa//lkfGrl
lJq4/SfZn6T05PCdIblZbrH5fdeVN72t1ckLUICcels+1c6uAAaQYl+Pfc++fladAjAAFvb6ufWm
v3oTdr4ZjCsxrgDMtwJgLNb6U/CP5xZMwKrBWP5tPQtjb4t9PwNgALGMvV4ADIJiXvYti/nWg72e
fb0A34cwbgWfkfTkYLK7Yq2f1zPy/dt67P2xB2O13oS5rp/lt/WYb1mMfVsZk30rgG9DJo8wy2u6
+usm5TvzJ81W0yGlJ2fvDBlZaLQy/LYY/GJjhuSdj+S1dUmsnV8S3B1zez5lZ0Cq/Nu6KM9yK1aK
c7Lsf8yCT5w8t7MJdpD/avqwC8er+/2AFQGs03bbmWO/vOoB3HdPq9PqmuBY/u5JNYDz7hpz7D+p
QvjkGHlacwmPhB7IN2bgyTRGWMjJjmqQYqyfV9hsvC1Ui43h913jAbgnwvPr2x/H8/ft26I8i31v
av3/YKrTb52NFgW/qVb2LGeCHTRngh0IOLnxSebYfzK98PzCp5SpJXPzi5q9zMmte+LiAdaUfzk5
/lw8QJdjmIsHOG2Xk+fvB7zwiYsHcP0A73Jy3F3Pwiw2/vGP4/H7pzD8uB/Hp+1TK6vlfqxF2b/z
6qK+Z1eVzMlxm/IfRv1Y/xieVTPMsU4o77YmTo5ZmOWy3PyiYNTb+tQae9eFT65pfT0nf/b7+W05
1ml/784yG8cs584yAbP8nk4yl/owx/5TAR9Wxa2L4th/+vHKmluT9255gk8vtzgUCma9d03cipWK
MRj1ZkzwADa/7Ye9/P0A/xupd/0+tcard3VNcbuSXRc+ue7KmWCHrsuQGGffFZs/ifm31WAeDort
PcMFHdYliAaASzXkwc4/yy9ssh8++U6rmT/J+nn9qXbTwCZy+9dQhny92sAm5Oo/GcPfPDMgEGg6
fSivUaMJFUa+0RsjIOrkRTT4+Yi4RdQ38s1B3cFv9DomijrUvY9e15yoV3/dELC6sdyeG/OqEwAG
4KJerzq1XqHRITdvZbgDf1zfmd3WivLnfPKu98iZ7WUQxtmIsFYAmH1bhAHaF3RaN1hbNjzrwihT
y0NADHtaj/TncbFvrUen1p3oPm6OyZt/HhiCXMR8TMAHm1//wjELNlNvO2D18HbAqvIl5t02xfy8
nlo3bp0nWOPZlnt6M6Z2XmMWbPoBrQZWT+38fUg1sy3C+YnXjey63iRgjlmou87y96c19mCbh2Go
mleOTGKYl0EY+7bgn1qd1hLTbD4Yj8fE4A+GIaQeuE35jw3g3ZgFn7C4dU/z2/74+wFw7K4mbEDk
a08gz16WSe+a1vnD8QBoN5/as4FN/IB2Xl3f+SLf5iEoenwUvzF84ZP3uK43wd1FbAz17p4o/zYd
wLNvZl5nTyu3oRj+jGHp39XlzCivSb2tSm7fNTSjvAYDPTEcx9HbYTh7XLeVZsiV5jTn7YY/ral3
etdZbs/U2wrg25DozV0cPW57noht/Y2jx+1xi+568pC0a35bmLPbF6RKtR0/D4DOKK/5Nue0GIqf
F4Ih7+qM8prNQY554QLaD2P424LNb7uuN/vsZRTK4MOwm346/fvA0xoblDPKa0hs+1VxFD8Sgz9Y
3LomhpB6MuR8ak/vmtaPZ60F7nqCNfKZOcHnvGJc72lgE3lt3RVr53cFd4YcwM8r+M8y5t/WnVsw
CtMbnlskliG/C588do37Sl5ZMxzLcuuuXBuzISSFzS14dYLflXdNmJ9XF5ZbDNvbbbFaGfs+XmNs
zJAyxrY/hWFjhvQ/hXGRe/wzYRv7sAufMPavsGn+KzZ+/fvAAtq5dVvTOrcsis0D+A/mB/y4FcMc
zKfWVupdNSrZb76NfT/LawebW/HcMsDGDKl1L8BmL/vPwb7HsL0zpGfhU/uCOszBkHpbcgAG4KJa
JOa/d+e53Sw0SNoFEQAlo/SZ4Wmiwdp+ntcN/J5W9/v5bU+v/2ssv+uf9fez26I8rhZw5v3C7bwW
0LLaOYY9b9Sex0NPe7Mq1XoVqEcP9PbIdR85EPxUM9sKMMAABKy+0xHAXRguO2zPKK9hwfMMm/zp
t2DPqXW3ACj5hV/Wr06wu1tTwew9fx3Agme5deIT+a8DSPK7aAyQyK/LGmCd19O7MWuVJQMmsp9b
pvbP8/r6z7qt6jSRZ+/xrmlt/zpgr/PKrrNnrf+TXUzAPL+ZDhtb7jqApF0QzOFZ6+tZ+I14Fn5d
1gANLPW2f/Z0mINNNDwLF2g2IgL++sLtfGolWEfSrnVez5F9UZLHPT7LrabkXCfO5diaU0os9DZF
xUKR0mlcqwgLPW/kndPY/rz9hZuHmvOtNacxT9SjHkHdv9HzPM1pDPy+vkHt7d3D3T8eeqHoiR7n
Ywhy+jpxrgJEoTh6nxd64chpjIv6A8Udfrt/4+7fuDf4jRsUQTHkNPZ5Xxh+X8j3DvceuZWOZf5A
Ktv+POTKHHqd75Cmsh0DT2FwtYHC4OJKhXAoDL5BrDvlJpib0Aa0AUUb0AZ1rKAGI3SjDRevcvjk
MGdwzgXOzwWOUugJENxKMEW7yHLhtBWyiueddUFFqgpi2TMHI60TMB42HYtnKN15EHjJxENs2gwu
a0boLmWTmjCLSm4mmLSrf5L6SRHE7hHERkUQG6URRAgihA5i3ebDSH0YhRahkCgxGqEM/sBA6QcG
Kk4wR+g2GSCRh3zslez1Sva6W8ledzikMhwOB+coYXQE8nCQVgZp9Zog/jSQpRPEBA4CLdQOJAQE
cdca2BbtGmANPIw9dSgfA6yGN8AakOGyIVH0PsAamCkjT4PSGQdV3w60YWnMDPxqWDIODgTasJgZ
ziQzcJKo5JjURsKkuFwhYTCKhy3itB0is8wdiHfQGGVRuqbp+oghCWgDijYkjNCdRRLQBvRRGaF7
Qj724/HQLu3i2qVd2tVB8vPIQK3n5KVCi2ztKuOUuikpDUprFgfuxUwb6s5IakHnIS2EZKf3BpLQ
cSYqwpmozjUkqBCFwbmGBPVJqkgkEpHUhgQliiA8jB5GKoJoYUKsJMS6zZYIsSI2SiOIII6tzxG6
uXOE7nOEnt4DAxUnxAlqqOPMheOxkr1jJXv98A6ea7O5YLXZgCxYzegO0uodpNVrFketeEQOj0fE
lTDaZHFUjroRgAQxwdZAlt6QgCydcSCxdII4sYEsnUDPH0sfYG3eQINgWwYWnETRFQTbMsB5GQdV
JuPgWJ3z2bDIErkyvx6OSXFMylFq4CW+G3iJt1hRydFMrWwY/sUV/+JXWXWsqCoKg48rE4XBxZXK
NjoG6gdRGDwWVxXtQnRTuna04Vx4dAmIjYo8zoWH45TTR4v04OdC47dSVqO0wFI8VDA9hhUMueOI
MoqYZAKSTACSCWBGmVGDeKoKj2eSJxdFGoIqaH5g+J6oigzoQWIVelQkhr+7hVHCEZ4q8khCFRHk
ybeCG7gq8gF+GjncVJQ1I+1KbDaGTGDdtAYaRuzyJpG/RKsINsFjVV6zSICenCIaErzSWeBpCZAj
QJDq7ZAiFBK8BEgptX4nwYMzAe2h+lIQimpul1pf5PBU3+jFTbeCKZVwJer0RyKIZOaqsmUclgTJ
Wo6FRqZdH5YEyTwNIUiG0Dkh2Z4QPDJSRIxkmlVlyzwOKkgWKjqNrCVAcly7iKqd4Bj3wvGPlywX
Ig4+bpABzUAzBR0PvQJyZlxOW7tIaadHsklN2BaIhJKbIGei9MeZ2tU5UyUqeYIokiAquXbtEIXB
QxQG166LKERhcO0ywU/6JLUhmRcoDN6QQBqSCGJvEOvWLr1BrIiV2yBWyAaxbsR+GD2MVHOkVhAe
NIh1UxHrZvCBUaHkcYIaColCoswI3eYDA6UfDwyU5ttK9pqywaqu4yIT8rEfE/Kx+0qGQ2oFDqlw
CCR6SMjHNmW4dHzaR+S4PqKPyOHgIpDN0XCUMNrcpNVr7WJxVI+oUcJos0CGS0bPDiQWgpjA00CW
rl3ZgcRCEF8OCsjSCWJ1rGgLAm2DLARxpoEsfeZILNvA11sD27Jp1wWH1Mo2oB9GRd98QpbO2BJF
35YB1gFCouhXH2BxGQfHw0itbBmVnDqUjz2fDcuWcWBsc8Yhk5hlBl521oyDQ7temYGjK4PV2pVx
UMdKxNFNcmWubNr1IFmw2ry46IGBIolKzpG0S0MwUXRMykGANiwbBrESlZhuqmwYTEpuEJVcux4q
s0JDghI9MFAqG/IOXoNIq9falVCsKAxedtbEuOIDo0LJVbZhorRdNq44hqFYt8qqsojC4Nr1xVVl
e/AODBa4rVFCHrjJ1NAKQdli4OnagGwoDM5okbQrM64qWyrD5fbATZVNu+Itm9QONHt9+aSrOxcE
ao8hCCvZa7RhweKolQ1dcByvExENwbZsKGoiH6ERCpEkrhmSsHCU1EhCZUNluNy0K4YkwFwp1peG
VhIQG6XjBLUC2SanaMVoOA4fLdLmQGLZThkuN97JONrVWnAqBxAUol0ZbWHwxOlVnXZ6VVea5GOX
nRXeQDS0osoM6D4X1LFyilZtUcJoDkgdK6szZXAq22lgorSdpn0Duuys5iNibvCCU9kONu9WWoBs
EGRf+odtxJWys2LMBQar3VZaIMMl2EAkLJiSCoaTEErIPmHAtrQrRFAgNjKBVKGyRVUUbbHhh6Qq
rvLEEj8ePvXzqhg+5yhKQ0byqeLBaKMhqoI8Ebe70FVxczoYPqSVhuSJZ/qYXBUzikFkgyAb8UkY
/nFELhMYtcCHBEZUSByoY0RdT6SBveCfD5zqHjMkaFcDZ3UUB+Un1YjfsSlCTkhw1QU3BiV4hzUR
YGhShKxdDhNlxHjFTfeJMFwy1sBX2Rg9Udl6AcidbBEOhcETiwXLlVofRZCP7TBRBFCaznEc3v8t
EUXh8ToNkSTaLsFIlDD6RFQYnE1HolS7OOpmOVuLLCJ+PJvl9Az4xIkw0JXmXp0fCuZL2QUVObOb
QDc286LLAj7DE6qGBi83vOkUkNOGJB02SotCp7QvbGQGPuEbDZe9ziNibianhNFxLOCIK936QLg8
HJRS63elTEB7NCSl1u9OBkMms0jk/x2rPDDIlJzyP9doHFy6dh2QKP9bCrJGiD1A0BAZB4aKQaO8
AWJOC0Q+DSEIChetoQfSwoOBR9YOyGFIEP0qAj+weCd3loOoYENJsoN2qToFju6gOKTkZvQBTRIE
o4mU7VcgPErNBUHADDUEKujCClqAGoQcUWgNhG+FAjESCiUcFAEBo1W5FJBAgta3bRrtYtAEcmIg
QzCZMwcogsElW2RChcoW5xnzYRgMwv/GKgyCL1i9FWkIvibMqSi1DJKmCDnBkdddYczbKKMFsrU1
MNKAErSLIZYCXdtAr4n8ny9YnTmYRYjFWKA5C6ZPXkgIaJdGA7J0gYVWgjAfdyCxOCztb6ULVjss
NGFkg0qt32nXAAUz2UAOb0AyM3CGQUpmCRCCNiwjPJLly4LVKZgYZZwm1a5IYiAlfy4TwDQqDP4V
bPRC9T3GlaLzKH3KgtXaRWK1GhyThIG4eQsIrW/JBNQZsWp0AUKGEcGIA6q1y5TJhe+4GCBkgYcD
kk5HosXGYWnXATczPAROkUPKoMDxMWDQ3sj7H2EmSaQE+DBZEMgSQkPIYdEg5EuPQ5pgQbtctVNB
8NaERNaU8rQSGoQsihghbS54/2tXOYAZFUwsJM8M8SzBKEKA0C7ZSzzasUCapNWl/Y8NeJBYhfID
BiFr14cuaPRFwQutNB6wEUlIKEON1q44XLB6k11CyyM9EB8KTiz/LhMwuKQ8o6AFUbV2eULdSpvI
KaKDYUg1Q4I3+SGPIgFqVwUdU82wYGk4tF1q/b8IGXIDIQAnLMCedmE6Ayl3kPGCp1R1hLRFw19n
hIH7pQINsLdg9WZuKN4Cpzo8XqcLBjjD164EeYKayMdm3XnSVZHB4ICrw+iLOFOGcYmoCNn0JO0C
KSRUXSJJD0dLgCfCJ9UFjk6o0C6QTRGbh061KMFDExhrQ/VFNhCGrF0PCUd4o8352pZ/IxPQVIIW
1BQoGriGjORrCNWD0Ybhj86EBatd9atsDpMH0VNFueNW50bmsrfI1MrWEjWdE5A1w1eBHcq7F6xe
rOAGHppTUTY9qeHxOp2BKIz+R578L2sOtEsxajqnAjsUDIouukdNcfwg7Bzdcg227q6gg5er4uSJ
S+cJ2qVdcuS0ZSniYMs6KYdk/eCNZAvxAyRzsCwN7RKFUN8lgWWQdpUlIdBzrDh5aVAp6DAR7bQ1
Ke7gko4Fi645UDwHqTQPofjj2tXxTwdnIta7iUhQkuywcQfX3qJdqETcUCRNh9Eu7QpbiDEVeZZP
ml02Ws4ZZnN5uMgiE7JBXUOGGyyIto6AExtSu2A43nSej1j0shQo8VP3RVfYLHOK0JV9klk8tKvB
yEQWHUGMEvM7IgwBskMe1k4zUDKH1SXTh7sipaR2PYS+ysT7PyOlDQ6gDzl6dCygdlUkGi57nsVn
AYH1PXiGUGXOnrG16y+ISl6CrcDmWMhas8EIOxn5DIUtG2BJkCz13C27NBS2TLsKGF0k450TgkYW
SPzgw5xLq/JAaQujc/IHDwPnsvJUr9FUw6brNswiF2b24ItWZU2U1q6feHikIqetXQ3tWpAQaO3S
Lu1qrSqbdmkXnKBd2qVBNtVzd/GEUoWTA30YksAydxeNp0RAPbv1qbwlomPBd7hcNLPXsVpS9wOr
8HRap7ToF/BR4nLjxMmBg3J1eU/Gdxn4KnPEQD+s3addJUenXdp10LBRCNqlXZoRrrbNVbQrW7Qq
FTa0EnWaUcogGeR/B9vJSFbpZCTzpByS6YYQJJMUCDiylOM08QMkE0gwHpl2ZZrJa1On8pKoQovs
ygYZ0CnZ0OkOOo1qz1txcEm7Toc8yIgcf2QKnEojdhsemXaNbocysLl2KeLUI2PQLtKptKKm0+yS
RLxAnQcdk9euhcOiXaaXgjJ/qcOdJNVocnmQTtyR0Zi2dkG0S+SWVWXLtEtzpy0bHbyRrOK2jqwh
IkYySPlwZJeLA5J5RBtI9iOnLSNYVbZMw+gimQcdRTJePhwFKYdkJXfasgt32rJRocCRgTMXeU0D
qyIzCbrSBp+R1ledVKdThJ3TPhjTlkwfjxIHMaAEGoFBQNnA0kPoq2yxdgmIHBjtek/O35B8nIoQ
qKJdGAcQSrtYBydvNBIo2iUzQu4cvYxVaZrxVRHNXruqdh2c2Sc7R2K5Ej4GvjyIGKHLX6StCZlY
tGuiOZBYXDHyRhgzHzWYH5J0ml0yekAlF4UiY5H4thA5+cPzjND9X03Vlb7vTBkuHIPyPRXz7y4G
nLV1KhpM7mdgYvjoJP6gLtqFoFpbpwbBEHLg2yxnWnBwIi86hpAFCSUCyjVXmg6bqyIZHYESlx0a
ILsgJFU78ZE2SZHAm44xQFpdToV079Fj5F4i2iW3tD6ORuhmkRiP066TWhmdCgLNoFa2BkQ00XDZ
sxicTq7JqYd2UdYGyG6cEKtOow8SqPLApRaBC6LR8pAD4ELmbDhMPmRChxG6lLqH97JCSyt6mT+W
PoEvIzQikKhsjUTH5HVmJ5ScP/TzQ64oqnpq1+hUEEwGFpWJ5aDRWJWtRyWXjkaSAEkpoyMfe5+L
ctGqbIhR07kCuT29crtnKreszEl25MNltxwM2lkNTC6bYSalCAIcd3KEGOPgHSnttMkAa1lLVCFY
BZRjrCuq6WAeQlXW8pCrP/qJdn2E0IdUKbQbKicm3mfTVRwUmP0Tvgpr4KtsCQXK166OfIA6Fd75
ITUCKQMygYGQUpUBWEKzz0xKMxSC0MWHZK1FhUNviRJGV1gQSmsJlE5HWCetN9SHhcFD3+NKSCXV
mmASAwwm21GMaeVLjKmHF5TKxf6gLh1fIqAZiE+ckhNeSAoSBaWyFAAx5AUEMeS44TBIwpQlq1C2
PY8Ivot2HTgUNiTpwZuu4xJwLpvMc9JULDj4JdNQ90e/nGQ9CMCRCdfQGuKTvXSY2afweomXdumr
bFx0kr0rhfLc6fFhdUIXWrvojQO1sn0MX8UTWQQuH1lDnS6GpQCiW0LJe2reBjiSfGwuUcLobmEW
0PDFgUgdKw4eKpCCGG9KshBYuzOiAFY27RJLX2XDpCBLN30KX2UTNxxNvZQFpFByE19XnaaYinU3
yBG6IyL52FtFrJsDrTqtXV/K1OmZNSp6RqDFwBPYRlzBdIWSPzwUBo+EEOuGnCN0QxIljOY6pk6z
uAWrV42p09pV6p+vsv1MM7m0opOj4bFQ8lHDZQ/hJT9kTBCqzNJkASJNCQUEalDXSei020orYkJ0
S4MUVJkoR4OfcAYJk20+9pZJ6LSDUzi492PpIk6nZXabZAauO5xOcxpOp1uRDKfTb0g+NuQjH3sz
OeO8YvQjUcJoDlI7ndAkir595GNrA7XrNGqnGyKp2rBdVEeihNEOiao1l4YWA5dWNBYGh1wEJA3o
fkgkjQarGRpepdO7W1tTu8o4ZOkjBISO4zgTgvkqGwSxUQ/MV9keK9k3GJivsnWJEkbLo0iihNEQ
kqjkitOrui+BwuAECw5SOmIp1p3JYBppS4OcWwMCAgEpa2AWKV5mrzfbqOkcppMR7SJgSZDsvDgg
mZlyEkSMZBjRRtRKGR4MCGZDcg9ZSxo4TI5XdF90UxSbdkZg9S4DiodNp3AqcDLuWF5rl6uVMPob
INbd2bQGWsfQ/ky7IpkUIafbCN3oyGMYqg/cLEaPkvGUGAqu1Prnq7Faw1I8VBqbbdxCJ42CY6JS
aG0jKEDICsD38h8UIJVHu7zteobG8660M4HgUUjAhgRE10nJrPNQMhyqE4WkpBoyVThnGSHyctID
elmOWEU83UDB+F0eQIGVjh9WKlK73AEG55JhXE67xYngCCg3NMz5KhvrFVr7fFIyezdfZSsVeDGk
lZkMFqOX0U5uQrsaBd2IQGgr1WJA9XC00p2ICo5IWzyylAJD6IIaCzAGGLaNMGb+/9wRSa0fenfr
T+CR/Dv0/3/Q2JQ0Q9RLeu4dkkaJGhq0AcLEoLXLAYIo6EYogqBblIaQdmcd0iiVDNKhNvPRY994
tMq7BjqFYmk0qSCj0ZHxQNWaFk71eUCQaknEAdWdA4pqeNV+toTORGAbHyqTjXSgcJkK08DItIt8
bEa22EZa9lfOZ/MgiJscEJi7S9up/G/6mDyhQDo4opFx9MsA2nQGCw/fZYDUQhcVbgBd/gBJ4gnU
IrLJlQAc0S5QbugC0Bt0GuZKm5hS2fjuHAEBAQFB4RGrh9gorV2IjdIPI3VrlKEEdSsToMXn8/l8
Bhas3t7Ba+/gtXfwpnYRxNqldQTbMqBdj90ltIiAxIhC6OaAkEQl59qFSTkaDu3itEthwUFKFRbM
x3Zb6bnw0OfCubBNzgUE4z9cFhzWglNhDXyVzuVggRDAaLO3vnZ1lEfrZxWQ/DuWgpQ8+b8EGS+o
ih1h6xo+WFFLlRQL+lSxYwpNNg3yZKJRiTxV5IROhs6hJsgTVdRUWqO//VSUQfLkf+3qCWODqyIn
JY+SVnS8SnRQIDoyAVXY8IyEr6qgQi2ZQt1K654CFGctkCb9VSRAA7BM9Zoi5IT4gXB5qD51oQDV
W1Jq/QN3wdJwqL6NwWmyrUitr10Ls3NCsnRV2bLN6CLZZByYz4xAd07u4YIQJ98FAztoNtzgWGlV
YPUuDErb6T6KkXvRNKMMJ6Wd1q5uNSq6p0FxnqZ3XTapCaIEUYJo67JJ3ZzpEIk+SaS9X0OC8rgp
0i5TIzZqQ2yUjhPihDghTogT1ErIfGCgQptjg0MqHFIroIWpIR/bjKxkrx3b63k96utRPR6PxyO6
rF6vXpNWz+I4KAcSS+sCPoyK3gdYA/xhVLQG9gCDYFsGWAME2xKPCnlgW0AGXSUzcO2SKzOGY+SY
taikVLbBTBJKh5QhgBkAgiQAAxMAIBgcGA+KJvPhutYDFAADRzIqRERCMC4iLCaOxcLQOCQWB+U4
DEMpCsJIDiqonLNJAFhBSHx5v4cG+4C2o9nhLEeuk6Y+7kUpx+50lO8fidmoRG27aXzSFgYkHRuA
0ykTt9udWbYe7OB5g/+gOTzpzaL0MI0R7CdHfR8Axz55dFD2VSYvDYyIj5qOIPO7KkdqNYM0gr7G
Aiy2ID6V5DRbiDBksu19yHtFMVdnbMgSdK5zjtu0HwFi9oJXD2l62RhxSEaH1ZJgumnMw8SSliF7
WL2+niUebBkklVn9ru0NxlYjnq29I8tifhvSkihV1n/cXZBQEmwNUYWarLaQ2ZO8sGjzrD3nCegC
XI9hPGGIRPNEyh8kRhzLJuqZHp0DxQ3/B3KaeR750tWjTciMHUojY08H+0gpDIE22CF4diOh5ydf
5jj2kBeOFW22YLt3pJTQY7BkDyOT6bWREXS+eucobhyrdKCpVUAjDg6SNuRk7RT2kEcZ52rl7q0v
a5/zMhj7as7hKXIQPVO4vV6XaAPLN3PhDPj2SdFDW1vw5JFFonYIIObU4P6S2G15IfCNhKBGTGjC
bijbKdyRLysegP58563Enrx10dPsvuX3sj76OyNmw0xmPzWv9NTP7yHkI5VDRCmuErnpZLQfKyGs
dnxv8FTZ29nfuD20cM3mnEAxHoc95klE5CVDHqNGlBbC3fdnk9WFwNe0xu3GD/WH9MZkIRRb2U5A
rbnqYoD8Wg5b2A9BURe1w3bWn3qjfmtpdQGx7hr9JPeT+yPKBRbjdTzXGUaGo5zK+42tyv5tu+5V
Il9V4WbEkF0iLEtOB6kPSC1ku2TUVXs5zM5e1wdro4qqjTB/09BM5NTVdMh5Modoiq1iy5XDXWWd
EC82JUVTjhO0VYQhiQ37W/YZxMY+JTzThpet0Ck3DJp+VzpQFNGoV43futAeweFR0hHmL6/VDnHS
ZeKkDk/VRrGWW83F7EFiwlUSk/ClrwT1paQCeKkjPpuDg4eEq2AhlEhIeO5HwJ6qRMk9qdYNlrup
QbtM6JwhOCcQw9GPgve7RreYkYNL+jEIMGDONIj+cDADAIBoG7AypZqwogwCKSFRlhMpcrdg8BDt
T91LaKzWrXRC5y1cR0jBXJkdCePh+Tdia0czDboY2yb24daO0Db7Y0uzLruu/ftrCSJJZzEWj1T/
adIas7T2sn++oZUdBMb+sEmppX9zfR2DFS3qp2d1sVP6XV0XIKRdaJHKn4e06SaoL9qssYPd0LzC
rH7975AL+EIPX4MP/ZHrhO51B7RlrVWSZwFPAOG+sESfiSr5aPW1dP1+Kr1n1bwW/nteXDStcbhI
iUqJWeoUEdO+rGU1AbmhcQUNqlUfdKRINdEc/86AXUGuEKysluYvcxt4VJjvUo2jf3aivths8opV
D50NR9gcxs7pFWtiNSX68uOw1a5IfsRgE0TftZlM63uIXKRjNxlUq8PBtFRuREhXiayXZuc6CK/d
jWOwM66VaPdx3LfvHVOpmDbq2sgYFZk4zHRiVmdOiBcTfL8a10EkL5b4ACsnnV7tCbJ6fjW/EbO3
Bh/UZhSYwTeekKzD0MtUSQMBPsp+7pJlA1In/SSTGR2QuE8G2CU70o77eGNhFCAdiGkjH5M0GSAb
pNcfpPKGp6H/RhfQ9/Rc+v4njODN4Y6n3ckzBkV1HHlmCqbQYV4RBfDkHc9BzU/bRJJxqFb5vfd/
/AhVtDK1T0lMNTPbQ31paQjypcwMi1NDTuu751Ka+okJskDMCyJS8O/JHEV1vQjV9NEHe1pJR6S7
mEgHxXJLqf0JhubO4ijOTcQJZTzciecUT9Tmvz7CDw28LT05qNuYXhQyBSOfxmpy+onTh0NYLC3R
l0HtpJrzsG1McnMgnZpDqmkmXGv/qW0P49sR/X06yPW35QMz1ZP7aKwZ5XMyz9Ymyhz77/UUV9F2
tXpgrwK4n9b6J8DtPJNXgg3kHacu3v2K7CudJEgcyptytXV7APMVm2GOSrBLXiKyMgp6QN4AjaiW
vgXpSgdgh3WREMtJRmbun/UIdMGN0IN2w/VEeTr/Sd2eBy95bEWIOUm1oXbZOJhtuANnKhy6PTr2
u8mBAFV8GDp+7XEEYlb+HRbprioK4iLVTlcmV3Nn5VR7LDZVCGn+g2pFA+qN/z8NSl34+Pw+o3+C
pMfi55KWRBuSE4E79ISpZIHeKCmQCKx/M6tXyfabj5GntBXPX3rsKOHRdzb9ZdUeKU3z70pgCEpY
yDlA0bX2W9Q0FRiIuPJIao0TBWi38omzGHGpArol/cI8dzum6ukcr3xatVW3MBc26YlzS6IeARRX
TIvjhQCeoSF7fYv8T6/nqLTvLUCfwjTGJj1bI+diz5/AWBajk/NSz23IrcpWSn0M/+6/0TWL0Xmv
kR2NbmhwdB2hkaJz9Ffwol7J1jjB/I2zy/nbm01XTbCp+DH1U3t0JKVXSljmZOkQ39rV3e4rIyCx
p68MnHQAz7i1XXEnhQVdSa1zXwdjXHIanl3dfjlG2HEpS0GQvbmvxgGzDP0WlEctBVtdFuMUPH5S
eoHzZlIc1qH+02rGdxJoxcF1Ugvu2eqd77qZEGaelRXzCIWtrmgwWU9nutnqpEP6taYs1ukOIF4j
fZGmXkyvULrlks4f21hvmcA26Wf2aaTzJL6yLOomHOJGQNLfmQxIbqWYDl4EmhFAtt4bX9v/zM3j
vxFIr6tXKWTnXvjni1R+fifC/qfpohKC44/f39yk9LIIW/4xVR6JFH3UpDg35nOp8FOL4OXqTv4S
AGbiNjbFfTuGO+ZwTSnu5aVr7OcmWk7uiMBRCpTWY8h1dPLOJ+7m8z0fUtwYqKH9tDpTintxv3kC
VyeWFNW30yPS4VJd6BAeMiv8emUyZERd3MOJOwQhkBQSRCacr/nJt8mW6r4kYpN7GHegp9fVqAFn
mJWMQw+NoLJntHCqUjPchRfiqZ5YXLy0w3c0Uta5LLJZPbZM1Hn41zpv/n/vfrmt1nn7iYuUoHPA
uZ+1zmEVQfADAOPLDFHnzMnwG6RB/rg44/C2e2H2Ke3yrHNrQnYZgolOLtrjsIMiZ/2KAGWy/mv2
g1WGMDQprnA3f2br4Dh/cDa7Ff8RtYHJ6TydHfhKDwZr2KrxXgh9IFePf/svkCQeWoKK4PbNBHRO
BktaeA6hsxbfLyrhIFWG0NNkyC7SDv/UkU3d3APhX3AwEfqX9WzVn7pB6LCvbTPGpA7wvwxwNWnR
Cv9XaxPmxKVPYG6Bn4kB5plJrB8qzOF98LnDwjzvlAYMmPN0VgfuQShpsXV0ZPiTrevjzTvfaw5R
RXiumuwHHXmXaUJpHT991zNoR8jmuJ8/2RpRqqF8i2bAHPMK2Rp3E0eK98FyB2wsIPsulZ2i7KKq
fbMj7mJ5OdBu4UfcIAtDie317mUj7rJ4f/sWvLKkto9OcmlsklfEnT+COgfzq8gi7m5xGbbpT1HN
K1HVxw0jbvl1kJLIMs+w9TrwEini7nJYVyoP1EfcHXWIzLSKMvpF9YhbfVU/+FWGHToTczg+Xuh5
jpdL0biNFTqWyDsTQ+Z2hAmypPD6/mZiHb+q4KXB8qaZY2Lbf2WvzMRsvwePjQInJq+mwjzyBXXG
xlRrhO9DktgsxgRpthzp3JOzwBfCcSomlVSYWXN1x91zQoEm133dJvphD++SqZAYJtBNSW5SwnjV
Q133J/e3twMIy0gdmgWMiEZreb2maL+w3JSw0xz+3WpNN8mDLYnRHu8REGNJcBHL5/2NnMk6i4Zy
MYlR9UUybrwcy+YgXqe1EBVZ7AQzqomr2a8iHjrfwKWYABfnd//rTLAYeARqC29j7NE1i46ATLD/
pDp2qgJauY/B91UqTT3XE9aDugNf8mo3EqkpKGyU7RjRz6w0E8xJtKytZxEi5QRzG/P+Mk0bTDA/
CqkwEp0Vot/UeDb79fBUtCoqgpZX1InT5R2CHa2J/CU2O6dV8yXie5417YHynSgB5nS4ZRZ2AKHM
6TvKhIHpxoEsXR0DASZX2WW6croQi1GYnth+Pd07GeJk6vzE/oBh1DUgH7u/ZHAQvXmk83kT6ICs
VoK+D2lU+i1k8uhdhbk90a1NIuVkxm/57d0lVY7eiRKiOEblx5PRcyt6F6wz2YGOXzyA6C2Uk+Oj
A1UXF5je2WE5MADzCUSoe2spTLB0+HEy2H4ZHWCPynKahkztE+707MWFig8m0Gf8dQf3fV35UFSm
Tx44FIGys6MolqQnbzC15YFhXGOCmYZJrma0DpjA0Ol4V1/7GbdkhZHp9/jkSeg5yLqnMLJSF0j8
R+JkyvTaO34FNhxf11EX13rLPf1jdxpEIxBbSQks8k45WJTPX3i/FecmSeiTdsXHo7oEJOiQJmgj
KzPFW3Qu4beFzZiBW6QCHndqsS9PYNTCag4dyg+6kjOiEzjuK6SjOqKNyLV1xTfiuVhSfYnaX5za
6ZAcjGrYCmHqS5stLjHjxxZ1b1GUL7axPeapp5rW3A4ont1kDyZfX1fPRjZc2dozZ1LFzXVRxg6M
8nZbK8n5PL6cHnK4nXE6KA7XenjFFd/E5UVE0063N8AmoOjMjR42tFwv6naBxWW76xKbudZ3TRyr
TbgqfK1+/RPSCnm86CZ+sWvdJVWe/B3RwrNjkhFf3OM7RrKig3a5Re3CiOWKaMDqRj87L1UOWhya
s+jdwWbOdaBYNMy8nzeuFoiKa+WjqvE/nXU4AJOmGo8GJa5zv3Vy+HL5bR2vYoWVqZfFuO75zMzJ
by6haIdCLsUAOXThQbMUjrakpj1Mij4EEAGrLSWpcKr2bozcYkhbkgxQ6qqwcCGvfU6AlmrzX96w
OUJjKS3jreh2qrGOjB4MUGGHY5DaoAOYnhT7R3sA0bdFGnIMDSOrgEp8JHZRqsZG06zVbU3j61mP
retJ3CII4UWxXjyBr/B12So9dXissd81aHvqiYEHUONg5+P0nqPuolMnv1wO9XmIkkzrM49jbXkP
Mn7Txje35UIyARB3hBpcdilBZsIib7r/Bj9Yi2sqmtycUn+It+luK25UlJUCvAOilVsDjYNoL3si
xeQLE2I25KF4UWLT2XAugMZFURcVjqCuod+VFpkMO9p+S1RltBnU5a7yIpSIoRDwJgfMFXXdsN7M
Vjf9P1AXTytl5/hdJuWBuqbiy1gufYi6Wl9BrAGoax3TOJc1Vt4fnGuWmQbURatUp1YTy7VGceXf
SrnNo04vUHRy9BqGy7iCeyIRKMYdSTz5VQPOZPMgNtj5G4qpLwsUai5YsfJJZLkK/SGK5cXIfUMc
J1XTlfkbIbQiKLWez+EqpjufCzcadNPNqQ81AHsXfSN21mOvFUOQ+WQVM7SFV+mKPySL8+F8jyZe
1BclVXy6LibJbiGPMYndLsqsxHFGQf8BrCN4UwTNsGziBC3xQS/JUl4N8TYqYMsWb+r/mNdieN9u
gt/6z6vu+vTf9x+IVYIOejiXiIiD3fWQE2/qD+Orw7KetKYT0CHYxtf2VU+14csCU0B2nNjpWt4V
Qff8un+/RoItaF6xutU6t4Koqf/WXvAuw58IQwSD5BIj8YVkMNgmu8tJg7bd7dXWCR4kPy29Kf3T
BMmIW4vRPEi2KGAx5kIRwNog6YLQSsAlIGuU70/Tw7X2p5WPFWliDjmdlRGQ+dlR4W+CGW8vzbOp
NkWpS3bw1BXA9qFolLo8OVZUUmZoXvAk5pjqWwdJZqemzpX+p1lf6kI43hn59tklMi91nTMgNYYF
70fq2XGJRW2pixRWqJHh8Zn+KnVt4B0MsMYpT12Glnhrl7p6pK6r84kfmxytAtE3xt+toS4F1xKp
za6xQuJA7hCUwQEeuM4jfPWXkRkJ1KAvkEHYBstBbT4ePHy5AifzUWCmteF76eVtCzoPkeBRV8Bm
x247296ppCjkADBgJ54NXjMcLrShxYr1uaT0tYinv1XWM196ac54sSGaxviWQTyOeezauJBuenUj
RHbFwm7mKXnjxT11DnuXijeigHRWBgdMsqEyk788B6/lWkxNIT46/jaYJP9h09SIghIlxHM7sf6J
l1jG8Qe4KfqNNj75vzrn1hZn4P+WlFUbs10hsrLHwXjNpntp0kNyFGMvB9CvRYCFraneMitX4XqR
s8A6UTvueFyYaI+SnvT3xKSUUpzdS2pa6MpfJr5EaxQr9oVbWl8V+31qi2zZpN2KjkOVEBrOKjwD
bmbx/dyKAIcx6vH6lAX4IBfGuFWm98db9Mpk6D3FRbD0PEtCSxaD3MP7sMy5z9B0v6q5hQ3n66O4
p1ARVOFldCSKNsQpQUCmNZobgfBp4ZHzSXMPL6cL7NpbNl7ztcQX9zAwHyywdNROqJJzvniu1eJR
QxAMmoulX9FHzqcAnseAqBgqzYJn9CWuB+iA8wzMAed2QBxzdu//gieC4/S3Dww2acj0WiVrVXyV
9eciVgNmLLlJ6NhZILni6G84QiglnCcMinFb+Ssmi46IAtKA6yH/6Pl0JmXQMDGHO6hfACAK/4X1
FwyWMQEEr+0ZC00m/aWV4l5x/IVSeVaBI5P1HpUeYh0WHaR5BQ9K5bdlTrEmvbshdQcXCP5V5MUk
F1xMyDJVodJ6HBxyCxWZ4UF14sxVundYBjyo1tH67OgK270ooiVtrZ7ze1Mb/3Dzt2jJUupbU91I
q5V0Inntm8nXcuTKUjlGJHvtZdGi7S/7G2gwwrJsD0by+2w8vefjBeFFkrYcok7PWPduYpAp01s+
DAX7goDBwgo/bQ27HE/ncsqaZF7BkeP9mkIX/7NDuelUHv9ikpka369G4gqxKmZU0Svu4q6obRqg
w7y0jfE/x3xfr7n/jG5qJlpQYC1Elxyn+ocHexsHs9BS6pVbxTO3iFFmSCTDin9yYjLlvdinzK2/
WL8eRblFj7m1UoUuOhjl1l39wDScMOVWsvGZW5W/dkryRbMAXLdy6zirjDt+Zm7BLLbKrV2ObG6X
mQGIgSq3bu2MK8Xc+g6whSvnavUwW0I+3VRu8Vmy1prBsAzRVrl1RXas0sUoTLmFuOuZNEnm1p1X
K1dj5VZ5PTjP/yC3cqs/RwcgcBNz6365SlgEJcwtbdB2qsqtmE7P7WB/HnPYNl+G6a1QdEipiWQR
qEqPX+ntyX+pQvShDY759KRTTLN8wBHalWjGr3y+E+HUql8zTQexTiKuUaQtCFx4P8uf9BA2CURX
U1S4C9gA2DSfeXzz5jQr7tmxhSaVDpdtpAXChtdN2YITaieOZf5BIFpz9wKB4CbQ64LMG1v4tq0B
aBLofPjiBe/wljhVAj2VQ1Tw0AKNusxBu/Et/5USaDZkLO/Svsx3kfFJlrZzyiuhVETsFzz1BNpX
5ol5miVBjhgkvUJWDB/2bOE+sLHHKdKk3fjAYGufNqz9xL6JyVtw7kTSiBonnzBGw5lrgfWSaQvj
hGnjUvNGFLUlDqVCW9KQnCmJyufkQRxdNsD7JnEptEbr98INs1RZr4NII1zZ4DJdRi++eG1hC7c9
TAgwictGRhTOJaISA03qFVoP8Xa5YSqOheYRD4aVrCXEe/M3FMec1OHrIKOzoGb5ADNZ4UKx80RO
q/KIUVC8gs0YuTKq2NUUlvGP+C+4N8VkR2IY8aBrk1cMH8vYiPmoGD9IhEldgeko79cUqGx68JpC
Zas58a7YJ/J45blnESZ2kTo54pFcSNmDyumSPw4ARQ3exZXCJL0SgYKPFPpab4Sm1QOM+OH/DlQl
fxB1MEfQWuM9V+vry5zCJqTs2Yzpc7hs4NmwR6wU3tPUAPeJ8zI7tTnQUpGAFPTSI5kO9NstIN4b
HfFzVYj6Qtx3hwNdHR5IcaDFwjrT9QwDbAeCtsrmxuGZT25Sr64CpBRQMZ1s0eSWIrA3gecgLPwa
T5YnCY0XEDFpGYIWv2W0JHPaXmg+gHS8wqNjYXH6mWoxB9po2XWpOWGqDjT59NeTMwfDgQ5S0KUm
QtD5LcE869kA52kc083aeTX2E2BNcEqEkGuiyTM8ZLQe0pQ533K9++kEWS+DmwK1RlY0yJ8nr1UE
dFZ5j/D9NxP47xP03hxKKh8o6DUBjblLoswr2Xt4I1tiiOGKZazk3GB8mDbePGnWY2X4QI5pZfmk
yXu57QB/lDTZbTkyZNe5OSJakTTjBL4LZC0vWkEmhzmxOz0ih/gjldO4z05YcAagnZDDPUTN0LKg
avrg/BynBmd/QMbjs3SLvMIz1Yzo0pZ+713zxsJn7L+CKCq7DtnXWN+WKDljQvKpU2ncaUP+PluO
a+i9AhqvrKLAwP43n6O/u9w5CEGjSBIPbBmN2mFtyQX0SZf+PZf1kUtuASLHPpWqUqvvAIIa7oe8
Ytyd16GKOLO0q+coUA04jSpyAjy3FpaIsz+vNEpKIRAp9p+fLEjJMtGsv355SvqzXboYpZNEHA/D
Dci//Cg6UQfumdkpNYn3l5vok4sTMEZgtU2FqdRKy/eUQDY7vv6SdEgMXnpx/H6R2UVcomFr2E7y
ZgdoTAcRpARZ4794jQAlBLZGmSfSrtaWMk1dilYOq1srWA7tq3a/vsXxsBW+y/AMMZPL1R2db6Cy
ucbnambBBgkvLrTjJSL70u8BpS+6hQU/wYeWvYxxdzzB9OoxROgZCP5WOTyBX7BaUlvLaQ//26su
1BmQRSZZdRbQkwdXTDUAWnPW1qxEfDUV0Mz/9jZEvtDZCfT1z25adSYljhOikquEmlDWVfneGZrg
YIeWCgIXuLsgE9+SwJ8/X3QqoM7O12J9HLwR9/g1MoLQLNRZ3GWU/NpDwOJ60Ulk1BkOx3srDisC
dfbeSc43DBKhZ3CbQAb8vtqq88ERGD0puCGK7/GgiSulGlPAfmqaWKhzGh8WmRkZ+qK5iTqn05uU
H5wGvgwqH/QtovebpAFBjt+fJdOGdzpAQREklXttuDSbFU4WbQlSA2j66hXr+vtIMJaFfv2Mb+Lf
mITj3QQ+JN7nTJGDtHJpAAPv35tGnPj40hvhU9deQpTk2b56GtKaGqi3SAp+cHjNtg2GJys5jrq9
WMnGakit1xlL9cv7YKtpu8Wyv2Xl2qcyNE88zFWlJfoAnhRlFalpR/8C/ye2GegfD/xdegqNpdxP
AwnNk3TkrbQBKzSfsonM1KfRox2nhx+m4IxQHlMv1q4/M4qH5uq822dBWLmJumJsuJoNzRmUi0FK
Iupnliud8ZEIH2UjRphaPmiaDoZm45s8lBWM8DBqUIE1ya/sxbg61exLQ/zM/IdmvQAxbxUQh7Uz
n3kxuWgTmmOlUNJhXfuZUYRmY0AFGsDp4VhC87g+s8r1DQ/NMwFYGUEnNO/7zNndFJrDun9mH5GN
Ds1+1OszH711aK4KEMNnRofg2j0080QhMcsj0IRmfp9ZthFnU2g2ZrmWs8/subLQoXkvZF0T08+M
luTQ7Npc2fjMduoKza1g1dVVdN5nRmRGdllo1lzmICm0s+8hE3xmkb370Bz3mYsEX2ieIobgESC+
zfOU85YeS8gxzy8AU9995j6jPnRovmY5/T+z0PAvhGbHapgfQ9QZGOXrhuYrIy2fth/KiPczo4Nb
oXngZtKdt5PCmC2OITS/Aivv0ZFowxsaLzcdnqsDZr4/MxvwJVaMIEbxZGqpmFUdHYScFEIjwzBk
lYP5Wwt7hfJgzhTxUBKvAwTolv/y3kGNmPweG3yv3KU87+M98pnVh7jRLhZVjGIM+M+V2uW80bd0
7yonrZ+agGRlZPPLqh6cKOA466wtxMMydhBbDAdFjOLempLVzBc0zUTK/n9z74oicGW1NRNqVWtm
kHAkIqMJHrx9WKhP28HeMeH7Bf5jlWKy0Ml416VEfVJcgSVcWZ0yJCVxlcE4km4CIwjXNkC+I33Z
0Y2OuLthw4dU5Y//oxhsulTDNi4nbByI0b5UxzGzyM6mHa+Qv6fmz7w6Necs1HvvJ297ZXaFokzV
FnBYJRsFqBrnCsVFqPwtr6HQm+3yK+aIhsEWJJe/zC/E+1xuOBrFjG4REuLTD0GKoT9ocEQN0IQL
rVVmmrsiIi1pjg+Nyd0Rj8GFppS5iUf0L14Wh/v4hxWZWRq8AM+zGuAuaRb9I7EwWKCRJEyYUhtV
1XwweO/XNOtyRLkYSbPU/mea+w6eEvS6pWnGfIyrlv6uAkQOyjQfMtEZsQn/qw4ZPmhhqw+rGQnc
Jxwkr/XYJsVz8qtqyQBUKNFMu9kD5rCAKDvdhJEUZNwSsQ6twBOHY6ZqJP+glPqWB+lUu2XOVG83
mQyhfMLqtxVEkVk4Ukm5f27uf+bqb7mRXEbhtYvOYKud9rZfhHpyXBklAf5A3Ag4uXnjwIYVJp6t
Y8fzZboCzNUzm0W8zSXjZUOsePCktOwvxWgXZvebIuuxrjNdyzf7PTv3skBSwC7hLrW1upG+x0AT
LhbR4OjAWtKTO0NkTsUS9kaybx7W6gvR6PQwNE6ODl94b+DRqf+3wllROqumSFW1HNK0N2figFfC
5TMMNUtTbtCfeCcGMKW5ERz3KnWyEkY2HqHqZniVFjipB5ip9xn7o3bbBGglIR9O7Ob1Y2A0zcL3
xkgzIoeGGZ5+loMkLDIfy2xD5L+CNDgSMB2ysggNJ/FbOldqRIZUDLHizZWB4Szzypj5TEB+V3OH
KD6kFEtWhgwLc2BGreI6PSKqk608XnpjLLs9y0MBm/ebYdCV8eJwgu+lJCGF8r9Fl5bgbc2vIwW3
Maja5MgujM8wGGLbstNZm8Gkh9mTqM0LpMnYZzNg4E5F8cgL7OofevDb1mj2UrgrEfpAGc/vC2JJ
5GuwcRSCs/X6SfU0BfW485houPndunlkeoj6UcJsFTGHcMVul/4SHsT/2lzSs0ZdfnO47Abv0lDz
CbaY/S4Knmm32H22ozjYNVfrUkafFBtVmj+XFlmXA0Xg6x5xxlM9jGNYZRVQUzSWdLyJOJtWap4R
q2Jocwax1L4i+DQvZ3wa625VURwbj0ghG60lJkMUCDtbxbt4VANNEIFxkG1CPvirma8nR6AzYTUu
PnuLZ74R9cTaf8a+p13CRfMyOXqDVRep4WEVGxREEQDsS7qKiFX/9wkuuOv9IcNR4AqqrjwG2qEN
QOeCXXUafydG187ImI122n8FBv5K9E5nSX6xgleQO+jUzRnD/RO9rCQlYt1GSD2GPWT9NNhXChbu
aawEobysXDWNsNCSkO126fVHCJZom+3dZcYH2cSg+7qmboiKXH0U3z383FgxsDUjRRyrCLWPMDnT
P6l+S9WAY6qvhM2/9Sub3xtDTm0bCirfcfxiMca/FWGY9u6Cuiy6TFspY/EthhQ6szupGDP3o9I4
nCdtggpgFVZKa+gpNl+HD1kXT/ntwArRMHM2hZhXjOdeggb2TzW7KZtPKMtRyVYG33Fm7HUzKIp2
QXTx5jBDMiK0tFt65QSjXT9KYOd0qUTPHkhgbX79vLDzn8hI0LTaYnSkwVvHxvXk0f2W4pAOG5Xw
h5krEtM88dMm7XUiYZn9Bmb1fcKqSw2cuscfd38VG/z83uPtPcU1z3cwJJhyT4p3+JlszwwN5iJK
NZnWDJYWiRdAmVjIrbpWFHLb90+YHZ+9+IjyLvuBOSI4EhpxS7SqUAKQG2sDEEt8+YD3bYs1HmgV
17ugk0kXOlV2C3msSAbB7Xa4udz62EyylkbeSSnU2cV+y7Td5eJws8sltmRa/tlfRxL/NozEDW46
OG9W3khsgTLdi5m2zGMRnFu9ttn269M13mVBCEHEYeCULX8xj5leUew8KDEwp4tPKVEC+15sqeAk
6GSq0yK8gsw+Kj1fyd7/yA6KHTijeY2gxCwED0tq8/5s1d1UEpzUcULjX5A1Z7dUf4h3hRuW8Kns
HvTXnjcrkmujLzlqFU7qqW8WFDdPVDGAdOkW7VxBYvzP2MuA0ulqo0Y1TPa+sCR8LaQq3UO3mlqs
Kmh2N99hb68J35YA2kVG5oROVDFl2Xlv/11k9Dtg1+HX7grHJg9gUZ6EtWWiOEmhiD6iwCkGl8XC
qsc2c1PEJA52MZnNCng7FQWAi3i6fDF0pCBQcUMn4l2a1Ebk6hyBHDMweEYmayo1c9xdcBRLYRll
N1eI78IXNhPiilVDh2cfHkkTsXEAtQhfsjJM1yzXcoEs6ksI35ZGKoDC+CREPKu/lvPEDzNlFjNA
rQJ0hcYID6V3HsKKsXqIFcSICm7CLaTYMtWnngXg+LgJdAsSh9l7notaR1JGqCEJ48NWmgtqCIt+
weU8fYoGBMj1fWu0FjpISdWqwNypZf6C1gf+MDfbtgrpyBnAPg9H2Fd3YgQyCkkfJ2hfnz0Meiri
wAk+XPDZ5AKCXLvY0x5D7BF0JvHZJxY7/0TTvybl5Jc+dNklC43GYwGaMiDswFB3/lM9FB6qgXJM
Cptqkqnw5CQrr9rsfzV9Qxi/riuSCjNHnYuJwV0RWM1ZQSUJEkWLb74xEC5CFg3CKITxM2oFS4E6
7mIxlFMQisk0DgQn00YZujcrr9A40YV2XhiEOa3si6REFiFYzQ7mjwk+vBY8avjofnkoI5/nM7kY
lU0a+cPO9QcIO0GWvLD9qtZlvWur/A3YY53XOMO954r8hhxPwzveXlcGeBz8j1avmNPSCMqHk1zy
9hCarcZrEPRZHrObMOfc3bW//bMgpNxH7L7BnoxcYo13NgLM14ln2rktqHqj30Uizbjl2QQ0wKtY
5+x94Ab7oYGjSVvDvGAq832qL1iByirD7B91A5wqNzZj5/01LGXRCrWPM12gSypCzGnIzYKa9MlX
MjswzpBTnQ2sbvVvHsTrKTMdGifMAa2HWd0hno4+uNa/Vi4Fxpa7tBHFoZwav3CUydtyp8ShNYYO
JPVSgbNfZlZvzLHEsrJDytX+rBJO+SrwllhkxsievAr2PyIRlgl6Y3MLw57gnQj+PH4tpUbOcSRN
jZ0QMVOGPNG0m49Ti0z5sivy6IeBGTKfTSn2umlRRC2E7/yUqpy3z7c+AUzKp47TfaB96/dCWrGb
fGW25DpzX6Cq8rrstP3neCHnP/jAKr2MwROQ6jRvw8F0BpvkFiVZD6KlKggB88XBBuD4d+M/34Ds
pGgynAnxXzict5h60ue1zrzIA6MW/ZXtQAnWdaMq412HoxVYcGVbMzBHc4lLiTX4cnZVAGIQ92c0
DOFwl3H8Qf5jCkdsCB0eGAhj+actR5eXmSlakMHNyEu7guKk2wsuCC8He/s5eIH2qqyIXDOQ1yjo
i/IaShhy50UZK1VDp1pXA7WdW1gjGUIOmn9T2e4ivjlDkCCT+3ZXlgKCbpbQwbARi4EhF3qp08Eb
htiqVUtA7P2Z9jHbF/LDZh3VlxtEqq1uKNhBkIE5LIBYeOM7HBSUQC7jiPVH3LwJz5AwPhdm1VUf
zxt1roxHyODQoB+4TLc/wEaXZctxW1fgGJ0mzFRoRLm6SAJCtHLjxlsoBgovzjxjo35ZZQsNXjZU
pBtpKMVleGcCF/YGUjULi5tfIbEgr/IvLyx72U/aQFiRVtOsljMObDyCumSCccygmXS6l2NEww19
H1hV5glcUeSWMbfEmA41tHR4+26zDDO/lWUr5ME0kmoTKgkuXAZMPcdLqBisJ/eEnF7nzLU9SYZ/
umkTVEJkI73MpmG51tsU1gQjuOnLUJA9PF0fP6gRkHwNcwsddLymCe3rEpXo30fxv8Z70vHTRSWM
smV2wPNPo0BqwpiqARsVVFx7wtyh4ZTFPcF4WFsmbcdCFYhguAppXLzG8+BLBR/WRO9nzsYiNyMs
u/oSD69PZufUO37BmD5evZhC6ImWx3gJJDCSJW1fSYAIagq6bHnMHzd40ECzPDLK9anc0FcmxeRB
GKH/TBr1rO4hHEaukOKxw5pDofKvE8IzcjPjTblmLcUFOnbEX4OWzMnmPtxnRGAukyIx70cXtjmV
aILrWbg+K/uuhKm0KajWT1aOPWlPDk8lwpkILhR5NROUZctpWPy6d6eP6cF3M7l7Ezh+Dpbf/E45
igTl+N8Ddnja7HZtfS8VsQaoLcgOaUd6JMNhxsFODrBgbegETjKyD/wRHOsGsrv5i2i8Iv7/A2/T
PmJ503wXBZQlhWKlWh3DoHsTh5tHXUzQ9l7i+d//LlLobmZq92zFx8UPP7qeRa+0jWfzoUQBFKjl
bkUsnwNdnBUREsyHlZf/2/rElRLETC+bWHR/fGOCMtVd3MirIiS4UKCJZ/E6or6IywbcKOfSMVQE
Nn7TII3xAE1povKZT7x7xhAmwtf2cBvVyM1B32iVquXrLLaIFOxjZDuzXKN4xqwbwvZuz3+nJgc3
fZJhJsPY98XoOpv1mpWybxWgq35ziAxnDVvrOFkGlA8Zy4ZusHrEwbNGUGxY2Eo88R+LH8VEKFh0
mtORLmCNI2j2tqfGMY6vPD5+3ATzXTp5Uy3aA13rGkZvmayDi6WS8buKlVwshTZEyPDefydMMOL2
27fhbd6eyewSC1x4Fjr5kdmwi2BOw4OzvoGL/n45Ey9Hw7jKnn7yYVRNT1Ee9YkYNO6914FZgQQ0
x2QJ+B04Ha4j1vh7iIXbkQ5ZX+axw5P8UV0v7jcs43K1NQ8YhtzM7p/EN3zE7L8TIEr+E7hSyPjg
4b8+cUhI8TTouT3ZxMyyGmPHoxqVGKMIkK5BOOD1Cc9sMMQFLHUA9/ZTxjw9OzH9WdgTbfY2MXID
p4R7hbGbG1wIipk9t0IIDbTL+n2RwrCY5MWKBQGxhpDWOHhZb5PK6CVKCchJFh2qAPUyaBZ+qyvn
/Mz2YTDP8BtlpmtP9YqVhzMQNCuwmfQaWro3IrEmwUKotzwLM5QKrrf0rU58+sEm9pgq8nhlxjzA
9Pjk+8q1fhDPKf9aWakGNbPrw2PzNp97foFTBXGzYs7FPZGAS2O/TXozkmyXSbqhDJcYnaCaOZuV
QeDxgxS5rx23hTHo8sgHZykxSWmIxocK39S0dfor1elUBH0RkSUFQZP2DBrHkQXIpoXVSIhv4fu/
MbGzUg6T2dCZL5i/gK88KEjee9x9Wy1MyDATCVdys6RJwyQMAaASlQ7kOQHR2wE9WHx/SqoUGdSi
CzhxNt9ZQVBEdeJvcvg/op89HLg3swn15/jSGTSPbLpjaIvV+D2D28NDRrxv4AntVV0Reb5OgFaH
Gx3exmHDDuL5An1NZEjEZAfvEQgIJobCrPgQ6KUBHMe6w6AxWLSLZIDbrwJ5kavAKfciAky2U62Z
AOH8YXPyKjnCWf1Ey1Mjy+WqzZY5HLEgMFHAyP8P5cQURQ2RWSKAzmuwciX7rONQ2N0wSsHtUjHR
ZgP33Q57HutTY+39rlYDWwAi5frtjQnL9Z9rqYoEj5TcYLQKUn2DzrwnFQoWBUw/BCu1mAgrnHax
W1NCdHvXzK388mESKTbzfgUALsxYMQ2G6WzS8ArmCAghEah/bsBSWoTl5ltaD1Uropw4P/jxgLRL
hfzNt1kIhdmB4cuADCaYeRYNBxhgw/5s5bRpd9odF8NVgP+sX3612QLsalGVCyWYo7uguTIkAxTk
MLTxYRDQtEMMWZXoEthYD8ZKYzoaL84BRjK07j1K6/NYbG7HfLVVJTusKCIG6UDkAm13C2XAl1gD
mGI5N4OHWzUm0B9S9AdcbeeMhoqgvMyMxFEVlnCSTEMuVaIHR6+uK6dL5sBiQRgOUlwrHTd/Te1O
7uBc0L6zRBOh0HtPv99M4ms/6SZ12vnJolWXj5Tlv4jA2KhKtAVzq58GN2O1ylRyL8M0sX2aodu6
F1+Ccb8ft3jaqZVeimevm9Yb35uVFl8AXroBSgjvarGz8Hh34eOL7ZQRxnIm7TEWL51kbJAszBgE
pIzmxLNstev+b1FZzISFeTr/KTQd1ZP75Dj1ScMg29skKYrjWPYlhHe5DIickrZnzayj6H66uu9z
2Vrqkpgn++9DJJ6oWRHJ5pUrkErzBWpf5v3g5ktxG+UJRnzCofGKwzoZnXlgp0F1CbpYjYZKd0cu
fEESQvWKunvLU93EvQ8M23nLWuslMOxObbELXp2eljXMh/HCniF5s+BWyvUfmnargVDaueuD8b6i
Zc9kGx18vmqam70KIVq3f5oHjzD4DXbmwSmHGj5VrK+2iSh4cFPOpAJideJRbJvQnaGXtafTDF4a
0etKEPTdHJN4/A9YOx2ReMBMzEQ7b5BEIqCQUrXw53CgQ+hRAXEOFihv7mVGTpy6b8a2VzDy6J4L
vxQBZKJ5ecohRAwqTyJ8AJTQuJOVaQ79yUV9+0mZEV0pR6ZsP6bWORJ837030G1P7ENvHSNCfyQ2
zQz40sDOdvl6hYksB12J8YSAo6Swv8iU7NtJ2o8OVlGTqWsJmEkEjfpiE9B8UHFTv2cPMlGHnzwz
Mk5jSELUlCSAEjYT9oc0iotFDXCGN22CdU19ZnGuk0s0RbmtH58dC8N/L1wmSkNf1tj94so0oij3
Z6V9/2knvY+gnv4M6thBL+U1A5AlDRvUN/EsFht3XNHquB7+5Ej6QsnDuKTvgzNb2n39V6wuSB7a
ZoBFW/WnzKOOWDcg9k1MsjcZjun17pl1uVmZk20EbS5LKUB8kcaeQ398/B5E3EmyaRHzHi/IHp75
/zz49hx5FzHu22MaQsLwUXNZ1qTTZX7aYqI7AZdNi3P1CQNrbcT3Rq2USeQLzkWRJYtmgHNe4sJP
RyiD7sUWcWgAm/SyAIUn9xTF20FVs3TnRQdxAetkmBmoGFt/w+Jw9YSaiD1sThq4XBk83ZXqOydn
FhXbWK3305H0/4b/3niG285YYJCu+SsUComC4dFFg3PSEspUM2Eqh9ArqvhQGB7P0ZV1YiGwICQA
2ExdwhsB0kpF0VZJteWyY9GNiFcYW8kt6oAoOxgU9jAeZgIXdgoC7wWZgMe3DlSXACpoFN+AOTkv
vo/VL17w6JMpMU1WLMoJ2jf5z5RjmOjb5qV/T3Kkxh3tzjywo62nGgygfAbX4jzpLjUiTGdd8RRP
qrO9rcOdMPtbH0tYBCgWa6a+kDQWViOytQsjEBKep0iocL9w70dHVsizRt58OoR7cPNVpCNZ5Chs
Thx4oEmHzLBz23/S23hhzcQS+eW5JJIwWzWE/WIGP/xYmeQFoM4eBdYlO00o7Fd6HaK//Qq17Uo4
RfcF2WSssjLIoncB3kQqepI6vGuMxfSxeK/fHYYV3FO8KtK6UX2oVAykqEuUK3SJipuby9vsPHNQ
lZ7ga+RVeppC5IZOsxFSF7jujYOhByr3AnDtrYSsG4Bk+EuKoA5BhelyEyQRD2eI4tp16RYk6JDq
nycsXZIsworSq9BeBCjm9r56UU5PVoNTQ3x5RgcJSDcu/Tpmrq7KGlniUmrTj6zZG4cyG3nSUr+6
GYJWOHJkkTsNGIGDU4cY1w+em6lgtrjFEQ9VxwB9MIGGJqYUnH3WRZsERUSm1C35GCqnskCTRyWc
QGiAUNdCVt5Z6ruiXOdn6OtWhzuU5jhS8jppCwluQ7FMNYNjKLsbCmO5zTab6TmpRscl6jmnKBPE
THKTHN9cX1O5EeFKJj0GMqZ2yH5oROsDPCXq7GP4hMUrIk8tQWcpxehsaK/OIkwpOvDMzdrDmdVv
IqxzUOCe/HowwBrWnswqtg/EfXjsk/7qZQrz8oL5btSCebvdbsiup/cLGQeBm6UUnSSbHId0UEk2
SSw/TG2CF5NBY5cb+8vqHaWIFQmXbAty6Nigr+EcETUQ7ofhvU3wsUltkzxgvH0mFLe4rYbIaQx5
qJn32XAxAtw0Qd6WLU0DQOninxZFdifA4LER4itDc4FvBk99o8WotrkpH7YWGwh7Qo2U+EDVBQqC
gKazc9Qlk0WxegB3XJLr4Ns/O54lhRHGQIUD3S+fQQC/K/cMQ962NxYg29UK8cV1Xbu/kiZowkfk
iddMgGmgSfo12/QwBq2R0YPC2arTVn1hXhLXkSQtfLuz1qBy7alYbulLsM1ui4u6uCMkks6KUiJF
K3Wb5GLMRkTr0xQqYiijL2ahm9zhng4MYAYx0E2ADpZCyhlUdrQnafj6OKB7WKkvkMlMgya+kQhq
hd2DOKbcT5oOCPKU/GmUBcqCln830ux+DHkWMSEuaEp4w63WJTKgd3twUqkZ0TU02TuPJJi3Z9Vz
kQ6aVcAzXYORVeDBL/OPlS5qw2S/bbq7uIz9ZaNBDw64nJgNuMtC6gqy8cUQiylDYDjqV7mUcJcM
fRReONRu8Er1zSlaZ+p5DRTMWRomAkoOgpEhJHWgERIrNBCmtsWVKB9o9/gF9LoGzB4452RAr7fq
IQDCcV/vM4MIdpt74MPAitjgsif3YrOaUfO4OQwW21uYhNtQckmrVIIcoDFAzzG8QuKZdhcg2aWF
xxhH6pPwDFPuaFjzqOQxFRjec8UCv/UCOSIAGOi52fXszYX7cbZUVDrLsucBvR4cn7RZkPojF1FS
8hXFJONau6h1oNhnrB9q7We8ueHvC5RcWeXpCjI1iAgFnv5HydkY0HiYesCY4MMslFQoWCbTQHpg
jrwEDyCfRUyBrjl4heJv6UkC9EEcXU4ADhQniw4vqPRqBSzNZb0Ei7w0lMBlpBYTOBYOV9ysiLXA
j2jwXaOJk0VZ5Zi/YsHMXrlNpUXLhNw4Vll/eWrntNbfoigvxg19m/+nNiYv+LMoumQKgGc0Uymh
9PpD6iUMIN5/F/BPWBsKqjh5EiJMSVGYZMjdRvvwAohoDAQXkM8HbGeZhus+GBQOWqUIxkxzKO9A
vjel1TRVBD6FGpf8RYlAbTR336mBtQuRitXAkh9HXhU8WdaqHhnkG1sYe42ETfdVbI+kQ5qqzISc
G/JvTKT3RFRDfEuQxCm3XBg9hMQ5wPNPYWzUjgKV3KACZL3AgASdbm/BPnn1wSpeoL/wIKJvKO7+
2q5BjeQQYez2ExKGBxtRc6VWkzC98gulgqMW+IfilmSWKgTsD4BdPSpwFUWLmr7vClBJGCamTboi
DR+Rof8dJmgQHaSHkXxWG5jVctZH1Szs239Dfnx9GwD79BdTA0onef4sM8nri/x6sG8j5SUR4ggk
j8hC4hduh+V8ERYbg3cTQX02cEwjxFBY6hdVokaNY1Lf+Hu7Ui/5o7iAvDhr0IN6t1+7VMDuAEaD
8pcuTqDrseXyPp0qYU78w7XvAJEhkgIaY4Ci00g4X7m2tG8fTob+cy47BOhp78sDn6tsjJF13q90
sSPPwZJCrRNURzPVFRZR8GCMUDF0uzH0V+zQ8GlKKu3/oQfhFp/R0BHTTUuIPmcb5InSGO4RyP7Q
ly68p5uPLqOy4J5BojfbRwtmiaZBk/yepeDEi7dUzzBl5pXeYsM7fU+MuAigqD6MWmUqa5kf/owY
YVU8mOvBUr+awrWE0Z/eeJLri4oYfWLX0cdQoFkzZus4WxZ30txHdx9ac7sqN5WBfdteDr91t6Pe
zjeUUtzATt4QuX1Xtx5WfcXtlJsvvq3XKstQXPY9nPXv3x4rXGO5r0i66snodPQhus8varb5KRWJ
hVrTIPqfd427caiEe/AABXL5KCuU7fp/IfYdFK3L6YB0fhvUT6j0YhTR4z9UNBq3J30gpLOGMJ2R
apUeqhrwyalVStfSk/ZqcrBKdJxut9Jrq5SGJErPFkQF27BgLYNxTQMpOAjZ//4LBcuFDH4nzgIG
TZcxkbs3BB6GruYRbQ+bnTr+h95/Zpd5hVPt0uyX8bbymluTKZblh08beu/MljL1YbWeTZobi/TQ
Hr2+fF5PPyuIwRrGEdliqB87mHAHyqG/gdZ33gN1mk1Bol0M7FZSo7HjM5P0/a6H9HO1KyWL/Z/N
3bcx7h3i0qMU7ygFrhsMifwnioDQAUsL9AfFJou2E9Plf4G7NaRINAqXJska5w53NwzZNXDFkBNm
21oYGo4cmBCGYqL9T5QQtt2w693XBB933XcGAVkCOFx2fwADMDR9PqMzA8Dg8Dfql/xpMV29WObt
5eXG32YW7SpNVM0gD57fTTpiZbyWZRX8aT71GcrCswLuAOdazzjUdmu03TRo6zDr+sADzkHsRAKC
FsXOnyoos5DU0IVf9yfko+iRZgLUktjoAZCDxdxeXA29RhcW4kEgBK+DfQaZpcDLDIwuEDbAQQOs
gH5hYp9vbwHo8eTyonWe6abnHC0XF8YZ+m0425BlizeUr/mUn6ov94d4jB+Qk59IVLA28LNUbbDB
ZhLLVGDfBVhiz2vhWrMwbBT/AL0lR/xJAJwezFIwyn3WSVYmwfjOE4BAl69Jg+kzXPlGygat2nta
0hvrUvnNPT2tzDx6LxXoSxrbxkm+OWre8MaADcd8BP4GOojYHZVAmAaLU/SSFiC5z0yZjd67We0E
Qw0eJC66PwS7Kv+oq4bDCj5qYHMzWp41aVEBP1tGJPGVC/oijIHAINfeFvjj3aaiRdGCr6V3o9P2
79nMnn1XQUPy2+J/0HWyFPencEMq++pExjWT4I9BCzKSb5IniOhK3ucQz4xztS5k9SpQ7IJIzQ6O
MKV/UhxO1qoGLMIKwFSmgpM9L2gAVuX5O1m6eyAmzvXH9036SzN7SkuOtaWHOc60Op+32doi7u3J
pv5UmoaUlbacJMc1E/9U0NNOytyoKMYdaUsG4JXmMpO5kCK67wD2LzguwbWR6Vd+ISX2aQbwfjHL
Prb+pvcdU3o4EDDyRyaffwmoLv+sEHpntRl6ASwxAhqJ6U0moCQQcR5g3oXWEvWT6AMDQ5e6IjZX
cJv8lNRGCVt0wevwKhgvxyrZBPME4QScvnwU1Aoayy8mll8QDdZB2Sst5BOx8lHQUVH9rSeol6K0
vIa8gpLt0KsY1vajWbcfBUWiieVi7Sz4NJ49tgf06KSwBRX/G78YG95BgNgExkB6z97vsJFZMocS
YL9Kg6k+4MTydJNF9Rv1UXBrFqr3uePwq8yRpY+CH2Qpu4xPBv/IFpTEMkh4ygX6ISQe2x8DbHrX
NI1DX7F4yqa3l3KWb5NN78ytQDaDSA7Ecs2C6iffmbrKs2IqU2wU06CY3glNTO9bN7J8SqX3rE9O
tJtUI9UGqXRHhVGlxlRI78wRTgJf7xwOCunNmkA4rUMqYc0zY1I+7kS5IQKDgIIg56Ogj20s2o9e
zTuv3uHXmFdvGDNIcdPt8WiqGgH0o0WKcITk1fuUvnrfuo7DJ9hBinMNq/ctIC71jwR6dERoYG1x
wuqdOVO4sqYGq7eJY1A9KSVWtNRAvxKdZM6m6LhGN9F+FEx4ChfYfhQEawfXbj8KnlKtrUVvD2J6
o69XyGmb2Xs6ScA+hhp6iyFpcqNJKJk7y7fkLGWfaaxlKlSn4qVsxRpr7XNkzfKl7BtMaYCk2+OR
BqJGEdFYOCUUy+MTMSlMcAS0oZTWMMNoT1dsT0ppddnUDQI6gQ7IuOoSZuY8/vVKrjInNhIrrkDd
EmVhFXIaCL5UiGxSMTqibWy5RuGucbScu2S7JYmW67TQigpaYnyxoWjyOixHDFy29mBrHh4hCOSj
ZQcxnxQSnO+VphuvkJemkF8YqRucOdANeebAvIYcYTs/2Q55DOLBDiHP3E5NLq3ouDhxdDPXG14r
IHMQWU6InYCBWGor3wM2jOKx6249kRGymMIR0xRUnh5RN1gCojpG9qko8+qmb+TsOEhxkqag8mCj
0AOO6FkH+r4RhgctjN4WzGrBbzFM6WfvR3sjc4SzOwVy5eJxeBSDUfCgDBNA1zy0jSIMoCMdNICW
WTgLknD9MKwIpCXBgOEZixjf2LN1FXNl6BJx/E2RAigAFpa8muhOJyXRYUCWsAsQ9xpqQ1fZIMVp
4Pqqr0XB7xmDAtFpoRUvHwXF8cXy8PJREPPKMEbT67A8c6Xb49GCwjjGT8HqzVJjqLX1lCi4K63S
FHKJbCDwiKniUvdWVG/aZJDiJbDybWfIJ1/lo6CGHUK+IhIrKTW5RoqOazPKtwV7QhQUja5rJ9Kv
d81TuPa4GaT4ofx60xoqgnv86w2XqHaTvAqLgmJoYO2a54OUQeo7SfQeoLgPm049tccn4qIyK7XV
KZySiqcER2YOlcA/MUZwpCVQSqCWRAS5rZlEYbbXotb+iLIxhlMnzR398LHZCuS1cmbRI9jH+th7
vWkHZVhxwyFR4Q6wK1CWyqooeAO6cGQucyEi1MjcutIyFzopia5RaSXIAFjYXcQV2JHiCuwiNRbs
RgGwsLMhDARU19xAnGfudT3elH1MVjwSkIH0OFWAdNropdFe56P6k4aVqYjEmrnMdYaDTnSTzIFc
1insvECArys0XJYjNBfViwov6CccgkWFjNCLzlyEHK04IZ+1VMIpvamItLioHY1C+z1zmbt9RhsI
y1zmzLjw2xLoMSIiZhaqmXNhC+rRebWR9lMDgbPiqYKlbIF1ZblNRrCothvNojmEQlcvcZpJbQtE
yLUlc7RSQ/WBASlsMNBiaeY6mvbTVHRZ9GFlqD72qpAegzeLmqYKkP4IcF2bdV+Z5aOhMlbFqlgl
rq71YDAUWoMUX8ewdnDtz888HVCnAzKsX5k7ji6jqNAqPnccXUZR8TKKCq1CyCgcnzJAIScB36R9
8Rh12Hr74m0b7hAyWjOHihedIq7ALnPpq8MvRcrRNFa/uMB3QyQqkFNS6EUPJAYYd83XpLfkR/Uu
5/zpJisNMHPe2EFbiwjPCs48hrx62Ya9GCqf7x/sY1m2cMYoDhvLis1YZ1kWUCJJSWRh6IaAUy9z
oYxBDICFoZMSOSmJzuTVxCkjS3SZe8WjQQ+gZhTp1De0UVcDvJCyCSr0omGWScXXCkr1AzRIbH83
teVgoZL5YlWsgk9rpwKBwFPiq5FqJI/H4/F4Zp6ZB9QgEuvAyw687AbUQLV7zQpUu9cR1e4VPKVa
GyS1R9ggnmOSuVpc6p/tBUM8B8aDHv1kcNh6+/KdeyS0MNTDUMQU8bQwhEYSoYgpEoYQELEbIAjN
Cjq8iowZjzqGbOBm+jLXmgMs0fq1Jpx5zHjZVbxQEykNyS5Y8xQvRuvG5D9rkuKFYrU0Rsy7KIkG
1gTFC9L2s2dNiRlnjoMBRUw1Atn6zeNitl6ianLwoEL2d+wgPoUIpKYtSfZ+hUqfpiz0fUq2STKI
M1fqkARSl3l1BmiYSAYR20wBghX9WNeV+Fgl26ZTkRERQUKlgyPJ3PG54JDfTvjFJ5p/0eIDpkht
swCJf3QsSJ+xhzfljE4/ZXgz2T9JHoqAH2fOGzA8sjKDtpIHjRAjUq7oa82zdog7jNbQmo6QYONc
xLBjkBaeL7T8Y8YwGIFhuG1DjUEmL2bslcBw1zG0+LUmCBaZVITFvpDupWDSL1bnnxSmgf/JTsnD
vIsSLIoXMO2BPWsebD/7z4A1vYopQII98QIVddYcRykSfJrRDIqFoobhM49exvF88I5LbPBUwjNy
FFURfDPq7ZJenqM1M6GS2MAJZDKBRs8UXrIiFqAD01xAVKq0SdIIw5lDiECl/UkPlzMIGHBTRpk4
oQgCaz8rbKYfMJI5xMMdIB6OxGdbRqOnnoXPdmmnludRDLhLtqGoOnHDVCOxaK3seHa5QJxYHELC
PU3bsyEoDmnZqFYDDIGcBJhwHhHYpnNIJK7JV0q7cqu5mw2ObLF1sxGjkFYTK5lLcQxdJZBtSwAj
FkbLKuSat0GZGXD3QzR6G7LCcrcqGsVfmrG4tUEDmIwkkKXLxJ15Rm3q7k1D1P7AqS2ETq+XdNuy
jC6F0bGDOHPfqVSSvAsvK5gB3+h0LcxC5jCuAK1DGE2SQUwZCBDZndA4SpE6AhlrMYx4DjEgItvA
hhcFYaSm8bKowKyBgl/oIwftAuScKNxQCzh5i4P50BuuCNFgKgxnbm2tbGvvZCqZoncDKRjvnYAN
TVuxE3nfoXvjDlkRDJ0a97FSoPQBJ1LZGhDsTQuDuJOuj8ebPkAmr7PsS6MJnwimimSoTjp4SVrd
Eklbnlk1jE9EH9XbHgFUX7k8YimtnFm0ZwHrpAWvpuIHiM3gLyXkTT0F69QpMwzn6eq5Ng2GwtR4
dVm050Za8cwRFP7diIAVpIsOB+hBEBL0kTKk7NcxuWiKQwNJ5BWXwDRa6g0EuJwQU1otWDJjyToL
LhRowWmOpmCXZcmhAczGjIZtqTHUmgMsjxJZFDyoCANogw0UQIfi6tqlLj30w9cKaO1D641vaydq
KdoG4RkKn89AdGss2CVMSKHDiCuwg8RQsPMcdKIbL4yvM9hYt+zFQdxJM4TXTycy1ZhmrnBiL/pD
4Kz4ywAFUCNt5U09Jy9TQ9v7dOZK1Jg5k9ZoUBQZVnLJrEhORAQhByJeUUlHMokIYusWJMPg331y
7TA2sqgwhRdymDRQPdyYWXTukCa9JopgqmWnLT9YMlfKHEgFDdZPZAE6IN7iihaZexmMMeUhxcNz
eiGT1mYmi84TlOoXpg7LJ59YS1v+vSllgHj3QvaRZA7k2JmznJREtzDErddlboMRwOwEA6ZIMR1R
IJW82tbWMNKL1lsB47YYHHIB2snlBF6uOKfl/p65gdJF3eDMSRgdV6rRF9TD944AO7KlbpBv5112
Ir3imUwAFb+BOoGG6muhT8jTl+PrlogB0mk2tquxTZh10StRZE6ciRqWuL/iChRHBa/TJMxEV3OR
Xpe5G9pVdl5Y3pSm8Vg+8QE0OrsmjFNKjEh/WBSLn3oS3dNyZA6myWID2k8xBA/GNQeZRaUIqGBq
5iQkyus3ycyiYVUpltYyJLHvVu2niLd0wHDmvMwdbH0tzbLPnOlCfR0mYSa6xSADdqWDpyDI1IFt
de2byaLsuLHqqoDJAekzGApTuxi50ZkTtbxM5XAuIe9grje1hjxzXeYYtgSDwUFtXDVzhdcSQBW9
N4s6CSU2el8yEi+A+pDPuktsP87Dm2punJ9GXg6bHvGQZ471p8xlLgCMFut0j36rkkAeARaVuaJF
uDIjZ9r2yGMDoZRWuPutmrnbOGhthAvl2lvE0qjooGTR3/Yo0km3KNKJ1mjUyucRSwk6IZBmDjJh
Z2ItdQpkjjQQ6GrmVvlpYaKKvY3b1q6ck4uuXGAV72q6lhIYLI06YPiQf2gnSQwXjsxxXuY+79Kw
vGkGHg0+hHkCqJVwFfsLQcjUdGRbW/aJF30bWEOOamIUUeNAQI0DPqC/vM3y9tYmb58I2EAqVK39
B2x6eywRSeYknwlC1pAHJcmMpY+C48ti8Lz0UdD7hFBwlz4Kbsxn8GKp4NmL1PSdYSRvBDop+gEx
vQsxCKpPvMjUUV20RaLVSfRRkBxYRProPx8FZ5/PR8GvpELB/dGfNQPpn1XpgPSR400hvVGfD+d8
FNwvSxQJULht1JcpKrGtwmv7j4abyRxD0n79kbPrWr0q4iUKPDSL1PF1zkfBxJj4egE+tb6TWWU+
CpoTx4rrzEdB3kis+MMSooqysMp8FBTBlyrzUdBrHC3nl4+CXpJoBW4S1vgKLyqvwyIuHwURtQfL
XytNJyofBV0wBTuEfLdaIq7/QEA1vt60iccP4tfbdvooKHo2HfpoKPCiIOr0+VTUzMksptqGcEoq
JuiAQDg98lg/FtQjc7fDo1S7cfR1D8RA9Qlq9qbwhpR9d6hZ9PtvFjVW8O92RedP9el2ENVaZJaP
hrS4PkCfxIB/MgfG4k4H5BGtgBaiFYBoBcg2LMqhlWTuOLqMokKjDNTOgs+c1/fhEwg19O6Z66Pv
WjxjHhsOUD5qciJfl3qgxIo/Cp3Bt674xWNPiauynl60BSGuOMNkCvkhkWupoVBpgKV7g91gItD3
xYq29RFTjyGteSKBWhKFQ7Zqp9jNz7mCz82y5J8cSfMczV5yq4A0y22SvCz5yMX/uYCT/p/fnyRJ
muK5v+A8T396/83SNM+S9/83WZYmF839BZw0T3Mczb89/36fvdzm51ss9+dWgnJv/8nOT3KTIv9c
cJqbk7w8y5Ps3+ykycfy9OQW9+cCzs3yn+RpjuYolvuf2yw7+X3ZuSma+3MDArMcOSdNvvc/O+f9
NMeS/N6Lm38X567n53t7kvyj/2LpvYD03fym/9v8pniOXGCS4y9JcyU4T7J33k9+epH/kyz9/n1z
8fy9HP0397lF/7mA9OdIfi+e3gv82L95nmbZTbLse5vnKZrec67APP83z7Ik/SmOnwv+9mNZlpsc
z3EVlOc/S8/7WPby9Nvcf//R8y5+sgtIf5rm36Pn/xQ/yQW8j+W5+yfHUfRkF5j9n/2Tpj/PUhxJ
LuAmP8eTm7/kp8jJzq0EZu/kybnoyXL383uSJDu5zc77330UOckF5y87ycddjnwUOdkFI1mSJP/m
N7k5jqPZzf9/L7/Yyc65AnPk5O///J/84vm54P/+S7I0R252cSQ7twpO03Pff9+jWfJzj/yPZPn/
uU/z/H1/cty9ND1p8lL83nvTPM/ePXmWpvi95wYEnBy/ycu+vy9Fc+QCXpKlH8lOkn2LpPecRTBy
KZDJ3RAuAdcVcAOCzTGCzcWqYORuXEsVbA4AIMHmVMHmTpmzweppCz5+EigEkEnHtYB0S0AVIA7g
EdhgVZ8yF1EFm5O5spYq4nEqV7AF+wu24P8uMDsXnCWHWwFIuSxZJGeOJYucOOfQADIFD2aTimfO
JVt+cdkvIWHBlBUH7ncRiJ0F33iFCdAN+YhlinBPgYoh0KNnzvDtydeSD8KwJyquX+9EGzZ+5nQY
i1vbO6VaqchnfxXBES2MzPIN2sD6NG/vzRiDaK13aEh5W5clkmbTm5EmVJEDiaNRVwMBiKTEWTHq
gMfgfemjAVIQgx8tDJ1uKE1u2RsMa/dBqcYA1gLVRaAPpbmdeuZcgVMXRR8FxVR06v/zUfC/+in1
RbppdIP0jgAEs4L0iQjSP40W0sNPjHX7OmoU6jair3usUqlexg5KwosOyIgJmm/NETkK8uugJKT5
SLTCtCu++0quxMxHC4nMR8EEp6nATDapeL98l2z5vmwFjeWTi2hiLwOX5RfYpJDgmduuxivkFd4J
eajKa8gz56Um1z604lI/pP8wfn0gHAYdh0/A4lKvpfQOWe2pJ2IgWQvjfQYfKcJtIGlyNkzOKhSK
l7Ih3SwxQEmIKyyuru0GwMLOZJ3CrtQnojs9hWu3HwVpNRUK2mSqONaZYFhLhYJdpfIcOhKBIBrc
Qh1suslCsRAohoXgVqimCoFOoRAptApZ2j0QGgdwaPaiYOyF1VZHARG3XtVrOr00UuZmodfkeTuB
FgsCkUKlEOmWiqKJJVo/mUXl4REChgiVvlEkehv2apsQBbcWUVKEg22IggmUFOH9EqLgRzhsxtbQ
3jIYRkRkIMR942BkIJcx6z9o7TZANzr4UvEBw4tySaINCIEe/RMn7LeyH4KsPVhe2S6qKx8FRd4J
K2AD7QSTNKgUeIRBjjM0MwAACADABAMTCAAYHBKLCGRCwVQW/hQAAzksIEpCRC4qHCgiGg8FQmFI
KBALROFgIEVRGEqiHElZyR0AAAALNu5u4vitF6pp7cMZM71SIFf9T2v76Dkq68kgB79OzkxfBIe1
Lm/XLtSs9Iq4L/wPrN8PtOxu7w3sh33i1Aesgb9W4OnptnU7cXAm5gK5XuG0nX4p8T/C2AMy+e9p
Qf3vO9wU0JLaI9YxeaCPXy4xa3d9cqAUGmrXbzHtUQUGfWW9vnZa19f9MrDpgZYFl73CrtCrhW+1
GwEl47qrbT1bJTJI+GIV1DrCa7mm6u7wimwE/r7ovf5QrTvVPK77Mf+mMyihTxUaGVLbG8QC2Cc6
qaQvONEgOR1EDnX+i/oDGfSSu94K2DhB7qe6IRVQUk3U9a6qhISKjOel9vBIwSnut80xU9RyvDOC
puKQEPlraWJarvImiryoTt8ecUHmKNqhjFVe4xRc7Ur0CJ5E7f12I5hjb2qAMQ5ZubIzsR205lsk
ihi5bOVx31tV1t67SdEKd4G0MlV8SZunPSMj0IstTB7EgXwdsK53KcnSTOlaorPdHsa5kv6FLTrQ
hT5B9XQ8fSIvvBc0Ae1UOfRioOVzT+DXzM+AMk9DDO+czGrdmYFez5a6oJyy2EWxjxs6Dq/kZwJ1
1eWuOSI6btxMuPQlz+K+iKZnVvYHOlx04yrjGMAgbAOFYlgxWTQ1DdYMK/dxOEnTcxnzV80kPJLK
qmLs4wv+l7AR0C0WTZ7jeXC8JBDIGIE6xUtidFjegtjccgeLFtgywLDrLHSis4giBK3+2HEJDscU
9tuSvYpoIbooZxQg4MEWqVDGBWUxOKOyRJubn3Qzu7tcuDVQyIR8JG+7ukf3LIoSYR4hGeQSrACW
i4QlL010QwdV/KLhsMTVYiFQgUEncqztGHC8oiKw7XFrBBbwAyoaXdAh1RZOrAQ9wUiko6P9wQNN
DwokORfUELtCe7S2D5sW/ukcxLIM5Kf8htNI+inC+MedtXY2xsaNu8AZvLclDCq+Uwo4Gk2DPI/d
9jZHKfuKo+ARQvtE5bGHFejyktrKLvCo8uZy6616PVyduPdC+pB5LzDroThgmXkvlK184QNGHJbp
FiQ+pV2nYMDMg8I8Z0T+8ETFdokEmrJQk68jABCJfXboTM0kGL7+10mi32ec0gBojgOhC3QGqp2R
ZkgkYnW4PbH1+cM2dUPKQRwTqESAEGrZYIWwHJIocOE837dm2tX0Ryz3/2fh+UU+25wjeYURN0WY
1sq5hHjimKcnQovlXBm0wAnkBkP8gvDchBWZqx5Lwvu83gmumgCirXD7VFVATaZTFc6046hGxsal
ek4Ia7YqF2j8qIF4TavMGUhqOuhg4tEPa3GIVfM0bGwup/TwN6VXqT5Y40fG6tP1iy1AJ/uAwVoV
gfCMryG3zYuoqCZVrYGe7BakUqrlUELFqmNgNaeLN6Umu+YISjfJdovg6wugohdVFgTAycyTHc2T
4ggAesn49Ur9I6HGj9CBC1WiMgt0LCVUWoONprhf6VQkVJCKTqcvUON6UpfILoPQApnONkwJdX7U
89yn6AqfCci0cLxKwRD6N26iICgONXDJt2OHcTYcMh81C+wpzFSwIRAdjUQoiL8WUb+U7I4cgcuA
3xE4Af0IYZcT9FZfvfGI/rqf5vmDWVkm8GPgqWxMG11187L9OeO9M9fOh2yWNy6M98JXw/IYGvah
cro3cH2rvyqF2CeNg6MUupTXbggXFZGsyen2d2ticXaf8VrToLI2HmmTmagMkxcp/UYuDSpflJ+g
Elvpo+JdwXwFbNTLjxDfJqbvDoJqLM2UJqa+HnlotkBUpQtTbOjLcem1ypIpryYSrv/ldN9aVynT
jtovL0KTvLqISqRH6nvMuXBt4PImqm7Lf1oiGz4986u3OJbzGOdPUUKCqWd9CKagYAe4MH58GFBD
5gTSRlLBqwkbdeb5LddUn0s1DP8knxpMJVHFuckfrVCWOfB0bJgTGQwfubX7hJ3E/awYl47Db3JK
gTMRO2jv8uMk0qIYc616I20rjfavo52Yt7m4Ro4f+rqgKU958b1ofKte2A5WcYdNFizl3bilnsnR
Roov01JNfUnBdl8aDWa+r0lvtPhO0c5PUBxroOpFLfUepg5116THsaw1VCebdIG4CWPo2wruZpeE
unvo6X1dLzqPd7qR9Ch4svzXqrSUk9H6x4DY1IcrvB4WyRCF7A1M6iyG9CvbGo7c5SHd/Zp+0RfB
h9tHXHE/S8eKnhRoNT8z2cGgSLMyWtxK7TinSFd6p4OFOl/iUQcLj/geE92oyudXW/Jy3ihgRSZp
egbH21I59jWKU+AWLAKBwWNrlQ+5sVGoiPROZ8bTnZ1vB+oo/Yn8iroQs5EU5EQzsQpT4omlsaCB
uLvPd9KYEryriN9jPGzGUB5rIumAfYnGhu52Cu/8ZMG1ARITh/yrzCP5RgpxClMDp4u7wZNQdSQq
a3A8qKCbinRnh/dOUZNw6pWEle6+GXCDXybOZ4swg0UOQtEugsqc3c6llnB/KbuxjZruDoW1u/2/
p/rniXV9Ojsw9eDDASx3u8uAmrsctMlC+1PM7XRFoO2b1qDHSOvxkt07sj9Euf62J6s2+8ftwr3Q
8R6vVoI0nrbZ2Bp07b0ChRo04tagnuLeIkeeQmojWz3fvKptpOkHstQVpLdHDPu0bSe3H5t6kVoE
eMAhEMnuKF49J7+npLUdHsbR5oI0ea/BPEcNuoXWoB8bflVjOWpQ2/uTtUv/nWXi2byOfYgf/EMT
xRihpTl5SLFxvP2o12b0KxAF9Vqx1QhG+Ww1PxvSAXZD4NhmHOmb9vNboe5k2/lA7m86y+mx6llQ
jYjjhcix5iv3fQa8M+oVKdWTfpuxeumRHeLwKToon2IPNOQRven06kll4M0BDrCO4bityIeCz9V5
tlVDmN/TyTl2oR3p0OQpXb16gGFQY5L5Pv02kqy1wHWCY8aT7eFJq9AFYS+TiHnHchH7I/NXM8Vf
Nrr84LdebjNFHVNuKbKOk5V1vhwbHWG83pdrFx8JZbBfsgVT8crLkiAR/Ojox0AKCfYn+Lf9kYHr
LZKBANKtUTL/t0hlFaG2HZ8+31nE4XA6MEmWCroQiy8RJH/2+5Ogt5E6qFh+T5uQ2Innz4ioOrq+
ZhrOYx/cmjMbmNOo/ZEOs0B1YaQD9fSgDhQyZa+fQuC51xThl2U/8e9pzeOAuX9GOZOPxUal1SlT
hH33F5w1wFZFubpY9/Kim1f8HwoeL4qKvJcEW4CdWmOMHL2bOk9pVU6PPIqPG1ZHRAQisq+77D/f
bxl1/v6m+D1lCDjB43Sh82SKIcyMVTdv/8Oxw1+zY4E7jpBB2n1G2KHZTLRfwI9zFUvw8uYasbJQ
rNwrmlUtlHw5on9PLSASro0/LWqNGFIfxYZV8AxVl61lw99C6hH60mw/H2RfZS9WNy4gFnAtWFZn
dL3bVCyZqr3l0b2g8iQHe/fjT6SkI2Yg5AYvpcqTLj6+nFYgJoG9EnQpIW+Rk378aT/J51H3MllO
hxyE0kGK05MbCCeLLoWvesXUytApfRRetoO8547iOybDER0ZzAXwe9ENHf5wLbcnz+qtmPnTwyl8
rzsHBkr/a3vIqdbCiOzpYtyujnJBwyEVbS8Tj+TiSoHdCdyoXnWZOX9hIn2ZXsYHidOFyVzTTvQ/
U/NGy9qAYEUr6ybnvDXHnx/jr1uv4mysUjLQfJqrNwPN/dOEsV0p/FnZhIoevrNqZM40/EGvzcSa
ms7OIlOOKoRnZbB89e/pB2jVDh5wO9do8SIWpp7WTGCnXYD5qtGh9yht7slPE6lnbyKS1KHj5HaO
hM4EIlDRZZAJY/MPqYltw6IcPZboKPNg82u6UuEfKophwnPgGdNYVRqrFvGvGetcTdjNbqObkM9q
83tKKYZJJzRZQahFFDwYebqnDX06VzvyOMTC9+antEMEd7nzFbjz2XDss4mWMW080VgM0yC+pjab
U/JMTbBQzaZaWxn9nsr76PqrNprMsZ1qxx1NgTJ6UHTQiRTVIHvNBsnN9T2YVOclj7ndiz3b1FiK
yqFb0cc8Y7IoPCZk7+h0w6bhz7UKz89h4THRvTzmnBKnOnCr9rHPV4WZXO+g+s8WHfZtIozd3OTL
LiD49lJXhoZ6kLQVPJSx7NWx3JPLmzrYesvAtnTNpQXQwFB0aYoEZEIzMDRBA1vA76lBFYU10Gcs
zR8j3X9PleliRpRxz7koY8zAEC4aWOajy7Q5w3zkgqUeKgrVBQ2sXBdlpM3AWHgBDcz0BshaMzDe
gwbGirSaOjMwTtHAUEKCrTES2n8s/+HapaaarFaRldAiQJ8Ib6YaxzNJ6nTXUUWQxYCS/uHvREhB
1QbgjNo2oJKpTXUvGqIy7uYvmC7RRybRRPx6U7/nicuY4i2fMeo1uxXCUUz6xuQcnfOimdBPr0xL
MbMbp30LMgFS/2wcXsLet5ra+OtfqzLWu2PwfWnpadvhbFr49RnTs3AAgv0B4yoxyPb7eoc0K2i+
zbpccROfBMt7M1uMo33/rBAjVe6CCDDhzq1IYmKSlLuMBiB+j3OzodWld6pN6NVi++9LI1z3QoIP
fwoEpR9ZgSP6JfkV6Mq0ThG43o97XoHX0twBNvfGpyPYOXMKMemt9cbfGtbh2IRyCjA9EBXtASxB
rZwcg0zpCZsjhGbwFCASqfSzYM6X+0sjOAsKNrW/oIXYK7ugz8BH5EAgvAp7tOy44JFP7AbE9y9D
k6izYB6aUJ6Ydspwza7slF3JyM/lTJUFUiQl6La2TpqpoW2BpQl5bbxQBHr+UEZMnSLnF1IX9ss0
QJKVlZEdnCN1lFdE4X+Bu6ZoV3+e5kcQbXMlNyZpwGwFYie2QonKabGBvVXZCjEYop4v6Pci6msy
YnSNpvDT9K2LqXSU/kpl7viixqKx1YV7mp/Khx2Sd805VkXl7JXhJJ3mR4KwmAHHJUKed7KblZPi
Er2efOy0pKs4I5j6V+L0/SayjzpcD+Jfid3a0+VHusQMz82bJsuczHp9JYyPnjLtfyUOtsw1k97q
r8R35eWk6RpL95rOPU69uyBhoIyopelDci38FPOszkeii3MD/deL/+bepjPfJbH6ynbSvZFkPCU8
MexikqzM6cNuCjtlSsjfx4m7NM1zVRN983glYdOriRDolPGjr2tI8zWZ78c4CxgcdHRspF81JEsZ
k4IeVsgUSulEFky6KMe8sZ1VG/2ydaFDpRgErUJn6vyULI+xzvW9xU82wrMI9/PAWF66URawmdAv
btmFVNHaRVTbODHpClfrPMxQnfw4o2wIb6Nf04MkAmOLafngiczm2m43Xn9G4k4pSUS8K85s5FrM
Yn2yLQvtEDDiX0bewc/BGIM92NR+po5WsCqkeYiZ3u6WQ1nancNJd+ECTvI8cGnZU/MXuaUMvpYV
/iQ/Zy48bdFbjC7RJIwpQY/PKu7LqILRRblnJ8+NeWhhpqtGl8d0hf3xEL5uhxhdwgC5Rbq/FTC6
BMjBjUMZmCgByzDvQ/ogoUZoLqvXrDSXTc7sIeprXXSaLqbq0i/NxSYiA/d+D45AcyEXXZQPxVoD
zcUt85aE9ZblhTQXX00DpLFmypu+oRQA2RpmWEjzYjggIK4aXKckaGaUlzTeHyXNxSZjwGDxDra9
eJuhybOIuz4y5Bvl2Yhbc3zHt1esvNdG3La3j3CbrrOrRtxmlkOQCIc/4hYInjzqu/L+9cRgLDUN
G09f1vVFkw0ySgc+kHbETe9eCWftUHFG3PdjO6G7i4JfiNTaoR4PsJ2JOT9QlLo+yDUPbOVGrXye
EY+JofhMTPFjCXAj8chcs2Ck84u7/Y0lkT2jc64KXuRl7kPuEmW5H1YzopiYED5vuD/PmdgvBeSh
3/gLMSLLTAwJUZg6CONEurbZZ8CM/g7PBD9NXZu9QQMnxW5G+BKnkMFn81GX5ZG0LqBieenZvgbG
If9YjUDuxBbzf8aULab2yqJE39IF3CT35TzvRvVOCcvd47aj1wjcySXeCesISlhGACDOcVU6Ps7j
CyXs6Ep+Wrvli+X0CgpCc+XxBpKEct5Ubu1FwVnsNlqYXSws5F4QDUtALE015qpi1KJeJhhq1wyu
GcagryQvw1OaDJEWH2Cfjc7NAX1B9G9T1pusqK/5eIFz8zf1ahfCtCL2s/i5uS9epuRuUJ/r3GYo
MXi7jHH4QOTJS2MbwvDc/LHMkUGbu+XJNvuS/GDPzncFuo9lHGWC4Ugkk4RU4G7FcK3Ih6Kmak6C
XP8Zix8OigmOB0W36QgLMqooOnyiYww6RLj0kJEoss1yc4td4Dex314j/6qwgc5ch8Niw5wWtWV3
cQxgctUw0+MpZsVS1J4sG+XmEv6T+UEniHMFgqKfbkp9DoMlICqBdSMGcxMLNpf7ggCshWsTolIQ
wYAmxAXXJoSf+euuMrKYTYiISEPuVfoWoC4W6ZY3SUlSM+mzRG8JoKxexf1S0bu2KpCRRFVUL3or
A7uQDo4FYrBHEUq05ozURq59fT5JZ3M9Wtwn3C++L+UPZVInRW6xBfNoqdWAqHw6xRnutA+N+76+
9w1it5AVzNEgu75ZtntaWnptDcHUi5yatTXDvpSY87YeK/IdUquw1NYSTH5MLIqmSmBmAWtHkMW4
VSwHn7z86UdX8dgJaMkTIJp/i+R9IuMDDDZwXFSL/Qd+ndWmqQsK6t5BHvuyOJsvShLn+iikSn/e
Djp5SwP6Q2j2cqcpCGU03s9baoJY7C5Bb5xjipg4TkU+ml0OTZ30dAQpp+zKW7nA0eG0U0sSJ9cs
PBWPH0LMqMZycqnmf1pP4Mg8foiEVeAx3o7obbmc/GYXTL46Ps7WoTSWblAn1mlsrneg46qAEEKH
vtZArKyvJ9GbTGk+Ejx+aET+C8GBxrg2lLKEjscPvSNnn+gN1wguaoj1e2PnXbFCDgsQaFgdshj2
uOV2TBCiNzwcklRBWyHskl5JVi7TRr9ZAExzftQR4u5qyNLdE+AeFSVaeWDF/t7FE0Nhu4VWvhnr
jL60WC9ABbTy0c2Mwn+SdSvRysud2qD1DKOV88mD8J+blDQr99KkOgBDcjjYDl2GycYxRANaeVvW
GE3GJgS+yI8js3LJ0DX9qgyc/iV2tL2hlde5xxmFxzq6bM9Ee32gj4FX+Cc7+JPFLPu3VFfI1njf
zpzuMNgYQIyYKfuzvwSsxT3GVUZjOQ+ZeXoaj7KCMNPuAna8GHBrBdZxsbMoFy122jUwH2JR3BQN
95O9k95KUZ92l9cyyLj/VGur6ONTZY3F1HpSD1be8Jv5DhBoDFgkdk5JDbFaAe9Z6oMEZmHYwRVW
xBCXYnpobn/4NM865GwsPW6PcZEabMiE9RUY7TywceS4l2KXcF4/SFm+I2lFmUqUW5aIZIQ/CvoM
HY4ot46qASkDaQRSUW7ddV3jqmjG+dev3EK2wNwKDKYfXbnl6ACs+Qmbcouk+NKfFJxxdnGIcqsX
5DyJM9YO/JRqElivaj1W5Za2lxXt0jvJGYp7aSi3dj7JDeYWpw7/xxI8RvhB3t+/USTKLb3MLWtV
q7W0c+w+VVmdcqtYXB8gLhQ+ybzFq9ElW062zX8VbLfwwpCEfcwlf4M49RHac3P2NETD6tO6htOS
knVFIdxvxPHNFsxzAq+aYo4xX0rJzNGgkTprhMGKFBX2MHOoTVo2gRZX97fx+glno608yQnKvoXS
utLq7/D9dKAM5LwzhzSBrjNNwnrlJwa85Kbg/H+MZUC6QLOzQOw6eSdS1QQaajZgFHZd1MyCDr4E
ovWpE7lAl913Ak0ZQfKWhwPTgOAGJ0AOuCRdfSBoOjLTl0BfEWxVVMDJ0Igv0HXLaNWcGNGYMoZR
ZzkSlVhwUeynW2J1Nim44CCxKnQAyCLAOJResAUcGYwoN7PLPbzJVuVOyWDqTGeM8d+CE2YwMzbp
vkNeCqzZcLvUzVzypG2jqfKsMIHxwBxKAoWqSPmMQSmWUUz5SIbE9mEdK8xeH+1QLLLV1VIzBykr
zDaFCGuoZMQKwzvYOsrUPQKlgOteVy/z8WEZHxc+9Y4xi1kMkbEwA7Fi1En5ZNkdC4O1YPtVx4f1
61uXX931wI/+p1lGzYeBH/g3onoN+TAd36dbCS7zZscHUlQvUz4+7Lh36FgG+TClKry6WlOZD7Pa
FRBPGQzqnzCvQMUzXF3CECiorAQTiuEfIbAtBZ2cx1mMm8EfUvSaDHdkO6RbfWvJKkBlE4fEEhEt
PhSLeZgJcnApRVVVWBUJu4X/01SuZTqJgCb4xo9ZXIqHiBIRnsR/wQNZgBim2Vuj/qN141c585AD
zQNIOJqZ4cWZAWxWdnrMgTTzPa6WtE2OtNmBoGVzMeVXz90OdDk3ybZFR2orViHxsvve86RltI+x
rclGqIWZOG1nm1/fgS7rPUJYYNfqB8DlQAsPTY2WM8yaGxC00Y8fQROLN4oc6J2SImuAHkfO0YH2
0FCHeviFAMWIYLpiGT0IOg1974IOtF3mkwjkxofGFrBESIn4Xt6Cx5iAyMJlnPorbzau5pXdJ1tG
1327aBPnND+HrhxwNSfCBAaIwVQT+UCtIPmw+PBLyn2AyCW+SZbj5ZgSSeSDCZYJPNG7RyJRlWSY
CdaSfDjk4ewShOSD/fIDsrqzSpCyU5DWt5N8aGYZjZmxxa5ileSDB7h7VBLqUHeUKfJhLzAWcJIP
QQGk+wyDCHFTplIPw/N8ryWwUCGG9946gjwPxFgpSXqWP8VyKcsKIG28I7liPG+TGs7cQ2cdh15P
n61dWebx/mfAgA+R469nDCTm65FTPrW0RleWJm1uXrkKyeMLXPfwF5ghAo5hwnymA/bPtNN/TvmL
OoNkrU3IgNopP6SmjkoDHYENHmVWNw19Gu8FJy47VpZmeWBJQd7lv2AHAn8C0YcSgNKYTY2cUkVF
5umBj0x4Gp1bzIbe/0DpiBYD5kxNlF5JUdEa99NjEg1gOUKqxtJPo/P3wf7+Mb3Q8TMNe0HkzgjQ
fyqg6bCSY9qy0OEuWUO5OTAN7rGdZhChQUPmGPrl1WkwsWCfQC6kEpE9KfP/jjcwY0SdHweA1hsm
W4bhjl6nypuYsbwePG21nswYPNOJAl0hVtwUiajLqeIYz4wzv/3Mqy1gNFNNaZwY5OV/IPZWoCJK
3Z/56nr5qWlKgSF2mpYj9g1eoDrrsBY3w5U8Y6F8ELXswk0ehlNHLl9Ja5L0y+T7zDWwUeyEZqGR
CT/GnpiG5gIxafD4zGaJumr7JuX5EaSb5ZFGEpqzho5+qJx2mGo2RRSFJtwWf2ajMNVAybLNP/NX
mWoCsFPNhozDUj7zWZRIAaaakwNKDKWakjJTzZUP4s9jtD5zTIZgqhGIVKpJGnVKFJqjWKWaywhh
qnkMkJdSzW4x1ZBc9q5UA1dMNbBl1yf5zFEkwVTTi3yTp1SDfx+mGmKU82GxNj7zotB7VJhq4s21
vs8sl0w1bFgVesv+/sy9ll4Jl5N137TM2+Z62i45TDWPls9czEMfG6p1SA/7mVsA5+z0oXmp9zGk
iPOWDLLgQmh2PF+KfGZ9QSgO+mZopqIV+Z6wgvzM1K0x6VhQp8foW9hgnTm+GDYNYMOvveEDhe2i
QtlcMTQXstTFf0JjPrNUNO4LFTQlppoWNst9wRnDZobGgD/0ec0YzVbOffCJCfG+jkHoMXIucAA/
WYgufzkw6RO8lO2/FslFGDYrvHRPqOpAhOWlTtLUnipKHfy1BWAScWq4uktLKBg22yMIdAfvU2iD
AuFarul/wv3qkeqAflZxXryfTxzs06NQb9VQCim+gmotuXy2+GyDcR6nScjftlgjNSFhgF13TfzP
KIbNm/cJaAlYMlBhYNgsty/1VcK6wouTdTjOH8Cw0T3TdYhP1FcSw+bk0mCvwmPYcBMDVXx6Z6in
P1hnlx0WNg33B4tHGPBt2lBBia9Xt3Cbhc2txTeUDaxGFkax1YRgsYbr5/p8xf5ESmTSgFrdmSQW
NuuBYUO0ZQZlqQTDZqWRk4jYtShGwSt2HCQBElUV2HbBAcMm2O2Inznu1QrAwxxQ57dLaEs6wcew
yS/9S5pZbd7AMWxeSgHK4567u7RNU3q9gmxnM+BbmrqMicTnJFn1vZhGBm6a0yOGTflZkdf2OYvv
JmrTfGdWkVqroS1suEHS8A8ya45BHbyanZepCEBgYJoVEEKhZfdFIc1jGZeEckl/7IY0D9c6mIVh
kzimhkbwCgwbGeI4rfggwXj1a3kMG+EtT4T9OuFBhwVVnQqMMMQfq8rC5vNM9d0UVUhXLJLtVGS+
RobI6z4YNqPU+AviQuhW4mPY1PTjkYgpi4XN/YAB7IRUTOR3RREhg5ftGZ2z8kXBsLmansiXYith
2CTClDBlAQdm5V0AwEvtpTc8ZMrK45zdDlDJ+LrbrXxFe6nF80Tt4IfN2BYwbIJ4KgeNTOhzzHku
bZRmcfbAdC+OObrzoH7ZyjfFO/M3K6/4uaScrOIcOCgMm+pYpm+O/jfvO+V1ypj2hGnmH4bNseHY
C82yM6lZ2GyHGTeGzbViFPpJB8PmF5j9rrwUna83o4ZhU9Q9mCSwEbMg7jJZZU5s3StXjDZiTIWL
pbTs3g5hBOXvBBA0lkup2ntg2IzEPXDzqY/2rYDYGDZ6KLR599IcbgsdcD0E6WFh44gDrRfDho8x
Hs74X4xhQ1BlTPCMhdScA2LYKFVGPNj6LWXIyNFbl8uRv+TrJetAy/qOCXmkakfvhGN7LAmgLssG
NoZUxoGtHqgpA4Hk4VpPuC2fOY1ZNnMciGzigcFO1JxZYjbnv9rctYxzszl/qM3teeyNNaNRAA/H
O9iF/2ZSeLi6dWGCJanW2n5bNosESTifsLzMgkzMgjVdcOauE/9x9vXIIFF2z9hxXXhX1/TMmPr0
42CJ0yYjtvoEUE6bbFqFJIy8R6u9Rhz1rwAT9OTcfF/OX4cY4wPQ7CwHTodHSOjklWWeZRd2uIff
2iuIuIsa13kysEq+1p8n9HkbqOOffkbBiIxNJ+zJAJ0Fi14V9nHGlrnftvBljTABRU9KMpv36Lax
4repnL8ov0Z9qMUz21BxkVzaGOGorP6BtBRh59h42+wQeCyyY4tfd6gfWs8fJi4wQ10FeIjJmtbg
tlvvmQxfXRqFtrDQcl+9SmiMtJFEmrZJDW6zkWn1veFiROij5btJluukcO5h/Mu6V4QKszQ5F98b
nmWP9XbHlv1EXhPeEISZSLyfzlsoH+/nA3SO4b5Wclz3wG3s8Cb0gri7GMZVqmvZkKL5lGRfX5OG
WcHud5aRpWWMf5pxFpYFFx57/JU4JU/nMVipsTexkwSRG7nurURh9B2grBSE7YzcdQ9LNMmjdyGC
YwrM/bwfXsa+bRVOtNFQOuah5Bct6TCws5yFb3VIT7PuukfTP3agFQFHe3EQV0th95Uqrvu7JX+6
Nc/Krvt7dnpYYfeviap77LqnMpgZxcF1H9wAbtj9cHBQr7vuUy4xiC27zXUPypAhy7QR7J6myAW9
xEu1615wJ08nct7BdY/M4cKA3d8kGiyfQjxc92bUYPf3THtCcN2b66oyechJ8CWOirju7yEyqzuI
jmYCjrjuUUt+nqaeryp9JRsXogfjRUBuAAhvT2JxgUvypdRj8V2Pz0gGjuBVtv5C6X+g9PhueWr4
NwGTA2qKap7tCy4bzwySa1OIHh8OaW0f55CPX59rEIoePw/qq1yvYPJcmdLNcD103pogpNq49zjO
wVjBuzS4uLeJ2lmIy1Hhma/9O/DC6xtFUNMbPE3L/6LhUzU4NvnXcbAqRmuszb/VtO9WEG+YU3jH
dvg9BJ2J0u8kWusH+/cYBOOQqOwxfyRYTmxZnUM7U0/1iqHoazfL23Hjcog0cgAwEee1izv/RlrD
0ocsfcbHdnEjkWOlxbKPuWnnx7dNhPDMUfFwpD68WWEeg8M+XhLQLs4sSVZOFxfNq6pqFyfWgwOW
cfuwOUTB7OLefQCW//A5TLu4xcYnngtb0dPNMtx+eKo8RcNwwyrpjsNNe3u5PzwkZ36hEABS8SPT
WgTLcm282Uj7Qo60iPQDbzk87EmdfMxwQ5zwdJSkbMpw41oy+yHC5ZKQfIabWY35UjtCwhT4q+OH
2k+ZhuEugGrSSBCKR+bDBS1GcKiG+Q9Zx/csquE97sLfBkbhRTW5z2ct1/HB4Oe8QA+SYQXNSXB3
6viLmvBWcxauhBeTOKrVCCvYFDPHX3AIJ6owfz7pA46+f0XC2MwBFA7zFGwgJ/sVj0Plhz2f6Tgo
PxOtDAtewTzVbSAcKuE1Ja1yOqTXVf2mNH7LOEMXd7E2Gesqyi/536ZFwjCw1IOdNYCQJ4ODyTC5
4DLx62NUXXTsG30SAypqXvZDNLG2EOaTEKXPv1nthiZ2ZpeOfacFphuNTEhD5gVXZdxhKv1MeF3C
fZwFZcoLJC7uiHwPv4kHdVftRfQcwy5r3ri4RBXs3BsQdi+ca15p40bUrkMQ0NipofflhLi46hhG
7ThlKS6OqZm2w1xCaz+0uDhUGNYDOJ33ELG3uEMoFf+QuDheA2fseIqLMw8uikJtRnKYy1NIq9T5
v9nw6jQnbbDIgW+amZv/G2w2kDgIKQ+w7v/AnjyP8n9L3I/ko5XIarVWE1VhQI3FVCdD+onsMQ9u
xsJiRGbhdX3LjQc3pWd4HLs5Xx6A4yNPPh7LM+0LwDhXhPVPCAnJo882gC0ursWVWzXdiha5g2Pu
yhwppmyVuBrEyn9Qy6g0zfnIiSMCk7MTuny3rJ6ctpzF5Sw8Xy9BTOphuPFq4gmuQDdKgExpIRM5
gG4oTYJCVgh0o0GA6zEr4c1ygUYXA20pn4+gYdlhUB9iceg2tu++kQjHJf0PLPVkQXLKLD4iKyxN
F44o5xf/DoBTrf/0jhBTNX4kYxGO9X4pPAwSXvzK0M7x1is0moby02XjJQJTecgje7r+rXdWvAa7
QuPUiiEd4qvEUSF0xYV/POEbEbhsLj+Et/1IbZvfmpnbVmXJdmcZiuVupEpoxsFbIAS9lLe8jwmF
TCLRyEHOkS4VPm21MCAS1ktmblSzhlnOW/PoX93q8r7lH5PsbUy1nqajS2naUtvTgw2DEbp3gAPJ
rI6RcvQ1gpdvPOHKo6/vKPLvN0f3UtyoYSkqrqPyjXw5RzfPUXn02QCAQ1jcTY/+F9cRfXGYmhLv
EktPxtkWviVCE9UMIgulIj58cQpGRYGG1I3iAxUkmWw+ep8rG57j0cfNEifUKFsFqOBHZBjhyBFZ
p+Adgfbw6KOC50ATLl5jJBsbpGGAJlAlOOjRn7pZMnlFkL0ReW7Q3Fse6bZm/WofkfukuATNJopA
EWntyFuUuQllw8pKdfHUIRIIr9s5anCzWF+KeSg5RSX9mINyVFrD9hzFuqs1ra87D3LSA1ZGLwc1
m5ng8Y59xEaCsaYyq5L6axZY/UbXKpYVJy+pGFxkc25QSMt5Yp62m7UJiyBWbmHC+PdYdTxi8TDF
fVvJCef/JqS0rlM4nO4DCEbGbhRKRpoewwwYNYKBiGNCOH4rS72JrN/0htWb+wHlPQzH54cF+FGi
sATLtXg/XuZuwHi8gyw5dPAUm7zHpXkoba71QHoTn0LXhekNeDGypjb22/QmKg9ggwqpc6H6H66e
AvSjqOyg/4+KyMCW7EN5GWYofJ1tTklntcitHh+yyLfMti1ByRBllMZa3rk1fF/r4/cvlLQuMIwr
/dRPyAjgrcBS8ARQnuSXezkei4926PEQTSBkGl5bE1hmt0G+QgDmoVYXJCpgJxLVBRCkBkKySlTU
4zRsxLcHC1yD0BVAjf+TP3Yu7xvy7exMsKCqZTgk+lnRdui55n8K5PFydEC766xYXOX1G0qFedYM
BFraBbzLGl0eBhw9bVMY7dtIZbh8F1X89vc/avLoPp1XvJrnqu7WN9rHQvjgeWmQQlUW2hLRQEdm
6ix+3rM0aW7J9Yxscz3sfZX3YyodCZXQQxTUUkPNz/91fb+vKl3fTIHMAeK1UV5cfHg++VPXzYEo
twFlRvwz67yv4Obd0j2beGxtF1EBeAzoMqHmqY1lrsBOW/7wFvOOWI2BhZdSypSMifh26kGyKDUy
w3byeivw+1V2CrBgXX3vIUtLn611hFjK1E7Fe2zKIkkoJzyL5MrHlJzClYAOlVnsu6q/7yy65Kjl
7SQZ8q6/ljwoMi8Dk0c+MAC/TeNNRs9e7eWqz8rGgRrkSCzcfYzxKyV7HBJpbcg5BuU4oGB40jdC
WwThR/85cUpR0H6pxvyoHnGgbMlJVnaRiQCPRpC2cbyg7t7KbYICJR1frHtF9fGapSWwco5pZYn5
liOSTGJL423qos2a+ivP+Ran2rYovNEf/quXL0z/aO9l2sOmPMNSHiczaGo7bs5JCkXVbq4HyFb1
+v+NgTYpzdy7UAELfNZxh/oD9jLwJ7Dv82STo0xHbnufFO7OJEPBgSEvEUAvKMDea0t1PIrrIy+B
Sy97eRy7REKlFXUJkEGPpiP+PK3BfRNTsyRSqivNUu4jrVKirLNrAEUQQ2CrwapDBFHwde/6f591
SqHFrJCv4JzpgUJS7Ho0lj1K/op48E53Vle7UMbYTqXau36ac9A3nqpGT3f6jF2tto5V2cZmqTtt
FBZyn1uDj0kFqLRZJ2Bc5aCl1nldVYQVu9GJtBNuEOhSBcSZWlduAS92saqp1meUDBVLlHUMa6Yp
FUchTWBzUz3np9dXPdVmnZNLgEfM+WtRjS5ONgwkTBselUnQeOY5psJnGNoeEUQLnq3JjBh+dTs/
GSoAYQMO4F85M+CDGrSKNFYenO1PocypQgIx9qBdWmdy0w3639zC/jcg0W/fDPXhiCq+uKE66IrR
am42jb+FwliT6Q75k9hyH3WCZ6r7eK1w729JtADDgrpUP4fw+F4oLOZJwdYI5WqlINpsZOz6hoTK
5rNvahUibyMKopQpCqiDXcALnp4EWu9RQsH+uNuhybGCT8/1nXPjR1RNwwWHDZnNs9wB64i/JOg3
Yqg31lP3bfKKxp4+E3HnkrJzqLXxN5CrFKKEyTYXMZcgUSLcSUmsZMQ17exgz8GV0bpb0EKnPhL9
w0go04vUP7HkRO39IxcgGIAubERmFduhNjJjhmnWWArwMT/p3otATPBkPK9/HC8Vu4fcO38m8Ql9
VUeKnL4PlBA2pU+dZSjTNqlD6rtqmUrMJXFFzJ68X82PZS9JCeMadhuXEKkVR34IlXgOWfE07Srv
kuGWSuIRtmR8OSzjI2OP2GuCNKtwtJj4IEPachIoJyDCLL3OWxqantZ8eoRJSNafQR/Lb5/4vghx
IhYSS3uJsFT+OA+G7qBh0F/ln2gSJ5pp9EWkZHDk9yLNYHb5LdzDa2RFUAPyQCKPiorfFR2xMcop
2C9pAirI9kP2xiG18DIevPET+miNDgdYSqDX6BZbon34hrfdvUH/gwPOPp4RR/6yrpeVxGAVf9pP
/Csdn/HrNPMKtd8xq2oLHpCfWkh6BB7NLVxYquG7S6HSF2uiTZaAL0+Y5QVkXVxPWqH8VKEVrlwa
1aSHG8c4QX/MH5xLB/ROTJFYq0ZIfzmyMbIO1F/h/zEnh2AmZ3ie+WG570rNmPWwlMXyksOaTKj8
EBldQo490YyZM59pgH8kwmt8DCB8RgveYlnTcQFV9igREtP7nUI9IYVKHrcKfgG+Sq0kLrHnHFJu
KxJ/YInOdrhjkVj00Xpk5klstKVWPcuVfiGWVAErqvQAlYMilVEqByWEKYbFDNQkAC1+pxWebErc
TWIyxYVPu7DYlyuzHbQOv33l2jUmkLV3iQE5lVFVSgolKRZeZEz4qXGtmDTzq6K67K2ZICykMeEr
eaPsoFV4BEBEFnPNTiyN/CE8B8HrzejA8mv6doCasboAQgNwmREP5PcYFNfrGzADwQsxWbLsbeCU
1afx/p/gWGsMsH/IQZkxI4Pg/eO94nQaaPQ7OuYbpVVkbwgs5Bvkz9r4fay70v8vVlIdYQOmGtCp
gjB4uyYTG04Yo5++FvaWhsRggxlUJTCYkjQ3E12FeTYwY9TM3k2KpPNYDYXY/YRAOmnq3CAoQ6BF
Bxttde4I0fh3l+rxiClDcD+d5vq5l9GZd8TkPi5NnDsM+fT1CMZlpCBK5g04vcv/CowHbxpFbTUU
29QSQ2wZsr6fSalNZ38QuO5SBs4zdrCNks1acw4kQDCXZ/wrCrYDM0nyAC4/MFlVR3eAqXEj8THG
GLZE0B4MPINUJ9AkRpvJGYhK3SNBJe5p1/Uo5RMr4h37SzjSO6uyP9E/ZamAPk26OuYofz8d9lG2
1eA5PqqVPjU4Mz672RVWRqFLDHEgIGMyILGsb2N1DCFWQpPtZkQvUo9Uepfcu3oY1fbGI3mn07H9
DF0BRwScM/LLmrz+55GNtb9paW43FoiRySf3QhMQ661Ooe0A3YefQzvyyCdoL2x2ukebnRlAY4UI
XFXh38XvJU0dVc5d7sQtCaPDHbVyxzfLTh9JYjhxdTngZ0r6Hii0nVLfqZl4NNsMdef/7iQza6lM
LsGMFmNUgb3KCbX/H8vN8rivcGJM6t0QsYiKpNOmwiRUSzEDE7f+D0ImGLcABer628zvDcUvaiFn
I/w1oD4wCledioAJEj/B2sVSV0/PzMa6m4AmLfGTPoXGzQYJoTkVQl0yhhyt7fP/I4r+QXSWTVI5
cYdtmlCk4iLRs5E0sZDp5vOWnlUNGbbVt5jRjoizJ15tH9qoaru7QQM8SljInHj45Juui7CppoiQ
Hfib7B+GQWKnQ1piij2jHd1Bxd1favdFY076Hbqn6HwZo4LG3APfXc+2B/dgRUSP2T0ia9TiJPbG
83mzXWLOOSBHkECaMTYXLrvmKg6hBCZLqjyEmViBRngh+Fb+VWNJ1dZN6iZOYwBZEYJeVNDOdtCk
ovhQZG21scUeO0i7+80/+ucbj7nHjO5dJv9IYzMR7RHMHuvoj1+v+OwhLL844S1+dy8PzEYK1BRF
d8sJLfUFaU9Ce7Cj8PYBc6BzD67QwEVWMBn3o5Lp7neBmbD0kggLjs2KKKUljlX87oW3HP9S4S12
d4v5624LxbVpdu5wGZDK0uHoXZ9Ve+blPTtxi+kwSEt3q2t477oeLBD8oQ0kbcs//1niVxeVCNjs
W7Vdpn6k6Zfc1lozDDaTmrafetTpD2I7aKzCQeCrQdjYVJibvldGI+ZFZirnWimm2YKyW9Q2lriN
GOv8EMfl0XH6t9vSNkpLvADYJqZq/1dj7vUiu1JajTEiXX0AClahKR8gU0EKbfM5w3+MwGSu96Ap
LMvunTLJ7W20IIS4APAKewKAApACU1pqZBd/w0i95sTMDcuE/zPXmp3FP69+qGQelwllRreZDYVP
ZkeIhD0yVR3NXXE04Uc099x1RKY3h9EEz4hYkVwN24SrXW7X4oPKVRYjMhm1CUWy5ba8Nb8xlm9B
m2Arz2YM0wTOWBHLxiyuhihUU5tMhdUEGtb08TKxu0/mrCUjc5E65A1U5Kuia3XI5mMjNBJ8jWwS
uxur4DuwQLipmVrdCUNkApG5xuhuHvMSEGu1x24PkgfWblGvSo7nE/N8C9GCakDQk54vPhtzF6pb
ZNH79VRk7JzDdx653Hu4KWRn4bWsancx9/BqyY3RsDN3zeB9UEC+qcqLUJyN6XopKyu3PnV2r7A8
UInRsENyZT/iROXUE2NNfXjbsR4zYjximUnVyizJ2dPHnmfSfxO2J2RRuVtlVXf+hW2mCH1SR7IR
GSQnMHlCmsZBN1DnTsya5ES1H+MOHgh0R2RXVnlUMdRU7rtTYhSqF+0kccVg9AjPthZ1w0lCr4tz
IcZwc88YNYn1sloUNIahOk9Bd0AQqKSIvtTm78mQ1a+CPqFshKyTW8IzoWybZSua+Ce0Q/lZRByd
CW2dn7mqRj+DBwJ5V0jWLTgT6Ltwfi/FVNGnaDZkJrz1zdhZWfATzsQtzzXUilX0Mz7mI+xRTR1F
cQLlqv4ZXcKfAUEwXZkEFYPV2fkk8sLa8KGTs2/K4nXX6lici6h4vlPFi6G+sU2Iayc8h/DhjIZj
FKF7yZTIhgoCHelSlDPzYp/KVgTB59Pv/+ufcuyBij6//YSW+5BLqqL2QDJWMnMRFs3sxOgL21xt
fIswWsrppMzIWDurMHHkWL3D9vASuXSn13pdlTS3H8nZhLw4tCKp3gU74ep2skvpISegORJdzihe
DuP3ViMrvN1TPBVUHhSE0uXmpt35dbw7aVR5QBDqQ5/+PFRil6dXJGdhklE5rwvyQ3lITvPSbD3r
dEge5NPcPM8RhyY3tMT9YHLzXp2qzaV71A/zI9/0j4co6A8Kgt2m0xfd3FWSTRXii8inMnXH3lNl
vipmJhBJctYxMlhmXehkxujB8uCBYPZ0MpYujlUf4lTIZCY7ChFxTlPo0fLFl8uwTCwQjurILGN5
UXxWJz6FywSttjaWMaYajmRIy3QexyEOsa6Y6uCB0HmN50g0jxxNfqLVUDIdu5mzpurKNSFWuBra
kfOVmClmqMokN8arLxKOlKRWDYmQqnVz7KsoqChWNZ7i0kCimS3znKqc6oYqLA0vMxL2QrIqMcqv
tRjp4IFglZ/kPKwTKZWJnBqUhiIyqc9bCzqyIhLhxd2Es1RlhI4XLk9uwjvuZlAQTiLX0TbwZkVn
uTZEOiAIPxuS59zKmmjYowujBheNZ0bL0cxvzuZ/HPXIjmOe20Oqx5Zw6o3jhF9uJiulYJVgyiIa
qe/hM92rmfj5aOoz+wjPPOOLnwcEgcRcxJctLJnYQuyBXlQyZVMkixRpNxyjbWdnN7GSibEPHgjv
yTYiC1li099eCdqJyGgo15fc+a5OeFH1kZLUZFc3QqKg+oAg3PeMUFl5bKHTQ1WsoSMxEodrEyly
yyHnYCvZbNbbri5W/zXkPHggFG3u92w0JtaOix48h3/YDp3wVfGLMWce/XXqUKJnYf11VtPOLXom
8JVo7hk1+gbLHSecjKRcOeF38ECYDv2EQmRQJpiNhd/gkitJd2qMTLgEF9oUBKqzlrNe1LxvnTRm
fOQdUjWslMRpcjSOcmtmU1N+GFWRjKqkiITuQia7BPkBQehdmVTJWMPs8PF9JibXpWeVg9jrmTwu
w3bBFM1LiNF6YIHQE2LLZjgxZqAjkZcvnSg9MEDgQAMFCMVBBAYFBC+jIke1oPgpiTEuGBCQdCh2
tZoo/hbe2E2N8/W9aFR+QFBk3cuHGlefyVhkVOaoelBAplxd1CEYOt0Z7gMxEL3eacknprzWPkZy
cfNAZWWXAiKy+9XdBYkHBKEV2jxSVOEKR5E6EdotpiCcSraP5kPqxDqfNw6y80hJ2A6/k45yRcZN
WWbfzXEkSLZ/0/obJRKTLCYzxh4QBHvd8SGukIrsPi+qDGVyxl+df77PsToXJh4UsFYfKpogPbCA
kHtSgspgttcDDRQgcBYwgEEEBgUE5iL7BN1nLjWVmA7/oOvggXA7sSglw5uulHEt6Eqp0dnrof/l
NZxJdmylroOCMGN3Od9c9+pd2JFfbonZIlYmJzNbpOKh/YiOEkINxpNGd2a+qlPr2rALEV12FxtZ
7xT0BrLMXWR84WrdkfnuTC3pmFwrBcHVlhRvpiJnPFtkbr/Um7VljV7LLpSzqVL4OrBAqPFYSaoi
Yj5zx3fxKwFRRHzBO9AJ61JEOZrprL8zFoQxEYVIR1VU99GbbLBbT+grs6fsks6178zV3M+xJDK9
E9aRNavBO63sfvWJ7Wd5xZl5Rndk8WqgubmazkqYOfzoxZucmXtzsTRukHlQEO4l9VjxllPL4uYB
QZh50S7eCS6jWxVdH599TjKbcCfQI3M6kjDKDuXQNbr6a/c6xyTMHjwQXj05UkJOj8456VScHWjK
Q5eakZhOIOuUxYQoIXcDBCTgAAAAAAAAMICABB6AgQ5cYAIaCBDIwAU2oAEIZEACAQISeAAGILCB
BwQIcOABGpDAIAMQqAAFHpABBRCAAAUDBjQgCCAA4QYLGERgQMChnI2M08/oW5H+QYEDQsUqNc4L
XHU1h8gHBDR0rXGi7q1Hz5XGOHSSsdM9YhjjuOLYloQu1IsZPBCMv/LOpCxoSK6Pi65CSnYO2txp
blVU9rDNoIBqJpwbH2YzIKiFjjXVy0yVjX/ajGN+q1bQK5OK7k1dfBhY1nPFyIPZ5nfH/66+sp/V
fk51GiwD2YvNu2lBmawjZ++FOWMz6e6iZaiIVblK7KJ+f0ZKm1ITdr0JvURl1xB68EDFJj9TONFx
KM65UzM+h1GgDq2qREtQHXjuuIasYDJy1FOMBceKcOpG4u4BwYfIjOpezLk1e/QUGXQek/2qL+uJ
2dOyhM+mVceqdxRzprIr1zK7MPOAIHSubE8MhSLpZaPfSpmj20SJUiD2TBV959rO1W+Ygu1iN3Wk
Y95Q2spXYkrFKdxoZxqRIo19i8bMUVK18thrHL2EJHHF2miFK7O5X6YNee9q7vroOyjotRVWvRx1
s6tZyVEeVOF6rled4igrBxEYENDI0pOwX9WIXEMhPpU3HdUX+IZMysgNMQ8IWC6yLs69x7nhb0ll
0864QqNJjQpCvVQRm44leDce6ce5A4JAUvW7ae9CLtyJ1G5kqhi+owwZdv/JmZWFRsyG5mYkSPry
FCdOOigY68mMskYqUonndObPmxvVO+fej8ZmRS2eaayOtLgLthdtUe+goOyGdSIiQ6gDgnAVGtGY
+Bh1/pRmGmTjKtpduKtL+9cbcShVJm+MMBzlPkmuVBaWVKkcQ2IyHCp24sJgcc3RfmAAFhCE32Nm
uCzp0Cst8KlTgk6V78Kk5MY6RGyQr7Ebc4IXqEJqCqIoiIEgSjnDFEUjZNQGogimcyhKtliy6AMS
wEB7hhgiIIQYJ70ikZSRgqQNyKHUOR+SDVxAmy/kUhrvbqzIjZLCmcHQY4F3lJgPUaoKupq2TmQ8
ks2YQ0OIIu1fguGqfNQKEEOPL+nB9OuFk6Q4Tckea1b7/Z/GE2Nco9Vvk9GuoyGRr2bdGBrGQQ05
7FdGTxC+fSdqolKVU9lwyT6szUiqWYkQi5mMzwPk1rB/ILbyERHLTsfPCFnkr6rZDDqbQd/ZYC3p
NoV8qxy9gAnEYTi7n1s3XwNjB766zHoq6e+Iuk4GC9E65X8yw+DtNBvLlqMBLPl53NMJFrL3BjbY
w6WwV2kq7U5ohDq+RSV11m4rPdKzwfVophmOFOW5Z5fjNZQA21IGfAGa0ZIendF25oyJR263/Be5
8GDgAHQyiluhv6riEALdkwTr4rTCL0H+/G26UTVMCTK8RDRZ97CwhW8H4sFRCJcrZDlA43gRz47q
FP5oaI86RyCPTp4JUa/+n9JPk+PTH/EOfm03CQuDGEnLHiTI+Pcdvx/BK2Yfg2LmQiq0+vljQByF
Yfg4jaFQGpfX7BkIjkrkzQ3Ad25xQWH65Y9UvJVq9r0cpToZG+F56RBNc9DuWjcM/wu/gNxLrvYM
Ryl2ePC4i9/8SsxZhSJVV42bCd+YbkILc42FP0fLDBPD4UEjdClDmnO1ydojSGcOcEZoqhrTPYji
iMonAxPOGPbbPz4Vr/wS9SaSA+84TyV8dOdHgiCdjv29DmHB+H36s7lUvIdKShyjMdywnY6blHYg
mJK+KNegb45B85cznVzv7NtJUeb8ksG1UIPJIwfeToOInxU/P8Hpu1E6FSHxLDNxuQHOY9J86pd0
ugNkLAzPmokcHL1BSV7GHCzixAQpp3/OOoFxF2dYF1Mt6czCAjDkNUmXBYai+zIH2p7oSoJb0c9f
1CIzSbUXwYSCen6iTSz0FMcOUQC1hcfD2i8mnRTp5kd/kfKsmGwS/7tXtRU6Tgt89r3KFC4/7qrq
jcEvl8BaqaK3OzJVB5qIlSYdwQ8IQWT2CNzuPvGI/zBoRic3teLMXz2T7IyBh4A+YN+eE4pwHoAs
5figXdB2ksDfCgzBupJIiXFT6Y7TUhZITuXomFwK4AglBbUVWxehpVCNKimXB1aTMS0CuNNRj+dq
ncThQTCWP+68MPh77+UbPeddGy6LOpWD0UgUQQNyuQChxRXePNkKvRKNAcH2kaJPopBMTafQW8Ak
dF6LlLgtGOdnBd024f84oO0kGyjI+9H1ZPtifCAshPtlVZFYO0hdsF0Cp7+kQbH6Zd+7zyvasuX8
7DHpnLRdV4g05jZ7009ZUWc4kaQYLkZzEFkoOqFoEjBRmNFAyjjGdKgaw9EkeW361j5T0FulCRZB
cfg+E3iTwJHnDdmDR7Zi3Yw5M8cLZEUJjulWJyw2CTrLn8Y3m74SHA8yufMZqOqQ92GfYf6ecGK8
p4kT1py27uoUfspBKR2bLI4LpARKdRE4CbxXV+EkYBAlx+lEBOW11PFAFLltAb/jZVsLcMmmIYy/
ErYcsH6OPDmu8sU4/UZI+QMbeqomH4elUKfET0ZwDEBCdBnw0OY3nOlSQo3lZgVxOvOQVuXc9rPD
HadQ0GuEn77+01dBhwNQHrYCt7QTAJRWq+Jp5TkWpxCrA/6E2sdaKoSieA8zi/C7HITP6N7igEf3
d6ja5N5hODnlP05MR1+bOKQH9ktaeKd6UOsZUfDmLHotYNExcVyE0nymn1ZWJVYn7nIsLmOPDLsK
]]>
</i:aipgf>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 68 KiB

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg id="plus-add-more-detail" style="enable-background:new 0 0 15 15;" version="1.1" viewBox="0 0 15 15" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M7.5,0C3.364,0,0,3.364,0,7.5S3.364,15,7.5,15S15,11.636,15,7.5S11.636,0,7.5,0z M7.5,14C3.916,14,1,11.084,1,7.5 S3.916,1,7.5,1S14,3.916,14,7.5S11.084,14,7.5,14z"/><polygon points="8,3.5 7,3.5 7,7 3.5,7 3.5,8 7,8 7,11.5 8,11.5 8,8 11.5,8 11.5,7 8,7 "/></svg>

After

Width:  |  Height:  |  Size: 492 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.2893 5.70708C13.8988 5.31655 13.2657 5.31655 12.8751 5.70708L7.98768 10.5993C7.20729 11.3805 7.2076 12.6463 7.98837 13.427L12.8787 18.3174C13.2693 18.7079 13.9024 18.7079 14.293 18.3174C14.6835 17.9269 14.6835 17.2937 14.293 16.9032L10.1073 12.7175C9.71678 12.327 9.71678 11.6939 10.1073 11.3033L14.2893 7.12129C14.6799 6.73077 14.6799 6.0976 14.2893 5.70708Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 538 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.71069 18.2929C10.1012 18.6834 10.7344 18.6834 11.1249 18.2929L16.0123 13.4006C16.7927 12.6195 16.7924 11.3537 16.0117 10.5729L11.1213 5.68254C10.7308 5.29202 10.0976 5.29202 9.70708 5.68254C9.31655 6.07307 9.31655 6.70623 9.70708 7.09676L13.8927 11.2824C14.2833 11.6729 14.2833 12.3061 13.8927 12.6966L9.71069 16.8787C9.32016 17.2692 9.32016 17.9023 9.71069 18.2929Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 544 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="#FEFEFE" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2ZM16.06 13.79C15.91 13.94 15.72 14.01 15.53 14.01C15.34 14.01 15.15 13.94 15 13.79L12 10.79L9 13.79C8.71 14.08 8.23 14.08 7.94 13.79C7.65 13.5 7.65 13.02 7.94 12.73L11.47 9.2C11.76 8.91 12.24 8.91 12.53 9.2L16.06 12.73C16.35 13.03 16.35 13.5 16.06 13.79Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 523 B

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 3C4.55228 3 5 3.44772 5 4V5H7V4C7 3.44772 7.44772 3 8 3C8.55228 3 9 3.44772 9 4V5H11V4C11 3.44772 11.4477 3 12 3C12.5523 3 13 3.44772 13 4V5H15V4C15 3.44772 15.4477 3 16 3C16.5523 3 17 3.44772 17 4V5H19V4C19 3.44772 19.4477 3 20 3C20.5523 3 21 3.44772 21 4V6V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6V4C3 3.44772 3.44772 3 4 3ZM19 7H16H12H8H5V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V7ZM13 10C13 10.5523 12.5523 11 12 11C11.4477 11 11 10.5523 11 10C11 9.44771 11.4477 9 12 9C12.5523 9 13 9.44771 13 10ZM16 11C16.5523 11 17 10.5523 17 10C17 9.44771 16.5523 9 16 9C15.4477 9 15 9.44771 15 10C15 10.5523 15.4477 11 16 11ZM9 13C9 13.5523 8.55228 14 8 14C7.44772 14 7 13.5523 7 13C7 12.4477 7.44772 12 8 12C8.55228 12 9 12.4477 9 13ZM12 14C12.5523 14 13 13.5523 13 13C13 12.4477 12.5523 12 12 12C11.4477 12 11 12.4477 11 13C11 13.5523 11.4477 14 12 14ZM17 13C17 13.5523 16.5523 14 16 14C15.4477 14 15 13.5523 15 13C15 12.4477 15.4477 12 16 12C16.5523 12 17 12.4477 17 13ZM8 17C8.55228 17 9 16.5523 9 16C9 15.4477 8.55228 15 8 15C7.44772 15 7 15.4477 7 16C7 16.5523 7.44772 17 8 17ZM13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16C11 15.4477 11.4477 15 12 15C12.5523 15 13 15.4477 13 16ZM16 17C16.5523 17 17 16.5523 17 16C17 15.4477 16.5523 15 16 15C15.4477 15 15 15.4477 15 16C15 16.5523 15.4477 17 16 17Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1 @@
<?xml version="1.0" ?><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;}</style></defs><title/><g id="cross"><line class="cls-1" x1="7" x2="25" y1="7" y2="25"/><line class="cls-1" x1="7" x2="25" y1="25" y2="7"/></g></svg>

After

Width:  |  Height:  |  Size: 334 B

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M1.293,11.293l4-4A1,1,0,1,1,6.707,8.707L3.414,12l3.293,3.293a1,1,0,1,1-1.414,1.414l-4-4A1,1,0,0,1,1.293,11.293Zm17.414-4a1,1,0,1,0-1.414,1.414L20.586,12l-3.293,3.293a1,1,0,1,0,1.414,1.414l4-4a1,1,0,0,0,0-1.414ZM13.039,4.726l-4,14a1,1,0,0,0,.686,1.236A1.053,1.053,0,0,0,10,20a1,1,0,0,0,.961-.726l4-14a1,1,0,1,0-1.922-.548Z"/></svg>

After

Width:  |  Height:  |  Size: 482 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Icons" x="-1024" y="0" width="1280" height="800" style="fill:none;"/><g id="Icons1" serif:id="Icons"><g id="Strike"></g><g id="H1"></g><g id="H2"></g><g id="H3"></g><g id="list-ul"></g><g id="hamburger-1"></g><g id="hamburger-2"></g><g id="list-ol"></g><g id="list-task"></g><g id="trash"></g><g id="vertical-menu"></g><g id="horizontal-menu"></g><g id="sidebar-2"></g><g id="Pen"></g><g id="Pen1" serif:id="Pen"><path d="M55.944,51.712l0,4.201l-33.652,-0.027l4.71,-4.174l28.942,0Zm-7.555,-43.712c1.649,0 2.505,0.128 4.752,2.011c2.294,1.921 2.707,3.419 2.803,5.087c0.102,1.795 -0.504,3.976 -2.188,5.681l-31.961,31.961c-0.52,0.475 -0.629,0.45 -0.977,0.553l-10.226,2.557c-1.472,0.299 -2.854,-1.049 -2.55,-2.55l2.557,-10.226c0.1,-0.334 0.133,-0.517 0.553,-0.976c10.696,-10.697 21.195,-21.594 32.09,-32.087c1.421,-1.335 3.497,-2.011 5.147,-2.011Zm-32.375,35.182l-1.477,1.477l-1.566,6.262l6.262,-1.566c0.493,-0.492 0.985,-0.983 1.479,-1.474l-4.698,-4.699Zm30.176,-20.573l-4.802,-4.801l-22.493,22.493l4.712,4.713c7.549,-7.448 15.196,-14.801 22.583,-22.405Zm2.826,-2.936c0.618,-0.648 1.234,-1.298 1.848,-1.951c1.673,-1.826 0.443,-5.454 -2.307,-5.578c-0.056,-0.002 -0.112,-0.002 -0.168,-0.002c-0.861,0.016 -1.699,0.372 -2.312,0.977l-1.807,1.808l4.746,4.746Z" style="fill-rule:nonzero;"/></g><g id="clock"></g><g id="external-link"></g><g id="hr"></g><g id="info"></g><g id="warning"></g><g id="plus-circle"></g><g id="minus-circle"></g><g id="vue"></g><g id="cog"></g><g id="logo"></g><g id="eye-slash"></g><g id="eye"></g><g id="toggle-off"></g><g id="shredder"></g><g id="spinner--loading--dots-" serif:id="spinner [loading, dots]"></g><g id="react"></g></g></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
<rect style="opacity:0.2" width="25" height="13" x="3" y="15" rx="1.25" ry="1.25"/>
<path style="fill:#60924b" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
<rect style="opacity:0.2" width="25" height="13" x="3" y="13" rx="1.25" ry="1.25"/>
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
<rect style="fill:#87b158" width="25" height="13" x="3" y="14" rx="1.25" ry="1.25"/>
<path style="opacity:0.1;fill:#ffffff" d="M 4.25,5 C 3.557,5 3,5.5575 3,6.25 V 7.25 C 3,6.5575 3.557,6 4.25,6 H 12.75 C 13.443,6 14,6.5575 14,7.25 V 8 H 26.75 C 27.442,8 28,8.5575 28,9.25 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 918 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
<rect style="opacity:0.2" width="25" height="13" x="3" y="15" rx="1.25" ry="1.25"/>
<path style="fill:#727272" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
<rect style="opacity:0.2" width="25" height="13" x="3" y="13" rx="1.25" ry="1.25"/>
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
<rect style="fill:#8e8e8e" width="25" height="13" x="3" y="14" rx="1.25" ry="1.25"/>
<path style="opacity:0.1;fill:#ffffff" d="M 4.25,5 C 3.557,5 3,5.5575 3,6.25 V 7.25 C 3,6.5575 3.557,6 4.25,6 H 12.75 C 13.443,6 14,6.5575 14,7.25 V 8 H 26.75 C 27.442,8 28,8.5575 28,9.25 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 918 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
<rect style="opacity:0.2" width="25" height="16" x="3" y="12" rx="1.25" ry="1.25"/>
<path style="fill:#dd772f" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
<rect style="opacity:0.2" width="25" height="16" x="3" y="10" rx="1.25" ry="1.25"/>
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
<rect style="fill:#ee923a" width="25" height="16" x="3" y="11" rx="1.25" ry="1.25"/>
<path style="opacity:0.1;fill:#ffffff" d="M 4.25 5 C 3.5575 5 3 5.5575 3 6.25 L 3 7.25 C 3 6.5575 3.5575 6 4.25 6 L 12.75 6 C 13.443 6 14 6.5575 14 7.25 L 14 8 L 26.75 8 C 27.442 8 28 8.5575 28 9.25 L 28 8.25 C 28 7.5575 27.442 7 26.75 7 L 14 7 L 14 6.25 C 14 5.5575 13.443 5 12.75 5 L 4.25 5 z"/>
</svg>

After

Width:  |  Height:  |  Size: 946 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
<rect style="opacity:0.2" width="25" height="16" x="3" y="12" rx="1.25" ry="1.25"/>
<path style="fill:#bf4b4b" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
<rect style="opacity:0.2" width="25" height="16" x="3" y="10" rx="1.25" ry="1.25"/>
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
<rect style="fill:#e25252" width="25" height="16" x="3" y="11" rx="1.25" ry="1.25"/>
<path style="opacity:0.1;fill:#ffffff" d="M 4.25 5 C 3.5575 5 3 5.5575 3 6.25 L 3 7.25 C 3 6.5575 3.5575 6 4.25 6 L 12.75 6 C 13.443 6 14 6.5575 14 7.25 L 14 8 L 26.75 8 C 27.442 8 28 8.5575 28 9.25 L 28 8.25 C 28 7.5575 27.442 7 26.75 7 L 14 7 L 14 6.25 C 14 5.5575 13.443 5 12.75 5 L 4.25 5 z"/>
</svg>

After

Width:  |  Height:  |  Size: 946 B

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" version="1">
<rect style="opacity:0.2" width="25" height="16" x="3" y="12" rx="1.25" ry="1.25"/>
<path style="fill:#b58f1b" d="M 3,21.75 C 3,22.442 3.5575,23 4.25,23 H 26.75 C 27.442,23 28,22.442 28,21.75 V 8.25 C 28,7.5575 27.442,7 26.75,7 H 14 V 6.25 C 14,5.5575 13.443,5 12.75,5 H 4.25 C 3.5575,5 3,5.5575 3,6.25"/>
<rect style="opacity:0.2" width="25" height="16" x="3" y="10" rx="1.25" ry="1.25"/>
<rect style="fill:#e4e4e4" width="21" height="10" x="5" y="9" rx="1.25" ry="1.25"/>
<rect style="fill:#e2b322" width="25" height="16" x="3" y="11" rx="1.25" ry="1.25"/>
<path style="opacity:0.1;fill:#ffffff" d="M 4.25 5 C 3.5575 5 3 5.5575 3 6.25 L 3 7.25 C 3 6.5575 3.5575 6 4.25 6 L 12.75 6 C 13.443 6 14 6.5575 14 7.25 L 14 8 L 26.75 8 C 27.442 8 28 8.5575 28 9.25 L 28 8.25 C 28 7.5575 27.442 7 26.75 7 L 14 7 L 14 6.25 C 14 5.5575 13.443 5 12.75 5 L 4.25 5 z"/>
</svg>

After

Width:  |  Height:  |  Size: 946 B

@@ -0,0 +1,3 @@
<?xml version="1.0" ?><svg data-name="Livello 1" id="Livello_1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><title/>
<path d="M64,39A25,25,0,1,0,89,64,25,25,0,0,0,64,39Zm0,44A19,19,0,1,1,83,64,19,19,0,0,1,64,83Z"/>
<path d="M121,48h-8.93a1,1,0,0,1-.94-.68,49.9,49.9,0,0,0-2-4.85,1,1,0,0,1,.18-1.15L115.62,35a7,7,0,0,0,0-9.9L102.89,12.38a7,7,0,0,0-9.9,0l-6.31,6.31a1,1,0,0,1-1.15.18,49.76,49.76,0,0,0-4.85-2,1,1,0,0,1-.68-.94V7a7,7,0,0,0-7-7H55a7,7,0,0,0-7,7v8.93a1,1,0,0,1-.68.94,49.9,49.9,0,0,0-4.85,2,1,1,0,0,1-1.15-.18L35,12.38a7,7,0,0,0-9.9,0L12.38,25.11a7,7,0,0,0,0,9.9l6.31,6.31a1,1,0,0,1,.18,1.15,49.76,49.76,0,0,0-2,4.85,1,1,0,0,1-.94.68H7a7,7,0,0,0-7,7V73a7,7,0,0,0,7,7h8.93a1,1,0,0,1,.94.68,49.9,49.9,0,0,0,2,4.85,1,1,0,0,1-.18,1.15L12.38,93a7,7,0,0,0,0,9.9l12.73,12.73a7,7,0,0,0,9.9,0l6.31-6.31a1,1,0,0,1,1.15-.18,49.76,49.76,0,0,0,4.85,2,1,1,0,0,1,.68.94V121a7,7,0,0,0,7,7H73a7,7,0,0,0,7-7v-8.93a1,1,0,0,1,.68-.94,49.9,49.9,0,0,0,4.85-2,1,1,0,0,1,1.15.18L93,115.62a7,7,0,0,0,9.9,0l12.73-12.73a7,7,0,0,0,0-9.9l-6.31-6.31a1,1,0,0,1-.18-1.15,49.76,49.76,0,0,0,2-4.85,1,1,0,0,1,.94-.68H121a7,7,0,0,0,7-7V55A7,7,0,0,0,121,48Zm1,25a1,1,0,0,1-1,1h-8.93a7,7,0,0,0-6.6,4.69,43.9,43.9,0,0,1-1.76,4.26,7,7,0,0,0,1.35,8l6.31,6.31a1,1,0,0,1,0,1.41L98.65,111.38a1,1,0,0,1-1.41,0l-6.31-6.31a7,7,0,0,0-8-1.35,43.88,43.88,0,0,1-4.27,1.76,7,7,0,0,0-4.68,6.6V121a1,1,0,0,1-1,1H55a1,1,0,0,1-1-1v-8.93a7,7,0,0,0-4.69-6.6,43.9,43.9,0,0,1-4.26-1.76,7,7,0,0,0-8,1.35l-6.31,6.31a1,1,0,0,1-1.41,0L16.62,98.65a1,1,0,0,1,0-1.41l6.31-6.31a7,7,0,0,0,1.35-8,43.88,43.88,0,0,1-1.76-4.27A7,7,0,0,0,15.93,74H7a1,1,0,0,1-1-1V55a1,1,0,0,1,1-1h8.93a7,7,0,0,0,6.6-4.69,43.9,43.9,0,0,1,1.76-4.26,7,7,0,0,0-1.35-8l-6.31-6.31a1,1,0,0,1,0-1.41L29.35,16.62a1,1,0,0,1,1.41,0l6.31,6.31a7,7,0,0,0,8,1.35,43.88,43.88,0,0,1,4.27-1.76A7,7,0,0,0,54,15.93V7a1,1,0,0,1,1-1H73a1,1,0,0,1,1,1v8.93a7,7,0,0,0,4.69,6.6,43.9,43.9,0,0,1,4.26,1.76,7,7,0,0,0,8-1.35l6.31-6.31a1,1,0,0,1,1.41,0l12.73,12.73a1,1,0,0,1,0,1.41l-6.31,6.31a7,7,0,0,0-1.35,8,43.88,43.88,0,0,1,1.76,4.27,7,7,0,0,0,6.6,4.68H121a1,1,0,0,1,1,1Z"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,3 @@
<svg viewBox="0 0 512 512">
<path d="M377.9 105.9L500.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L377.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1-128 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM160 96L96 96c-17.7 0-32 14.3-32 32l0 256c0 17.7 14.3 32 32 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c-53 0-96-43-96-96L0 128C0 75 43 32 96 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32z"></path>
</svg>

After

Width:  |  Height:  |  Size: 502 B

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
␍<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 52 52" xmlns="http://www.w3.org/2000/svg">
␍<g>
␍<path d="m28.6 11.4h5.1a1.11 1.11 0 0 0 1.1-1.1 1.32 1.32 0 0 0 -.3-.8l-7.2-7.1a.89.89 0 0 0 -.7-.3 1.11 1.11 0 0 0 -1.1 1.1v5.1a3.12 3.12 0 0 0 3.1 3.1z"/>
␍<path d="m49.83 32.45a1.43 1.43 0 0 0 -1.39-1.45h-12a1.43 1.43 0 0 0 -1.44 1.44v1.44a1.43 1.43 0 0 0 1.4 1.44h6.14l-11 11a1.42 1.42 0 0 0 0 2l1 1a1.54 1.54 0 0 0 1.09.45 1.32 1.32 0 0 0 .94-.38l11-11v6a1.43 1.43 0 0 0 1.43 1.44h1.53a1.43 1.43 0 0 0 1.47-1.4z"/>
␍<path d="m18.35 20.68h-1.46v4.93h1.46a1.56 1.56 0 0 0 1.56-1.1 3.93 3.93 0 0 0 .24-1.44 3.61 3.61 0 0 0 -.36-1.77 1.53 1.53 0 0 0 -1.44-.62z"/>
␍<path d="m11.29 20.93a1.27 1.27 0 0 0 -.84-.25h-1.45v2.23h1.46a1.18 1.18 0 0 0 .84-.28 1.09 1.09 0 0 0 .3-.86 1 1 0 0 0 -.33-.84z"/>
␍<path d="m33.24 16h-7.84a4.67 4.67 0 0 1 -4.7-4.6v-7.8a1.42 1.42 0 0 0 -1.34-1.5h-12.56a4.67 4.67 0 0 0 -4.7 4.6v29.4a4.6 4.6 0 0 0 4.62 4.6h17.88v-.1a15.92 15.92 0 0 1 10.2-14.9v-8.1a1.58 1.58 0 0 0 -1.56-1.6zm-20.75 7.65a2.93 2.93 0 0 1 -1.89.56h-1.6v2.71h-1.51v-7.55h3.24a2.64 2.64 0 0 1 1.76.63 2.3 2.3 0 0 1 .66 1.8 2.35 2.35 0 0 1 -.66 1.85zm8.66 1.9a2.73 2.73 0 0 1 -2.53 1.45h-3.26v-7.63h3.26a3.85 3.85 0 0 1 1.17.17 2.49 2.49 0 0 1 1.28.95 3.43 3.43 0 0 1 .54 1.22 5.6 5.6 0 0 1 .14 1.29 5 5 0 0 1 -.6 2.55zm7.92-4.84h-3.81v1.74h3.34v1.31h-3.34v3.16h-1.57v-7.54h5.38z"/>
␍</g>
␍</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="Icons" x="-896" y="-128" width="1280" height="800" style="fill:none;"/><g id="Icons1" serif:id="Icons"><g id="Strike"></g><g id="H1"></g><g id="H2"></g><g id="H3"></g><g id="list-ul"></g><g id="hamburger-1"></g><g id="hamburger-2"></g><g id="list-ol"></g><g id="list-task"></g><g id="trash"></g><g id="vertical-menu"></g><g id="horizontal-menu"></g><g id="sidebar-2"></g><g id="Pen"></g><g id="Pen1" serif:id="Pen"></g><g id="clock"></g><g id="external-link"></g><g id="hr"></g><g id="info"></g><g id="warning"></g><g id="plus-circle"></g><g id="minus-circle"></g><g><path id="caret-down" d="M45.274,29.772c-1.094,-8.15 -8.075,-14.435 -16.525,-14.435c-9.203,0 -16.674,7.471 -16.674,16.674c0,9.203 7.471,16.675 16.674,16.675c4.423,0 8.664,-1.757 11.791,-4.884l2.862,2.863c-3.886,3.886 -9.157,6.069 -14.653,6.069c-11.437,0 -20.723,-9.285 -20.723,-20.723c0,-11.437 9.286,-20.723 20.723,-20.723c10.623,0 19.379,7.994 20.582,18.294l3.551,-3.551l3.118,3.117l-8.792,8.792l-8.796,-8.796l3.118,-3.117l3.744,3.745Z" style="fill-rule:nonzero;"/></g><g id="vue"></g><g id="cog"></g><g id="logo"></g><g id="eye-slash"></g><g id="eye"></g><g id="toggle-off"></g><g id="shredder"></g><g id="spinner--loading--dots-" serif:id="spinner [loading, dots]"></g><g id="react"></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M1.75 4.5a.25.25 0 00-.25.25v14.5c0 .138.112.25.25.25h20.5a.25.25 0 00.25-.25V4.75a.25.25 0 00-.25-.25H1.75zM0 4.75C0 3.784.784 3 1.75 3h20.5c.966 0 1.75.784 1.75 1.75v14.5A1.75 1.75 0 0122.25 21H1.75A1.75 1.75 0 010 19.25V4.75z"/>
<path d="M9 15.584V8.416a.5.5 0 01.77-.42l5.576 3.583a.5.5 0 010 .842L9.77 16.005a.5.5 0 01-.77-.42z"/>
</svg>

After

Width:  |  Height:  |  Size: 515 B

File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
<footer id="footer">
<div class="legal-links">
<a href="https://nak-reinickendorf.de/impressum.php">Impressum</a>
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
<a href="https://nak-reinickendorf.de/datenschutz.php">Datenschutz</a>
</div>
</footer>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/snippets/contact_modal.php'; ?>
@@ -0,0 +1,47 @@
<?php /* Name: header.php
Beschreibung: Ausgabe des Seitenkopfes inkl. Intranet-Menü
- Profil-Leiste */
?>
<header>
<div class="header_menue unselectable" id="header_menue">
<a class="nak-logo-link" href="<?php echo $g_httpHomePath; ?>">
<img src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK" class="nak-logo-icon">
<div class="nak-logo-text">
<span class="nak-name">Neuapostolische Kirche</span>
<span class="nak-gemeinde">Berlin-Reinickendorf</span>
</div>
</a>
<div class="header_profile" id="header_profile">
<?php // Profilbild
// Generiere den Dateinamen basierend auf Benutzer-ID und Datum/Uhrzeit
$profileImg = glob($g_profilePicturePath."{$_SESSION['user_id']}-*.jpg");
// Überprüfe, ob eine passende Datei existiert
if (!empty($profileImg)) {
// Sortiere die Dateien nach Datum/Uhrzeit und nimm die neueste
usort($profileImg, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$latestProfileImg = $profileImg[0];
// Entferne den absoluten Pfad
$headerProfileImg = str_replace($_SERVER['DOCUMENT_ROOT'], '', $latestProfileImg);
} else {
// Standardbild, wenn keine passende Datei gefunden wurde
$headerProfileImg = "/intranet_repo/general_files/profile_pictures/default.jpg";
}
// Bild anzeigen
echo "<img src='$headerProfileImg' alt='Profil' id='header_profile_img' onclick='openCloseHeaderProfile();'>";
?>
<div class="header_profile_bar" onclick="window.location.href='<?php echo $g_httpLogoutPath; ?>';">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>
<span>Abmelden</span>
</div>
</div>
</div>
</header>
@@ -0,0 +1,18 @@
<?php /* Name: html_head.php
Beschreibung: Zentraler HTML Head für das Intranet und Kalli
Anforderung: Umschliessendes Tag für diese Datei erforderlich: <head>...</head> */
?>
<meta charset="UTF-8">
<meta http-equiv="Content-Language" content="de">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/svg+xml" href="/intranet_repo/projects/_central/img/nak-logo.svg">
<link rel="shortcut icon" href="/intranet_repo/projects/_central/img/favicon.ico">
<link rel="stylesheet" href="/intranet_repo/projects/_central/layouts/nak-design.css">
<script src="/intranet_repo/projects/_central/scripts/intranet.js"></script>
<title>NAK Berlin-Reinickendorf</title>
@@ -0,0 +1,26 @@
<?php /* Name: button_edit.php
Beschreibung: Button-Ausgabe für die Umschaltung auf den Bearbeitungsmodus
GET-Parameter: action=edit
id=[$edit_id] */
?>
<div class="right">
<?php
if (isset($_GET['action']) && $_GET['action'] == 'edit') {
display_button('edit','Bearbeiten','right');
} else {
if (isset($edit_id)) {
echo "<a href='".$g_httpCurrentCleanUrl."?action=edit&id=$edit_id' style='display:table-cell;'>";
} else {
echo "<a href='".$g_httpCurrentCleanUrl."?action=edit' style='display:table-cell;'>";
}
display_button('edit','Bearbeiten','right');
echo "</a>";
}
?>
</div>
@@ -0,0 +1,119 @@
<?php /* Name: contact_modal.php
Beschreibung: Globales Kontaktformular-Modal
- Wird überall eingebunden wo technik@-Link war
- Sendet per AJAX an process_contact.php */
?>
<!-- Kontaktformular Modal -->
<div class="standard_overlay" id="contactModal" onclick="closeContactModalOnBackdrop(event)">
<div style="position:relative;width:min(520px,calc(100vw - 24px));max-height:min(700px,calc(100vh - 24px));overflow:auto;background:#FFF;border-radius:0;box-shadow:0 24px 70px rgba(0,0,0,0.36);">
<button type="button" onclick="closeContactModal()" style="position:absolute;top:14px;right:14px;width:34px;height:34px;border-radius:0;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.16);color:#FFF;font-size:1.35em;cursor:pointer;z-index:1;">×</button>
<div style="padding:24px;color:#FFF;background:radial-gradient(circle at 10% 0%,rgba(255,255,255,0.24),transparent 32%),linear-gradient(135deg,var(--theme-color),var(--theme-hover));border-radius:0;">
<div style="font-size:1.3em;font-weight:850;">Admin kontaktieren</div>
<div style="margin-top:6px;color:rgba(255,255,255,0.8);font-weight:600;">Ihr Anliegen wird an den Administrator weitergeleitet.</div>
</div>
<div style="padding:22px 24px 24px 24px;">
<form id="contactForm" onsubmit="submitContactForm(event)">
<?php
$isLoggedIn = isset($user) && !empty($user['first_name']) && !empty($user['mail']);
$prefillName = $isLoggedIn ? htmlspecialchars($user['first_name'] . ' ' . $user['last_name']) : '';
$prefillMail = $isLoggedIn ? htmlspecialchars($user['mail']) : '';
$readonlyAttr = $isLoggedIn ? 'readonly' : '';
$readonlyStyle = $isLoggedIn ? 'cursor:not-allowed;opacity:0.7;' : '';
?>
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">Name</label>
<input type="text" name="contact_name" id="contact_name" required <?= $readonlyAttr ?>
value="<?= $prefillName ?>"
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:12px;box-sizing:border-box;<?= $readonlyStyle ?>">
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">E-Mail</label>
<input type="email" name="contact_email" id="contact_email" required <?= $readonlyAttr ?>
value="<?= $prefillMail ?>"
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:12px;box-sizing:border-box;<?= $readonlyStyle ?>">
<?php if ($isLoggedIn): ?>
<div style="margin:-8px 0 12px 0;font-size:0.82em;color:hsl(210,14%,50%);font-weight:600;">Ihre Kontaktdaten werden automatisch aus Ihrem Profil übernommen.</div>
<?php endif; ?>
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">Betreff</label>
<input type="text" name="contact_subject" id="contact_subject" required
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:12px;box-sizing:border-box;">
<label style="display:block;margin-bottom:4px;font-weight:750;font-size:0.88em;color:hsl(210,18%,36%);text-transform:uppercase;letter-spacing:0.04em;">Nachricht</label>
<textarea name="contact_message" id="contact_message" rows="5" required
style="width:100%;padding:12px 14px;border:1px solid hsl(210,18%,84%);border-radius:0;background:hsl(210,12%,98%);font-size:1em;font-weight:650;outline:none;margin-bottom:16px;resize:vertical;box-sizing:border-box;font-family:inherit;"></textarea>
<div style="display:flex;justify-content:flex-end;gap:8px;">
<button type="button" onclick="closeContactModal()" style="padding:8px 16px;border:1px solid hsl(210,18%,84%);border-radius:0;background:#FFF;font-weight:850;font-size:0.86em;cursor:pointer;">Abbrechen</button>
<button type="submit" id="contactSubmitBtn" style="padding:8px 16px;border:1px solid var(--theme-color);border-radius:0;background:linear-gradient(135deg,var(--theme-color),var(--theme-hover));color:#FFF;font-weight:850;font-size:0.86em;cursor:pointer;">Nachricht senden</button>
</div>
</form>
<div id="contactFormResult" style="display:none;margin-top:12px;padding:10px 14px;border-radius:0;font-weight:700;text-align:center;"></div>
</div>
</div>
</div>
<script>
function openContactModal() {
document.getElementById('contactModal').style.display = 'flex';
}
function closeContactModal() {
document.getElementById('contactModal').style.display = 'none';
document.getElementById('contactFormResult').style.display = 'none';
document.getElementById('contactSubmitBtn').disabled = false;
document.getElementById('contactSubmitBtn').textContent = 'Nachricht senden';
}
function closeContactModalOnBackdrop(event) {
if (event.target.id === 'contactModal') closeContactModal();
}
function submitContactForm(event) {
event.preventDefault();
const form = document.getElementById('contactForm');
const resultDiv = document.getElementById('contactFormResult');
const submitBtn = document.getElementById('contactSubmitBtn');
submitBtn.disabled = true;
submitBtn.textContent = 'Wird gesendet...';
const formData = new FormData(form);
fetch('/intranet_repo/projects/adminpanel/process_contact.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
resultDiv.style.display = 'block';
if (data.success) {
resultDiv.style.background = 'hsl(145, 50%, 93%)';
resultDiv.style.color = 'hsl(145, 50%, 28%)';
resultDiv.textContent = 'Nachricht erfolgreich gesendet!';
form.reset();
setTimeout(() => closeContactModal(), 2000);
} else {
resultDiv.style.background = 'hsl(0, 78%, 95%)';
resultDiv.style.color = 'hsl(0, 78%, 40%)';
resultDiv.textContent = data.error || 'Ein Fehler ist aufgetreten.';
submitBtn.disabled = false;
submitBtn.textContent = 'Nachricht senden';
}
})
.catch(() => {
resultDiv.style.display = 'block';
resultDiv.style.background = 'hsl(0, 78%, 95%)';
resultDiv.style.color = 'hsl(0, 78%, 40%)';
resultDiv.textContent = 'Verbindungsfehler. Bitte erneut versuchen.';
submitBtn.disabled = false;
submitBtn.textContent = 'Nachricht senden';
});
}
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && document.getElementById('contactModal').style.display === 'flex') {
closeContactModal();
}
});
</script>
@@ -0,0 +1,30 @@
<?php
/*
Name: countdown.php
Beschreibung: Reload der aktuellen Seite mit Countdown
*/
?>
<div class="countdown"><span id="countdown">120</span></div>
<script>
var countdown = document.getElementById("countdown").textContent;
let pauseCountdown = false; //Von außen kann der Countdown angehalten werden: pauseCountdown = true
var countdownTimer = setInterval(function() {
if (!pauseCountdown) {
countdown--;
document.getElementById('countdown').textContent = countdown;
if ((countdown <= 0)) {
clearInterval(countdownTimer);
window.location.reload();
}
}
}, 1000);
</script>
@@ -0,0 +1,21 @@
<?php /* Name: overlay_addNewUser.php
Beschreibung: Neuen Nutzer anlegen in einem Overlay
- wird in admin.php aufgerufen */
// Neuen Nutzer anlegen im Overlay (Formular) ?>
<div class="standard_overlay" id="addNewUser">
<div class="bg_blur"></div>
<form method="post" action="">
<div class="eventbox_mini center">
<div class="exit" onclick="openCloseOverlay('addNewUser')">
<?php display_button('cancel','','center'); ?>
</div>
<h3 style="margin-top:15px;margin-left:5px;margin-bottom:15px">Neuen Nutzer anlegen</h3>
<div class="item">
<input class="field" placeholder="E-Mailadresse" type="text" name="newMail"required>
<input class="button" type="submit" name="addRecord" value="Hinzufügen">
</div>
</div>
</form>
</div>
@@ -0,0 +1,73 @@
<?php /* Name: overlay_message.php
Beschreibung: Ausgabe einer Message als Toast-Notification
Anforderung: Ausgabe möglichst zum <body>-Beginn. */
// Mapping aller Intranet-Messageboxen mit Typ und Ausgabetext
function getMessageInfo($message) {
return match ($message) {
1 => ['type' => 'success', 'text' => "Passwort zurückgesetzt."],
2 => ['type' => 'success', 'text' => "Nutzer entfernt."],
3 => ['type' => 'success', 'text' => "Nutzer angelegt."],
4 => ['type' => 'error', 'text' => "Nutzer existiert bereits."],
5 => ['type' => 'success', 'text' => "Speicherung erfolgt."],
6 => ['type' => 'success', 'text' => "Einladungsmail gesendet."],
7 => ['type' => 'error', 'text' => "Bitte Name und E-Mail ausfüllen."],
8 => ['type' => 'success', 'text' => "Brandschutzschulung abgeschlossen. Vielen Dank für deine Teilnahme!"],
default => ['type' => 'error', 'text' => "Unbekannter Fehler."],
};
}
// Laden der Message
if (isset($_GET["message"])) {
$message = intval($_GET["message"]);
$messageInfo = getMessageInfo($message);
$toastType = $messageInfo['type'];
$toastText = $messageInfo['text'];
?>
<div class="nak-toast nak-toast--<?= $toastType ?>" id="nakToast">
<div class="nak-toast__icon">
<?php if ($toastType === 'success'): ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
<?php else: ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<?php endif; ?>
</div>
<span class="nak-toast__text"><?= htmlspecialchars($toastText) ?></span>
<button class="nak-toast__close" onclick="dismissNakToast()" aria-label="Schließen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<script>
(function() {
var toast = document.getElementById('nakToast');
if (!toast) return;
// Slide in
requestAnimationFrame(function() {
toast.classList.add('nak-toast--visible');
});
// Auto-dismiss after 4s
var timer = setTimeout(function() { dismissNakToast(); }, 4000);
toast._timer = timer;
// Clean URL
if (window.history && window.history.replaceState) {
var url = new URL(window.location);
url.searchParams.delete('message');
window.history.replaceState({}, '', url);
}
})();
function dismissNakToast() {
var toast = document.getElementById('nakToast');
if (!toast || toast.classList.contains('nak-toast--hiding')) return;
clearTimeout(toast._timer);
toast.classList.add('nak-toast--hiding');
toast.classList.remove('nak-toast--visible');
setTimeout(function() { toast.remove(); }, 350);
}
</script>
<?php } ?>
@@ -0,0 +1,81 @@
<?php /* Name: overlay_profile.php
Beschreibung: Profil eines ausgewählten Nutzers im Overlay */
?>
<?php
if (isset($_SESSION["profile_id"])) {
$pdo = require($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
$sql = "SELECT * FROM user_general_information
WHERE user_id = {$_SESSION["profile_id"]}";
$result = $pdo->query($sql);
$partner = $result->fetch(PDO::FETCH_ASSOC);
if (isset($partner["mail"])) {
$sql_groups = "SELECT * FROM user_permissions WHERE user_id = ?";
$stmt = $pdo->prepare($sql_groups);
$stmt->execute([$_SESSION["profile_id"]]);
$groups = $stmt->fetch(PDO::FETCH_ASSOC);
}
}
else {
header("Location:{$g_httpLoginPath}");
exit();
}
// Profile image
$profileImg = glob($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/profile_pictures/{$_SESSION["profile_id"]}-*.jpg");
if (!empty($profileImg)) {
usort($profileImg, function($a, $b) { return filemtime($b) - filemtime($a); });
$profileImgUrl = str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImg[0]);
} else {
$profileImgUrl = "/intranet_repo/general_files/profile_pictures/default.jpg";
}
$fullName = trim(($partner["first_name"] ?? '') . ' ' . ($partner["last_name"] ?? ''));
$isRegistered = !empty($partner["first_name"]);
?>
<div class="profile-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="openCloseProfile(0, '<?= $profile_container_name ?>', '<?= $profile_overlay_id ?>');">×</button>
<div class="profile-modal-hero">
<img class="profile-modal-img" src="<?= htmlspecialchars($profileImgUrl) ?>" alt="Profilbild">
<div>
<?php if ($isRegistered): ?>
<div class="profile-modal-title"><?= htmlspecialchars($fullName) ?></div>
<?php else: ?>
<div class="profile-modal-title" style="color:rgba(255,255,255,0.7);">Nicht registriert</div>
<?php endif; ?>
<div class="profile-modal-subtitle">
<span><?= htmlspecialchars($partner["mail"]) ?></span>
</div>
</div>
</div>
<div class="profile-modal-body">
<?php if ($partner["phone"] != null): ?>
<div class="profile-modal-field">
<div class="profile-modal-field-label">Telefon</div>
<div class="profile-modal-field-value">
<a href="tel:0<?= htmlspecialchars($partner["phone"]) ?>">0<?= htmlspecialchars($partner["phone"]) ?></a>
</div>
</div>
<?php endif; ?>
<?php if (isset($groups)): ?>
<div class="profile-modal-field">
<div class="profile-modal-field-label">Gruppen</div>
<div class="profile-modal-chips">
<?php
foreach ($groups as $key => $value) {
if ($value == 1) {
echo '<span class="profile-modal-chip">' . htmlspecialchars($key) . '</span>';
}
}
?>
</div>
</div>
<?php endif; ?>
</div>
</div>
@@ -0,0 +1,199 @@
////==========================================//
//// GLOBALER BEREICH //
////////////////////////////////////////////////
//// Globale Konstanten
const url = new URL(window.location.href);
const basisUrl = url.origin + url.pathname;
////
////////////////////////////////////////////////
////==========================================//
////==========================================//
//// FUNKTIONEN und LISTENER //
////////////////////////////////////////////////
////
// Position der Elemente – min-height wird jetzt vollständig per CSS gelöst (nak-design.css)
// Die alte JS-Berechnung wurde entfernt, da sie nach dem Laden die Höhe reduzierte.
// JavaScript zum Erkennen der Scroll-Richtung und zum Umschalten der Klasse
// Funktioniert sowohl bei Body-Scroll als auch bei internem Container-Scroll (z.B. eventbox_table)
var lastScrollTop = 0;
var lastContainerScrollTop = 0;
function handleHeaderScroll(scrollTop, lastScroll) {
const elemHeaderMenue = document.getElementById('header_menue');
const elemHeaderTitle = document.getElementById('header_title');
// Dynamische Höhe aus CSS-Variable lesen
const headerHeight = parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--header-height')) || 64;
// Kein Einfahren, wenn der Content auf die Seite passt (kein Scrollen nötig)
if (document.documentElement.scrollHeight <= window.innerHeight) {
return;
}
// Am unteren Ende der Seite: Header wieder einblenden
const atBottom = (window.innerHeight + window.scrollY) >= (document.documentElement.scrollHeight - 5);
if (scrollTop > lastScroll && !atBottom) {
// Menü raus bei Scroll nach unten
if (elemHeaderMenue) {
elemHeaderMenue.style.top = `-${headerHeight}px`;
elemHeaderMenue.style.transition = "top 0.3s ease-in-out";
}
if (elemHeaderTitle) {
elemHeaderTitle.style.top = "0";
elemHeaderTitle.style.transition = "top 0.3s ease-in-out";
}
} else {
// Menü wieder rein bei Scroll nach oben
if (elemHeaderMenue) {
elemHeaderMenue.style.top = "0";
elemHeaderMenue.style.transition = "top 0.3s ease-in-out";
}
if (elemHeaderTitle) {
elemHeaderTitle.style.top = `${headerHeight}px`;
elemHeaderTitle.style.transition = "top 0.3s ease-in-out";
}
}
}
// Body/Document Scroll
document.addEventListener("scroll", function() {
var st = window.scrollY || document.documentElement.scrollTop;
handleHeaderScroll(st, lastScrollTop);
lastScrollTop = st <= 0 ? 0 : st;
}, false);
// Auch bei internem Container-Scroll (eventbox_table, display_within_dev)
document.addEventListener("DOMContentLoaded", function() {
const scrollContainers = document.querySelectorAll('.eventbox_table, form.display_within_dev');
scrollContainers.forEach(function(container) {
var lastCST = 0;
container.addEventListener("scroll", function() {
var st = container.scrollTop;
handleHeaderScroll(st, lastCST);
lastCST = st <= 0 ? 0 : st;
}, false);
});
});
function openCloseHeaderProfile() {
if (window.innerWidth >= 769) {
// Desktop: click on profile image goes to profile page
window.location.href = '/intranet_repo/projects/intranet/profile.php';
} else {
// Mobile: toggle expand/collapse
const hpElem = document.getElementById('header_profile');
hpElem.classList.toggle('header_profile_slideLeft');
}
}
function openCloseOverlay(id) {
//Overlay ein-/ausblenden - beim Ausblenden zurück auf eigene Seite
const elem = document.getElementById(id);
if (elem.style.display === "flex") {
elem.style.display = "none";
} else {
elem.style.display = "flex";
}
}
function openCloseProfile(id, name, overlay) {
// Profil per Klick öffnen/schließen, alle anderen immer schließen
const profiles = document.getElementsByName(name); //Ausgewählter Profilbereich (visit, calendar, ...)
const oProfile = document.getElementById(overlay); //Ausgewähltes Overlay
const aProfile = document.getElementById(id); //Ausgewähltes Profil
const aId = (id > 0) ? aProfile.id : 0; //Ausweichen, wenn kein Profil gewählt
console.log('id:' + id);
console.log('overlay:' + overlay);
console.log('name:' + name);
if (oProfile.style.display === "flex") { //Overlay ein-/ausblenden
oProfile.style.display = "none";
} else {
oProfile.style.display = "flex";
//TODO: folgender ScrollTop greift nicht, WARUM?
//oProfile.scrollTop = 0; //Hinscrollen zum Beginn des Overlays, vor allem bei @Media
}
for (let i = 0; i < profiles.length; i++) {
const cProfile = profiles[i]; //Aktuelles Profil (Style: profile_container)
if (cProfile.id === aId && cProfile.style.display !== "block") {
profiles[i].style.display = "block";
} else {
profiles[i].style.display = "none";
}
}
document.activeElement.blur(); //Fokus entfernen
// TEST console.log(id);
}
function mix(content) { //wird aktuell nicht eingesetzt, Daten werden per SQL randomized
const list = document.getElementById(content);
const items = Array.from(list.children);
console.log('Vor dem Mischen:', items.map(item => item.textContent || item.innerText));
for (let i = items.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[items[i], items[j]] = [items[j], items[i]];
}
console.log('Nach dem Mischen:', items.map(item => item.textContent || item.innerText));
items.forEach(item => list.appendChild(item));
}
function showHideText(element) {
element.classList.toggle("open");
}
// Per Taste Overlay verlassen
document.addEventListener('keydown', function(event) {
const profileVisitOverlay = document.getElementById('profile_visit_overlay');
const profileCalendarOverlay = document.getElementById('profile_calendar_overlay');
const addNewUser = document.getElementById('addNewUser');
const showMessage = document.getElementById('showMessage');
if (event.key === 'Escape') {
if (profileVisitOverlay && profileVisitOverlay.style.display === 'flex') {
openCloseProfile(0, 'profile_visit', 'profile_visit_overlay');
}
if (profileCalendarOverlay && profileCalendarOverlay.style.display === 'flex') {
openCloseProfile(0, 'profile_calendar', 'profile_calendar_overlay');
}
if (addNewUser && addNewUser.style.display === 'flex') {
openCloseOverlay('addNewUser');
}
} else if (event.key === 'Enter' || event.key === 'Return' || event.key === " ") {
if (showMessage && showMessage.style.display === 'flex') {
openCloseOverlay('showMessage'); // ausblenden
window.location.href=basisUrl; // zur aktuellen Seite zurückkehren
}
}
});
// Fokus auf passendes Element beim Laden der Intranet-Seiten
document.addEventListener("DOMContentLoaded", function() {
document.querySelector("#admintable tr[tabindex='0']")?.focus(); // Adminpanel
document.querySelector("#visittable tr[tabindex='0']")?.focus(); // Besuchsstellen
});
@@ -0,0 +1,247 @@
////==========================================//
////GLOBALER BEREICH//
////////////////////////////////////////////////
////Globale Konstanten
////
////////////////////////////////////////////////
////==========================================//
////==========================================//
////FUNKTIONEN und LISTENER//
////////////////////////////////////////////////
////
// Warten, bis das DOM vollstaendig geladen ist
document.addEventListener('DOMContentLoaded', function() {
var menuItems = document.querySelectorAll('.content-tab-bar__item[data-target]');
// Event-Listener fuer Klickereignisse auf Menueintraege
menuItems.forEach(function(item) {
item.addEventListener('click', function() {
// Entferne die aktive Klasse von allen Menueintraegen
menuItems.forEach(function(menuItem) {
menuItem.classList.remove('active');
});
// Fuege die aktive Klasse zum geklickten Menuelement hinzu
item.classList.add('active');
// Verstecke alle Inhaltsbereiche
document.querySelectorAll('.content_seelsorge').forEach(function(section) {
section.classList.remove('active');
});
// Zeige den entsprechenden Inhalt an
var contentId = item.getAttribute('data-target');
document.getElementById(contentId).classList.add('active');
});
});
// ESC-Taste schliesst offene Modals
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCalendarHelp();
closeDayModal();
}
});
});
// Wechsel der Kalenderblaetter Monat 1 und 2
function switchCalendar(type) {
const calendar_header_1 = document.querySelector('#calendar_header_1');
const calendar_header_2 = document.querySelector('#calendar_header_2');
const calendar_1 = document.querySelectorAll('[name="calendar_1"]');
const calendar_2 = document.querySelectorAll('[name="calendar_2"]');
const isLeft = type === "left";
calendar_header_1.style.display = isLeft ? "flex" : "none";
calendar_header_2.style.display = isLeft ? "none" : "flex";
calendar_1.forEach(cal => cal.style.visibility = isLeft ? "visible" : "collapse");
calendar_2.forEach(cal => cal.style.visibility = isLeft ? "collapse" : "visible");
}
// Tages-Modal oeffnen
async function openDayModal(calendarDate) {
if (!calendarDate) return;
const modal = document.getElementById('seelsorge-day-modal');
const body = document.getElementById('modal-day-body');
const title = document.getElementById('modal-day-title');
const subtitle = document.getElementById('modal-day-subtitle');
if (!modal || !body || !title || !subtitle) {
console.error('openDayModal: Modal-Elemente nicht gefunden.');
return;
}
// Modal sofort anzeigen mit Ladezustand
title.textContent = 'Laden...';
subtitle.innerHTML = '';
body.innerHTML = '<div class="seelsorge-modal-loading"><div class="loading-spinner"></div></div>';
modal.style.display = 'flex';
try {
const response = await fetch('content/calendar_day.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'calendarDate=' + encodeURIComponent(calendarDate)
});
const html = await response.text();
if (response.ok) {
body.innerHTML = html;
// Metadaten aus dem eingebetteten JSON lesen
const metaEl = body.querySelector('#modal-meta');
if (metaEl) {
const meta = JSON.parse(metaEl.textContent);
title.textContent = meta.title;
let chips = '<span class="seelsorge-modal-chip">KW ' + meta.kw + '</span>';
if (meta.feiertag) {
chips += '<span class="seelsorge-modal-chip seelsorge-chip-holiday">' + meta.feiertag + '</span>';
}
subtitle.innerHTML = chips;
metaEl.remove();
}
} else {
// PHP-Fehler direkt im Modal anzeigen
title.textContent = 'Fehler ' + response.status;
body.innerHTML = '<div style="padding:16px;font-family:monospace;font-size:0.85em;color:#d21400;white-space:pre-wrap;word-break:break-word;background:#fff5f5;border:1px solid #fcc;">'
+ (html || 'Keine Antwort vom Server.') + '</div>';
}
} catch (error) {
body.innerHTML = '<div class="seelsorge-modal-empty">Fehler beim Laden der Daten: ' + error.message + '</div>';
console.error('Fehler beim Laden der Tagesdetails:', error);
}
}
// Modal schliessen
function closeDayModal() {
const modal = document.getElementById('seelsorge-day-modal');
if (modal) modal.style.display = 'none';
}
// Backdrop-Klick schliesst Modal
function closeDayModalOnBackdrop(event) {
if (event.target === event.currentTarget) {
closeDayModal();
}
}
// Verfuegbarkeit im Modal togglen (Modal bleibt offen)
async function toggleAvailabilityModal(date) {
const btn = document.getElementById('modal-toggle-btn');
if (!btn) return;
const originalText = btn.textContent;
btn.disabled = true;
btn.textContent = 'Wird gespeichert\u2026';
try {
const response = await fetch("toggle_availability.php", {
method: "POST",
headers: { "Content-type": "application/x-www-form-urlencoded" },
body: "date=" + encodeURIComponent(date)
});
const data = await response.json();
if (data.ok) {
// Modal-Inhalt neu laden um aktuellen Stand zu zeigen
await openDayModal(date);
// Kalender-Tageszelle visuell aktualisieren
updateCalendarCell(date, data.nowAvailable, data.availCount);
} else {
btn.textContent = originalText;
btn.disabled = false;
console.error("Fehler:", data.error);
}
} catch (error) {
btn.textContent = originalText;
btn.disabled = false;
console.error("Fehler beim Aktualisieren der Verfuegbarkeit:", error);
}
}
// Kalender-Tageszelle visuell aktualisieren ohne Page-Reload
function updateCalendarCell(date, nowAvailable, availCount) {
var cells = document.querySelectorAll('#calendar_day_item');
cells.forEach(function(cell) {
var onclick = cell.getAttribute('onclick') || '';
if (onclick.indexOf(date) === -1) return;
// Alle Verfuegbarkeits-Klassen entfernen
cell.classList.remove('available_single', 'available_multiple', 'normalday');
if (availCount >= 2) {
cell.classList.add('available_multiple');
} else if (availCount === 1) {
cell.classList.add('available_single');
} else {
cell.classList.add('normalday');
}
});
}
// Profil eines Diakons aus dem Kalender-Modal oeffnen
var _lastCalendarDate = null;
function openCalendarProfile(userId) {
// Datum merken, damit wir beim Schliessen zurueckkehren koennen
var metaEl = document.getElementById('modal-meta');
var bodyEl = document.getElementById('modal-day-body');
if (!_lastCalendarDate && bodyEl) {
// Datum aus dem Toggle-Button extrahieren
var toggleBtn = bodyEl.querySelector('#modal-toggle-btn');
if (toggleBtn) {
var onclickAttr = toggleBtn.getAttribute('onclick') || '';
var match = onclickAttr.match(/'(\d{4}-\d{2}-\d{2})'/);
if (match) _lastCalendarDate = match[1];
}
}
closeDayModal();
openCloseProfile(userId, 'profile_calendar', 'profile_calendar_overlay');
}
// Globalen Listener fuer Kalender-Profil-Overlay: bei Schliessen Tagesmodal wieder oeffnen
document.addEventListener('DOMContentLoaded', function() {
// MutationObserver auf das Kalender-Profil-Overlay
var calOverlay = document.getElementById('profile_calendar_overlay');
if (calOverlay) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(m) {
if (m.attributeName === 'style' && calOverlay.style.display === 'none' && _lastCalendarDate) {
var date = _lastCalendarDate;
_lastCalendarDate = null;
openDayModal(date);
}
});
});
observer.observe(calOverlay, { attributes: true, attributeFilter: ['style'] });
}
});
// Hilfe-Modal oeffnen/schliessen
function openCalendarHelp() {
var modal = document.getElementById('calendar-help-modal');
if (modal) modal.style.display = 'flex';
}
function closeCalendarHelp() {
var modal = document.getElementById('calendar-help-modal');
if (modal) modal.style.display = 'none';
}
@@ -0,0 +1,32 @@
<?php
/* Zentrale Reiternavigation des Adminpanels.
* Erwartet: $pdo (PDO) und optional $adminActiveTab.
*/
$adminActiveTab = $adminActiveTab ?? '';
$adminInboxBadgeCount = 0;
try {
$adminInboxBadgeCount = (int)$pdo->query("SELECT COUNT(*) FROM admin_messages WHERE status = 'pending'")->fetchColumn();
} catch (Throwable $e) {
// Die Navigation soll auch dann benutzbar bleiben, wenn das Postfach temporär nicht verfügbar ist.
$adminInboxBadgeCount = 0;
}
$adminTabs = [
'users' => ['label' => 'Nutzerverwaltung', 'href' => 'admin.php'],
'inbox' => ['label' => 'Postfach', 'href' => 'admin_inbox.php', 'badge' => $adminInboxBadgeCount],
'kalli' => ['label' => 'Kalli', 'href' => 'admin_kalli.php'],
'brandschutz' => ['label' => 'Brandschutz', 'href' => 'admin_brandschutz.php'],
'notices' => ['label' => 'Meldungen', 'href' => 'admin_notices.php'],
];
?>
<div class="content-tab-bar">
<?php foreach ($adminTabs as $tabKey => $tab): ?>
<a class="content-tab-bar__item<?= $adminActiveTab === $tabKey ? ' active' : '' ?>" href="<?= htmlspecialchars($tab['href'], ENT_QUOTES, 'UTF-8') ?>">
<?= htmlspecialchars($tab['label'], ENT_QUOTES, 'UTF-8') ?>
<?php if (!empty($tab['badge'])): ?>
<span class="content-tab-bar__badge"><?= (int)$tab['badge'] ?></span>
<?php endif; ?>
</a>
<?php endforeach; ?>
</div>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,677 @@
<?php /* Name: admin_brandschutz.php
Beschreibung: Brandschutz-Schulungsverwaltung im Adminpanel
- Übersicht aller Nutzer mit Brandschutz-Rolle
- Fälligkeitsstatus und Details
- Manuelle Anpassung des Schulungsdatums */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
// Admin-Check
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
$stmt->execute([$_SESSION["user_id"]]);
$isAdmin = $stmt->fetchColumn();
if ((int)$isAdmin !== 1) {
header("Location: {$g_httpHomePath}");
exit();
}
// Hilfsfunktionen
function getProfileImageUrl_bs(array $row, string $g_profilePicturePath): string {
$profileImg = glob($g_profilePicturePath."{$row['user_id']}-*.jpg");
if (!empty($profileImg)) {
usort($profileImg, function($a, $b) {
return filemtime($b) - filemtime($a);
});
return str_replace($_SERVER['DOCUMENT_ROOT'], '', $profileImg[0]);
}
return '/intranet_repo/general_files/profile_pictures/default.jpg';
}
function getDisplayName_bs(array $row): string {
$fullName = trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? ''));
return $fullName !== '' ? $fullName : ($row['mail'] ?? 'Unbekannt');
}
// Schulungsdatum manuell anpassen (POST)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bs_update_date'])) {
$editUserId = (int)($_POST['bs_user_id'] ?? 0);
$newDate = $_POST['bs_new_date'] ?? '';
if ($editUserId > 0 && preg_match('/^\d{4}-\d{2}-\d{2}$/', $newDate)) {
// Prüfen ob das neue Datum innerhalb der Frist liegt (365 Tage ab Datum > heute)
$newDueDate = date('Y-m-d', strtotime($newDate . ' +365 days'));
$isWithinDeadline = (strtotime($newDueDate) >= strtotime(date('Y-m-d')));
if ($isWithinDeadline) {
// Datum in der Frist: Zähler zurücksetzen
$stmtUpsert = $pdo->prepare("INSERT INTO brandschutz_training (user_id, last_training_date, reminder_count, admin_reminder_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id) DO UPDATE SET last_training_date = EXCLUDED.last_training_date, reminder_count = 0, admin_reminder_count = 0");
} else {
// Datum außerhalb der Frist: Nur Datum aktualisieren, Zähler beibehalten
$stmtUpsert = $pdo->prepare("INSERT INTO brandschutz_training (user_id, last_training_date, reminder_count, admin_reminder_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id) DO UPDATE SET last_training_date = EXCLUDED.last_training_date");
}
$stmtUpsert->execute([$editUserId, $newDate]);
// Systemnachricht in erledigte Admin-Nachrichten
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
if (empty($adminName)) $adminName = 'Ein Administrator';
$stmtUser = $pdo->prepare("SELECT first_name, last_name, mail FROM user_general_information WHERE user_id = ?");
$stmtUser->execute([$editUserId]);
$editedUser = $stmtUser->fetch(PDO::FETCH_ASSOC);
$editedName = trim(($editedUser['first_name'] ?? '') . ' ' . ($editedUser['last_name'] ?? ''));
if (empty($editedName)) $editedName = $editedUser['mail'] ?? 'Unbekannt';
$formattedDate = date('d.m.Y', strtotime($newDate));
$sysSubject = "Brandschutz-Schulungsdatum angepasst: {$editedName}";
$sysMessage = "{$adminName} hat das Schulungsdatum von {$editedName} manuell auf den {$formattedDate} gesetzt.";
$stmtMsg = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$senderMail = $editedUser['mail'] ?? '';
$stmtMsg->execute([$adminName, $senderMail, $sysSubject, $sysMessage]);
header("Location: admin_brandschutz.php?message=5");
exit();
}
}
// Brandschutz-Nutzer laden
$sql = "SELECT ugi.user_id, ugi.first_name, ugi.last_name, ugi.mail,
bt.last_training_date, bt.reminder_count, bt.admin_reminder_count,
CASE WHEN ul.user_id IS NOT NULL THEN 1 ELSE 0 END AS IstRegistriert
FROM user_permissions up
INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id
LEFT JOIN brandschutz_training bt ON ugi.user_id = bt.user_id
LEFT JOIN user_login ul ON ugi.user_id = ul.user_id
WHERE up.\"Brandschutz\" = 1
ORDER BY bt.last_training_date ASC, ugi.last_name ASC, ugi.first_name ASC";
$bsUsers = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC);
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
.admin-users-shell {
padding: 18px;
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
min-height: 100%;
}
.admin-table-card {
overflow: hidden;
border: 1px solid rgba(120, 140, 160, 0.22);
background: rgba(255,255,255,0.86);
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
}
.admin-list-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 16px 18px 12px 18px;
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
}
.admin-list-title {
font-size: 1.05em;
font-weight: 800;
color: hsl(210, 20%, 15%);
}
.admin-list-hint {
font-size: 0.86em;
color: hsl(210, 15%, 46%);
}
table.admin-user-table {
display: table !important;
width: calc(100% - 24px);
margin: 8px 12px 14px 12px;
border-collapse: separate;
border-spacing: 0 8px;
padding: 0;
background: transparent;
overflow: visible;
}
.admin-user-table thead th {
position: sticky;
top: 0;
padding: 10px 14px;
color: hsl(210, 16%, 46%);
font-size: 0.78em;
font-weight: 800;
text-align: left;
letter-spacing: 0.04em;
text-transform: uppercase;
background: transparent !important;
border: none !important;
}
.admin-user-table tbody tr.admin-user-row {
position: relative;
cursor: pointer;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 6px 18px rgba(31, 38, 46, 0.06);
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
}
.admin-user-table tbody tr.admin-user-row:hover {
transform: translateY(-1px);
background: #FFFFFF;
box-shadow: 0 12px 26px rgba(31, 38, 46, 0.13);
}
.admin-user-table td {
padding: 12px 14px !important;
vertical-align: middle;
border: none !important;
background: transparent !important;
}
.admin-user-table tbody tr.admin-user-row td:first-child {
width: 82px;
min-width: 82px;
max-width: 82px;
}
.admin-profile-button {
display: block;
width: 58px;
height: 58px;
border-radius: 12px;
overflow: hidden;
border: 0;
padding: 0;
background: transparent;
cursor: pointer;
box-shadow: 0 8px 20px rgba(31, 38, 46, 0.18), 0 0 0 3px rgba(255,255,255,0.95);
}
.admin-profile-button img {
display: block;
width: 58px !important;
height: 58px !important;
object-fit: cover;
border-radius: 12px !important;
transition: transform 0.2s ease;
}
.admin-user-table tbody tr.admin-user-row:hover .admin-profile-button img {
transform: scale(1.08);
}
.admin-user-name {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 1em;
font-weight: 800;
color: hsl(210, 20%, 15%);
}
.bs-status-badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
font-size: 0.78em;
font-weight: 800;
}
.bs-status-ok {
color: hsl(145, 50%, 28%);
background: hsl(145, 50%, 93%);
border: 1px solid hsl(145, 50%, 82%);
}
.bs-status-warning {
color: hsl(35, 78%, 30%);
background: hsl(42, 90%, 93%);
border: 1px solid hsl(42, 90%, 78%);
}
.bs-status-overdue {
color: hsl(0, 65%, 38%);
background: hsl(0, 65%, 95%);
border: 1px solid hsl(0, 65%, 82%);
}
.bs-status-critical {
color: #FFFFFF;
background: hsl(0, 65%, 45%);
border: 1px solid hsl(0, 65%, 38%);
}
.bs-due-date {
font-size: 0.9em;
font-weight: 650;
color: hsl(210, 14%, 35%);
}
/* Modal */
.admin-modal-card {
position: relative;
width: min(720px, calc(100vw - 24px));
max-height: min(760px, calc(100vh - 24px));
overflow: auto;
padding: 0;
background: #FFFFFF;
box-shadow: 0 24px 70px rgba(0,0,0,0.36);
}
.admin-modal-hero {
position: relative;
display: grid;
grid-template-columns: 112px 1fr;
gap: 18px;
align-items: center;
padding: 24px;
color: #FFFFFF;
background:
radial-gradient(circle at 10% 0%, rgba(255,255,255,0.24), transparent 32%),
linear-gradient(135deg, hsl(25, 90%, 45%), hsl(25, 95%, 35%));
}
.admin-modal-profile-img {
width: 104px;
height: 104px;
object-fit: cover;
border-radius: 16px;
box-shadow: 0 12px 28px rgba(0,0,0,0.32), 0 0 0 4px rgba(255,255,255,0.18);
}
.admin-modal-title {
margin: 0 42px 6px 0;
font-size: 1.45em;
font-weight: 850;
color: #FFFFFF;
}
.admin-modal-subtitle {
display: flex;
flex-flow: row wrap;
gap: 8px;
align-items: center;
margin-bottom: 10px;
color: rgba(255,255,255,0.86);
font-weight: 600;
}
.admin-modal-chip {
display: inline-flex;
align-items: center;
padding: 4px 9px;
border-radius: 9999px;
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.18);
font-size: 0.82em;
}
.admin-modal-body {
padding: 22px 24px 24px 24px;
}
.admin-section-title {
margin: 0 0 10px 0;
font-size: 0.9em;
font-weight: 850;
letter-spacing: 0.04em;
text-transform: uppercase;
color: hsl(210, 18%, 36%);
}
.admin-modal-card .exit {
position: absolute;
top: 14px;
right: 14px;
width: 34px;
height: 34px;
border: 1px solid rgba(255,255,255,0.25);
background: rgba(255,255,255,0.16);
color: #FFFFFF;
font-size: 1.35em;
line-height: 1;
cursor: pointer;
}
.bs-detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 20px;
}
.bs-detail-card {
padding: 14px 16px;
border: 1px solid hsl(210, 18%, 88%);
background: linear-gradient(180deg, #FFFFFF, hsl(210, 12%, 98%));
}
.bs-detail-label {
margin-bottom: 3px;
color: hsl(210, 14%, 46%);
font-size: 0.74em;
font-weight: 850;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.bs-detail-value {
color: hsl(210, 20%, 18%);
font-size: 1.02em;
font-weight: 850;
}
.bs-detail-value.overdue {
color: hsl(0, 65%, 42%);
}
.bs-detail-value.ok {
color: hsl(145, 50%, 32%);
}
.admin-modal-actions {
display: flex;
justify-content: space-between;
gap: 10px;
border-top: 1px solid hsl(210, 18%, 90%);
padding-top: 16px;
}
.admin-modal-actions button {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 36px;
padding: 8px 13px;
border: 1px solid hsl(210, 18%, 84%);
background: #FFFFFF;
color: hsl(210, 20%, 22%);
font-size: 0.86em;
font-weight: 850;
cursor: pointer;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
}
.admin-modal-actions button:hover {
background: hsl(210, 57%, 96%);
}
.admin-save-button {
color: #FFFFFF !important;
background: linear-gradient(135deg, var(--nak-orange), hsl(25, 95%, 40%)) !important;
border-color: hsl(25, 95%, 40%) !important;
}
.bs-date-input {
padding: 10px 12px;
font-size: var(--font-size-sm);
font-family: var(--font-primary);
border: 1px solid var(--gray-300);
background: var(--gray-50);
width: 100%;
box-sizing: border-box;
font-weight: 650;
}
.bs-date-input:focus {
border-color: hsl(25, 80%, 55%);
background: #FFFFFF;
outline: none;
box-shadow: 0 0 0 4px hsla(25, 80%, 55%, 0.14);
}
@media (max-width: 780px) {
.admin-users-shell { padding: 10px; }
.admin-list-header {
flex-direction: column;
align-items: flex-start;
padding: 14px;
gap: 8px;
}
table.admin-user-table {
display: block !important;
width: auto;
margin: 8px;
}
.admin-user-table thead { display: none; }
.admin-user-table tbody {
display: grid;
gap: 10px;
}
.admin-user-table tbody tr.admin-user-row {
display: grid;
grid-template-columns: 72px minmax(0, 1fr);
gap: 0 12px;
padding: 12px;
border: 1px solid rgba(120, 140, 160, 0.16);
box-shadow: 0 8px 22px rgba(31, 38, 46, 0.08);
}
.admin-user-table tbody tr.admin-user-row td {
display: block;
padding: 0 !important;
}
.admin-user-table tbody tr.admin-user-row td:first-child {
grid-row: 1 / span 3;
width: 72px;
min-width: 72px;
max-width: 72px;
}
.admin-profile-button,
.admin-profile-button img {
width: 64px !important;
height: 64px !important;
border-radius: 12px !important;
}
.admin-modal-card {
width: calc(100vw - 12px);
max-height: calc(100dvh - 12px);
}
.admin-modal-hero {
grid-template-columns: 78px minmax(0, 1fr);
gap: 14px;
padding: 18px;
}
.admin-modal-profile-img {
width: 72px;
height: 72px;
border-radius: 14px;
}
.admin-modal-title {
font-size: 1.15em;
margin-right: 40px;
}
.admin-modal-body { padding: 18px; }
.bs-detail-grid { grid-template-columns: 1fr; }
.admin-modal-actions { flex-direction: column-reverse; }
.admin-modal-actions button { width: 100%; }
}
</style>
</head>
<body class="theme-admin">
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
<div class="header_title ht_admin text_white unselectable" id="header_title">
<span class="header_title_text">Adminpanel</span>
</div>
<div class="content" id="fullsize">
<?php $adminActiveTab = 'brandschutz'; require __DIR__.'/_tabs.php'; ?>
<div class="admin-users-shell">
<div class="admin-table-card">
<div class="admin-list-header">
<div>
<span class="admin-list-title">Brandschutz-Schulungen</span>
<div class="admin-list-hint"><?= count($bsUsers) ?> Nutzer mit Brandschutz-Rolle</div>
</div>
</div>
<table class="admin-user-table" id="bsTable">
<thead>
<tr>
<th>Profil</th>
<th>Name</th>
<th>Nächste Fälligkeit</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($bsUsers as $bsUser): ?>
<?php
$nameOutput = getDisplayName_bs($bsUser);
$profileUrl = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
$lastTraining = $bsUser['last_training_date'];
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
// Berechne Fälligkeit
if (empty($lastTraining)) {
$dueDate = null;
$daysOverdue = null;
$statusText = 'Nie geschult';
$statusClass = 'bs-status-overdue';
$dueDateDisplay = 'Sofort fällig';
} else {
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
$today = date('Y-m-d');
$diff = (strtotime($dueDate) - strtotime($today)) / 86400;
if ($diff > 30) {
$statusText = 'Aktuell';
$statusClass = 'bs-status-ok';
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
} elseif ($diff > 0) {
$statusText = 'Bald fällig';
$statusClass = 'bs-status-warning';
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
} elseif ($diff > -120) {
$daysOverdue = abs((int)$diff);
$statusText = "{$daysOverdue} Tage überfällig";
$statusClass = 'bs-status-overdue';
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
} else {
$daysOverdue = abs((int)$diff);
$statusText = "{$daysOverdue} Tage überfällig";
$statusClass = 'bs-status-critical';
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
}
}
?>
<tr class="admin-user-row" tabindex="0" role="button" aria-label="<?= htmlspecialchars($nameOutput) ?> Details" onclick="openBsModal(<?= (int)$bsUser['user_id'] ?>)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();openBsModal(<?= (int)$bsUser['user_id'] ?>)}">
<td>
<button class="admin-profile-button" type="button" tabindex="-1" aria-hidden="true">
<img src="<?= htmlspecialchars($profileUrl) ?>" alt="Profilbild von <?= htmlspecialchars($nameOutput) ?>">
</button>
</td>
<td>
<span class="admin-user-name"><?= htmlspecialchars($nameOutput) ?></span>
</td>
<td>
<span class="bs-due-date"><?= $dueDateDisplay ?></span>
</td>
<td>
<span class="bs-status-badge <?= $statusClass ?>"><?= $statusText ?></span>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($bsUsers)): ?>
<tr><td colspan="4" style="padding:24px !important;text-align:center;color:hsl(210,20%,45%);background:#FFF !important;">Keine Nutzer mit der Rolle "Brandschutz" gefunden.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Modals für jeden Brandschutz-Nutzer -->
<?php foreach ($bsUsers as $bsUser): ?>
<?php
$nameOutput = getDisplayName_bs($bsUser);
$profileUrl = getProfileImageUrl_bs($bsUser, $g_profilePicturePath);
$lastTraining = $bsUser['last_training_date'];
$reminderCount = (int)($bsUser['reminder_count'] ?? 0);
$adminReminderCount = (int)($bsUser['admin_reminder_count'] ?? 0);
if (empty($lastTraining)) {
$dueDate = null;
$dueDateDisplay = 'Sofort fällig';
$lastTrainingDisplay = 'Nie';
$overdueText = 'Keine Schulung hinterlegt – sofort fällig';
$overdueClass = 'overdue';
} else {
$dueDate = date('Y-m-d', strtotime($lastTraining . ' +365 days'));
$dueDateDisplay = date('d.m.Y', strtotime($dueDate));
$lastTrainingDisplay = date('d.m.Y', strtotime($lastTraining));
$today = date('Y-m-d');
$diff = (strtotime($dueDate) - strtotime($today)) / 86400;
if ($diff > 0) {
$overdueText = 'Nicht überfällig (' . (int)$diff . ' Tage verbleibend)';
$overdueClass = 'ok';
} else {
$daysOverdue = abs((int)$diff);
$overdueText = "{$daysOverdue} Tage überfällig";
$overdueClass = 'overdue';
}
}
?>
<div class="standard_overlay" id="bsModal-<?= (int)$bsUser['user_id'] ?>" onclick="if(event.target.id==='bsModal-<?= (int)$bsUser['user_id'] ?>')closeBsModal(<?= (int)$bsUser['user_id'] ?>)">
<div class="admin-modal-card">
<button type="button" class="exit" aria-label="Schließen" onclick="closeBsModal(<?= (int)$bsUser['user_id'] ?>)">×</button>
<div class="admin-modal-hero">
<img class="admin-modal-profile-img" src="<?= htmlspecialchars($profileUrl) ?>" alt="Profilbild von <?= htmlspecialchars($nameOutput) ?>">
<div>
<div class="admin-modal-title"><?= htmlspecialchars($nameOutput) ?></div>
<div class="admin-modal-subtitle">
<span><?= htmlspecialchars($bsUser['mail'] ?? '') ?></span>
<span class="admin-modal-chip">Brandschutz</span>
</div>
<div class="admin-modal-subtitle" style="margin-bottom:0;">Schulungsdetails &amp; Fälligkeiten</div>
</div>
</div>
<div class="admin-modal-body">
<div class="admin-section-title">Schulungsstatus</div>
<div class="bs-detail-grid">
<div class="bs-detail-card">
<div class="bs-detail-label">Letzte Schulung</div>
<div class="bs-detail-value"><?= $lastTrainingDisplay ?></div>
</div>
<div class="bs-detail-card">
<div class="bs-detail-label">Nächste Fälligkeit</div>
<div class="bs-detail-value"><?= $dueDateDisplay ?></div>
</div>
<div class="bs-detail-card">
<div class="bs-detail-label">Überfälligkeit</div>
<div class="bs-detail-value <?= $overdueClass ?>"><?= $overdueText ?></div>
</div>
<div class="bs-detail-card">
<div class="bs-detail-label">Erinnerungsmails gesendet</div>
<div class="bs-detail-value"><?= $reminderCount ?></div>
</div>
<?php if ($adminReminderCount > 0): ?>
<div class="bs-detail-card">
<div class="bs-detail-label">Admin-Eskalationen</div>
<div class="bs-detail-value overdue"><?= $adminReminderCount ?></div>
</div>
<?php endif; ?>
</div>
<div class="admin-section-title">Schulungsdatum anpassen</div>
<form method="post" action="">
<input type="hidden" name="bs_user_id" value="<?= (int)$bsUser['user_id'] ?>">
<div style="display:flex;gap:10px;align-items:flex-end;margin-bottom:8px;">
<div style="flex:1;">
<label style="display:block;font-size:var(--font-size-xs);font-weight:600;color:var(--gray-600);margin-bottom:4px;">Datum der letzten Schulung</label>
<input type="date" name="bs_new_date" class="bs-date-input" value="<?= htmlspecialchars($lastTraining ?? '') ?>" required>
</div>
</div>
<p style="margin:0 0 16px 0;font-size:0.82em;color:hsl(210,14%,46%);font-weight:600;">Setzt das Schulungsdatum und alle Erinnerungszähler zurück.</p>
<div class="admin-modal-actions">
<div>
<button type="button" onclick="closeBsModal(<?= (int)$bsUser['user_id'] ?>)">Schließen</button>
</div>
<div>
<button type="submit" name="bs_update_date" value="1" class="admin-save-button">Datum speichern</button>
</div>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</div><!-- content -->
<script>
function openBsModal(userId) {
var modal = document.getElementById('bsModal-' + userId);
if (modal) modal.style.display = 'flex';
}
function closeBsModal(userId) {
var modal = document.getElementById('bsModal-' + userId);
if (modal) modal.style.display = 'none';
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
document.querySelectorAll('.standard_overlay[id^="bsModal-"]').forEach(function(m) {
m.style.display = 'none';
});
}
});
</script>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,564 @@
<?php /* Name: admin_inbox.php
Beschreibung: Admin-Nachrichteninbox
- Registrierungsanfragen
- Kontaktformular-Anfragen
- Systemnachrichten
- Seelsorge-Nachrichten
- Info-Nachrichten */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
// Admin-Check
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
$stmt->execute([$_SESSION["user_id"]]);
$isAdmin = $stmt->fetchColumn();
if ((int)$isAdmin !== 1) {
header("Location: {$g_httpHomePath}");
exit();
}
// Typ-Konfiguration für Inbox
$typeConfig = [
'registration' => ['label' => 'Registrierung', 'class' => 'inbox-type-registration'],
'contact' => ['label' => 'Kontakt', 'class' => 'inbox-type-contact'],
'system' => ['label' => 'System', 'class' => 'inbox-type-system'],
];
// Filter
$activeFilter = $_GET['filter'] ?? 'all';
$validFilters = array_merge(['all'], array_keys($typeConfig));
if (!in_array($activeFilter, $validFilters, true)) $activeFilter = 'all';
function getTypeLabel($type, $typeConfig) {
return $typeConfig[$type]['label'] ?? ucfirst($type);
}
function getTypeClass($type, $typeConfig) {
return $typeConfig[$type]['class'] ?? 'inbox-type-system';
}
// Kontaktnachricht als erledigt markieren
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["mark_done"])) {
$msgId = (int)$_POST["mark_done"];
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
$stmt->execute([$msgId]);
header("Location: admin_inbox.php?inbox_status=done");
exit();
}
// Brandschutz-Eskalation: Admin bestätigt persönliche Erinnerung
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST["bs_escalation_confirm"])) {
$msgId = (int)$_POST["bs_escalation_msg_id"];
$bsUserId = (int)$_POST["bs_escalation_user_id"];
// Nachricht als erledigt markieren
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
$stmt->execute([$msgId]);
// Log-Eintrag erstellen
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
if (empty($adminName)) $adminName = 'Ein Administrator';
$stmtUser = $pdo->prepare("SELECT first_name, last_name, mail FROM user_general_information WHERE user_id = ?");
$stmtUser->execute([$bsUserId]);
$bsUserData = $stmtUser->fetch(PDO::FETCH_ASSOC);
$bsUserName = trim(($bsUserData['first_name'] ?? '') . ' ' . ($bsUserData['last_name'] ?? ''));
if (empty($bsUserName)) $bsUserName = $bsUserData['mail'] ?? 'Unbekannt';
$logSubject = "Persönliche Brandschutz-Erinnerung bestätigt: {$bsUserName}";
$logMessage = "{$adminName} hat bestätigt, {$bsUserName} persönlich an die ausstehende Brandschutzunterweisung erinnert zu haben.";
$stmtLog = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$senderMail = $bsUserData['mail'] ?? '';
$stmtLog->execute([$adminName, $senderMail, $logSubject, $logMessage]);
header("Location: admin_inbox.php?inbox_status=done");
exit();
}
// Nachrichten laden (mit optionalem Filter)
$filterClause = '';
if ($activeFilter !== 'all') {
$filterClause = " AND type = '" . $activeFilter . "'";
}
$pendingMessages = $pdo->query("SELECT * FROM admin_messages WHERE status = 'pending'{$filterClause} ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC);
$doneMessages = $pdo->query("SELECT * FROM admin_messages WHERE status = 'done'{$filterClause} ORDER BY processed_at DESC LIMIT 20")->fetchAll(PDO::FETCH_ASSOC);
// Rollen laden für Registrierungs-Modal
$permissionRoles = [];
$rolesResult = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_name = 'user_permissions' AND table_schema = 'public'");
while ($col = $rolesResult->fetch(PDO::FETCH_ASSOC)) {
if (!in_array($col['column_name'], ['p_id', 'user_id'], true)) {
$permissionRoles[] = $col['column_name'];
}
}
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
.inbox-shell {
padding: 18px;
background: linear-gradient(135deg, var(--gray-50) 0%, var(--theme-light, var(--nak-blue-light)) 100%);
min-height: 100%;
}
.inbox-card {
overflow: hidden;
border: 1px solid rgba(120, 140, 160, 0.22);
background: rgba(255,255,255,0.86);
box-shadow: 0 20px 45px rgba(31, 38, 46, 0.10), 0 1px 0 rgba(255,255,255,0.9) inset;
margin-bottom: 18px;
}
.inbox-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 18px 12px 18px;
border-bottom: 1px solid rgba(120, 140, 160, 0.16);
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(255,255,255,0.72));
}
.inbox-title {
font-size: 1.05em;
font-weight: 800;
color: hsl(210, 20%, 15%);
}
.inbox-count {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 24px;
height: 24px;
padding: 0 7px;
background: hsl(0, 78%, 47%);
color: #FFFFFF;
font-size: 0.78em;
font-weight: 850;
}
.inbox-item {
padding: 16px 18px;
border-bottom: 1px solid rgba(120, 140, 160, 0.1);
transition: background 0.18s ease;
}
.inbox-item:last-child { border-bottom: none; }
.inbox-item:hover { background: hsl(210, 57%, 97%); }
.inbox-item-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 6px;
}
.inbox-item-type {
display: inline-flex;
align-items: center;
padding: 3px 8px;
font-size: 0.72em;
font-weight: 800;
}
.inbox-type-registration {
color: hsl(210, 57%, 34%);
background: hsl(210, 57%, 94%);
border: 1px solid hsl(210, 57%, 88%);
}
.inbox-type-contact {
color: hsl(145, 50%, 32%);
background: hsl(145, 50%, 93%);
border: 1px solid hsl(145, 50%, 85%);
}
.inbox-type-system {
color: hsl(30, 70%, 36%);
background: hsl(30, 70%, 94%);
border: 1px solid hsl(30, 70%, 85%);
}
.inbox-item-date {
font-size: 0.82em;
color: hsl(210, 14%, 50%);
font-weight: 600;
}
.inbox-item-subject {
font-weight: 800;
color: hsl(210, 20%, 15%);
margin-bottom: 4px;
}
.inbox-item-sender {
font-size: 0.9em;
color: hsl(210, 14%, 40%);
margin-bottom: 8px;
}
.inbox-item-message {
font-size: 0.9em;
color: hsl(210, 12%, 35%);
white-space: pre-wrap;
background: hsl(210, 12%, 98%);
padding: 10px 12px;
border: 1px solid hsl(210, 12%, 92%);
margin-bottom: 10px;
}
.inbox-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.inbox-actions form { display: inline; }
.inbox-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 32px;
padding: 6px 12px;
border: 1px solid hsl(210, 18%, 84%);
background: #FFFFFF;
color: hsl(210, 20%, 22%);
font-size: 0.82em;
font-weight: 850;
cursor: pointer;
text-decoration: none;
box-shadow: 0 4px 12px rgba(31,38,46,0.06);
transition: background 0.18s ease;
}
.inbox-btn:hover { background: hsl(210, 57%, 96%); }
.inbox-btn-approve {
color: #FFFFFF !important;
background: linear-gradient(135deg, var(--nak-darkgreen), #005a10) !important;
border-color: #005a10 !important;
}
.inbox-btn-reject {
color: var(--nak-red) !important;
border-color: hsl(352, 72%, 88%) !important;
background: hsl(352, 72%, 97%) !important;
}
.inbox-btn-done {
color: hsl(210, 14%, 46%) !important;
background: hsl(210, 12%, 95%) !important;
border-color: hsl(210, 12%, 88%) !important;
}
.inbox-empty {
padding: 24px;
text-align: center;
color: hsl(210, 14%, 50%);
font-weight: 600;
}
.inbox-section-title {
margin: 18px 0 10px 0;
font-size: 0.88em;
font-weight: 850;
text-transform: uppercase;
letter-spacing: 0.04em;
color: hsl(210, 14%, 46%);
}
.inbox-done-item {
opacity: 0.6;
}
@media (max-width: 780px) {
.inbox-shell { padding: 10px; }
.inbox-item-header { flex-direction: column; align-items: flex-start; gap: 4px; }
.inbox-filter-bar { gap: 6px; }
.inbox-filter-btn { font-size: 0.72em; padding: 5px 10px; }
}
.inbox-filter-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.inbox-filter-btn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 6px 14px;
border: 1px solid hsl(210, 18%, 84%);
background: #FFF;
color: hsl(210, 18%, 42%);
font-size: 0.82em;
font-weight: 750;
cursor: pointer;
transition: all 0.18s ease;
text-decoration: none;
}
.inbox-filter-btn:hover { background: hsl(210, 57%, 96%); }
.inbox-filter-btn.active {
background: hsl(210, 57%, 94%);
border-color: hsl(210, 57%, 72%);
color: hsl(210, 57%, 30%);
font-weight: 850;
}
</style>
</head>
<body class="theme-admin">
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
<?php if (isset($_GET["inbox_status"])): ?>
<?php
$inboxStatusMap = [
'done' => ['type' => 'success', 'text' => 'Nachricht als erledigt markiert.'],
'approved' => ['type' => 'success', 'text' => 'Registrierung freigegeben.'],
'rejected' => ['type' => 'success', 'text' => 'Registrierung abgelehnt.'],
'error' => ['type' => 'error', 'text' => 'Ein Fehler ist aufgetreten.'],
];
$statusKey = $_GET["inbox_status"];
$statusInfo = $inboxStatusMap[$statusKey] ?? ['type' => 'success', 'text' => 'Aktion ausgeführt.'];
?>
<div class="nak-toast nak-toast--<?= $statusInfo['type'] ?>" id="nakToast">
<div class="nak-toast__icon">
<?php if ($statusInfo['type'] === 'success'): ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
<?php else: ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<?php endif; ?>
</div>
<span class="nak-toast__text"><?= htmlspecialchars($statusInfo['text']) ?></span>
<button class="nak-toast__close" onclick="dismissNakToast()" aria-label="Schließen">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<script>
(function() {
var toast = document.getElementById('nakToast');
if (!toast) return;
requestAnimationFrame(function() { toast.classList.add('nak-toast--visible'); });
var timer = setTimeout(function() { dismissNakToast(); }, 4000);
toast._timer = timer;
if (window.history && window.history.replaceState) {
window.history.replaceState({}, '', 'admin_inbox.php');
}
})();
function dismissNakToast() {
var toast = document.getElementById('nakToast');
if (!toast || toast.classList.contains('nak-toast--hiding')) return;
clearTimeout(toast._timer);
toast.classList.add('nak-toast--hiding');
toast.classList.remove('nak-toast--visible');
setTimeout(function() { toast.remove(); }, 350);
}
</script>
<?php endif; ?>
<div class="header_title ht_admin text_white" id="header_title">
<span class="header_title_text">Adminpanel</span>
</div>
<div class="content" id="fullsize">
<?php $adminActiveTab = 'inbox'; require __DIR__.'/_tabs.php'; ?>
<div class="inbox-shell">
<!-- Filter-Leiste -->
<div class="inbox-filter-bar">
<a href="?filter=all" class="inbox-filter-btn <?= $activeFilter === 'all' ? 'active' : '' ?>">Alle</a>
<?php foreach ($typeConfig as $typeKey => $typeCfg): ?>
<a href="?filter=<?= $typeKey ?>" class="inbox-filter-btn <?= $activeFilter === $typeKey ? 'active' : '' ?>"><?= $typeCfg['label'] ?></a>
<?php endforeach; ?>
</div>
<!-- Offene Nachrichten -->
<div class="inbox-card">
<div class="inbox-header">
<div class="inbox-title">Offene Anfragen</div>
<?php if (count($pendingMessages) > 0): ?>
<span class="inbox-count"><?= count($pendingMessages) ?></span>
<?php endif; ?>
</div>
<?php if (count($pendingMessages) === 0): ?>
<div class="inbox-empty">Keine offenen Anfragen vorhanden.</div>
<?php else: ?>
<?php foreach ($pendingMessages as $msg): ?>
<div class="inbox-item">
<div class="inbox-item-header">
<div>
<span class="inbox-item-type <?= getTypeClass($msg['type'], $typeConfig) ?>">
<?= getTypeLabel($msg['type'], $typeConfig) ?>
</span>
</div>
<span class="inbox-item-date"><?= date('d.m.Y, H:i', strtotime($msg['created_at'])) ?> Uhr</span>
</div>
<div class="inbox-item-subject"><?= htmlspecialchars($msg['subject'] ?? '') ?></div>
<div class="inbox-item-sender">
Von: <?= htmlspecialchars($msg['sender_name'] ?? 'Unbekannt') ?>
(<?= htmlspecialchars($msg['sender_email'] ?? '') ?>)
</div>
<div class="inbox-item-message"><?= htmlspecialchars($msg['message'] ?? '') ?></div>
<div class="inbox-actions">
<?php if ($msg['type'] === 'registration'): ?>
<?php $data = json_decode($msg['data_json'], true); ?>
<button type="button" class="inbox-btn inbox-btn-approve" onclick="openRegistrationModal(<?= (int)($data['pending_id'] ?? 0) ?>, <?= (int)$msg['id'] ?>, '<?= htmlspecialchars($msg['sender_name'] ?? '', ENT_QUOTES) ?>', '<?= htmlspecialchars($msg['sender_email'] ?? '', ENT_QUOTES) ?>')">Anfrage bearbeiten</button>
<?php elseif ($msg['type'] === 'system' && !empty($msg['data_json'])): ?>
<?php
$data = json_decode($msg['data_json'], true);
if (isset($data['action']) && $data['action'] === 'brandschutz_escalation'):
?>
<button type="button" class="inbox-btn inbox-btn-approve" style="background:linear-gradient(135deg,var(--nak-orange),hsl(25,95%,40%)) !important;border-color:hsl(25,95%,40%) !important;" onclick="openBsEscalationModal(<?= (int)$msg['id'] ?>, <?= (int)$data['user_id'] ?>, '<?= htmlspecialchars($data['user_name'] ?? '', ENT_QUOTES) ?>', <?= (int)($data['days_overdue'] ?? 0) ?>)">Persönliche Erinnerung bestätigen</button>
<?php else: ?>
<form method="post" action="">
<input type="hidden" name="mark_done" value="<?= (int)$msg['id'] ?>">
<button type="submit" class="inbox-btn inbox-btn-done">Als erledigt markieren</button>
</form>
<?php endif; ?>
<?php else: ?>
<form method="post" action="">
<input type="hidden" name="mark_done" value="<?= (int)$msg['id'] ?>">
<button type="submit" class="inbox-btn inbox-btn-done">Als erledigt markieren</button>
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- Erledigte Nachrichten -->
<?php if (count($doneMessages) > 0): ?>
<div class="inbox-section-title">Erledigte Anfragen (letzte 20)</div>
<div class="inbox-card">
<?php foreach ($doneMessages as $msg): ?>
<div class="inbox-item inbox-done-item">
<div class="inbox-item-header">
<div>
<span class="inbox-item-type <?= getTypeClass($msg['type'], $typeConfig) ?>">
<?= getTypeLabel($msg['type'], $typeConfig) ?>
</span>
</div>
<span class="inbox-item-date">Erledigt: <?= date('d.m.Y, H:i', strtotime($msg['processed_at'])) ?> Uhr</span>
</div>
<div class="inbox-item-subject"><?= htmlspecialchars($msg['subject'] ?? '') ?></div>
<div class="inbox-item-sender">
Von: <?= htmlspecialchars($msg['sender_name'] ?? 'Unbekannt') ?>
(<?= htmlspecialchars($msg['sender_email'] ?? '') ?>)
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div><!-- inbox-shell -->
</div><!-- content -->
<!-- Registrierungs-Modal -->
<div class="standard_overlay" id="registrationModal" onclick="if(event.target.id==='registrationModal')closeRegistrationModal()">
<div style="position:relative;width:min(560px,calc(100vw - 24px));max-height:min(700px,calc(100vh - 24px));overflow:auto;background:#FFF;box-shadow:0 24px 70px rgba(0,0,0,0.36);">
<button type="button" aria-label="Schließen" onclick="closeRegistrationModal()" style="position:absolute;top:14px;right:14px;width:34px;height:34px;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.16);color:#FFF;font-size:1.35em;cursor:pointer;z-index:1;">×</button>
<div style="padding:24px;color:#FFF;background:radial-gradient(circle at 10% 0%,rgba(255,255,255,0.24),transparent 32%),linear-gradient(135deg,var(--gray-800),var(--gray-900));">
<div style="font-size:1.3em;font-weight:850;">Registrierung bearbeiten</div>
<div id="regModalUserInfo" style="margin-top:6px;color:rgba(255,255,255,0.8);font-weight:600;"></div>
</div>
<div style="padding:22px 24px 24px 24px;">
<form method="post" action="approve-registration.php" id="regModalForm">
<input type="hidden" name="pending_id" id="regModalPendingId">
<input type="hidden" name="message_id" id="regModalMessageId">
<input type="hidden" name="action" id="regModalAction">
<div style="margin-bottom:16px;">
<label style="display:block;margin-bottom:8px;font-weight:750;font-size:0.92em;color:hsl(210,18%,36%);">Rollen zuweisen:</label>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<?php foreach ($permissionRoles as $role): ?>
<label style="display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid hsl(210,18%,84%);background:hsl(210,12%,98%);cursor:pointer;font-weight:650;font-size:0.88em;hyphens:auto;-webkit-hyphens:auto;overflow-wrap:break-word;">
<input type="checkbox" name="roles[]" value="<?= htmlspecialchars($role) ?>" style="accent-color:hsl(210,57%,45%);flex-shrink:0;min-width:16px;min-height:16px;">
<?= htmlspecialchars(genderRole($role)) ?>
</label>
<?php endforeach; ?>
</div>
</div>
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:20px;">
<button type="button" onclick="submitRegistrationAction('reject')" class="inbox-btn inbox-btn-reject" style="padding:10px 18px;font-size:0.9em;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>Ablehnen</button>
<button type="button" onclick="submitRegistrationAction('approve')" class="inbox-btn inbox-btn-approve" style="padding:10px 18px;font-size:0.9em;"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>Freischalten</button>
</div>
</form>
</div>
</div>
</div>
<script>
function openRegistrationModal(pendingId, messageId, name, email) {
document.getElementById('regModalPendingId').value = pendingId;
document.getElementById('regModalMessageId').value = messageId;
document.getElementById('regModalUserInfo').textContent = name + ' (' + email + ')';
document.getElementById('registrationModal').style.display = 'flex';
}
function closeRegistrationModal() {
document.getElementById('registrationModal').style.display = 'none';
}
function submitRegistrationAction(action) {
var confirmMsg = action === 'approve' ? 'Nutzer mit den gewählten Rollen freischalten?' : 'Registrierung ablehnen?';
if (!confirm(confirmMsg)) return;
document.getElementById('regModalAction').value = action;
document.getElementById('regModalForm').submit();
}
// Brandschutz Eskalation Modal
function openBsEscalationModal(msgId, userId, userName, daysOverdue) {
document.getElementById('bsEscMsgId').value = msgId;
document.getElementById('bsEscUserId').value = userId;
document.getElementById('bsEscUserName').textContent = userName;
document.getElementById('bsEscDaysOverdue').textContent = daysOverdue + ' Tage';
document.getElementById('bsEscalationModal').style.display = 'flex';
}
function closeBsEscalationModal() {
document.getElementById('bsEscalationModal').style.display = 'none';
}
function submitBsEscalation() {
document.getElementById('bsEscForm').submit();
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
if (document.getElementById('registrationModal').style.display === 'flex') {
closeRegistrationModal();
}
if (document.getElementById('bsEscalationModal').style.display === 'flex') {
closeBsEscalationModal();
}
}
});
</script>
<!-- Brandschutz Eskalation Modal -->
<div class="standard_overlay" id="bsEscalationModal" onclick="if(event.target.id==='bsEscalationModal')closeBsEscalationModal()">
<div style="position:relative;width:min(520px,calc(100vw - 24px));max-height:min(500px,calc(100vh - 24px));overflow:auto;background:#FFF;box-shadow:0 24px 70px rgba(0,0,0,0.36);">
<button type="button" aria-label="Schließen" onclick="closeBsEscalationModal()" style="position:absolute;top:14px;right:14px;width:34px;height:34px;border:1px solid rgba(255,255,255,0.25);background:rgba(255,255,255,0.16);color:#FFF;font-size:1.35em;cursor:pointer;z-index:1;">×</button>
<div style="padding:24px;color:#FFF;background:radial-gradient(circle at 10% 0%,rgba(255,255,255,0.24),transparent 32%),linear-gradient(135deg,hsl(25,90%,45%),hsl(25,95%,35%));">
<div style="font-size:1.3em;font-weight:850;">Persönliche Erinnerung bestätigen</div>
<div style="margin-top:6px;color:rgba(255,255,255,0.8);font-weight:600;">Brandschutzunterweisung – Eskalation</div>
</div>
<div style="padding:22px 24px 24px 24px;">
<p style="margin:0 0 14px 0;font-size:0.95em;color:hsl(210,20%,25%);line-height:1.6;">
Bitte bestätigen Sie, dass Sie <strong id="bsEscUserName"></strong> persönlich an die ausstehende Brandschutzunterweisung erinnert haben.
</p>
<div style="padding:12px 14px;background:hsl(0,65%,95%);border:1px solid hsl(0,65%,85%);margin-bottom:18px;">
<div style="font-size:0.78em;font-weight:850;text-transform:uppercase;letter-spacing:0.04em;color:hsl(0,65%,38%);margin-bottom:4px;">Überfälligkeit</div>
<div style="font-size:1.1em;font-weight:850;color:hsl(0,65%,35%);" id="bsEscDaysOverdue"></div>
</div>
<form method="post" action="" id="bsEscForm">
<input type="hidden" name="bs_escalation_msg_id" id="bsEscMsgId">
<input type="hidden" name="bs_escalation_user_id" id="bsEscUserId">
<input type="hidden" name="bs_escalation_confirm" value="1">
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:16px;">
<button type="button" onclick="closeBsEscalationModal()" class="inbox-btn" style="padding:10px 18px;font-size:0.9em;">Abbrechen</button>
<button type="submit" class="inbox-btn inbox-btn-approve" style="padding:10px 18px;font-size:0.9em;background:linear-gradient(135deg,var(--nak-orange),hsl(25,95%,40%)) !important;border-color:hsl(25,95%,40%) !important;">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" width="16" height="16" style="vertical-align:middle;margin-right:4px;"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
Bestätigen
</button>
</div>
</form>
</div>
</div>
</div>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,471 @@
<?php
/* Name: admin_notices.php
Beschreibung: Verwaltung der Meldungen auf dem Intranet-Hauptmenü */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
// Admin-Check
$stmt = $pdo->prepare('SELECT "Admin" FROM user_permissions WHERE user_id = ?');
$stmt->execute([$_SESSION['user_id']]);
$isAdmin = $stmt->fetchColumn();
if ((int)$isAdmin !== 1) {
header("Location: {$g_httpHomePath}");
exit();
}
const NOTICE_MAX_TITLE_LENGTH = 180;
const NOTICE_MAX_MESSAGE_LENGTH = 4000;
$noticeTypes = [
'info' => 'Information',
'warning' => 'Warnung',
'success' => 'Erfolg',
'error' => 'Wichtig',
];
if (empty($_SESSION['admin_notices_csrf'])) {
$_SESSION['admin_notices_csrf'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['admin_notices_csrf'];
function noticeRedirect(string $status): void {
header('Location: admin_notices.php?status=' . rawurlencode($status));
exit();
}
function noticeVerifyCsrf(): void {
$postedToken = $_POST['csrf_token'] ?? '';
$sessionToken = $_SESSION['admin_notices_csrf'] ?? '';
if (!is_string($postedToken) || !is_string($sessionToken) || $sessionToken === '' || !hash_equals($sessionToken, $postedToken)) {
http_response_code(400);
exit('Ungültige Anfrage. Bitte laden Sie die Seite neu.');
}
}
function noticeParseLocalDateTime(?string $value): ?string {
$value = trim((string)$value);
if ($value === '') {
return null;
}
$tz = new DateTimeZone('Europe/Berlin');
$date = DateTimeImmutable::createFromFormat('!Y-m-d\TH:i', $value, $tz);
$errors = DateTimeImmutable::getLastErrors();
if (!$date || ($errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0))) {
throw new InvalidArgumentException('Ungültiges Datum.');
}
// Mit explizitem Offset an PostgreSQL senden, damit TIMESTAMPTZ eindeutig ist.
return $date->format('Y-m-d H:i:sP');
}
function noticeFormatLocalDateTime(?string $value): string {
if (empty($value)) {
return '';
}
try {
return (new DateTimeImmutable($value))->setTimezone(new DateTimeZone('Europe/Berlin'))->format('Y-m-d\TH:i');
} catch (Throwable $e) {
return '';
}
}
function noticeDisplayDate(?string $value): string {
if (empty($value)) {
return 'unbegrenzt';
}
try {
return (new DateTimeImmutable($value))->setTimezone(new DateTimeZone('Europe/Berlin'))->format('d.m.Y, H:i') . ' Uhr';
} catch (Throwable $e) {
return '–';
}
}
function noticeDbBool($value): bool {
return $value === true || $value === 1 || $value === '1' || $value === 't' || $value === 'true';
}
function noticeTextLength(string $value): int {
return function_exists('mb_strlen') ? mb_strlen($value, 'UTF-8') : strlen($value);
}
function noticeValidateInput(array $noticeTypes): array {
$title = trim((string)($_POST['title'] ?? ''));
$message = trim((string)($_POST['message'] ?? ''));
$type = (string)($_POST['type'] ?? 'info');
$sortOrder = filter_var($_POST['sort_order'] ?? 0, FILTER_VALIDATE_INT);
$isActive = isset($_POST['is_active']);
if ($title === '' || noticeTextLength($title) > NOTICE_MAX_TITLE_LENGTH) {
throw new InvalidArgumentException('Titel fehlt oder ist zu lang.');
}
if ($message === '' || noticeTextLength($message) > NOTICE_MAX_MESSAGE_LENGTH) {
throw new InvalidArgumentException('Meldung fehlt oder ist zu lang.');
}
if (!array_key_exists($type, $noticeTypes)) {
throw new InvalidArgumentException('Ungültiger Meldungstyp.');
}
if ($sortOrder === false || $sortOrder < -9999 || $sortOrder > 9999) {
throw new InvalidArgumentException('Ungültige Sortierung.');
}
$startsAt = noticeParseLocalDateTime($_POST['starts_at'] ?? null);
$endsAt = noticeParseLocalDateTime($_POST['ends_at'] ?? null);
if ($startsAt !== null && $endsAt !== null) {
$start = new DateTimeImmutable($startsAt);
$end = new DateTimeImmutable($endsAt);
if ($end <= $start) {
throw new InvalidArgumentException('Das Ende muss nach dem Beginn liegen.');
}
}
return [
'title' => $title,
'message' => $message,
'type' => $type,
'sort_order' => (int)$sortOrder,
'is_active' => $isActive,
'starts_at' => $startsAt,
'ends_at' => $endsAt,
];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
noticeVerifyCsrf();
$action = (string)($_POST['action'] ?? '');
try {
if ($action === 'create') {
$data = noticeValidateInput($noticeTypes);
$stmt = $pdo->prepare(
'INSERT INTO mainpage_notices
(title, message, type, is_active, starts_at, ends_at, sort_order, created_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())'
);
$stmt->execute([
$data['title'], $data['message'], $data['type'], $data['is_active'] ? 'true' : 'false',
$data['starts_at'], $data['ends_at'], $data['sort_order'], (int)$_SESSION['user_id'],
]);
noticeRedirect('created');
}
if ($action === 'update') {
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if (!$id || $id < 1) {
throw new InvalidArgumentException('Ungültige Meldungs-ID.');
}
$data = noticeValidateInput($noticeTypes);
$stmt = $pdo->prepare(
'UPDATE mainpage_notices
SET title = ?, message = ?, type = ?, is_active = ?, starts_at = ?, ends_at = ?, sort_order = ?, updated_at = NOW()
WHERE id = ?'
);
$stmt->execute([
$data['title'], $data['message'], $data['type'], $data['is_active'] ? 'true' : 'false',
$data['starts_at'], $data['ends_at'], $data['sort_order'], (int)$id,
]);
noticeRedirect('updated');
}
if ($action === 'toggle') {
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if (!$id || $id < 1) {
throw new InvalidArgumentException('Ungültige Meldungs-ID.');
}
$stmt = $pdo->prepare('UPDATE mainpage_notices SET is_active = NOT is_active, updated_at = NOW() WHERE id = ?');
$stmt->execute([(int)$id]);
noticeRedirect('toggled');
}
if ($action === 'delete') {
$id = filter_var($_POST['id'] ?? null, FILTER_VALIDATE_INT);
if (!$id || $id < 1) {
throw new InvalidArgumentException('Ungültige Meldungs-ID.');
}
$stmt = $pdo->prepare('DELETE FROM mainpage_notices WHERE id = ?');
$stmt->execute([(int)$id]);
noticeRedirect('deleted');
}
throw new InvalidArgumentException('Unbekannte Aktion.');
} catch (InvalidArgumentException $e) {
$_SESSION['admin_notices_error'] = $e->getMessage();
noticeRedirect('error');
} catch (PDOException $e) {
$_SESSION['admin_notices_error'] = 'Datenbankfehler. Prüfen Sie, ob die Tabelle mainpage_notices angelegt wurde.';
noticeRedirect('error');
}
}
$noticeError = $_SESSION['admin_notices_error'] ?? '';
unset($_SESSION['admin_notices_error']);
$status = (string)($_GET['status'] ?? '');
$statusMessages = [
'created' => 'Meldung wurde angelegt.',
'updated' => 'Meldung wurde gespeichert.',
'toggled' => 'Status der Meldung wurde geändert.',
'deleted' => 'Meldung wurde gelöscht.',
];
$tableAvailable = true;
$notices = [];
try {
$notices = $pdo->query(
'SELECT id, title, message, type, is_active, starts_at, ends_at, sort_order, created_at, updated_at
FROM mainpage_notices
ORDER BY sort_order ASC, created_at DESC, id DESC'
)->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
$tableAvailable = false;
$noticeError = 'Die Tabelle mainpage_notices fehlt. Führen Sie zuerst general_files/ini/mainpage_notices.sql aus.';
}
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
.notice-admin-shell { padding: 18px; min-height: 100%; }
.notice-admin-card { background:#fff; border:1px solid var(--gray-200); box-shadow:var(--shadow-md); margin-bottom:18px; }
.notice-admin-header { display:flex; align-items:center; justify-content:space-between; gap:16px; padding:16px 18px; border-bottom:1px solid var(--gray-200); }
.notice-admin-title { font-size:1.05em; font-weight:800; color:var(--gray-900); }
.notice-admin-hint { margin-top:3px; font-size:.86em; color:var(--gray-500); }
.notice-admin-button { display:inline-flex; align-items:center; justify-content:center; min-height:36px; padding:7px 14px; border:1px solid var(--gray-800); background:var(--gray-900); color:#fff; font:inherit; font-size:.86em; font-weight:750; cursor:pointer; text-decoration:none; }
.notice-admin-button:hover { color:#fff; background:#111; text-decoration:none; }
.notice-admin-button--secondary { color:var(--gray-800); background:#fff; border-color:var(--gray-300); }
.notice-admin-button--secondary:hover { color:var(--gray-900); background:var(--gray-50); }
.notice-admin-button--danger { color:var(--nak-red); background:#fff; border-color:hsl(352,72%,84%); }
.notice-admin-button--danger:hover { color:#fff; background:var(--nak-red); }
.notice-admin-grid { display:grid; gap:12px; padding:16px 18px 18px; }
.notice-admin-item { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:16px; padding:16px; border:1px solid var(--gray-200); background:var(--gray-50); }
.notice-admin-item.is-disabled { opacity:.62; }
.notice-admin-item-title { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:6px; font-weight:800; color:var(--gray-900); }
.notice-admin-chip { display:inline-flex; align-items:center; padding:3px 8px; font-size:.72em; font-weight:800; border-left:3px solid currentColor; background:#fff; }
.notice-admin-chip--info { color:var(--nak-blue); }
.notice-admin-chip--warning { color:var(--nak-orange); }
.notice-admin-chip--success { color:var(--nak-darkgreen); }
.notice-admin-chip--error { color:var(--nak-red); }
.notice-admin-message { white-space:pre-wrap; line-height:1.45; color:var(--gray-700); font-size:.92em; }
.notice-admin-meta { display:flex; gap:14px; flex-wrap:wrap; margin-top:10px; color:var(--gray-500); font-size:.8em; }
.notice-admin-actions { display:flex; align-items:flex-start; gap:7px; flex-wrap:wrap; justify-content:flex-end; }
.notice-admin-actions form { margin:0; }
.notice-admin-empty { padding:28px 18px; text-align:center; color:var(--gray-500); }
.notice-admin-alert { margin:16px 18px 0; padding:11px 13px; border-left:4px solid var(--nak-darkgreen); background:hsl(145,50%,94%); color:hsl(145,50%,28%); font-size:.9em; }
.notice-admin-alert--error { border-color:var(--nak-red); background:hsl(352,72%,96%); color:hsl(352,62%,35%); }
.notice-modal { position:fixed; inset:0; z-index:5000; display:none; align-items:center; justify-content:center; padding:20px; background:rgba(16,24,32,.62); }
.notice-modal.open { display:flex; }
.notice-modal-card { width:min(680px,100%); max-height:calc(100dvh - 40px); overflow:auto; background:#fff; box-shadow:var(--shadow-xl); }
.notice-modal-header { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:18px 20px; border-bottom:1px solid var(--gray-200); }
.notice-modal-title { font-size:1.08em; font-weight:800; color:var(--gray-900); }
.notice-modal-close { border:0; background:transparent; font-size:1.5em; line-height:1; cursor:pointer; color:var(--gray-500); }
.notice-modal-body { padding:20px; }
.notice-form-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
.notice-field { display:flex; flex-direction:column; gap:6px; }
.notice-field--full { grid-column:1 / -1; }
.notice-field label { font-size:.83em; font-weight:750; color:var(--gray-700); }
.notice-field input, .notice-field select, .notice-field textarea { width:100%; box-sizing:border-box; padding:10px 11px; border:1px solid var(--gray-300); background:#fff; color:var(--gray-900); font:inherit; font-size:.9em; }
.notice-field textarea { min-height:120px; resize:vertical; }
.notice-checkbox { display:flex; align-items:center; gap:8px; min-height:40px; }
.notice-checkbox input { width:auto; }
.notice-form-note { margin-top:5px; font-size:.78em; color:var(--gray-500); }
.notice-modal-actions { display:flex; justify-content:flex-end; gap:8px; padding:0 20px 20px; }
@media (max-width:760px) {
.notice-admin-shell { padding:10px; }
.notice-admin-header, .notice-admin-item { grid-template-columns:1fr; display:grid; }
.notice-admin-actions { justify-content:flex-start; }
.notice-form-grid { grid-template-columns:1fr; }
.notice-field--full { grid-column:auto; }
.content-tab-bar { overflow-x:auto; }
}
</style>
</head>
<body class="theme-admin">
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<?php include $g_phpSnippetsPath.'overlay_message.php'; ?>
<div class="header_title ht_admin text_white unselectable" id="header_title">
<span class="header_title_text">Adminpanel</span>
</div>
<div class="content" id="fullsize">
<?php $adminActiveTab = 'notices'; require __DIR__.'/_tabs.php'; ?>
<div class="notice-admin-shell">
<div class="notice-admin-card">
<div class="notice-admin-header">
<div>
<div class="notice-admin-title">Meldungen im Hauptmenü</div>
<div class="notice-admin-hint">Meldungen erscheinen ausschließlich oberhalb der Kacheln auf der Intranet-Hauptseite.</div>
</div>
<?php if ($tableAvailable): ?>
<button type="button" class="notice-admin-button" onclick="openNoticeModal()">+ Neue Meldung</button>
<?php endif; ?>
</div>
<?php if ($status === 'error' && $noticeError !== ''): ?>
<div class="notice-admin-alert notice-admin-alert--error"><?= htmlspecialchars($noticeError, ENT_QUOTES, 'UTF-8') ?></div>
<?php elseif (!$tableAvailable && $noticeError !== ''): ?>
<div class="notice-admin-alert notice-admin-alert--error"><?= htmlspecialchars($noticeError, ENT_QUOTES, 'UTF-8') ?></div>
<?php elseif (isset($statusMessages[$status])): ?>
<div class="notice-admin-alert"><?= htmlspecialchars($statusMessages[$status], ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
<?php if ($tableAvailable): ?>
<?php if (empty($notices)): ?>
<div class="notice-admin-empty">Noch keine Meldungen vorhanden.</div>
<?php else: ?>
<div class="notice-admin-grid">
<?php foreach ($notices as $notice): ?>
<?php
$noticeId = (int)$notice['id'];
$noticeType = array_key_exists($notice['type'], $noticeTypes) ? $notice['type'] : 'info';
$editPayload = [
'id' => $noticeId,
'title' => $notice['title'],
'message' => $notice['message'],
'type' => $noticeType,
'is_active' => noticeDbBool($notice['is_active']),
'starts_at' => noticeFormatLocalDateTime($notice['starts_at']),
'ends_at' => noticeFormatLocalDateTime($notice['ends_at']),
'sort_order' => (int)$notice['sort_order'],
];
?>
<div class="notice-admin-item<?= noticeDbBool($notice['is_active']) ? '' : ' is-disabled' ?>">
<div>
<div class="notice-admin-item-title">
<?= htmlspecialchars($notice['title'], ENT_QUOTES, 'UTF-8') ?>
<span class="notice-admin-chip notice-admin-chip--<?= htmlspecialchars($noticeType, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($noticeTypes[$noticeType], ENT_QUOTES, 'UTF-8') ?></span>
<?php if (!noticeDbBool($notice['is_active'])): ?><span class="notice-admin-chip">Deaktiviert</span><?php endif; ?>
</div>
<div class="notice-admin-message"><?= htmlspecialchars($notice['message'], ENT_QUOTES, 'UTF-8') ?></div>
<div class="notice-admin-meta">
<span>Von: <?= htmlspecialchars(noticeDisplayDate($notice['starts_at']), ENT_QUOTES, 'UTF-8') ?></span>
<span>Bis: <?= htmlspecialchars(noticeDisplayDate($notice['ends_at']), ENT_QUOTES, 'UTF-8') ?></span>
<span>Sortierung: <?= (int)$notice['sort_order'] ?></span>
</div>
</div>
<div class="notice-admin-actions">
<button type="button" class="notice-admin-button notice-admin-button--secondary" onclick='openNoticeModal(<?= json_encode($editPayload, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>)'>Bearbeiten</button>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
<input type="hidden" name="action" value="toggle">
<input type="hidden" name="id" value="<?= $noticeId ?>">
<button type="submit" class="notice-admin-button notice-admin-button--secondary"><?= noticeDbBool($notice['is_active']) ? 'Deaktivieren' : 'Aktivieren' ?></button>
</form>
<form method="post" onsubmit="return confirm('Meldung wirklich dauerhaft löschen?');">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="<?= $noticeId ?>">
<button type="submit" class="notice-admin-button notice-admin-button--danger">Löschen</button>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
</div>
<div class="notice-modal" id="noticeModal" aria-hidden="true">
<div class="notice-modal-card" role="dialog" aria-modal="true" aria-labelledby="noticeModalTitle">
<form method="post" id="noticeForm">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken, ENT_QUOTES, 'UTF-8') ?>">
<input type="hidden" name="action" id="noticeAction" value="create">
<input type="hidden" name="id" id="noticeId" value="">
<div class="notice-modal-header">
<div class="notice-modal-title" id="noticeModalTitle">Neue Meldung</div>
<button type="button" class="notice-modal-close" onclick="closeNoticeModal()" aria-label="Schließen">×</button>
</div>
<div class="notice-modal-body">
<div class="notice-form-grid">
<div class="notice-field notice-field--full">
<label for="noticeTitle">Titel</label>
<input id="noticeTitle" name="title" type="text" maxlength="180" required>
</div>
<div class="notice-field notice-field--full">
<label for="noticeMessage">Meldung</label>
<textarea id="noticeMessage" name="message" maxlength="4000" required></textarea>
</div>
<div class="notice-field">
<label for="noticeType">Typ</label>
<select id="noticeType" name="type">
<?php foreach ($noticeTypes as $typeKey => $typeLabel): ?>
<option value="<?= htmlspecialchars($typeKey, ENT_QUOTES, 'UTF-8') ?>"><?= htmlspecialchars($typeLabel, ENT_QUOTES, 'UTF-8') ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="notice-field">
<label for="noticeSortOrder">Sortierung</label>
<input id="noticeSortOrder" name="sort_order" type="number" min="-9999" max="9999" value="0" required>
<div class="notice-form-note">Kleinere Zahlen erscheinen zuerst.</div>
</div>
<div class="notice-field">
<label for="noticeStartsAt">Anzeigen ab</label>
<input id="noticeStartsAt" name="starts_at" type="datetime-local">
<div class="notice-form-note">Leer = sofort.</div>
</div>
<div class="notice-field">
<label for="noticeEndsAt">Anzeigen bis</label>
<input id="noticeEndsAt" name="ends_at" type="datetime-local">
<div class="notice-form-note">Leer = ohne Ablaufdatum.</div>
</div>
<div class="notice-field notice-field--full">
<label class="notice-checkbox">
<input id="noticeIsActive" name="is_active" type="checkbox" value="1" checked>
Meldung aktiv
</label>
</div>
</div>
</div>
<div class="notice-modal-actions">
<button type="button" class="notice-admin-button notice-admin-button--secondary" onclick="closeNoticeModal()">Abbrechen</button>
<button type="submit" class="notice-admin-button">Speichern</button>
</div>
</form>
</div>
</div>
<script>
function openNoticeModal(data) {
data = data || null;
var modal = document.getElementById('noticeModal');
document.getElementById('noticeAction').value = data ? 'update' : 'create';
document.getElementById('noticeId').value = data ? data.id : '';
document.getElementById('noticeTitle').value = data ? data.title : '';
document.getElementById('noticeMessage').value = data ? data.message : '';
document.getElementById('noticeType').value = data ? data.type : 'warning';
document.getElementById('noticeSortOrder').value = data ? data.sort_order : 0;
document.getElementById('noticeStartsAt').value = data ? data.starts_at : '';
document.getElementById('noticeEndsAt').value = data ? data.ends_at : '';
document.getElementById('noticeIsActive').checked = data ? !!data.is_active : true;
document.getElementById('noticeModalTitle').textContent = data ? 'Meldung bearbeiten' : 'Neue Meldung';
modal.classList.add('open');
modal.setAttribute('aria-hidden', 'false');
document.getElementById('noticeTitle').focus();
}
function closeNoticeModal() {
var modal = document.getElementById('noticeModal');
modal.classList.remove('open');
modal.setAttribute('aria-hidden', 'true');
}
document.getElementById('noticeModal').addEventListener('click', function(event) {
if (event.target === this) closeNoticeModal();
});
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') closeNoticeModal();
});
</script>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,53 @@
<?php /* Name: admin_profiles.php
Beschreibung: Administratoren-Seite zur Nutzerverwaltung */
// Initialisierung
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
</head>
<body class="theme-admin">
<?php /* Besondere Admin-Rechte checken */
// Fetch user details
$stmt = $pdo->prepare("SELECT * FROM user_permissions up INNER JOIN user_general_information ugi ON up.user_id = ugi.user_id WHERE up.user_id = ?");
$stmt->execute([$_SESSION["user_id"]]);
$isAdmin = $stmt->fetch(PDO::FETCH_ASSOC);
// Check if the user has admin access (Admin = 1)
if ($isAdmin["Admin"] == 1) {
$sql_groups = "SELECT * FROM user_permissions";
$stmt = $pdo->prepare($sql_groups);
$stmt->execute([]);
$groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
header("Location: {$g_httpHomePath}");
exit();
}
?>
<?php // Es folgen ab hier die HTML-Ausgaben beginnend mit dem HTML-Header: ?>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/header.php'; ?>
<?php // Header (einfach): Titel der Seite ?>
<div class="header_title ht_admin text_white" id="header_title">
<div class="flexrow fullwidth" style="justify-content:space-between !important;">
<div class="header_title_text">Nutzerverwaltung</div>
</div>
</div>
<?php // Profilanzeige
require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/projects/intranet/show_profile.php"); ?>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,182 @@
<?php /* Name: approve-registration.php
Beschreibung: Admin genehmigt oder lehnt eine Registrierungsanfrage ab.
- Setzt Rollen bei Freischaltung
- Sendet Mails an Nutzer UND Admins
POST-Parameter: action (approve/reject), pending_id, message_id, roles[] */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/initialise.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
// Admin-Check
$stmt = $pdo->prepare("SELECT \"Admin\" FROM user_permissions WHERE user_id = ?");
$stmt->execute([$_SESSION["user_id"]]);
$isAdmin = $stmt->fetchColumn();
if ((int)$isAdmin !== 1) {
header("Location: {$g_httpHomePath}");
exit();
}
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: admin_inbox.php");
exit();
}
// Admin-Name ermitteln
$adminName = trim(($user['first_name'] ?? '') . ' ' . ($user['last_name'] ?? ''));
if (empty($adminName)) $adminName = 'Ein Administrator';
$action = $_POST["action"] ?? '';
$pending_id = (int)($_POST["pending_id"] ?? 0);
$message_id = (int)($_POST["message_id"] ?? 0);
$selectedRoles = $_POST["roles"] ?? [];
if (!is_array($selectedRoles)) $selectedRoles = [];
if ($pending_id === 0) {
header("Location: admin_inbox.php?inbox_status=error");
exit();
}
// Pending Registration laden
$stmt = $pdo->prepare("SELECT * FROM pending_registrations WHERE id = ? AND status = 'pending'");
$stmt->execute([$pending_id]);
$pending = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$pending) {
header("Location: admin_inbox.php?inbox_status=error");
exit();
}
// Rollen-Spalten laden
$permissionRoles = [];
$rolesResult = $pdo->query("SELECT column_name FROM information_schema.columns WHERE table_name = 'user_permissions' AND table_schema = 'public'");
while ($col = $rolesResult->fetch(PDO::FETCH_ASSOC)) {
if (!in_array($col['column_name'], ['p_id', 'user_id'], true)) {
$permissionRoles[] = $col['column_name'];
}
}
$loginUrl = "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/authorization/login.php";
if ($action === 'approve') {
$pdo->beginTransaction();
try {
// Nutzer in user_general_information anlegen
$stmt = $pdo->prepare("INSERT INTO user_general_information (mail, first_name, last_name, phone) VALUES (?, ?, ?, ?)");
$stmt->execute([$pending['email'], $pending['first_name'], $pending['last_name'], $pending['phone']]);
$newUserId = $pdo->lastInsertId();
if (!$newUserId) {
$checkStmt = $pdo->prepare("SELECT user_id FROM user_general_information WHERE mail = ?");
$checkStmt->execute([$pending['email']]);
$newUserId = $checkStmt->fetchColumn();
}
// Login-Daten anlegen
$stmt = $pdo->prepare("INSERT INTO user_login (user_id, password_hash) VALUES (?, ?)");
$stmt->execute([$newUserId, $pending['password_hash']]);
// Berechtigungen mit gewählten Rollen anlegen
$roleColumns = array_map(function($r) { return '"' . str_replace('"', '""', $r) . '"'; }, $permissionRoles);
$columns = array_merge(['"user_id"'], $roleColumns);
$placeholders = array_fill(0, count($columns), '?');
$roleValues = [];
foreach ($permissionRoles as $role) {
$roleValues[] = in_array($role, $selectedRoles, true) ? 1 : 0;
}
$params = array_merge([(int)$newUserId], $roleValues);
$insertSql = "INSERT INTO user_permissions (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
$stmt = $pdo->prepare($insertSql);
$stmt->execute($params);
// Pending-Status aktualisieren
$stmt = $pdo->prepare("UPDATE pending_registrations SET status = 'approved' WHERE id = ?");
$stmt->execute([$pending_id]);
// Inbox-Nachricht als erledigt markieren
if ($message_id > 0) {
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
$stmt->execute([$message_id]);
}
$pdo->commit();
// Zugewiesene Rollen als Text
$assignedRoles = !empty($selectedRoles) ? implode(', ', $selectedRoles) : 'Keine besonderen Rollen';
// Mail an den Nutzer
$userBetreff = "Registrierung freigeschaltet";
$userNachricht = "Hallo {$pending['first_name']},\n\n";
$userNachricht .= "Ihr Account für das Intranet der NAK Reinickendorf wurde freigeschaltet!\n\n";
$userNachricht .= "Sie können sich ab sofort mit Ihrer E-Mail-Adresse und dem von Ihnen gewählten Passwort anmelden.";
send_intranet_mail($pending['email'], $userBetreff, $userNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt anmelden", $loginUrl);
// Mail an Admin-Team
$adminBetreff = "Registrierung freigegeben: {$pending['first_name']} {$pending['last_name']}";
$adminNachricht = "{$adminName} hat die Registrierung bearbeitet:\n\n";
$adminNachricht .= "Nutzer: {$pending['first_name']} {$pending['last_name']}\n";
$adminNachricht .= "E-Mail: {$pending['email']}\n";
$adminNachricht .= "Aktion: Freigeschaltet\n";
$adminNachricht .= "Zugewiesene Rollen: {$assignedRoles}";
send_admin_team_mail($pdo, $adminBetreff, $adminNachricht);
// Systemnachricht im Posteingang
$sysSubject = "Nutzer freigeschaltet: {$pending['first_name']} {$pending['last_name']}";
$sysMessage = "{$adminName} hat {$pending['first_name']} {$pending['last_name']} ({$pending['email']}) freigeschaltet.\nZugewiesene Rollen: {$assignedRoles}";
$stmtSys = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$stmtSys->execute([$adminName, $pending['email'], $sysSubject, $sysMessage]);
header("Location: admin_inbox.php?inbox_status=approved");
exit();
} catch (Throwable $e) {
$pdo->rollBack();
error_log("approve-registration error: " . $e->getMessage());
header("Location: admin_inbox.php?inbox_status=error");
exit();
}
} elseif ($action === 'reject') {
// Ablehnen
$stmt = $pdo->prepare("UPDATE pending_registrations SET status = 'rejected' WHERE id = ?");
$stmt->execute([$pending_id]);
// Inbox-Nachricht als erledigt markieren
if ($message_id > 0) {
$stmt = $pdo->prepare("UPDATE admin_messages SET status = 'done', processed_at = NOW() WHERE id = ?");
$stmt->execute([$message_id]);
}
// Mail an den Nutzer
$userBetreff = "Registrierung abgelehnt";
$userNachricht = "Hallo {$pending['first_name']},\n\n";
$userNachricht .= "Ihre Registrierungsanfrage für das Intranet der NAK Reinickendorf wurde leider abgelehnt.\n\n";
$userNachricht .= "Bei Fragen wenden Sie sich bitte an den Administrator über das Kontaktformular auf der Anmeldeseite.";
send_intranet_mail($pending['email'], $userBetreff, $userNachricht);
// Mail an Admin-Team
$adminBetreff = "Registrierung abgelehnt: {$pending['first_name']} {$pending['last_name']}";
$adminNachricht = "{$adminName} hat die Registrierung bearbeitet:\n\n";
$adminNachricht .= "Nutzer: {$pending['first_name']} {$pending['last_name']}\n";
$adminNachricht .= "E-Mail: {$pending['email']}\n";
$adminNachricht .= "Aktion: Abgelehnt";
send_admin_team_mail($pdo, $adminBetreff, $adminNachricht);
// Systemnachricht im Posteingang
$sysSubject = "Registrierung abgelehnt: {$pending['first_name']} {$pending['last_name']}";
$sysMessage = "{$adminName} hat die Registrierung von {$pending['first_name']} {$pending['last_name']} ({$pending['email']}) abgelehnt.";
$stmtSys = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$stmtSys->execute([$adminName, $pending['email'], $sysSubject, $sysMessage]);
header("Location: admin_inbox.php?inbox_status=rejected");
exit();
}
header("Location: admin_inbox.php");
exit();
?>
@@ -0,0 +1,56 @@
<?php /* Name: process_contact.php
Beschreibung: Verarbeitung von Kontaktformular-Anfragen
- Speichert in admin_messages
- Sendet Mail an technik@nak-reinickendorf.de */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Methode nicht erlaubt']);
exit;
}
// Felder auslesen
$name = trim($_POST["contact_name"] ?? '');
$email = trim($_POST["contact_email"] ?? '');
$subject = trim($_POST["contact_subject"] ?? 'Kontaktanfrage');
$message = trim($_POST["contact_message"] ?? '');
// Validierung
if (empty($name) || empty($email) || empty($message)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Bitte alle Felder ausfüllen.']);
exit;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Ungültige E-Mail-Adresse.']);
exit;
}
// In admin_messages speichern
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message) VALUES ('contact', ?, ?, ?, ?)");
$stmt->execute([$name, $email, $subject, $message]);
// Mail an Admin-Team (Admin + Technik)
$mail_betreff = "Kontaktanfrage: {$subject}";
$mail_nachricht = "Neue Kontaktanfrage über das Intranet:\n\n";
$mail_nachricht .= "Name: {$name}\n";
$mail_nachricht .= "E-Mail: {$email}\n";
$mail_nachricht .= "Betreff: {$subject}\n\n";
$mail_nachricht .= "Nachricht:\n{$message}";
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht);
// JSON-Antwort für AJAX
header('Content-Type: application/json');
echo json_encode(['success' => true, 'message' => 'Nachricht wurde gesendet.']);
exit;
?>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,106 @@
<?php /* Name: login.php
Beschreibung: Login-Seite (Authorisierung) */
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
$sql = "SELECT
u.*,
l.password_hash AS password_hash
FROM
user_general_information AS u
INNER JOIN
user_login AS l ON u.user_id = l.user_id
WHERE
u.mail = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$_POST["email"]]);
$user = $stmt->fetch();
if ($user) {
if (isset($user['password_hash']) && password_verify($_POST["password"], $user["password_hash"])) {
session_start();
session_regenerate_id();
$_SESSION["user_id"] = $user["user_id"];
header("Location: {$g_httpHomePath}");
exit;
}
}
$is_invalid = true;
}
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<script src="https://unpkg.com/just-validate@latest/dist/just-validate.production.min.js" defer></script>
<script src="validation.js" defer></script>
</head>
<body>
<div class="login_panel">
<?php if (isset($is_invalid) && $is_invalid): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastLogin">
<div class="nak-toast__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
</div>
<span class="nak-toast__text">Ungültiger Login!</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastLogin').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php if (isset($_GET["message"]) && $_GET["message"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastPw">
<div class="nak-toast__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg>
</div>
<span class="nak-toast__text">Passwort erfolgreich geändert. Sie können sich jetzt anmelden.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastPw').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link auth-logo">
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
<div class="auth-logo__text">
<div class="auth-logo__name">Neuapostolische Kirche</div>
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Anmelden</h2>
<form method="post">
<div class="flexcolumn_center">
<input class="login_field" placeholder="E-Mailadresse" type="email" name="email" id="email"
value="<?= htmlspecialchars($_POST["email"] ?? "") ?>">
<input class="login_field" placeholder="Passwort" type="password" name="password" id="password">
<input class="login_button" type="submit" name="submit" value="Anmelden">
</div>
</form>
<div class="small_text" style="text-align:center;margin-top:24px;color:var(--gray-600);">
Sie haben noch keinen Account für das Intranet?<br>
<a href="signup.php">Registrieren</a><br>
<br>
Passwort vergessen?<br>
<a href="reset-password.php">Passwort zurücksetzen</a><br>
<br>
Probleme bei der Anmeldung?<br>
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,13 @@
<?php /* Name: loginout.php
Beschreibung: Logout-Seite */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
session_destroy();
header("Location: {$g_httpLoginPath}");
exit();
@@ -0,0 +1,223 @@
<?php /* Name: reset-password-process.php
Beschreibung: Verarbeitung des Passwort-Resets (3 Schritte)
- Schritt 1: OTP generieren und per SMS + Mail senden
- Schritt 2: OTP verifizieren → Reset-Token erzeugen
- Schritt 3: Neues Passwort setzen mit Reset-Token */
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
// PostgreSQL-Zeitzone auf PHP-Zeitzone setzen
$pdo->exec("SET timezone = 'Europe/Berlin'");
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
header("Location: reset-password.php");
exit;
}
$action = $_POST["action"] ?? '';
// =============================================
// SCHRITT 1: OTP anfordern
// =============================================
if ($action === "request_otp") {
$email = trim($_POST["email"] ?? '');
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
header("Location: reset-password.php?error=1");
exit;
}
// Nutzer suchen (muss registriert sein = user_login existiert)
$stmt = $pdo->prepare("SELECT ugi.user_id, ugi.phone, ugi.first_name FROM user_general_information ugi INNER JOIN user_login ul ON ugi.user_id = ul.user_id WHERE ugi.mail = ?");
$stmt->execute([$email]);
$user = $stmt->fetch();
if (!$user) {
header("Location: reset-password.php?error=1");
exit;
}
if (empty($user['phone'])) {
header("Location: reset-password.php?error=2");
exit;
}
// OTP generieren (6-stellig)
$otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// Ablaufzeit als UNIX-Timestamp speichern (vermeidet Timezone-Probleme)
$expires_timestamp = time() + 600; // 10 Minuten
$expires_at = date('Y-m-d H:i:s', $expires_timestamp);
// Alte OTPs für diesen Nutzer invalidieren
$stmt = $pdo->prepare("UPDATE password_reset_otp SET used = 1 WHERE user_id = ? AND used = 0");
$stmt->execute([$user['user_id']]);
// Neues OTP speichern
$stmt = $pdo->prepare("INSERT INTO password_reset_otp (user_id, otp_code, expires_at) VALUES (?, ?, ?)");
$stmt->execute([$user['user_id'], $otp, $expires_at]);
// SMS senden (wie im Seelsorgeplaner)
$phone_full = "+49" . $user['phone'];
$messageText = "Intranet NAK Reinickendorf\n\nIhr Bestaetigungscode: {$otp}\n\nDer Code ist 10 Minuten gueltig.";
$smsData = array(
"phone" => $phone_full,
"message" => $messageText
);
$options = array(
'http' => array(
'header' => "Content-Type: application/json; charset=utf-8",
'method' => 'POST',
'content' => json_encode($smsData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'timeout' => 10,
),
);
$context = stream_context_create($options);
$smsResult = @file_get_contents(rtrim(intranet_env('SMS_GATEWAY_URL'), '/') . '/send_sms', false, $context);
if ($smsResult === FALSE) {
header("Location: reset-password.php?error=4");
exit;
}
// OTP zusätzlich per Mail senden
$greeting = !empty($user['first_name']) ? "Hallo {$user['first_name']}" : "Hallo";
$mailBetreff = "Ihr Bestätigungscode";
$mailNachricht = "{$greeting},\n\n";
$mailNachricht .= "Sie haben das Zurücksetzen Ihres Passworts angefordert.\n\n";
$mailNachricht .= "Ihr Bestätigungscode lautet: {$otp}\n\n";
$mailNachricht .= "Der Code ist 10 Minuten gültig.\n\n";
$mailNachricht .= "Falls Sie diese Anfrage nicht gestellt haben, können Sie diese Nachricht ignorieren.";
send_intranet_mail($email, $mailBetreff, $mailNachricht);
// Telefonnummer maskieren für Anzeige (nur letzte 3 Ziffern zeigen)
$phone_masked = str_repeat('*', max(0, strlen($user['phone']) - 3)) . substr($user['phone'], -3);
header("Location: reset-password.php?step=verify&uid={$user['user_id']}&phone=" . urlencode($phone_masked));
exit;
}
// =============================================
// SCHRITT 2: OTP verifizieren → Reset-Token erzeugen
// =============================================
if ($action === "verify_otp") {
$user_id = (int)($_POST["user_id"] ?? 0);
$otp_code = trim($_POST["otp_code"] ?? '');
if ($user_id === 0 || strlen($otp_code) !== 6) {
header("Location: reset-password.php?error=3");
exit;
}
// OTP prüfen – Vergleich mit PHP-Zeit statt DB NOW()
$now = date('Y-m-d H:i:s');
$stmt = $pdo->prepare("SELECT id FROM password_reset_otp WHERE user_id = ? AND otp_code = ? AND used = 0 AND expires_at > ?");
$stmt->execute([$user_id, $otp_code, $now]);
$otpRecord = $stmt->fetch();
if (!$otpRecord) {
header("Location: reset-password.php?step=verify&uid={$user_id}&error=3");
exit;
}
// OTP als verwendet markieren
$stmt = $pdo->prepare("UPDATE password_reset_otp SET used = 1 WHERE id = ?");
$stmt->execute([$otpRecord['id']]);
// Einmal-Token für Passwort-Änderung generieren (in Session speichern)
$reset_token = bin2hex(random_bytes(32));
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$_SESSION['reset_token'] = $reset_token;
$_SESSION['reset_user_id'] = $user_id;
$_SESSION['reset_token_expires'] = time() + 600; // 10 Minuten
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token));
exit;
}
// =============================================
// SCHRITT 3: Neues Passwort setzen (nach OTP-Verifizierung)
// =============================================
if ($action === "set_password") {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$reset_token = $_POST["reset_token"] ?? '';
$new_password = $_POST["new_password"] ?? '';
$new_password_confirm = $_POST["new_password_confirm"] ?? '';
// Token validieren
if (empty($reset_token)
|| !isset($_SESSION['reset_token'])
|| $reset_token !== $_SESSION['reset_token']
|| !isset($_SESSION['reset_token_expires'])
|| $_SESSION['reset_token_expires'] < time()) {
// Session-Daten aufräumen
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_token_expires']);
header("Location: reset-password.php?error=7");
exit;
}
$user_id = (int)$_SESSION['reset_user_id'];
// Passwort-Validierung
if (strlen($new_password) < 8 || !preg_match("/[a-z]/i", $new_password) || !preg_match("/[0-9]/", $new_password)) {
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token) . "&error=5");
exit;
}
if ($new_password !== $new_password_confirm) {
header("Location: reset-password.php?step=newpassword&token=" . urlencode($reset_token) . "&error=6");
exit;
}
// Neues Passwort setzen
$password_hash = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE user_login SET password_hash = ? WHERE user_id = ?");
$stmt->execute([$password_hash, $user_id]);
// User-Daten laden für Mail und Vermerk
$stmt = $pdo->prepare("SELECT mail, first_name, last_name FROM user_general_information WHERE user_id = ?");
$stmt->execute([$user_id]);
$resetUserData = $stmt->fetch();
if ($resetUserData) {
$loginUrl = "https://" . ($_SERVER['HTTP_HOST'] ?? intranet_env('INTRANET_FQDN')) . "/intranet_repo/projects/authorization/login.php";
$greeting = !empty($resetUserData['first_name']) ? "Hallo {$resetUserData['first_name']}" : "Hallo";
$mailBetreff = "Passwort erfolgreich geändert";
$mailNachricht = "{$greeting},\n\n";
$mailNachricht .= "Ihr Passwort für das Intranet der NAK Reinickendorf wurde erfolgreich geändert.\n\n";
$mailNachricht .= "Sie können sich ab sofort mit Ihrem neuen Passwort anmelden.";
send_intranet_mail($resetUserData['mail'], $mailBetreff, $mailNachricht, null, "intranet@nak-reinickendorf.de", "Jetzt anmelden", $loginUrl);
// Vermerk in erledigte Tasks
$userName = trim(($resetUserData['first_name'] ?? '') . ' ' . ($resetUserData['last_name'] ?? ''));
if (empty($userName)) $userName = $resetUserData['mail'];
$taskSubject = "Passwort selbst zurückgesetzt: {$userName}";
$taskMessage = "{$userName} ({$resetUserData['mail']}) hat das eigene Passwort erfolgreich zurückgesetzt.";
$stmt = $pdo->prepare("INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, status, processed_at, created_at) VALUES ('system', ?, ?, ?, ?, 'done', NOW(), NOW())");
$stmt->execute([$userName, $resetUserData['mail'], $taskSubject, $taskMessage]);
}
// Session-Daten aufräumen
unset($_SESSION['reset_token'], $_SESSION['reset_user_id'], $_SESSION['reset_token_expires']);
header("Location: login.php?message=1");
exit;
}
header("Location: reset-password.php");
exit;
?>
@@ -0,0 +1,159 @@
<?php /* Name: reset-password.php
Beschreibung: Passwort-Reset per SMS OTP (3 Schritte)
- Schritt 1: E-Mail eingeben → OTP per SMS
- Schritt 2: OTP-Code verifizieren
- Schritt 3: Neues Passwort setzen */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
$step = $_GET["step"] ?? "email";
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<style>
.login_button { position: relative; }
.login_button.is-loading {
color: transparent !important;
pointer-events: none;
}
.login_button.is-loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 22px;
height: 22px;
margin: -11px 0 0 -11px;
border: 3px solid rgba(255,255,255,0.35);
border-top-color: #FFFFFF;
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="login_panel">
<?php if (isset($_GET["error"])): ?>
<?php
$errorMessages = [
1 => "E-Mail-Adresse nicht gefunden oder nicht registriert.",
2 => "Keine Telefonnummer hinterlegt. Bitte wenden Sie sich an den Administrator.",
3 => "Ungültiger oder abgelaufener Code.",
4 => "SMS konnte nicht gesendet werden. Bitte versuchen Sie es erneut.",
5 => "Passwort muss mindestens 8 Zeichen, einen Buchstaben und eine Zahl enthalten.",
6 => "Passwörter stimmen nicht überein.",
7 => "Sitzung abgelaufen. Bitte starten Sie den Vorgang erneut."
];
$errorNum = (int)$_GET["error"];
$errorText = $errorMessages[$errorNum] ?? "Unbekannter Fehler.";
?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text"><?= htmlspecialchars($errorText) ?></span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
<span class="nak-toast__text">Passwort erfolgreich geändert. Sie können sich jetzt anmelden.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link auth-logo">
<img class="auth-logo__icon" src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo">
<div class="auth-logo__text">
<div class="auth-logo__name">Neuapostolische Kirche</div>
<div class="auth-logo__gemeinde">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Passwort zurücksetzen</h2>
<?php if ($step === "email"): ?>
<!-- SCHRITT 1: E-Mail eingeben -->
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Geben Sie Ihre E-Mail-Adresse ein. Ein Bestätigungscode wird an Ihre hinterlegte Telefonnummer und per Mail gesendet.</p>
<form action="reset-password-process.php" method="post" id="otpRequestForm">
<input type="hidden" name="action" value="request_otp">
<label for="email">E-Mailadresse</label>
<div class="item">
<input class="login_field" type="email" id="email" name="email" required>
</div>
<button class="login_button" type="submit" id="requestBtn">Code anfordern</button>
</form>
<script>
document.getElementById('otpRequestForm').addEventListener('submit', function() {
var btn = document.getElementById('requestBtn');
btn.classList.add('is-loading');
btn.disabled = true;
});
</script>
<?php elseif ($step === "verify"): ?>
<!-- SCHRITT 2: Nur OTP-Code eingeben -->
<?php $phone_masked = htmlspecialchars($_GET["phone"] ?? "***"); ?>
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Ein 6-stelliger Code wurde an die Nummer +49 <?= $phone_masked ?> und an Ihre E-Mail-Adresse gesendet.</p>
<form action="reset-password-process.php" method="post">
<input type="hidden" name="action" value="verify_otp">
<input type="hidden" name="user_id" value="<?= (int)($_GET["uid"] ?? 0) ?>">
<label for="otp_code">Bestätigungscode</label>
<div class="item">
<input class="login_field" type="text" id="otp_code" name="otp_code" maxlength="6" inputmode="numeric" pattern="[0-9]{6}" placeholder="000000" required autocomplete="one-time-code" style="text-align:center;font-size:1.4em;letter-spacing:8px;">
</div>
<input class="login_button" type="submit" value="Code bestätigen">
</form>
<?php elseif ($step === "newpassword"): ?>
<!-- SCHRITT 3: Neues Passwort setzen (nur nach verifiziertem OTP) -->
<p style="text-align:center;color:var(--gray-600);font-size:var(--font-size-sm);margin-bottom:var(--space-sm);">Ihr Code wurde bestätigt. Geben Sie nun Ihr neues Passwort ein.</p>
<form action="reset-password-process.php" method="post">
<input type="hidden" name="action" value="set_password">
<input type="hidden" name="reset_token" value="<?= htmlspecialchars($_GET["token"] ?? '') ?>">
<label for="new_password">Neues Passwort</label>
<div class="item">
<input class="login_field" type="password" id="new_password" name="new_password" required>
</div>
<label for="new_password_confirm">Neues Passwort wiederholen</label>
<div class="item">
<input class="login_field" type="password" id="new_password_confirm" name="new_password_confirm" required>
</div>
<input class="login_button" type="submit" value="Passwort ändern">
</form>
<?php endif; ?>
<div class="small_text" align="center">
<br>
<a href="login.php">Zurück zur Anmeldung</a><br>
<br>
Probleme beim Zurücksetzen?<br>
<a href="#" onclick="openContactModal(); return false;">Admin kontaktieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>
@@ -0,0 +1,97 @@
<?php /* Name: self-signup-process.php
Beschreibung: Verarbeitung der Selbstregistrierung
- Validierung der Eingaben
- Speicherung in pending_registrations
- Mail an Admin (samuel.bias@aol.de)
- Eintrag in admin_messages */
$pdo = require_once($_SERVER['DOCUMENT_ROOT']."/intranet_repo/general_files/ini/database.php");
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/send_mail.php';
// Validierung
if (empty($_POST["first_name"])) {
die("Vorname ist erforderlich.");
}
if (empty($_POST["last_name"])) {
die("Nachname ist erforderlich.");
}
if (!filter_var($_POST["email"], FILTER_VALIDATE_EMAIL)) {
die("Gültige E-Mail-Adresse erforderlich.");
}
if (strlen($_POST["password"]) < 8) {
die("Passwort muss mindestens 8 Zeichen lang sein.");
}
if (!preg_match("/[a-z]/i", $_POST["password"])) {
die("Passwort muss mindestens einen Buchstaben enthalten.");
}
if (!preg_match("/[0-9]/", $_POST["password"])) {
die("Passwort muss mindestens eine Zahl enthalten.");
}
if ($_POST["password"] !== $_POST["password_confirmation"]) {
die("Passwörter stimmen nicht überein.");
}
$email = trim($_POST["email"]);
$first_name = trim($_POST["first_name"]);
$last_name = trim($_POST["last_name"]);
// Prüfen ob E-Mail bereits existiert (in user_general_information oder pending_registrations)
$checkSql = "SELECT COUNT(*) FROM user_general_information WHERE mail = ?";
$stmt = $pdo->prepare($checkSql);
$stmt->execute([$email]);
$count1 = $stmt->fetchColumn();
$checkSql2 = "SELECT COUNT(*) FROM pending_registrations WHERE email = ? AND status = 'pending'";
$stmt2 = $pdo->prepare($checkSql2);
$stmt2->execute([$email]);
$count2 = $stmt2->fetchColumn();
if ($count1 > 0 || $count2 > 0) {
header("Location: self-signup.php?error=2");
exit;
}
// Passwort hashen und Token generieren
$password_hash = password_hash($_POST["password"], PASSWORD_DEFAULT);
$token = bin2hex(random_bytes(32));
// In pending_registrations speichern
$insertSql = "INSERT INTO pending_registrations (first_name, last_name, email, password_hash, token) VALUES (?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($insertSql);
if (!$stmt) {
header("Location: self-signup.php?error=1");
exit;
}
if (!$stmt->execute([$first_name, $last_name, $email, $password_hash, $token])) {
header("Location: self-signup.php?error=1");
exit;
}
$pending_id = $pdo->lastInsertId();
// Admin-Nachricht in Inbox speichern
$subject = "Neue Registrierungsanfrage: {$first_name} {$last_name}";
$message_text = "Neue Selbstregistrierung:\n\nName: {$first_name} {$last_name}\nE-Mail: {$email}\n\nBitte im Adminpanel freischalten.";
$data_json = json_encode(['pending_id' => $pending_id, 'token' => $token]);
$inboxSql = "INSERT INTO admin_messages (type, sender_name, sender_email, subject, message, data_json) VALUES ('registration', ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($inboxSql);
$sender_name = "{$first_name} {$last_name}";
$stmt->execute([$sender_name, $email, $subject, $message_text, $data_json]);
// Mail an Admin-Team (Admin + Technik)
$admin_email = ADMIN_EMAIL;
$mail_betreff = "Intranet: Neue Registrierungsanfrage von {$first_name} {$last_name}";
$mail_nachricht = "Hallo Admin,\n\n";
$mail_nachricht .= "Es liegt eine neue Registrierungsanfrage vor:\n\n";
$mail_nachricht .= "Name: {$first_name} {$last_name}\n";
$mail_nachricht .= "E-Mail: {$email}\n\n";
$mail_nachricht .= "Bitte melden Sie sich im Adminpanel an, um die Anfrage zu bearbeiten.\n\n";
$mail_nachricht .= "-- Intranet NAK Reinickendorf";
send_admin_team_mail($pdo, $mail_betreff, $mail_nachricht);
// Erfolg
header("Location: self-signup.php?success=1");
exit;
?>
@@ -0,0 +1,101 @@
<?php /* Name: self-signup.php
Beschreibung: Selbstregistrierung - Nutzer kann sich eigenständig registrieren.
Admin erhält Mail + Inbox-Nachricht zur Freischaltung. */
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/parameters.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/general_files/ini/functions.php';
?>
<!DOCTYPE html>
<html lang="de" dir="ltr">
<head>
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/html_head.php'; ?>
<script src="just-validate.js" defer></script>
<script src="validation.js" defer></script>
</head>
<body>
<div class="login_panel">
<?php if (isset($_GET["error"])): ?>
<?php if ($_GET["error"] == 1): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text">Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php elseif ($_GET["error"] == 2): ?>
<div class="nak-toast nak-toast--error nak-toast--inline" id="nakToastErr">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
<span class="nak-toast__text">Diese E-Mailadresse ist bereits registriert oder es liegt bereits eine Anfrage vor.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastErr').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<?php endif; ?>
<?php if (isset($_GET["success"]) && $_GET["success"] == 1): ?>
<div class="nak-toast nak-toast--success nak-toast--inline" id="nakToastOk">
<div class="nak-toast__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="square" stroke-linejoin="miter"><path d="M20 6L9 17l-5-5"/></svg></div>
<span class="nak-toast__text">Ihre Registrierungsanfrage wurde gesendet. Sie erhalten eine Benachrichtigung, sobald Ihr Konto freigeschaltet wurde.</span>
</div>
<script>requestAnimationFrame(function(){document.getElementById('nakToastOk').classList.add('nak-toast--visible');});</script>
<?php endif; ?>
<div class="login_content">
<div class="nak-logo-link" style="display:flex;align-items:flex-end;gap:12px;text-decoration:none;margin-bottom:24px;">
<img src="/intranet_repo/projects/_central/img/nak-logo.svg" alt="NAK Logo" style="width:52px;height:52px;flex-shrink:0;">
<div style="display:flex;flex-direction:column;justify-content:flex-end;line-height:1.2;">
<div style="font-family:var(--font-primary);font-weight:500;font-size:0.875rem;color:var(--gray-900);white-space:nowrap;">Neuapostolische Kirche</div>
<div style="font-family:var(--font-primary);font-weight:300;font-size:0.75rem;color:var(--gray-600);white-space:nowrap;">Berlin-Reinickendorf</div>
</div>
</div>
<h2>Registrierung beantragen</h2>
<h3 class="no_margins" align="center">Ihr Antrag wird von einem Administrator geprüft und freigeschaltet.</h3>
<form action="self-signup-process.php" method="post" id="signup" novalidate>
<label for="first_name"><br>Vorname</label>
<div class="item">
<input class="login_field" type="text" id="first_name" name="first_name" required>
</div>
<label for="last_name">Nachname</label>
<div class="item">
<input class="login_field" type="text" id="last_name" name="last_name" required>
</div>
<label for="email">E-Mailadresse</label>
<div class="item">
<input class="login_field" type="email" id="email" name="email" required>
</div>
<label for="password">Passwort</label>
<div class="item">
<input class="login_field" type="password" id="password" name="password" required>
</div>
<label for="password_confirmation">Passwort wiederholen</label>
<div class="item">
<input class="login_field" type="password" id="password_confirmation" name="password_confirmation" required>
</div>
<input class="login_button" type="submit" value="Registrierung beantragen">
</form>
<div class="small_text" align="center">
Sie haben bereits einen Account?
<br>
<a href="login.php">Anmelden</a>
<br><br>
Sie wurden bereits vom Admin vorregistriert?
<br>
<a href="signup.php">Direkt registrieren</a>
</div>
</div><!-- EOD: login_content -->
</div><!-- EOD: login_panel -->
<?php require_once $_SERVER['DOCUMENT_ROOT'].'/intranet_repo/projects/_central/php/footer.php'; ?>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More