This commit is contained in:
2025-12-10 01:40:05 +01:00
parent 10cc7b5d30
commit 8b5004b292
14 changed files with 374 additions and 100 deletions

View File

@@ -68,25 +68,25 @@ deploy:staging:
echo "🚀 Deploy ${CI_ENVIRONMENT_NAME} → ${FTP_HOST}:${TARGET_PATH}"
# -------------------------------------------
# 🔢 Versionierung: versions.php laden/bumpen
# 🔢 Versionierung aus Repo-Datei ableiten
# -------------------------------------------
VERSION_WORK_FILE=".ci_versions_${CI_ENVIRONMENT_NAME}.php"
REMOTE_VERSION_PATH="${TARGET_PATH}${CONFIG_BASE_DIR}/versions.php"
VERSION_SOURCE="${CONFIG_ENV_DIR}/versions.php"
if [ ! -f "${VERSION_SOURCE}" ]; then
VERSION_SOURCE="${CONFIG_BASE_DIR}/versions.php"
fi
echo "🔍 Prüfe versions.php auf dem Server: ${REMOTE_VERSION_PATH}"
if lftp -u "${FTP_USER}","${FTP_PASSWORD}" "${FTP_HOST}" -e "
set ftp:ssl-force true;
set ftp:passive-mode true;
set ftp:ssl-protect-data true;
set ssl:verify-certificate no;
get ${REMOTE_VERSION_PATH} -o ${VERSION_WORK_FILE};
bye
"; then
echo "✅ Remote versions.php geladen."
if [ -f "${VERSION_SOURCE}" ]; then
echo "📄 Verwende lokale versions.php: ${VERSION_SOURCE}"
cp "${VERSION_SOURCE}" "${VERSION_WORK_FILE}"
else
echo "⚠️ Remote versions.php nicht gefunden, verwende lokale ${CONFIG_ENV_DIR}/versions.php"
cp "${CONFIG_ENV_DIR}/versions.php" "${VERSION_WORK_FILE}"
echo "⚠️ Keine versions.php gefunden nutze Defaults 1.0.0"
cat <<'PHP' > "${VERSION_WORK_FILE}"
<?php
$mainversion = 1;
$subversion = 0;
$patchversion = 0;
PHP
fi
MAIN=$(grep '\$mainversion' "${VERSION_WORK_FILE}" 2>/dev/null | tr -cd '0-9')
@@ -97,15 +97,52 @@ deploy:staging:
[ -z "$SUB" ] && SUB=0
[ -z "$PATCH" ] && PATCH=0
PATCH=$((PATCH + 1))
FORCED=0
if [ -n "${CI_VERSION_FORCE:-}" ]; then
if expr "${CI_VERSION_FORCE}" : '^[0-9]\+\.[0-9]\+\.[0-9]\+$' >/dev/null; then
IFS='.' read -r FORCE_MAIN FORCE_SUB FORCE_PATCH <<EOF
${CI_VERSION_FORCE}
EOF
MAIN=$FORCE_MAIN
SUB=$FORCE_SUB
PATCH=$FORCE_PATCH
FORCED=1
else
echo "⚠️ CI_VERSION_FORCE ungültig (${CI_VERSION_FORCE}). Ignoriere Override."
fi
fi
echo "🆙 Neue STAGING-Version: ${MAIN}.${SUB}.${PATCH}"
if [ "$FORCED" -eq 1 ]; then
BUMP_NOTE="force override"
else
BUMP_MODE=$(printf '%s' "${CI_VERSION_BUMP:-patch}" | tr '[:upper:]' '[:lower:]')
case "$BUMP_MODE" in
major)
MAIN=$((MAIN + 1))
SUB=0
PATCH=0
;;
minor)
SUB=$((SUB + 1))
PATCH=0
;;
*)
BUMP_MODE="patch"
PATCH=$((PATCH + 1))
;;
esac
BUMP_NOTE="${BUMP_MODE}-bump"
fi
echo "🆙 Neue ${CI_ENVIRONMENT_NAME} Version (${BUMP_NOTE}): ${MAIN}.${SUB}.${PATCH}"
# Neue versions.php bauen
echo '<?php' > "${VERSION_WORK_FILE}"
echo "\$mainversion = ${MAIN};" >> "${VERSION_WORK_FILE}"
echo "\$subversion = ${SUB};" >> "${VERSION_WORK_FILE}"
echo "\$patchversion = ${PATCH};" >> "${VERSION_WORK_FILE}"
{
echo '<?php'
echo "\$mainversion = ${MAIN};"
echo "\$subversion = ${SUB};"
echo "\$patchversion = ${PATCH};"
} > "${VERSION_WORK_FILE}"
# Textdatei für die Webseite
echo "${MAIN}.${SUB}.${PATCH}" > public/build_version.txt
@@ -225,25 +262,25 @@ deploy:production:
echo "🚀 Deploy ${CI_ENVIRONMENT_NAME} → ${FTP_HOST}:${TARGET_PATH}"
# -------------------------------------------
# 🔢 Versionierung: versions.php laden/bumpen
# 🔢 Versionierung aus Repo-Datei ableiten
# -------------------------------------------
VERSION_WORK_FILE=".ci_versions_${CI_ENVIRONMENT_NAME}.php"
REMOTE_VERSION_PATH="${TARGET_PATH}${CONFIG_BASE_DIR}/versions.php"
VERSION_SOURCE="${CONFIG_ENV_DIR}/versions.php"
if [ ! -f "${VERSION_SOURCE}" ]; then
VERSION_SOURCE="${CONFIG_BASE_DIR}/versions.php"
fi
echo "🔍 Prüfe versions.php auf dem Server: ${REMOTE_VERSION_PATH}"
if lftp -u "${FTP_USER}","${FTP_PASSWORD}" "${FTP_HOST}" -e "
set ftp:ssl-force true;
set ftp:passive-mode true;
set ftp:ssl-protect-data true;
set ssl:verify-certificate no;
get ${REMOTE_VERSION_PATH} -o ${VERSION_WORK_FILE};
bye
"; then
echo "✅ Remote versions.php geladen."
if [ -f "${VERSION_SOURCE}" ]; then
echo "📄 Verwende lokale versions.php: ${VERSION_SOURCE}"
cp "${VERSION_SOURCE}" "${VERSION_WORK_FILE}"
else
echo "⚠️ Remote versions.php nicht gefunden, verwende lokale ${CONFIG_ENV_DIR}/versions.php"
cp "${CONFIG_ENV_DIR}/versions.php" "${VERSION_WORK_FILE}"
echo "⚠️ Keine versions.php gefunden nutze Defaults 1.0.0"
cat <<'PHP' > "${VERSION_WORK_FILE}"
<?php
$mainversion = 1;
$subversion = 0;
$patchversion = 0;
PHP
fi
MAIN=$(grep '\$mainversion' "${VERSION_WORK_FILE}" 2>/dev/null | tr -cd '0-9')
@@ -254,15 +291,52 @@ deploy:production:
[ -z "$SUB" ] && SUB=0
[ -z "$PATCH" ] && PATCH=0
PATCH=$((PATCH + 1))
FORCED=0
if [ -n "${CI_VERSION_FORCE:-}" ]; then
if expr "${CI_VERSION_FORCE}" : '^[0-9]\+\.[0-9]\+\.[0-9]\+$' >/dev/null; then
IFS='.' read -r FORCE_MAIN FORCE_SUB FORCE_PATCH <<EOF
${CI_VERSION_FORCE}
EOF
MAIN=$FORCE_MAIN
SUB=$FORCE_SUB
PATCH=$FORCE_PATCH
FORCED=1
else
echo "⚠️ CI_VERSION_FORCE ungültig (${CI_VERSION_FORCE}). Ignoriere Override."
fi
fi
echo "🆙 Neue PROD-Version: ${MAIN}.${SUB}.${PATCH}"
if [ "$FORCED" -eq 1 ]; then
BUMP_NOTE="force override"
else
BUMP_MODE=$(printf '%s' "${CI_VERSION_BUMP:-patch}" | tr '[:upper:]' '[:lower:]')
case "$BUMP_MODE" in
major)
MAIN=$((MAIN + 1))
SUB=0
PATCH=0
;;
minor)
SUB=$((SUB + 1))
PATCH=0
;;
*)
BUMP_MODE="patch"
PATCH=$((PATCH + 1))
;;
esac
BUMP_NOTE="${BUMP_MODE}-bump"
fi
echo "🆙 Neue ${CI_ENVIRONMENT_NAME} Version (${BUMP_NOTE}): ${MAIN}.${SUB}.${PATCH}"
# Neue versions.php bauen
echo '<?php' > "${VERSION_WORK_FILE}"
echo "\$mainversion = ${MAIN};" >> "${VERSION_WORK_FILE}"
echo "\$subversion = ${SUB};" >> "${VERSION_WORK_FILE}"
echo "\$patchversion = ${PATCH};" >> "${VERSION_WORK_FILE}"
{
echo '<?php'
echo "\$mainversion = ${MAIN};"
echo "\$subversion = ${SUB};"
echo "\$patchversion = ${PATCH};"
} > "${VERSION_WORK_FILE}"
# Textdatei für die Webseite
echo "${MAIN}.${SUB}.${PATCH}" > public/build_version.txt

