159 lines
4.7 KiB
PHP
159 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/bootstrap.php';
|
|
|
|
/**
|
|
* Vollständiger Katalog aller Tabellen, die zur Kaffeeliste gehören. Die
|
|
* zentrale SQL-Umschreibung beschränkt sich bewusst auf diese feste Liste;
|
|
* ein vom Request gelieferter Bezeichner kann dadurch nie in SQL gelangen.
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
function app_database_table_names(): array
|
|
{
|
|
return [
|
|
'schema_migrations',
|
|
'CoffeeSurveyVotedEmails',
|
|
'CoffeeSurveyResponses',
|
|
'dev_baseline_metadata',
|
|
'kl_Kaffeeverbrauch',
|
|
'kl_Einzahlungen',
|
|
'kl_Mitarbeiter',
|
|
'kl_hinweise',
|
|
'kl_config',
|
|
'payment_import_batches',
|
|
'payment_import_rows',
|
|
'stripe_webhook_events',
|
|
'totp_recovery_codes',
|
|
'public_page_views',
|
|
'rate_limit_attempts',
|
|
'tenant_memberships',
|
|
'legal_acceptances',
|
|
'outbound_emails',
|
|
'user_auth_tokens',
|
|
'tenant_settings',
|
|
'platform_admins',
|
|
'tenant_features',
|
|
'paypal_payments',
|
|
'ledger_entries',
|
|
'legal_requests',
|
|
'tenant_domains',
|
|
'tenant_billing',
|
|
'faq_entries',
|
|
'participants',
|
|
'audit_log',
|
|
'notices',
|
|
'tenants',
|
|
'users',
|
|
];
|
|
}
|
|
|
|
function app_validate_db_table_prefix(string $prefix): string
|
|
{
|
|
$prefix = trim($prefix);
|
|
if ($prefix === '') {
|
|
return '';
|
|
}
|
|
if (strlen($prefix) > 16 || preg_match('/^[a-z][a-z0-9_]*_$/', $prefix) !== 1) {
|
|
throw new RuntimeException(
|
|
'DB_TABLE_PREFIX muss leer sein oder aus höchstens 16 Kleinbuchstaben, Ziffern und Unterstrichen bestehen und mit Unterstrich enden.'
|
|
);
|
|
}
|
|
|
|
return $prefix;
|
|
}
|
|
|
|
function app_db_table_prefix(): string
|
|
{
|
|
$prefix = app_validate_db_table_prefix((string)app_env('DB_TABLE_PREFIX', ''));
|
|
if ($prefix === '' && !app_is_dev()) {
|
|
throw new RuntimeException('DB_TABLE_PREFIX darf außerhalb der Entwicklungsumgebung nicht leer sein.');
|
|
}
|
|
|
|
return $prefix;
|
|
}
|
|
|
|
function app_db_table_name(string $baseName, ?string $prefix = null): string
|
|
{
|
|
if (!in_array($baseName, app_database_table_names(), true)) {
|
|
throw new InvalidArgumentException('Unbekannte Anwendungstabelle: ' . $baseName);
|
|
}
|
|
|
|
return app_validate_db_table_prefix($prefix ?? app_db_table_prefix()) . $baseName;
|
|
}
|
|
|
|
/**
|
|
* Schreibt ausschließlich bekannte Tabellen- und FK-Namen um. Das gilt auch
|
|
* innerhalb der dynamischen DDL-Strings in den Migrationen 0003/0008 und für
|
|
* deren INFORMATION_SCHEMA-Vergleiche.
|
|
*/
|
|
function app_prefix_database_sql(string $sql, ?string $prefix = null): string
|
|
{
|
|
$prefix = app_validate_db_table_prefix($prefix ?? app_db_table_prefix());
|
|
if ($prefix === '') {
|
|
return $sql;
|
|
}
|
|
|
|
static $tablePattern = null;
|
|
if ($tablePattern === null) {
|
|
$tables = app_database_table_names();
|
|
usort($tables, static fn(string $a, string $b): int => strlen($b) <=> strlen($a));
|
|
$tablePattern = '/(?<![A-Za-z0-9_])('
|
|
. implode('|', array_map(static fn(string $table): string => preg_quote($table, '/'), $tables))
|
|
. ')(?![A-Za-z0-9_])/i';
|
|
}
|
|
$sql = preg_replace_callback(
|
|
$tablePattern,
|
|
static fn(array $match): string => $prefix . $match[0],
|
|
$sql
|
|
) ?? $sql;
|
|
|
|
// MySQL verlangt innerhalb einer Datenbank eindeutige Namen für
|
|
// Fremdschlüssel-Constraints. Die Migrationen verwenden durchgängig
|
|
// sprechende fk_*-Namen; sie brauchen daher ebenfalls den Umgebungs-Prefix.
|
|
$sql = preg_replace_callback(
|
|
'/(?<![A-Za-z0-9_])(fk_[A-Za-z0-9_]+)(?![A-Za-z0-9_])/i',
|
|
static fn(array $match): string => $prefix . $match[1],
|
|
$sql
|
|
) ?? $sql;
|
|
|
|
return $sql;
|
|
}
|
|
|
|
final class AppPrefixedPDO extends PDO
|
|
{
|
|
private string $tablePrefix = '';
|
|
|
|
public function setTablePrefix(string $prefix): void
|
|
{
|
|
$this->tablePrefix = app_validate_db_table_prefix($prefix);
|
|
}
|
|
|
|
public function tablePrefix(): string
|
|
{
|
|
return $this->tablePrefix;
|
|
}
|
|
|
|
public function prepare(string $query, array $options = []): PDOStatement|false
|
|
{
|
|
return parent::prepare(app_prefix_database_sql($query, $this->tablePrefix), $options);
|
|
}
|
|
|
|
public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
|
|
{
|
|
$query = app_prefix_database_sql($query, $this->tablePrefix);
|
|
if ($fetchMode === null) {
|
|
return parent::query($query);
|
|
}
|
|
|
|
return parent::query($query, $fetchMode, ...$fetchModeArgs);
|
|
}
|
|
|
|
public function exec(string $statement): int|false
|
|
{
|
|
return parent::exec(app_prefix_database_sql($statement, $this->tablePrefix));
|
|
}
|
|
}
|