update changelog and CLI php status handling
All checks were successful
CI/CD Pipeline / test (push) Successful in 3s
CI/CD Pipeline / deploy (push) Successful in 24s
CI/CD Pipeline / promote_stable (push) Successful in 3s

This commit is contained in:
2026-02-18 18:50:25 +01:00
parent d178b8da91
commit 942ab7858b
4 changed files with 102 additions and 11 deletions

View File

@@ -1,5 +1,11 @@
# Changelog
## 2026-02-18
- Added CLI default PHP version detection to system status (`php_default_version`) using the CLI `php` binary.
- Updated ACP System -> CLI to show the CLI default PHP path/version in the panel header with sufficiency indicator and warning tooltip.
- Simplified ACP CLI PHP selector to `php` or custom binary, and blocked saving `keyhelp-php-domain` from ACP.
- Added test coverage expectation for `php_default_version` in system status unit tests.
## 2026-02-12
- Refined ACP System tab with left navigation, section-specific requirements, and CLI PHP selector.
- Added CLI PHP interpreter options (php, keyhelp-php-domain, custom) with KeyHelp guidance.

View File

@@ -17,6 +17,7 @@ class SystemStatusController extends Controller
}
$phpDefaultPath = $this->resolveBinary('php');
$phpDefaultVersion = $phpDefaultPath ? $this->resolvePhpVersion($phpDefaultPath) : null;
$phpConfiguredPath = trim((string) Setting::where('key', 'system.php_binary')->value('value'));
$phpSelectedPath = $phpConfiguredPath ?: (PHP_BINARY ?: $phpDefaultPath);
$phpSelectedOk = (bool) $phpSelectedPath;
@@ -44,6 +45,7 @@ class SystemStatusController extends Controller
return response()->json([
'php' => PHP_VERSION,
'php_default' => $phpDefaultPath,
'php_default_version' => $phpDefaultVersion,
'php_configured' => $phpConfiguredPath ?: null,
'php_selected_path' => $phpSelectedPath,
'php_selected_ok' => $phpSelectedOk,

View File

@@ -63,6 +63,7 @@ const StatusIcon = ({ status = 'bad', tooltip }) => {
}
function Acp({ isAdmin }) {
const forcedMinPhpForTesting = '>=8.5'
const { t } = useTranslation()
const { roles: authRoles } = useAuth()
const canManageFounder = authRoles.includes('ROLE_FOUNDER')
@@ -299,11 +300,7 @@ function Acp({ isAdmin }) {
}
setGeneralSettings(next)
const configuredPhp = settingsMap.get('system.php_binary') || ''
const phpMode = configuredPhp === 'keyhelp-php-domain'
? 'keyhelp'
: configuredPhp === '' || configuredPhp === 'php'
? 'php'
: 'custom'
const phpMode = configuredPhp === '' || configuredPhp === 'php' ? 'php' : 'custom'
setSystemCliSettings({
php_mode: phpMode,
php_custom: phpMode === 'custom' ? configuredPhp : '',
@@ -399,11 +396,12 @@ function Acp({ isAdmin }) {
value = typeof systemCliSettings.php_custom === 'string'
? systemCliSettings.php_custom.trim()
: String(systemCliSettings.php_custom ?? '')
} else if (systemCliSettings.php_mode === 'keyhelp') {
value = 'keyhelp-php-domain'
} else {
value = 'php'
}
if (value === 'keyhelp-php-domain') {
throw new Error('`keyhelp-php-domain` is disabled in ACP CLI settings. Use a custom binary (e.g. keyhelp-php84).')
}
await saveSetting('system.php_binary', value)
setSystemCliSettings((prev) => ({
...prev,
@@ -417,6 +415,67 @@ function Acp({ isAdmin }) {
}
}
const normalizeSemver = (value) => {
if (!value) return null
const match = String(value).trim().match(/(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
if (!match) return null
return [Number(match[1]), Number(match[2] || 0), Number(match[3] || 0)]
}
const compareSemver = (a, b) => {
for (let i = 0; i < 3; i += 1) {
if (a[i] > b[i]) return 1
if (a[i] < b[i]) return -1
}
return 0
}
const parseMinPhpConstraint = (constraint) => {
if (!constraint) return null
const parts = String(constraint)
.split('||')
.map((part) => part.trim())
.filter(Boolean)
const mins = []
for (const part of parts) {
const tokens = part.split(/\s+/).filter(Boolean)
const geToken = tokens.find((token) => token.startsWith('>='))
if (geToken) {
const parsed = normalizeSemver(geToken.slice(2))
if (parsed) mins.push(parsed)
continue
}
const caretToken = tokens.find((token) => token.startsWith('^'))
if (caretToken) {
const parsed = normalizeSemver(caretToken.slice(1))
if (parsed) mins.push(parsed)
continue
}
const tildeToken = tokens.find((token) => token.startsWith('~'))
if (tildeToken) {
const parsed = normalizeSemver(tildeToken.slice(1))
if (parsed) mins.push(parsed)
continue
}
const plain = normalizeSemver(tokens[0] || '')
if (plain) mins.push(plain)
}
if (!mins.length) return null
return mins.reduce((lowest, current) => (compareSemver(current, lowest) < 0 ? current : lowest))
}
const cliDefaultPhpIsSufficient = useMemo(() => {
const current = normalizeSemver(systemStatus?.php_default_version)
const minimum = parseMinPhpConstraint(forcedMinPhpForTesting || systemStatus?.min_versions?.php)
if (!current || !minimum) return false
return compareSemver(current, minimum) >= 0
}, [systemStatus, forcedMinPhpForTesting])
const systemChecks = useMemo(() => {
if (!systemStatus) return []
return [
@@ -424,7 +483,7 @@ function Acp({ isAdmin }) {
id: 'php',
label: 'PHP',
path: systemStatus.php_selected_path || '—',
min: systemStatus.min_versions?.php || '—',
min: forcedMinPhpForTesting || systemStatus.min_versions?.php || '—',
current: systemStatus.php_selected_version || '—',
status: systemStatus.php_selected_ok ? 'ok' : 'bad',
},
@@ -3790,6 +3849,29 @@ function Acp({ isAdmin }) {
<div className="bb-acp-panel">
<div className="bb-acp-panel-header">
<h5 className="mb-0">CLI</h5>
<p className="bb-muted mb-0 mt-1">
CLI default php: {systemStatus?.php_default || '—'} (
{systemStatus?.php_default_version || 'unknown'}){' '}
{cliDefaultPhpIsSufficient ? (
<i className="bi bi-check-circle-fill text-success" aria-hidden="true" />
) : (
<OverlayTrigger
placement="top"
overlay={
<Tooltip id="cli-default-php-warning" data-bs-theme="light">
You must select a custom PHP interpreter, as the system default is not sufficient.
</Tooltip>
}
>
<span>
<i
className="bi bi-exclamation-triangle-fill text-warning"
aria-hidden="true"
/>
</span>
</OverlayTrigger>
)}
</p>
</div>
<div className="bb-acp-panel-body">
{systemCliError && <p className="text-danger">{systemCliError}</p>}
@@ -3807,7 +3889,6 @@ function Acp({ isAdmin }) {
}
>
<option value="php">php (system default)</option>
<option value="keyhelp">keyhelp-php-domain</option>
<option value="custom">Custom binary (e.g. keyhelp-php84)</option>
</Form.Select>
{systemCliSettings.php_mode === 'custom' && (
@@ -3824,8 +3905,9 @@ function Acp({ isAdmin }) {
/>
)}
<Form.Text className="bb-muted">
Used for CLI-based updates and maintenance tasks. `keyhelp-php-domain`
is available on KeyHelp Pro; on non-Pro setups use a custom binary.
Minimum required PHP (from composer.json):{' '}
{(forcedMinPhpForTesting || systemStatus?.min_versions?.php) || 'unknown'}. Use a custom binary
on like php84. On KeyHelp setups use e.g. `keyhelp-php84`.
</Form.Text>
</Form.Group>
<Button type="submit" variant="dark" disabled={systemCliSaving}>

View File

@@ -73,6 +73,7 @@ it('returns system status for admins', function (): void {
expect($payload)->toHaveKeys([
'php',
'php_default',
'php_default_version',
'composer',
'composer_version',
'node',