View File

@@ -6,10 +6,19 @@
// APP_COOKIE_PREFIX, APP_COOKIE_DOMAIN, APP_ENV etc.
// -----------------------------------------------------------
// Try to load primary environment bootstrap.
$bootstrapCandidates = [__DIR__ . '/config.php'];
$bootstrapCandidates = [__DIR__ . '/versions.php'];
$envHint = getenv('APP_ENV_FILE') ?: (getenv('APP_ENV') ?: null);
if ($envHint && !preg_match('/^[A-Za-z0-9_\-]+$/', $envHint)) {
$envHint = null;
}
if ($envHint === null) {
if (is_dir(__DIR__ . '/staging')) {
$envHint = 'staging';
} elseif (is_dir(__DIR__ . '/prod')) {
$envHint = 'prod';
}
}
$envHint = getenv('APP_ENV') ?: (getenv('APP_ENV_FILE') ?: null);
$bootstrapCandidates = [__DIR__ . '/config.php'];
if ($envHint) {
$bootstrapCandidates[] = __DIR__ . '/' . $envHint . '/config.php';
}
@@ -18,13 +27,32 @@ foreach ($bootstrapCandidates as $bootstrap) {
if ($bootstrap && is_file($bootstrap)) {
require_once $bootstrap;
$bootstrapLoaded = true;
break;
}
}
if (!$bootstrapLoaded) {
throw new RuntimeException('No environment config found under config/.');
}
$versionFiles = [__DIR__ . '/versions.php'];
if ($envHint) {
$versionFiles[] = __DIR__ . '/' . $envHint . '/versions.php';
}
$versionLoaded = false;
foreach ($versionFiles as $versionFile) {
if ($versionFile && is_file($versionFile)) {
require_once $versionFile;
$versionLoaded = true;
}
}
if (!$versionLoaded) {
$mainversion = $mainversion ?? 1;
$subversion = $subversion ?? 0;
$patchversion = $patchversion ?? 0;
}
$mainversion = (int)($mainversion ?? 1);
$subversion = (int)($subversion ?? 0);
$patchversion = (int)($patchversion ?? 0);
$emailtemplateConfigPath = __DIR__ . '/emailtemplate.conf.php';
if (is_file($emailtemplateConfigPath)) {
$GLOBALS['emailtemplate_config'] = require $emailtemplateConfigPath;
@@ -173,4 +201,4 @@ require_once __DIR__ . '/i18n.php';
// 4) Rest des Systems laden (DB, Funktionen, Hilfs-Libs)
// -----------------------------------------------------------
require_once __DIR__ . "/db.php";
//require_once __DIR__ . '/../src/functions.php';
require_once __DIR__ . '/../inc/helpers.php';

165
inc/helpers.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
if (!isset($GLOBALS['page_header_scripts'])) {
$GLOBALS['page_header_scripts'] = [];
}
if (!isset($GLOBALS['page_footer_scripts'])) {
$GLOBALS['page_footer_scripts'] = [];
}
if (!isset($GLOBALS['page_styles'])) {
$GLOBALS['page_styles'] = [];
}
function helper_asset_version(): ?string
{
if (isset($GLOBALS['layoutContext']['asset_version'])) {
return (string)$GLOBALS['layoutContext']['asset_version'];
}
if (defined('ASSET_VERSION')) {
return (string)ASSET_VERSION;
}
return null;
}
function helper_append_version(string $url, ?string $version = null): string
{
if ($version === null) {
$version = helper_asset_version();
}
if ($version === null || $version === '') {
return $url;
}
if (preg_match('/[?&]v=/', $url)) {
return $url;
}
return $url . (strpos($url, '?') === false ? '?' : '&') . 'v=' . rawurlencode($version);
}
function tpl_add_script(
string $src,
string $pos = 'footer',
bool $defer = false,
bool $async = false,
string $type = '',
?string $version = null,
bool $module = false
): void {
$data = [
'src' => helper_append_version($src, $version),
'defer' => $defer,
'async' => $async,
'type' => $type,
'module' => $module,
];
if ($pos === 'header') {
$GLOBALS['page_header_scripts'][] = $data;
} else {
$GLOBALS['page_footer_scripts'][] = $data;
}
}
function tpl_add_style(
string $href,
string $pos = 'header',
string $priority = 'normal',
?string $version = null,
string $media = 'all'
): void {
$GLOBALS['page_styles'][] = [
'href' => helper_append_version($href, $version),
'pos' => $pos,
'priority' => $priority,
'media' => $media,
];
}
function tpl(string $file, string $type = 'structure', string $site = 'admin'): void
{
$base = __DIR__ . '/../partials/';
$safe = static function ($value) {
return preg_match('/^[a-zA-Z0-9_\-]+$/', $value) === 1;
};
if (!$safe($file) || !$safe($type) || !$safe($site)) {
echo "<!-- tpl(): Ungültiger Parameter -->";
return;
}
$path = $type === 'landing'
? $base . "landingpage/{$site}/{$file}.php"
: $base . "structure/{$file}.php";
extract($GLOBALS, EXTR_SKIP);
if (is_file($path)) {
include $path;
} else {
echo "<!-- tpl(): Datei nicht gefunden: {$path} -->";
}
}
function tpl_render_scripts(?array $scripts = null, string $pos = 'footer'): void
{
if ($scripts === null) {
$scripts = $pos === 'header'
? ($GLOBALS['page_header_scripts'] ?? [])
: ($GLOBALS['page_footer_scripts'] ?? []);
}
foreach ($scripts as $script) {
if (empty($script['src'])) {
continue;
}
$attrs = [];
if (!empty($script['module'])) {
$attrs[] = 'type="module"';
} elseif (!empty($script['type'])) {
$attrs[] = 'type="' . htmlspecialchars((string)$script['type'], ENT_QUOTES) . '"';
}
if (!empty($script['defer'])) {
$attrs[] = 'defer';
}
if (!empty($script['async'])) {
$attrs[] = 'async';
}
$attrString = '';
if (!empty($attrs)) {
$attrString = ' ' . implode(' ', $attrs);
}
echo '<script src="' . htmlspecialchars($script['src'], ENT_QUOTES) . '"' . $attrString . '></script>' . PHP_EOL;
}
}
function tpl_render_styles(?array $styles = null, string $pos = 'header'): void
{
if ($styles === null) {
$styles = array_filter(
$GLOBALS['page_styles'] ?? [],
static fn ($style) => ($style['pos'] ?? 'header') === $pos
);
}
if (!$styles) {
return;
}
$priorityOrder = ['critical' => 0, 'high' => 1, 'normal' => 2, 'low' => 3];
usort($styles, static function ($a, $b) use ($priorityOrder) {
$aPriority = strtolower($a['priority'] ?? 'normal');
$bPriority = strtolower($b['priority'] ?? 'normal');
$aScore = $priorityOrder[$aPriority] ?? $priorityOrder['normal'];
$bScore = $priorityOrder[$bPriority] ?? $priorityOrder['normal'];
return $aScore <=> $bScore;
});
foreach ($styles as $style) {
if (empty($style['href'])) {
continue;
}
$media = trim((string)($style['media'] ?? ''));
$mediaAttr = $media && strtolower($media) !== 'all' ? ' media="' . htmlspecialchars($media, ENT_QUOTES) . '"' : '';
echo '<link rel="stylesheet" href="' . htmlspecialchars($style['href'], ENT_QUOTES) . '"' . $mediaAttr . '>' . PHP_EOL;
}
}

View File

@@ -144,8 +144,6 @@ PHP, ENT_QUOTES); ?></pre>
</form>
</dialog>
<?php
$layoutScripts = [
['src' => app_asset_url('/assets/js/toast.js')],
['src' => app_asset_url('/assets/js/bridge-setup.js'), 'module' => true],
];
tpl_add_script(app_asset_url('/assets/js/toast.js'));
tpl_add_script(app_asset_url('/assets/js/bridge-setup.js'), 'footer', false, false, '', null, true);
require dirname(__DIR__) . '/../structure/layout_end.php';

