From 482b6d2550e5c4efeb9a447cb4cb585052c534ff Mon Sep 17 00:00:00 2001 From: Ashar Fuadi Date: Fri, 23 May 2025 19:51:29 +0700 Subject: [PATCH 1/2] Dashboard v2: Implement PHP version settings --- .../site-configurations-modal/index.tsx | 4 +- client/dashboard/.eslintrc.js | 4 + client/dashboard/app/queries.ts | 10 ++ client/dashboard/app/router.tsx | 20 +++ client/dashboard/data/index.ts | 15 ++- client/dashboard/sites/settings-php/index.tsx | 120 ++++++++++++++++++ .../dashboard/sites/settings-php/summary.tsx | 34 +++++ client/dashboard/sites/settings-php/utils.tsx | 5 + client/dashboard/sites/settings/index.tsx | 2 + .../{use-php-versions.jsx => index.jsx} | 24 ++-- .../components/playground-iframe/index.tsx | 4 +- .../server/server-configuration-form.tsx | 4 +- 12 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 client/dashboard/sites/settings-php/index.tsx create mode 100644 client/dashboard/sites/settings-php/summary.tsx create mode 100644 client/dashboard/sites/settings-php/utils.tsx rename client/data/php-versions/{use-php-versions.jsx => index.jsx} (57%) 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..8a600f0885aa --- /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: version } = useQuery( { + ...sitePHPVersionQuery( siteSlug ), + enabled: canUpdate, + } ); + const mutation = useMutation( sitePHPVersionMutation( siteSlug ) ); + const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); + + const [ formData, setFormData ] = useState< { version: string } >( { + version: version ?? '', + } ); + + 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 !== version ) { + return false; + } + return true; + } ), + }, + ]; + + const form = { + type: 'regular' as const, + fields: [ 'version' ], + }; + + const isDirty = formData.version !== version; + 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 ( + + +
+ + + data={ formData } + fields={ fields } + form={ form } + onChange={ ( edits: { version?: string } ) => { + setFormData( ( data ) => ( { ...data, ...edits } ) ); + } } + /> + + + + +
+
+
+ ); + }; + + 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/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 ); From 43201ac42188d90288d83d4b9fc2c05692412662 Mon Sep 17 00:00:00 2001 From: Ashar Fuadi Date: Mon, 26 May 2025 08:33:57 +0700 Subject: [PATCH 2/2] version -> currentVersion --- client/dashboard/sites/settings-php/index.tsx | 8 ++++---- client/dashboard/sites/settings-wordpress/index.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/dashboard/sites/settings-php/index.tsx b/client/dashboard/sites/settings-php/index.tsx index 8a600f0885aa..b0dd8cb5e276 100644 --- a/client/dashboard/sites/settings-php/index.tsx +++ b/client/dashboard/sites/settings-php/index.tsx @@ -22,7 +22,7 @@ export default function PHPVersionSettings( { siteSlug }: { siteSlug: string } ) const { data: site } = useQuery( siteQuery( siteSlug ) ); const canUpdate = site && canUpdatePHPVersion( site ); - const { data: version } = useQuery( { + const { data: currentVersion } = useQuery( { ...sitePHPVersionQuery( siteSlug ), enabled: canUpdate, } ); @@ -30,7 +30,7 @@ export default function PHPVersionSettings( { siteSlug }: { siteSlug: string } ) const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); const [ formData, setFormData ] = useState< { version: string } >( { - version: version ?? '', + version: currentVersion ?? '', } ); if ( ! site ) { @@ -46,7 +46,7 @@ export default function PHPVersionSettings( { siteSlug }: { siteSlug: string } ) Edit: 'select', elements: phpVersions.filter( ( option ) => { // Show disabled PHP version only if the site is still using it. - if ( option.disabled && option.value !== version ) { + if ( option.disabled && option.value !== currentVersion ) { return false; } return true; @@ -59,7 +59,7 @@ export default function PHPVersionSettings( { siteSlug }: { siteSlug: string } ) 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-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 ) => {