Skip to content

Commit a0865df

Browse files
committed
Expose older WordPress versions in the site settings form
Replace the "Need an older version?" link in the Playground settings panel with a proper "Include older versions" checkbox. When checked, the WordPress version dropdown lists every minor release from 1.0 through the current minified set, grouped into "Current versions" (the minified 6.3 – 6.9 + trunk/beta bucket) and "Older versions" (non-minified 6.2 back to 1.0) with disabled separator rows. When the user picks an older version, the PHP version dropdown is automatically locked to whatever runs that WordPress: * WP 1.0 – 4.9 → PHP 5.2 (the only pre-7 WASM build we ship). * WP 5.0 – 6.2 → PHP 7.4 (safest single choice across the whole bucket; PHP 8.x is unreliable on WP 5.0 – 5.5). * WP 6.3+ → no lock, default stays RecommendedPHPVersion. Implementation notes: * Widen SiteFormData.phpVersion (and the redux SiteSettings / opfs-site-storage schema) from SupportedPHPVersion to AllPHPVersion so 5.2 is representable end-to-end. * The PHP Controller is disabled via SelectControl's own `disabled` prop, not react-hook-form's Controller.disabled — the latter nulls out the field value and breaks the unlock transition. * When the user unlocks from a legacy-locked state (e.g. 4.9 → 6.5), the effect resets phpVersion to RecommendedPHPVersion so the dropdown doesn't render a stale 5.2 that isn't in its option list. * The checkbox auto-initializes to `true` when the form loads with an already-older wpVersion, so bookmarking `?wp=4.9&php=5.2` shows the right state without the user toggling anything. * older-wordpress-versions.ts holds the hardcoded WP release list plus getForcedPhpVersionForWordPress() / isOlderWordPressVersion() helpers.
1 parent 8b55186 commit a0865df

4 files changed

Lines changed: 236 additions & 46 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type { AllPHPVersion } from '@php-wasm/universal';
2+
3+
/**
4+
* WordPress versions that ship as non-minified downloads from
5+
* wordpress.org (or via the Playground CORS proxy). The web worker
6+
* handles these via its `!isMinifiedVersion` branch in
7+
* playground-worker-endpoint-blueprints-v1.ts.
8+
*
9+
* Ordered newest-first so the UI dropdown shows the most recent
10+
* older versions at the top of the "older versions" group.
11+
*/
12+
export const OlderWordPressVersions = [
13+
// WP 6.0 – 6.2 work on PHP 7.4+ but run best on PHP 8.x. Still not
14+
// minified today, so they're fetched from wordpress.org like the
15+
// legacy bucket.
16+
'6.2',
17+
'6.1',
18+
'6.0',
19+
// WP 5.x — PHP 5.6.20+ required; PHP 7.4 is the safest choice.
20+
'5.9',
21+
'5.8',
22+
'5.7',
23+
'5.6',
24+
'5.5',
25+
'5.4',
26+
'5.3',
27+
'5.2',
28+
'5.1',
29+
'5.0',
30+
// WP 4.x — PHP 5.2.4+ required; our only 5.x WASM build is 5.2.
31+
'4.9',
32+
'4.8',
33+
'4.7',
34+
'4.6',
35+
'4.5',
36+
'4.4',
37+
'4.3',
38+
'4.2',
39+
'4.1',
40+
'4.0',
41+
// WP 3.x
42+
'3.9',
43+
'3.8',
44+
'3.7',
45+
'3.6',
46+
'3.5',
47+
'3.4',
48+
'3.3',
49+
'3.2',
50+
'3.1',
51+
'3.0',
52+
// WP 2.x (2.4 was never released)
53+
'2.9',
54+
'2.8',
55+
'2.7',
56+
'2.6',
57+
'2.5',
58+
'2.3',
59+
'2.2',
60+
'2.1',
61+
'2.0',
62+
// WP 1.x (1.1, 1.3, 1.4 were never released)
63+
'1.5',
64+
'1.2',
65+
'1.0',
66+
] as const;
67+
68+
export type OlderWordPressVersion = (typeof OlderWordPressVersions)[number];
69+
70+
/**
71+
* Returns the PHP version a given WordPress release must run on
72+
* inside Playground, or `null` if any supported modern PHP version
73+
* will do.
74+
*
75+
* - WP < 5.0 (the legacy bucket): only our PHP 5.2 WASM build works.
76+
* WP 4.x officially requires PHP 5.2.4+, but Playground's 5.6+
77+
* builds have been retired so 5.2 is the only option available
78+
* here.
79+
* - WP 5.0 – 6.2 (the older-but-not-legacy bucket): PHP 7.4 is the
80+
* safest single choice — old enough for WP 5.0's PHP 5.2.4 era
81+
* code (which runs fine on 7.4) yet new enough that nothing
82+
* depends on PHP 5 quirks. PHP 8.x would work for WP 5.6+ but not
83+
* reliably for WP 5.0 – 5.5, so we force 7.4 across the whole
84+
* bucket.
85+
* - WP 6.3+ (the minified bucket): returns `null`. The UI lets the
86+
* user pick any supported PHP version and we default to the
87+
* recommended one.
88+
*/
89+
export function getForcedPhpVersionForWordPress(
90+
wpVersion: string | undefined
91+
): AllPHPVersion | null {
92+
if (!wpVersion) {
93+
return null;
94+
}
95+
const major = parseFloat(wpVersion);
96+
if (!Number.isFinite(major)) {
97+
return null;
98+
}
99+
if (major < 5) {
100+
return '5.2';
101+
}
102+
if (major < 6.3) {
103+
return '7.4';
104+
}
105+
return null;
106+
}
107+
108+
/** True for WP versions that live in the non-minified "older" bucket. */
109+
export function isOlderWordPressVersion(
110+
wpVersion: string | undefined
111+
): boolean {
112+
if (!wpVersion) {
113+
return false;
114+
}
115+
return (OlderWordPressVersions as readonly string[]).includes(wpVersion);
116+
}