View File

@@ -67,8 +67,6 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<div id="toast-root"></div>
<?php
$layoutScripts = [
['src' => app_asset_url('/assets/js/toast.js')],
['src' => app_asset_url('/assets/js/dashboard.js'), 'module' => true],
];
tpl_add_script(app_asset_url('/assets/js/toast.js'));
tpl_add_script(app_asset_url('/assets/js/dashboard.js'), 'footer', false, false, '', null, true);
require dirname(__DIR__) . '/../structure/layout_end.php';

View File

@@ -56,8 +56,6 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<div id="toast-root"></div>
<?php
$layoutScripts = [
['src' => app_asset_url('/assets/js/toast.js')],
['src' => app_asset_url('/assets/js/account.js'), 'module' => true],
];
tpl_add_script(app_asset_url('/assets/js/toast.js'));
tpl_add_script(app_asset_url('/assets/js/account.js'), 'footer', false, false, '', null, true);
require dirname(__DIR__) . '/../structure/layout_end.php';

View File

@@ -138,8 +138,6 @@ require dirname(__DIR__) . '/../structure/layout_start.php';
<div id="toast-root"></div>
<?php
$layoutScripts = [
['src' => app_asset_url('/assets/js/toast.js')],
['src' => app_asset_url('/assets/js/account.js'), 'module' => true],
];
tpl_add_script(app_asset_url('/assets/js/toast.js'));
tpl_add_script(app_asset_url('/assets/js/account.js'), 'footer', false, false, '', null, true);
require dirname(__DIR__) . '/../structure/layout_end.php';

