diff --git a/client/a8c-for-agencies/components/site-configurations-modal/index.tsx b/client/a8c-for-agencies/components/site-configurations-modal/index.tsx
index 0b7b02e363b7..abacb76b8830 100644
--- a/client/a8c-for-agencies/components/site-configurations-modal/index.tsx
+++ b/client/a8c-for-agencies/components/site-configurations-modal/index.tsx
@@ -13,7 +13,7 @@ import useCreateWPCOMSiteMutation from 'calypso/a8c-for-agencies/data/sites/use-
import FormSelect from 'calypso/components/forms/form-select';
import FormTextInputWithAffixes from 'calypso/components/forms/form-text-input-with-affixes';
import { useDataCenterOptions } from 'calypso/data/data-center/use-data-center-options';
-import { usePhpVersions } from 'calypso/data/php-versions/use-php-versions';
+import { getPHPVersions } from 'calypso/data/php-versions';
import { useDispatch } from 'calypso/state';
import { errorNotice } from 'calypso/state/notices/actions';
import { useSiteName } from './use-site-name';
@@ -42,7 +42,7 @@ export default function SiteConfigurationsModal( {
const [ isSubmitting, setIsSubmitting ] = useState( false );
const translate = useTranslate();
const dataCenterOptions = useDataCenterOptions();
- const { phpVersions } = usePhpVersions();
+ const { phpVersions } = getPHPVersions();
const siteName = useSiteName( randomSiteName, isRandomSiteNameLoading );
const { mutate: createWPCOMSite } = useCreateWPCOMSiteMutation();
const { mutate: createWPCOMDevSite } = useCreateWPCOMDevSiteMutation();
diff --git a/client/dashboard/.eslintrc.js b/client/dashboard/.eslintrc.js
index d89a9eabe0c9..1c6146c9676f 100644
--- a/client/dashboard/.eslintrc.js
+++ b/client/dashboard/.eslintrc.js
@@ -7,6 +7,10 @@ module.exports = {
{
group: [
'calypso/*',
+ // Allowed: calypso/data/php-versions
+ '!calypso/data',
+ 'calypso/data/*',
+ '!calypso/data/php-versions',
// Allowed: calypso/lib/wp
'!calypso/lib',
'calypso/lib/*',
diff --git a/client/dashboard/app/queries.ts b/client/dashboard/app/queries.ts
index 748fd21d8823..a38b1f05841b 100644
--- a/client/dashboard/app/queries.ts
+++ b/client/dashboard/app/queries.ts
@@ -4,6 +4,7 @@ import {
fetchSiteMediaStorage,
fetchSiteMonitorUptime,
fetchPHPVersion,
+ updatePHPVersion,
fetchCurrentPlan,
fetchSiteEngagementStats,
fetchDomains,
@@ -72,6 +73,15 @@ export function sitePHPVersionQuery( siteIdOrSlug: string ) {
};
}
+export function sitePHPVersionMutation( siteSlug: string ) {
+ return {
+ mutationFn: ( version: string ) => updatePHPVersion( siteSlug, version ),
+ onSuccess: () => {
+ queryClient.invalidateQueries( { queryKey: [ 'site', siteSlug, 'php-version' ] } );
+ },
+ };
+}
+
export function siteWordPressVersionQuery( siteSlug: string ) {
return {
queryKey: [ 'site', siteSlug, 'wp-version' ],
diff --git a/client/dashboard/app/router.tsx b/client/dashboard/app/router.tsx
index 1e3afc41927e..ec91dd3778b4 100644
--- a/client/dashboard/app/router.tsx
+++ b/client/dashboard/app/router.tsx
@@ -6,6 +6,7 @@ import {
createLazyRoute,
} from '@tanstack/react-router';
import { fetchTwoStep } from '../data';
+import { canUpdatePHPVersion } from '../sites/settings-php/utils';
import { canUpdateWordPressVersion } from '../sites/settings-wordpress/utils';
import NotFound from './404';
import UnknownError from './500';
@@ -19,6 +20,7 @@ import {
siteCurrentPlanQuery,
siteEngagementStatsQuery,
siteWordPressVersionQuery,
+ sitePHPVersionQuery,
} from './queries';
import { queryClient } from './query-client';
import Root from './root';
@@ -180,6 +182,23 @@ const siteSettingsWordPressRoute = createRoute( {
)
);
+const siteSettingsPHPRoute = createRoute( {
+ getParentRoute: () => siteRoute,
+ path: 'settings/php',
+ loader: async ( { params: { siteSlug } } ) => {
+ const site = await queryClient.ensureQueryData( siteQuery( siteSlug ) );
+ if ( canUpdatePHPVersion( site ) ) {
+ return await queryClient.ensureQueryData( sitePHPVersionQuery( siteSlug ) );
+ }
+ },
+} ).lazy( () =>
+ import( '../sites/settings-php' ).then( ( d ) =>
+ createLazyRoute( 'site-settings-php' )( {
+ component: () => ,
+ } )
+ )
+);
+
const siteSettingsTransferSiteRoute = createRoute( {
getParentRoute: () => siteRoute,
path: 'settings/transfer-site',
@@ -358,6 +377,7 @@ const createRouteTree = ( config: AppConfig ) => {
siteSettingsSubscriptionGiftingRoute,
siteSettingsDatabaseRoute,
siteSettingsWordPressRoute,
+ siteSettingsPHPRoute,
siteSettingsTransferSiteRoute,
] )
);
diff --git a/client/dashboard/data/index.ts b/client/dashboard/data/index.ts
index ba9a9aefe148..6e9315805a46 100644
--- a/client/dashboard/data/index.ts
+++ b/client/dashboard/data/index.ts
@@ -80,14 +80,25 @@ export const fetchSiteMonitorUptime = async (
};
export const fetchPHPVersion = async ( id: string ): Promise< string | undefined > => {
- // TODO: check request in different contexts.. Also do we show this only for atomic sites?
- // TODO: find out what check is needed before this request to avoid 403 errors.
return wpcom.req.get( {
path: `/sites/${ id }/hosting/php-version`,
apiNamespace: 'wpcom/v2',
} );
};
+export const updatePHPVersion = async (
+ siteIdOrSlug: string,
+ version: string
+): Promise< void > => {
+ return wpcom.req.post(
+ {
+ path: `/sites/${ siteIdOrSlug }/hosting/php-version`,
+ apiNamespace: 'wpcom/v2',
+ },
+ { version }
+ );
+};
+
export const fetchWordPressVersion = async ( siteIdOrSlug: string ): Promise< string > => {
return wpcom.req.get( {
path: `/sites/${ siteIdOrSlug }/hosting/wp-version`,
diff --git a/client/dashboard/sites/settings-php/index.tsx b/client/dashboard/sites/settings-php/index.tsx
new file mode 100644
index 000000000000..b0dd8cb5e276
--- /dev/null
+++ b/client/dashboard/sites/settings-php/index.tsx
@@ -0,0 +1,120 @@
+import { DataForm } from '@automattic/dataviews';
+import { useQuery, useMutation } from '@tanstack/react-query';
+import {
+ Card,
+ CardBody,
+ __experimentalHStack as HStack,
+ __experimentalVStack as VStack,
+ Button,
+} from '@wordpress/components';
+import { useDispatch } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+import { store as noticesStore } from '@wordpress/notices';
+import { useState } from 'react';
+import { getPHPVersions } from 'calypso/data/php-versions';
+import { siteQuery, sitePHPVersionQuery, sitePHPVersionMutation } from '../../app/queries';
+import PageLayout from '../../components/page-layout';
+import SettingsPageHeader from '../settings-page-header';
+import { canUpdatePHPVersion } from './utils';
+import type { Field } from '@automattic/dataviews';
+
+export default function PHPVersionSettings( { siteSlug }: { siteSlug: string } ) {
+ const { data: site } = useQuery( siteQuery( siteSlug ) );
+ const canUpdate = site && canUpdatePHPVersion( site );
+
+ const { data: currentVersion } = useQuery( {
+ ...sitePHPVersionQuery( siteSlug ),
+ enabled: canUpdate,
+ } );
+ const mutation = useMutation( sitePHPVersionMutation( siteSlug ) );
+ const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore );
+
+ const [ formData, setFormData ] = useState< { version: string } >( {
+ version: currentVersion ?? '',
+ } );
+
+ if ( ! site ) {
+ return null;
+ }
+
+ const { phpVersions } = getPHPVersions();
+
+ const fields: Field< { version: string } >[] = [
+ {
+ id: 'version',
+ label: __( 'PHP version' ),
+ Edit: 'select',
+ elements: phpVersions.filter( ( option ) => {
+ // Show disabled PHP version only if the site is still using it.
+ if ( option.disabled && option.value !== currentVersion ) {
+ return false;
+ }
+ return true;
+ } ),
+ },
+ ];
+
+ const form = {
+ type: 'regular' as const,
+ fields: [ 'version' ],
+ };
+
+ const isDirty = formData.version !== currentVersion;
+ const { isPending } = mutation;
+
+ const handleSubmit = ( e: React.FormEvent ) => {
+ e.preventDefault();
+ mutation.mutate( formData.version, {
+ onSuccess: () => {
+ createSuccessNotice( __( 'Settings saved.' ), { type: 'snackbar' } );
+ },
+ onError: () => {
+ createErrorNotice( __( 'Failed to save settings.' ), {
+ type: 'snackbar',
+ } );
+ },
+ } );
+ };
+
+ const renderForm = () => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ const renderCallout = () => {
+ return
TODO: callout
;
+ };
+
+ return (
+
+
+ { canUpdate ? renderForm() : renderCallout() }
+
+ );
+}
diff --git a/client/dashboard/sites/settings-php/summary.tsx b/client/dashboard/sites/settings-php/summary.tsx
new file mode 100644
index 000000000000..903a8419150a
--- /dev/null
+++ b/client/dashboard/sites/settings-php/summary.tsx
@@ -0,0 +1,34 @@
+import { useQuery } from '@tanstack/react-query';
+import { Icon } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+import { code } from '@wordpress/icons';
+import { getPHPVersions } from 'calypso/data/php-versions';
+import { sitePHPVersionQuery } from '../../app/queries';
+import RouterLinkSummaryButton from '../../components/router-link-summary-button';
+import { canUpdatePHPVersion } from './utils';
+import type { Site } from '../../data/types';
+
+export default function PHPSettingsSummary( { site }: { site: Site } ) {
+ const { data: version } = useQuery( {
+ ...sitePHPVersionQuery( site.slug ),
+ enabled: canUpdatePHPVersion( site ),
+ } );
+
+ const { recommendedValue } = getPHPVersions();
+
+ const badge = {
+ text: version ?? __( 'Managed' ),
+ intent:
+ version && version !== recommendedValue ? ( 'warning' as const ) : ( 'success' as const ),
+ };
+
+ return (
+ }
+ badges={ [ badge ] }
+ />
+ );
+}
diff --git a/client/dashboard/sites/settings-php/utils.tsx b/client/dashboard/sites/settings-php/utils.tsx
new file mode 100644
index 000000000000..75131beddd52
--- /dev/null
+++ b/client/dashboard/sites/settings-php/utils.tsx
@@ -0,0 +1,5 @@
+import { Site } from '../../data/types';
+
+export function canUpdatePHPVersion( site: Site ) {
+ return site.is_wpcom_atomic;
+}
diff --git a/client/dashboard/sites/settings-wordpress/index.tsx b/client/dashboard/sites/settings-wordpress/index.tsx
index 231a4375364d..7c4e08e10ece 100644
--- a/client/dashboard/sites/settings-wordpress/index.tsx
+++ b/client/dashboard/sites/settings-wordpress/index.tsx
@@ -29,7 +29,7 @@ export default function WordPressVersionSettings( { siteSlug }: { siteSlug: stri
const { data: site } = useQuery( siteQuery( siteSlug ) );
const canUpdate = site && canUpdateWordPressVersion( site );
- const { data: version } = useQuery( {
+ const { data: currentVersion } = useQuery( {
...siteWordPressVersionQuery( siteSlug ),
enabled: canUpdate,
} );
@@ -37,7 +37,7 @@ export default function WordPressVersionSettings( { siteSlug }: { siteSlug: stri
const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore );
const [ formData, setFormData ] = useState< { version: string } >( {
- version: version ?? '',
+ version: currentVersion ?? '',
} );
if ( ! site ) {
@@ -61,7 +61,7 @@ export default function WordPressVersionSettings( { siteSlug }: { siteSlug: stri
fields: [ 'version' ],
};
- const isDirty = formData.version !== version;
+ const isDirty = formData.version !== currentVersion;
const { isPending } = mutation;
const handleSubmit = ( e: React.FormEvent ) => {
diff --git a/client/dashboard/sites/settings/index.tsx b/client/dashboard/sites/settings/index.tsx
index de47b5da5d2e..091fb1c59cb4 100644
--- a/client/dashboard/sites/settings/index.tsx
+++ b/client/dashboard/sites/settings/index.tsx
@@ -9,6 +9,7 @@ import { siteQuery, siteSettingsQuery } from '../../app/queries';
import { PageHeader } from '../../components/page-header';
import PageLayout from '../../components/page-layout';
import DatabaseSettingsSummary from '../settings-database/summary';
+import PHPSettingsSummary from '../settings-php/summary';
import SiteVisibilitySettingsSummary from '../settings-site-visibility/summary';
import SubscriptionGiftingSettingsSummary from '../settings-subscription-gifting/summary';
import WordPressSettingsSummary from '../settings-wordpress/summary';
@@ -37,6 +38,7 @@ export default function SiteSettings( { siteSlug }: { siteSlug: string } ) {
+
diff --git a/client/data/php-versions/use-php-versions.jsx b/client/data/php-versions/index.jsx
similarity index 57%
rename from client/data/php-versions/use-php-versions.jsx
rename to client/data/php-versions/index.jsx
index 9c51cc928cd1..374899dc8cea 100644
--- a/client/data/php-versions/use-php-versions.jsx
+++ b/client/data/php-versions/index.jsx
@@ -1,14 +1,10 @@
-import { useTranslate } from 'i18n-calypso';
-
-export const usePhpVersions = () => {
- const translate = useTranslate();
+import { __, sprintf } from '@wordpress/i18n';
+export const getPHPVersions = () => {
// PHP 8.3 is now the default recommended version
const recommendedValue = '8.3';
- const recommendedLabel = translate( '%s (recommended)', {
- args: recommendedValue,
- comment: 'PHP Version for a version switcher',
- } );
+ // translators: PHP Version for a version switcher
+ const recommendedLabel = sprintf( __( '%s (recommended)' ), recommendedValue );
const phpVersions = [
{
@@ -17,10 +13,8 @@ export const usePhpVersions = () => {
disabled: true, // EOL 6th December, 2021
},
{
- label: translate( '%s (deprecated)', {
- args: '7.4',
- comment: 'PHP Version for a version switcher',
- } ),
+ // translators: PHP Version for a version switcher
+ label: sprintf( __( '%s (deprecated)' ), '7.4' ),
value: '7.4',
disabled: true, // EOL 1st July, 2024
},
@@ -30,10 +24,8 @@ export const usePhpVersions = () => {
disabled: true, // EOL 26th November, 2023
},
{
- label: translate( '%s (deprecated)', {
- args: '8.1',
- comment: 'PHP Version for a version switcher',
- } ),
+ // translators: PHP Version for a version switcher
+ label: sprintf( __( '%s (deprecated)' ), '8.1' ),
value: '8.1',
disabled: false, // EOL 31st December, 2025
},
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/playground/components/playground-iframe/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/playground/components/playground-iframe/index.tsx
index b2bbe3794397..19dfea78d1d8 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/playground/components/playground-iframe/index.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/playground/components/playground-iframe/index.tsx
@@ -1,6 +1,6 @@
import { useEffect, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
-import { usePhpVersions } from 'calypso/data/php-versions/use-php-versions';
+import { getPHPVersions } from 'calypso/data/php-versions';
import { initializeWordPressPlayground } from '../../lib/initialize-playground';
import { PlaygroundError } from '../playground-error';
import type { PlaygroundClient } from '@wp-playground/client';
@@ -15,7 +15,7 @@ export function PlaygroundIframe( {
setPlaygroundClient: ( client: PlaygroundClient ) => void;
} ) {
const iframeRef = useRef< HTMLIFrameElement >( null );
- const recommendedPHPVersion = usePhpVersions().recommendedValue;
+ const recommendedPHPVersion = getPHPVersions().recommendedValue;
const [ searchParams, setSearchParams ] = useSearchParams();
const [ playgroundError, setPlaygroundError ] = useState< string | null >( null );
diff --git a/client/sites/settings/server/server-configuration-form.tsx b/client/sites/settings/server/server-configuration-form.tsx
index 7d3d45d3fbd0..cd35a3126a9d 100644
--- a/client/sites/settings/server/server-configuration-form.tsx
+++ b/client/sites/settings/server/server-configuration-form.tsx
@@ -13,7 +13,7 @@ import FormSettingExplanation from 'calypso/components/forms/form-setting-explan
import FormTextInput from 'calypso/components/forms/form-text-input';
import { PanelCard, PanelCardHeading } from 'calypso/components/panel';
import { useDataCenterOptions } from 'calypso/data/data-center/use-data-center-options';
-import { usePhpVersions } from 'calypso/data/php-versions/use-php-versions';
+import { getPHPVersions } from 'calypso/data/php-versions';
import { useSelector } from 'calypso/state';
import {
updateAtomicPhpVersion,
@@ -93,7 +93,7 @@ export default function ServerConfigurationForm( { disabled }: ServerConfigurati
const isPhpVersionChanged = selectedPhpVersion && selectedPhpVersion !== phpVersion;
const isStaticFile404Changed = selectedStaticFile404 && selectedStaticFile404 !== staticFile404;
- const { recommendedValue, phpVersions } = usePhpVersions();
+ const { recommendedValue, phpVersions } = getPHPVersions();
const dataCenterOptions = useDataCenterOptions();
const wpVersionRef = useRef< HTMLLabelElement >( null );