update changelog and CLI php status handling
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user