‎packages/playground/website/src/components/site-manager/site-settings-form/unconnected-site-settings-form.tsx‎

Lines changed: 113 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1-
import type { SupportedPHPVersion } from '@php-wasm/universal';
1+
import type { AllPHPVersion } from '@php-wasm/universal';
22
import { SupportedPHPVersionsList } from '@php-wasm/universal';
33
import css from './style.module.css';
44
import { CheckboxControl, SelectControl } from '@wordpress/components';
5-
import { useEffect, useMemo } from 'react';
6-
import { Controller, useForm } from 'react-hook-form';
5+
import { useEffect, useMemo, useState } from 'react';
6+
import { Controller, useForm, useWatch } from 'react-hook-form';
77
import classNames from 'classnames';
88
import { __experimentalVStack as VStack } from '@wordpress/components';
99
import { useSupportedWordPressVersions } from './use-supported-wordpress-versions';
1010
import { RecommendedPHPVersion } from '@wp-playground/common';
11+
import {
12+
getForcedPhpVersionForWordPress,
13+
isOlderWordPressVersion,
14+
OlderWordPressVersions,
15+
} from './older-wordpress-versions';
1116

1217
type ConfigurableFields = Record<
1318
keyof SiteFormData & ('wpVersion' | 'language' | 'multisite'),
@@ -24,7 +29,7 @@ export interface SiteSettingsFormProps {
2429
}
2530

