Survive a stale platform-tools instead of 500ing the whole site
Deploying the local-SMTP commit took fitme3070.pt down:
PHP Warning: require_once(.../shared/platform-tools/compat_secrets.php):
Failed to open stream: No such file or directory
in .../config/functions_secrets.php on line 20
config/functions_secrets.php is loaded on every request, so a hard require_once
on a submodule file is a site-wide fatal — public pages included — whenever
shared/platform-tools lags the code that needs it. It lagged: the server is
still on 603ff84, which predates compat_secrets.php, because "Atualizar
submódulo" was itself broken (fixed in 3626080, which the site had to be up to
deploy). A missing file should not be able to close that loop.
Three requires hardened, each to the failure the caller can actually report:
- config/functions_secrets.php: falls back to plaintext passthrough helpers
and logs loudly. secrets_available() then returns false, which is exactly
what the Comunicações page already renders a warning banner for — so
nothing silently pretends to be encrypted while the site keeps serving.
- config/functions_queue.php (_flush_email): throws instead of fataling. A
fatal killed the request outright, so flush_message()'s catch never ran and
the row sat in 'sending' forever with no explanation. It now lands in
last_error where the Monitor page shows it.
- views/sysadmin/comms.php: renders "Submódulo desactualizado" naming the
missing files, rather than a blank 500 on the page you would open to
diagnose mail in the first place.
Verified by loading config/functions_secrets.php with no shared/ directory
present: the bootstrap completes, the warning is logged once, secrets_available()
is false, and setting_secret()/setting_or_env() still return usable values.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d5cb50b432
commit
0e034232e9
3 files changed
+71
-5
No files matched your search
@@ -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
|
||||
|
||||
@@ -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) ────────────────────────────────
|
||||
//
|
||||
|
||||
@@ -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 '<div class="container" style="max-width:720px;"><div class="alert alert-danger">'
|
||||
. '<h1 class="h5">Submódulo desactualizado</h1>'
|
||||
. '<p class="mb-1">Faltam em <code>shared/platform-tools/</code>: <code>'
|
||||
. htmlspecialchars(implode('</code>, <code>', $_ptMissing)) . '</code>.</p>'
|
||||
. '<p class="mb-0">Actualize o submódulo em <a href="' . htmlspecialchars(base_url('views/sysadmin/platform_update.php'))
|
||||
. '">Sistema → Actualizações</a> (ou envie a pasta por FTP) e volte a esta página.</p>'
|
||||
. '</div></div>';
|
||||
include __DIR__ . '/../../includes/footer.php';
|
||||
exit;
|
||||
}
|
||||
|
||||
$repo_root = dirname(__DIR__, 2);
|
||||
$saved = false;
|
||||
|
||||
Reference in new issue
Block a user