View File

@@ -1,27 +1,38 @@
<?php
require_once __DIR__ . '/../../inc/helpers.php';
$layoutScripts = $layoutScripts ?? [];
if (!is_array($layoutScripts)) {
$layoutScripts = [$layoutScripts];
}
foreach ($layoutScripts as $script) {
if (is_string($script)) {
$script = trim($script);
if ($script === '') {
continue;
}
tpl_add_script($script);
continue;
}
if (!is_array($script) || empty($script['src'])) {
continue;
}
tpl_add_script(
(string)$script['src'],
$script['pos'] ?? 'footer',
!empty($script['defer']),
!empty($script['async']),
(string)($script['type'] ?? ''),
$script['version'] ?? null,
!empty($script['module'])
);
}
?>
<?php require __DIR__ . '/footer.php'; ?>
<?php foreach ($layoutScripts as $script): ?>
<?php if (is_string($script)): ?>
<?= $script . PHP_EOL ?>
<?php elseif (is_array($script) && isset($script['src'])): ?>
<?php
$attrs = [];
if (!empty($script['defer'])) {
$attrs[] = 'defer';
}
if (!empty($script['module'])) {
$attrs[] = 'type="module"';
} elseif (!empty($script['type'])) {
$attrs[] = 'type="' . htmlspecialchars($script['type'], ENT_QUOTES) . '"';
}
?>
<script src="<?= htmlspecialchars($script['src'], ENT_QUOTES) ?>" <?= implode(' ', $attrs) ?>></script>
<?php endif; ?>
<?php endforeach; ?>
<?php tpl_render_styles(null, 'footer'); ?>
<?php tpl_render_scripts(null, 'footer'); ?>
</body>
</html>