2631
export interface SiteFormData {
27-
phpVersion: SupportedPHPVersion;
32+
phpVersion: AllPHPVersion;
2833
wpVersion: string;
2934
language: string;
3035
withNetworking: boolean;
@@ -45,7 +50,7 @@ export function UnconnectedSiteSettingsForm({
4550
}: SiteSettingsFormProps) {
4651
const mergedDefaults = useMemo<SiteFormData>(
4752
() => ({
48-
phpVersion: RecommendedPHPVersion as SupportedPHPVersion,
53+
phpVersion: RecommendedPHPVersion as AllPHPVersion,
4954
wpVersion: 'latest',
5055
language: '',
5156
withNetworking: true,
@@ -68,6 +73,16 @@ export function UnconnectedSiteSettingsForm({
6873
const { supportedWPVersions, latestWPVersion } =
6974
useSupportedWordPressVersions();
7075

76+
// If the caller restored a stored site running an older WP
77+
// version, expand the dropdown automatically so the current
78+
// value is visible in the list.
79+
const [includeOlderVersions, setIncludeOlderVersions] = useState(() =>
80+
isOlderWordPressVersion(mergedDefaults.wpVersion)
81+
);
82+
83+
const currentWpVersion = useWatch({ control, name: 'wpVersion' });
84+
const forcedPhpVersion = getForcedPhpVersionForWordPress(currentWpVersion);
85+
7186
useEffect(() => {
7287
if (
7388
latestWPVersion &&
@@ -77,6 +92,81 @@ export function UnconnectedSiteSettingsForm({
7792
}
7893
}, [latestWPVersion, setValue, getValues]);
7994

95+
// Lock phpVersion to whatever is compatible with the selected
96+
// WordPress release. The callback fires on every wpVersion
97+
// change, so picking a different modern version (say 6.5 → 4.9)
98+
// correctly downgrades PHP, and picking 4.9 → 6.5 releases the
99+
// lock back to the recommended default.
100+
useEffect(() => {
101+
const current = getValues('phpVersion');
102+
if (forcedPhpVersion) {
103+
if (current !== forcedPhpVersion) {
104+
setValue('phpVersion', forcedPhpVersion);
105+
}
106+
return;
107+
}
108+
// Unlocking: if the current value isn't one of the modern
109+
// supported versions (e.g. it was just 5.2 or 7.4 for a
110+
// locked older WP), reset to the recommended default so the
111+
// dropdown doesn't render a value that isn't in its options.
112+
if (
113+
!(SupportedPHPVersionsList as readonly string[]).includes(current)
114+
) {
115+
setValue('phpVersion', RecommendedPHPVersion as AllPHPVersion);
116+
}
117+
}, [forcedPhpVersion, setValue, getValues]);
118+
119+
const wpVersionOptions = useMemo(() => {
120+
const modernOptions = Object.keys(supportedWPVersions || {}).map(
121+
(version) => ({
122+
label: `${supportedWPVersions[version]}`,
123+
value: version,
124+
})
125+
);
126+
if (!includeOlderVersions) {
127+
return [
128+
// Without an empty option, React sometimes says the
129+
// current selected version is "trunk" when `wp` is
130+
// actually "6.4".
131+
{ label: '-- Select a version --', value: '' },
132+
...modernOptions,
133+
];
134+
}
135+
return [
136+
{ label: '-- Select a version --', value: '' },
137+
{
138+
label: '── Current versions ──',
139+
value: '__modern_sep',
140+
disabled: true,
141+
},
142+
...modernOptions,
143+
{
144+
label: '── Older versions ──',
145+
value: '__older_sep',
146+
disabled: true,
147+
},
148+
...OlderWordPressVersions.map((version) => ({
149+
label: version,
150+
value: version,
151+
})),
152+
];
153+
}, [supportedWPVersions, includeOlderVersions]);
154+
155+
const phpVersionOptions = useMemo(() => {
156+
if (forcedPhpVersion) {
157+
return [
158+
{
159+
label: `PHP ${forcedPhpVersion}`,
160+
value: forcedPhpVersion,
161+
},
162+
];
163+
}
164+
return SupportedPHPVersionsList.map((version) => ({
165+
label: `PHP ${version}`,
166+
value: version,
167+
}));
168+
}, [forcedPhpVersion]);
169+
80170
return (
81171
<form
82172
onSubmit={handleSubmit(onSubmit)}
@@ -105,40 +195,24 @@ export function UnconnectedSiteSettingsForm({
105195
className={classNames(css.addSiteInput, {
106196
[css.invalidInput]: !!errors.wpVersion,
107197
})}
108-
options={
109-
/*
110-
* Without an empty option, React sometimes says
111-
* the current selected version is "trunk" when
112-
* `wp` is actually "6.4".
113-
*/
114-
[
115-
{
116-
label: '-- Select a version --',
117-
value: '',
118-
},
119-
...Object.keys(
120-
supportedWPVersions || {}
121-
).map((version) => ({
122-
label: `${supportedWPVersions[version]}`,
123-
value: version,
124-
})),
125-
]
126-
}
198+
options={wpVersionOptions}
127199
onChange={(value, extra) => {
128200
onChange(extra?.event);
129201
}}
130202
{...rest}
131203
/>
132204

133205
{enabledFields.wpVersion && (
134-
<a
135-
href="https://wordpress.github.io/wordpress-playground/blueprints/examples#load-an-older-wordpress-version"
136-
target="_blank"
137-
rel="noreferrer"
138-
style={{ fontSize: '0.9em' }}
139-
>
140-
Need an older version?
141-
</a>
206+
<CheckboxControl
207+
label="Include older versions"
208+
help={
209+
includeOlderVersions
210+
? 'Selecting an older version locks the PHP version to the one it runs on.'
211+
: undefined
212+
}
213+
checked={includeOlderVersions}
214+
onChange={setIncludeOlderVersions}
215+
/>
142216
)}
143217
</div>
144218
)}
@@ -159,16 +233,16 @@ export function UnconnectedSiteSettingsForm({
159233
__nextHasNoMarginBottom={true}
160234
label="PHP Version"
161235
labelPosition="side"
162-
help={errors.phpVersion?.message}
236+
disabled={!!forcedPhpVersion}
237+
help={
238+
forcedPhpVersion
239+
? `Locked to PHP ${forcedPhpVersion} for this WordPress version.`
240+
: errors.phpVersion?.message
241+
}
163242
className={classNames(css.addSiteInput, {
164243
[css.invalidInput]: !!errors.phpVersion,
165244
})}
166-
options={SupportedPHPVersionsList.map(
167-
(version) => ({
168-
label: `PHP ${version}`,
169-
value: version,
170-
})
171-
)}
245+
options={phpVersionOptions}
172246
onChange={(value, extra) => {
173247
onChange(extra?.event);
174248
}}

‎packages/playground/website/src/lib/state/opfs/opfs-site-storage.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
type PHPConstants,
1616
getBlueprintDeclaration,
1717
} from '@wp-playground/blueprints';
18-
import type { SupportedPHPVersion } from '@php-wasm/universal';
18+
import type { AllPHPVersion } from '@php-wasm/universal';
1919
import { RecommendedPHPVersion } from '@wp-playground/common';
2020
import { loadPersistedBlueprintBundle } from './opfs-blueprint-bundle-storage';
2121

@@ -210,7 +210,7 @@ function storedFormatToMetadata(data: string) {
210210
* The preferred PHP version to use.
211211
* If not specified, the latest supported version will be used
212212
*/
213-
php: SupportedPHPVersion | 'latest';
213+
php: AllPHPVersion | 'latest';
214214
/**
215215
* The preferred WordPress version to use.
216216
* If not specified, the latest supported version will be used
@@ -234,7 +234,7 @@ function storedFormatToMetadata(data: string) {
234234

235235
metadata.runtimeConfiguration = {
236236
phpVersion:
237-
(legacyConfig.preferredVersions?.php as SupportedPHPVersion) ??
237+
(legacyConfig.preferredVersions?.php as AllPHPVersion) ??
238238
RecommendedPHPVersion,
239239
wpVersion: legacyConfig.preferredVersions?.wp ?? 'latest',
240240
intl: legacyConfig.features?.intl ?? false,

‎packages/playground/website/src/lib/state/redux/site-management-api-middleware.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import { randomSiteName } from './random-site-name';
2727
import { persistTemporarySite } from './persist-temporary-site';
2828
import { selectClientBySiteSlug } from './slice-clients';
2929
import type { PlaygroundClient } from '@wp-playground/remote';
30-
import type { SupportedPHPVersion } from '@php-wasm/universal';
30+
import type { AllPHPVersion } from '@php-wasm/universal';
3131

3232
export interface SiteSettings {
33-
phpVersion?: SupportedPHPVersion;
33+
phpVersion?: AllPHPVersion;
3434
wpVersion?: string;
3535
networking?: boolean;
3636
language?: string;
@@ -51,7 +51,7 @@ export interface PlaygroundSitesAPI {
5151
name?: string,
5252
localFsHandle?: FileSystemDirectoryHandle
5353
): Promise<{ slug: string; storage: string }>;
54-
setPhpVersion(version: SupportedPHPVersion): Promise<void>;
54+
setPhpVersion(version: AllPHPVersion): Promise<void>;
5555
setNetworking(enabled: boolean): Promise<void>;
5656
delete(siteSlug: string): Promise<void>;
5757
setActiveSite(siteSlug: string): Promise<void>;
@@ -172,7 +172,7 @@ export function createSitesAPI(
172172
return { slug: site.slug, storage };
173173
},
174174

175-
async setPhpVersion(version: SupportedPHPVersion) {
175+
async setPhpVersion(version: AllPHPVersion) {
176176
const site = getActiveSiteOrThrow();
177177
if (site.metadata.storage === 'none') {
178178
throw new Error(

0 commit comments

Comments
 (0)