diff --git a/config/functions_queue.php b/config/functions_queue.php index 91e9854..6481fcb 100644 --- a/config/functions_queue.php +++ b/config/functions_queue.php @@ -128,8 +128,21 @@ function _flush_email(array $row): void { // the EMAIL_DEV_REDIRECT safety net itself, including the [DEV] subject // prefix and the banner naming the original recipient, so that logic no // longer lives here. - require_once __DIR__ . '/../shared/platform-tools/functions_mail.php'; - require_once __DIR__ . '/../shared/platform-tools/functions_settings.php'; + // A bare require_once would be a FATAL when shared/platform-tools lags the + // pinned commit (this install is deployed by file upload, so it does lag). + // A fatal here kills the whole request; flush_message()'s catch never runs + // and the row stays in 'sending' forever with no explanation. Throwing puts + // the reason in last_error, where the Monitor page shows it. + $_ptRoot = __DIR__ . '/../shared/platform-tools/'; + foreach (['functions_mail.php', 'functions_settings.php'] as $_ptFile) { + if (!is_file($_ptRoot . $_ptFile)) { + throw new RuntimeException( + "shared/platform-tools/{$_ptFile} em falta — o submódulo está desactualizado. " + . 'Actualize-o em Sistema → Actualizações.' + ); + } + require_once $_ptRoot . $_ptFile; + } // Settings come from system_settings first, .env second -- the fleet-wide // standard. This install stores them there via setting_or_env(); this diff --git a/config/functions_secrets.php b/config/functions_secrets.php index 754d030..45f5756 100644 --- a/config/functions_secrets.php +++ b/config/functions_secrets.php @@ -17,7 +17,41 @@ // (secret_encrypt, secret_decrypt, secret_is_encrypted, secret_mask, // secrets_available, SECRET_PREFIX) that the sysadmin pages already call. -require_once dirname(__DIR__) . '/shared/platform-tools/compat_secrets.php'; +// Loaded on every request, so a hard require_once here takes the entire site +// down — public pages included — whenever shared/platform-tools is older than +// the code that needs it. That is not hypothetical: this install is deployed by +// uploading files, and the submodule lags behind the rest of the tree every +// time "Atualizar submódulo" fails. It did, and the site 500'd on a missing +// compat_secrets.php with nothing but a PHP warning to explain it. +// +// So: degrade instead. Without the shared implementation the site keeps +// serving, secrets are stored as-is, and secrets_available() reports false — +// which is what the Comunicações page already renders a warning banner for. +// Nothing silently pretends to be encrypted. +$_compat_secrets = dirname(__DIR__) . '/shared/platform-tools/compat_secrets.php'; +if (is_file($_compat_secrets)) { + require_once $_compat_secrets; +} else { + error_log( + '[secrets] shared/platform-tools/compat_secrets.php is missing — the submodule is ' + . 'behind the pinned commit. Settings secrets are NOT encrypted until it is updated.' + ); + if (!defined('SECRET_PREFIX')) define('SECRET_PREFIX', 'enc:v1:'); + if (!function_exists('secret_encrypt')) { function secret_encrypt(string $plain): string { return $plain; } } + if (!function_exists('secret_decrypt')) { function secret_decrypt(?string $stored): string { return (string) $stored; } } + if (!function_exists('secret_is_encrypted')){ function secret_is_encrypted(string $stored): bool { return str_starts_with($stored, SECRET_PREFIX); } } + if (!function_exists('secret_mask')) { function secret_mask(string $plain, string $key = ''): string { return $plain === '' ? '' : '••••••••'; } } + if (!function_exists('secrets_available')) { function secrets_available(): bool { return false; } } + if (!function_exists('secrets_key_available')) { function secrets_key_available(): bool { return false; } } + // Read paths used by the SMTP resolution below and by the Comunicações page. + if (!function_exists('platform_settings_secret_available')) { function platform_settings_secret_available(): bool { return false; } } + if (!function_exists('platform_settings_is_secret')) { + function platform_settings_is_secret(string $key): bool { + return (bool) preg_match('/_(password|secret|token|api_key)$/', $key); + } + } +} +unset($_compat_secrets); // ─── Read helpers (site-specific, not crypto) ──────────────────────────────── // diff --git a/views/sysadmin/comms.php b/views/sysadmin/comms.php index fdaa506..0c40045 100644 --- a/views/sysadmin/comms.php +++ b/views/sysadmin/comms.php @@ -12,8 +12,27 @@ require __DIR__ . '/../../config/db.php'; require __DIR__ . '/../../config/functions.php'; $user = require_sysadmin(); -require_once __DIR__ . '/../../shared/platform-tools/functions_mail.php'; -require_once __DIR__ . '/../../shared/platform-tools/functions_settings.php'; +// Same reasoning as config/functions_queue.php: a stale submodule should show +// an explanation, not a blank 500 on the page you would go to in order to +// diagnose mail. +$_ptRoot = __DIR__ . '/../../shared/platform-tools/'; +$_ptMissing = []; +foreach (['functions_mail.php', 'functions_settings.php'] as $_ptFile) { + is_file($_ptRoot . $_ptFile) ? require_once $_ptRoot . $_ptFile : $_ptMissing[] = $_ptFile; +} +if ($_ptMissing) { + $page_title = 'Comunicações'; + include __DIR__ . '/../../includes/header.php'; + echo '
Faltam em shared/platform-tools/: '
+ . htmlspecialchars(implode(', ', $_ptMissing)) . '.
Actualize o submódulo em Sistema → Actualizações (ou envie a pasta por FTP) e volte a esta página.
' + . '