View File

@@ -1,5 +1,6 @@
<?php
require_once __DIR__ . '/app_config.php';
require_once __DIR__ . '/../../inc/helpers.php';
$pageTitle = $pageTitle ?? 'Email Template System';
$bodyClass = trim(($bodyClass ?? 'bg-slate-50 text-slate-800') . ' page-shell');
@@ -14,6 +15,13 @@ $sharedCss = [
app_asset_url('/assets/css/admin.css'),
app_asset_url('/assets/css/toast.css'),
];
foreach ($sharedCss as $href) {
tpl_add_style($href, 'header', 'high');
}
if ($debugRedirect) {
tpl_add_script(app_asset_url('/assets/js/debug-location.js'), 'header');
}
?>
<!doctype html>
<html lang="de">
@@ -31,12 +39,7 @@ $sharedCss = [
<?php endif; ?>
</script>
<script src="https://cdn.tailwindcss.com"></script>
<?php if ($debugRedirect): ?>
<script src="<?= app_asset_url('/assets/js/debug-location.js') ?>"></script>
<?php endif; ?>
<?php foreach ($sharedCss as $href): ?>
<link rel="stylesheet" href="<?= htmlspecialchars($href, ENT_QUOTES) ?>">
<?php endforeach; ?>
<?php tpl_render_styles(null, 'header'); ?>
<style>
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.35rem .7rem;border-radius:.75rem;border:1px solid #e5e7eb;background:#fff;font-size:.9rem;cursor:pointer;}
.btn:hover{background:#f8fafc}
@@ -50,6 +53,7 @@ $sharedCss = [
<?php foreach ($layoutExtraHead as $snippet): ?>
<?= $snippet . PHP_EOL ?>
<?php endforeach; ?>
<?php tpl_render_scripts(null, 'header'); ?>
</head>
<body class="<?= htmlspecialchars($bodyClass) ?>" <?= $pageId ? 'data-page="' . htmlspecialchars($pageId) . '"' : '' ?>>
<?php require __DIR__ . '/header.php'; ?>

View File

@@ -1,3 +1,3 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '../config/fileload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '../partials/landingpage/admin/bridge.php';
require_once __DIR__ . '/../../config/fileload.php';
require_once __DIR__ . '/../../partials/landingpage/admin/bridge.php';

View File

@@ -1,3 +1,3 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '../config/fileload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '../partials/landingpage/admin/dashboard.php';
require_once __DIR__ . '/../../config/fileload.php';
require_once __DIR__ . '/../../partials/landingpage/admin/dashboard.php';

View File

@@ -1,4 +1,4 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '../config/fileload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '../partials/landingpage/admin/profile.php';
require_once __DIR__ . '/../../config/fileload.php';
require_once __DIR__ . '/../../partials/landingpage/admin/profile.php';

View File

@@ -1,3 +1,3 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '../config/fileload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '../partials/landingpage/admin/settings.php';
require_once __DIR__ . '/../../config/fileload.php';
require_once __DIR__ . '/../../partials/landingpage/admin/settings.php';

View File

@@ -1,4 +1,6 @@
<?php
require_once __DIR__ . '/../config/fileload.php';
$pageTitle = 'Email Template System Admin';
$pageId = 'home';
$navActive = null;
@@ -125,8 +127,6 @@ require __DIR__ . '/../partials/structure/layout_start.php';
<div id="toast-root"></div>
<?php
$layoutScripts = [
['src' => app_asset_url('/assets/js/toast.js')],
['src' => app_asset_url('/assets/js/app.js'), 'module' => true],
];
tpl_add_script(app_asset_url('/assets/js/toast.js'));
tpl_add_script(app_asset_url('/assets/js/app.js'), 'footer', false, false, '', null, true);
require __DIR__ . '/../partials/structure/layout_end.php';