diff --git a/projects/js-packages/publicize-components/src/components/connection-management/connection-name.tsx b/projects/js-packages/publicize-components/src/components/connection-management/connection-name.tsx
index 7bdd740496e47..d28420646ea29 100644
--- a/projects/js-packages/publicize-components/src/components/connection-management/connection-name.tsx
+++ b/projects/js-packages/publicize-components/src/components/connection-management/connection-name.tsx
@@ -27,12 +27,10 @@ export function ConnectionName( { connection }: ConnectionNameProps ) {
return (
{ ! connection.profile_link ? (
-
- { connection.display_name || connection.external_name }
-
+
{ connection.display_name }
) : (
- { connection.display_name || connection.external_display || connection.external_name }
+ { connection.display_name }
) }
{ isUpdating ? (
diff --git a/projects/js-packages/publicize-components/src/components/connection-management/disconnect.tsx b/projects/js-packages/publicize-components/src/components/connection-management/disconnect.tsx
index ccc50575a39d9..e804757301458 100644
--- a/projects/js-packages/publicize-components/src/components/connection-management/disconnect.tsx
+++ b/projects/js-packages/publicize-components/src/components/connection-management/disconnect.tsx
@@ -30,15 +30,16 @@ export function Disconnect( {
const { deleteConnectionById } = useDispatch( socialStore );
- const { isDisconnecting } = useSelect(
+ const { isDisconnecting, canManageConnection } = useSelect(
select => {
- const { getDeletingConnections } = select( socialStore );
+ const { getDeletingConnections, canUserManageConnection } = select( socialStore );
return {
isDisconnecting: getDeletingConnections().includes( connection.connection_id ),
+ canManageConnection: canUserManageConnection( connection ),
};
},
- [ connection.connection_id ]
+ [ connection ]
);
const onClickDisconnect = useCallback( async () => {
@@ -49,7 +50,7 @@ export function Disconnect( {
} );
}, [ connection.connection_id, deleteConnectionById ] );
- if ( ! connection.can_disconnect ) {
+ if ( ! canManageConnection ) {
return null;
}
diff --git a/projects/js-packages/publicize-components/src/components/connection-management/reconnect.tsx b/projects/js-packages/publicize-components/src/components/connection-management/reconnect.tsx
index 22bf30e2e64d2..a3e50e1ef5200 100644
--- a/projects/js-packages/publicize-components/src/components/connection-management/reconnect.tsx
+++ b/projects/js-packages/publicize-components/src/components/connection-management/reconnect.tsx
@@ -24,15 +24,16 @@ export function Reconnect( { connection, service, variant = 'link' }: ReconnectP
const { deleteConnectionById, setKeyringResult, openConnectionsModal, setReconnectingAccount } =
useDispatch( socialStore );
- const { isDisconnecting } = useSelect(
+ const { isDisconnecting, canManageConnection } = useSelect(
select => {
- const { getDeletingConnections } = select( socialStore );
+ const { getDeletingConnections, canUserManageConnection } = select( socialStore );
return {
isDisconnecting: getDeletingConnections().includes( connection.connection_id ),
+ canManageConnection: canUserManageConnection( connection ),
};
},
- [ connection.connection_id ]
+ [ connection ]
);
const onConfirm = useCallback(
@@ -63,7 +64,7 @@ export function Reconnect( { connection, service, variant = 'link' }: ReconnectP
const formData = new FormData();
if ( service.ID === 'mastodon' ) {
- formData.set( 'instance', connection.external_display );
+ formData.set( 'instance', connection.external_handle );
}
if ( service.ID === 'bluesky' ) {
@@ -80,7 +81,7 @@ export function Reconnect( { connection, service, variant = 'link' }: ReconnectP
setReconnectingAccount,
] );
- if ( ! connection.can_disconnect ) {
+ if ( ! canManageConnection ) {
return null;
}
diff --git a/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/disconnect.test.js b/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/disconnect.test.js
index 0bb54b87314ba..52a8dbc52a9cf 100644
--- a/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/disconnect.test.js
+++ b/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/disconnect.test.js
@@ -38,7 +38,6 @@ describe( 'Disconnecting a connection', () => {
service_name: 'facebook',
connection_id: '2',
display_name: 'Facebook',
- can_disconnect: true,
} }
/>
);
diff --git a/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/mark-as-shared.test.js b/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/mark-as-shared.test.js
index cb946d29b2ad5..111c88a4b1c37 100644
--- a/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/mark-as-shared.test.js
+++ b/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/mark-as-shared.test.js
@@ -27,7 +27,6 @@ describe( 'Marking a connection as shared', () => {
service_name: 'facebook',
connection_id: '2',
display_name: 'Facebook',
- can_disconnect: true,
} }
/>
);
diff --git a/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/reconnect.test.js b/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/reconnect.test.js
index ca471f52581ed..c6eb17f78f62d 100644
--- a/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/reconnect.test.js
+++ b/projects/js-packages/publicize-components/src/components/connection-management/tests/specs/reconnect.test.js
@@ -17,8 +17,7 @@ describe( 'Reconnect', () => {
const mockConnection = {
connection_id: '123',
- can_disconnect: true,
- external_display: 'mockDisplay',
+ display_name: 'mockDisplay',
};
beforeEach( () => {
@@ -58,12 +57,8 @@ describe( 'Reconnect', () => {
} );
test( 'does not render the button if connection cannot be disconnected', () => {
- const nonDisconnectableConnection = {
- ...mockConnection,
- can_disconnect: false,
- };
-
- render(
);
+ setup( { canUserManageConnection: false } );
+ render(
);
expect( screen.queryByRole( 'button' ) ).not.toBeInTheDocument();
} );
diff --git a/projects/js-packages/publicize-components/src/components/connection/index.js b/projects/js-packages/publicize-components/src/components/connection/index.js
index b8601613d87b3..03d643a55d575 100644
--- a/projects/js-packages/publicize-components/src/components/connection/index.js
+++ b/projects/js-packages/publicize-components/src/components/connection/index.js
@@ -62,7 +62,7 @@ class PublicizeConnection extends Component {
}
isDisabled() {
- return this.props.disabled || this.connectionIsFailing() || this.connectionNeedsReauth();
+ return this.props.disabled || this.connectionIsFailing();
}
render() {
diff --git a/projects/js-packages/publicize-components/src/components/form/broken-connections-notice.tsx b/projects/js-packages/publicize-components/src/components/form/broken-connections-notice.tsx
index ffb87494effae..fc142cfe131c3 100644
--- a/projects/js-packages/publicize-components/src/components/form/broken-connections-notice.tsx
+++ b/projects/js-packages/publicize-components/src/components/form/broken-connections-notice.tsx
@@ -1,30 +1,18 @@
import { Button } from '@automattic/jetpack-components';
import { ExternalLink } from '@wordpress/components';
-import { useDispatch } from '@wordpress/data';
+import { useDispatch, useSelect } from '@wordpress/data';
import { createInterpolateElement, Fragment } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import usePublicizeConfig from '../../hooks/use-publicize-config';
-import useSocialMediaConnections from '../../hooks/use-social-media-connections';
import { store } from '../../social-store';
import { Connection } from '../../social-store/types';
-import { checkConnectionCode } from '../../utils/connections';
import { getSocialScriptData } from '../../utils/script-data';
import Notice from '../notice';
import { useServiceLabel } from '../services/use-service-label';
import styles from './styles.module.scss';
export const BrokenConnectionsNotice: React.FC = () => {
- const { connections } = useSocialMediaConnections();
-
- const brokenConnections = connections.filter( connection => {
- return (
- connection.status === 'broken' ||
- // This is a legacy check for connections that are not healthy.
- // TODO remove this check when we are sure that all connections have
- // the status property (same schema for connections endpoints), e.g. on Simple/Atomic sites
- checkConnectionCode( connection, 'broken' )
- );
- } );
+ const brokenConnections = useSelect( select => select( store ).getBrokenConnections(), [] );
const { connectionsPageUrl } = usePublicizeConfig();
@@ -87,11 +75,9 @@ export const BrokenConnectionsNotice: React.FC = () => {
{
// Since Intl.ListFormat is not allowed in Jetpack yet,
// we join the connections with a comma and space
- connectionsList.map( ( { display_name, external_display, id }, i ) => (
-
-
- { display_name || external_display }
-
+ connectionsList.map( ( { display_name, connection_id }, i ) => (
+
+ { display_name }
{ i < connectionsList.length - 1 &&
_x(
',',
diff --git a/projects/js-packages/publicize-components/src/components/form/connections-list.tsx b/projects/js-packages/publicize-components/src/components/form/connections-list.tsx
index c2c5e5c51b4dc..e15950ad91771 100644
--- a/projects/js-packages/publicize-components/src/components/form/connections-list.tsx
+++ b/projects/js-packages/publicize-components/src/components/form/connections-list.tsx
@@ -35,18 +35,17 @@ export const ConnectionsList: React.FC = () => {
{ connections.map( conn => {
- const { display_name, id, service_name, profile_picture, connection_id } = conn;
- const currentId = connection_id ? connection_id : id;
+ const { display_name, service_name, profile_picture, connection_id } = conn;
return (
);
diff --git a/projects/js-packages/publicize-components/src/components/form/unsupported-connections-notice.tsx b/projects/js-packages/publicize-components/src/components/form/unsupported-connections-notice.tsx
index fd64817250ccc..778db75f9b53f 100644
--- a/projects/js-packages/publicize-components/src/components/form/unsupported-connections-notice.tsx
+++ b/projects/js-packages/publicize-components/src/components/form/unsupported-connections-notice.tsx
@@ -3,16 +3,20 @@ import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { usePublicizeConfig } from '../../..';
import useSocialMediaConnections from '../../hooks/use-social-media-connections';
-import { checkConnectionCode } from '../../utils/connections';
import Notice from '../notice';
+import { useService } from '../services/use-service';
export const UnsupportedConnectionsNotice: React.FC = () => {
const { connections } = useSocialMediaConnections();
const { connectionsPageUrl } = usePublicizeConfig();
- const unsupportedConnections = connections.filter( connection =>
- checkConnectionCode( connection, 'unsupported' )
+ const getServices = useService();
+
+ const unsupportedConnections = connections.filter(
+ connection =>
+ // If getServices returns falsy, it means the service is unsupported.
+ ! getServices( connection.service_name )
);
return (
diff --git a/projects/js-packages/publicize-components/src/components/form/use-connection-state.ts b/projects/js-packages/publicize-components/src/components/form/use-connection-state.ts
index e994363e19d4a..7f3a5bc4daaa6 100644
--- a/projects/js-packages/publicize-components/src/components/form/use-connection-state.ts
+++ b/projects/js-packages/publicize-components/src/components/form/use-connection-state.ts
@@ -31,17 +31,16 @@ export const useConnectionState = () => {
*/
const isInGoodShape = useCallback(
( connection: Connection ) => {
- const { id, is_healthy, connection_id, status } = connection;
- const currentId = connection_id ? connection_id : id;
+ const { connection_id: id, status } = connection;
// 1. Be healthy
- const isHealthy = false !== is_healthy && status !== 'broken';
+ const isHealthy = status !== 'broken';
// 2. Have no validation errors
- const hasValidationErrors = validationErrors[ currentId ] !== undefined && ! isConvertible;
+ const hasValidationErrors = validationErrors[ id ] !== undefined && ! isConvertible;
// 3. Not have a NO_MEDIA_ERROR when media is required
- const hasNoMediaError = validationErrors[ currentId ] === NO_MEDIA_ERROR;
+ const hasNoMediaError = validationErrors[ id ] === NO_MEDIA_ERROR;
return isHealthy && ! hasValidationErrors && ! hasNoMediaError;
},
diff --git a/projects/js-packages/publicize-components/src/components/manage-connections-modal/confirmation-form/index.tsx b/projects/js-packages/publicize-components/src/components/manage-connections-modal/confirmation-form/index.tsx
index f99e92f74e942..a07f892d38a56 100644
--- a/projects/js-packages/publicize-components/src/components/manage-connections-modal/confirmation-form/index.tsx
+++ b/projects/js-packages/publicize-components/src/components/manage-connections-modal/confirmation-form/index.tsx
@@ -162,7 +162,7 @@ export function ConfirmationForm( { keyringResult, onComplete, isAdmin }: Confir
display_name: accountInfo?.label,
profile_picture: accountInfo?.profile_picture,
service_name: service.ID,
- external_id: external_user_ID,
+ external_id: external_user_ID.toString(),
} );
onComplete();
diff --git a/projects/js-packages/publicize-components/src/components/services/connect-form.tsx b/projects/js-packages/publicize-components/src/components/services/connect-form.tsx
index a700da6a8514a..a530834148449 100644
--- a/projects/js-packages/publicize-components/src/components/services/connect-form.tsx
+++ b/projects/js-packages/publicize-components/src/components/services/connect-form.tsx
@@ -24,7 +24,7 @@ type ConnectFormProps = {
*
* @param {ConnectFormProps} props - Component props
*
- * @return {import('react').ReactNode} Connect form component
+ * @return Connect form component
*/
export function ConnectForm( {
service,
diff --git a/projects/js-packages/publicize-components/src/components/services/custom-inputs.tsx b/projects/js-packages/publicize-components/src/components/services/custom-inputs.tsx
index 90f61a1350056..e2f114f412ba4 100644
--- a/projects/js-packages/publicize-components/src/components/services/custom-inputs.tsx
+++ b/projects/js-packages/publicize-components/src/components/services/custom-inputs.tsx
@@ -73,7 +73,7 @@ export function CustomInputs( { service }: CustomInputsProps ) {
name="handle"
defaultValue={
reconnectingAccount?.service_name === 'bluesky'
- ? reconnectingAccount?.external_name
+ ? reconnectingAccount?.external_handle
: undefined
}
autoComplete="off"
diff --git a/projects/js-packages/publicize-components/src/components/services/service-connection-info.tsx b/projects/js-packages/publicize-components/src/components/services/service-connection-info.tsx
index ca3075a16f584..7e5bf2c806391 100644
--- a/projects/js-packages/publicize-components/src/components/services/service-connection-info.tsx
+++ b/projects/js-packages/publicize-components/src/components/services/service-connection-info.tsx
@@ -1,5 +1,7 @@
import { IconTooltip, Text } from '@automattic/jetpack-components';
+import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
+import { store as socialStore } from '../../social-store';
import { Connection } from '../../social-store/types';
import { ConnectionName } from '../connection-management/connection-name';
import { ConnectionStatus } from '../connection-management/connection-status';
@@ -19,6 +21,11 @@ export const ServiceConnectionInfo = ( {
service,
isAdmin,
}: ServiceConnectionInfoProps ) => {
+ const canManageConnection = useSelect(
+ select => select( socialStore ).canUserManageConnection( connection ),
+ [ connection ]
+ );
+
return (
@@ -40,7 +47,7 @@ export const ServiceConnectionInfo = ( {
* if the user can disconnect the connection.
* Otherwise, non-admin authors will see only the status without any further context.
*/
- if ( conn.status === 'broken' && conn.can_disconnect ) {
+ if ( conn.status === 'broken' && canManageConnection ) {
return
;
}
@@ -63,7 +70,7 @@ export const ServiceConnectionInfo = ( {
* Now if the user is not an admin, we tell them that the connection
* was added by an admin and show the connection status if it's broken.
*/
- return ! conn.can_disconnect ? (
+ return ! canManageConnection ? (
<>
{ __(
diff --git a/projects/js-packages/publicize-components/src/components/services/service-item.tsx b/projects/js-packages/publicize-components/src/components/services/service-item.tsx
index 2ab25c5163801..56e2322786610 100644
--- a/projects/js-packages/publicize-components/src/components/services/service-item.tsx
+++ b/projects/js-packages/publicize-components/src/components/services/service-item.tsx
@@ -1,8 +1,10 @@
import { Button, useBreakpointMatch } from '@automattic/jetpack-components';
import { Panel, PanelBody } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
import { useEffect, useReducer, useRef } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import { Icon, chevronDown, chevronUp } from '@wordpress/icons';
+import { store as socialStore } from '../../social-store';
import { ConnectForm } from './connect-form';
import { ServiceItemDetails, ServicesItemDetailsProps } from './service-item-details';
import { ServiceStatus } from './service-status';
@@ -40,8 +42,13 @@ export function ServiceItem( {
const brokenConnections = serviceConnections.filter( ( { status } ) => status === 'broken' );
- const hasOwnBrokenConnections = brokenConnections.some(
- ( { can_disconnect } ) => can_disconnect
+ const hasOwnBrokenConnections = useSelect(
+ select => {
+ const { canUserManageConnection } = select( socialStore );
+
+ return brokenConnections.some( canUserManageConnection );
+ },
+ [ brokenConnections ]
);
const hideInitialConnectForm =
diff --git a/projects/js-packages/publicize-components/src/components/services/service-status.tsx b/projects/js-packages/publicize-components/src/components/services/service-status.tsx
index 0620edc6231ba..f21a5f97b77e3 100644
--- a/projects/js-packages/publicize-components/src/components/services/service-status.tsx
+++ b/projects/js-packages/publicize-components/src/components/services/service-status.tsx
@@ -1,5 +1,7 @@
import { Alert } from '@automattic/jetpack-components';
+import { useSelect } from '@wordpress/data';
import { __, _n, sprintf } from '@wordpress/i18n';
+import { store as socialStore } from '../../social-store';
import { Connection } from '../../social-store/types';
import styles from './style.module.scss';
@@ -16,13 +18,16 @@ export type ServiceStatusProps = {
* @return {import('react').ReactNode} Service status component
*/
export function ServiceStatus( { serviceConnections, brokenConnections }: ServiceStatusProps ) {
+ const canFix = useSelect(
+ select => brokenConnections.some( select( socialStore ).canUserManageConnection ),
+ [ brokenConnections ]
+ );
+
if ( ! serviceConnections.length ) {
return null;
}
if ( brokenConnections.length > 0 ) {
- const canFix = brokenConnections.some( ( { can_disconnect } ) => can_disconnect );
-
return (
( {
@@ -19,13 +20,22 @@ describe( 'ServiceConnectionInfo', () => {
profile_picture: 'https://example.com/profile.jpg',
display_name: 'Example User',
status: 'connected',
- can_disconnect: true,
};
const service = {
icon: () => ,
};
+ beforeAll( () => {
+ global.JetpackScriptData = {
+ user: {
+ current_user: {
+ id: 123,
+ },
+ },
+ };
+ } );
+
const renderComponent = ( connOverrides = {}, serviceOverrides = {}, props = {} ) => {
render(
{
);
};
+ afterEach( () => {
+ jest.clearAllMocks();
+ } );
+
test( 'renders profile picture if available', () => {
renderComponent();
const profilePic = screen.getByAltText( 'Example User' );
@@ -70,7 +84,9 @@ describe( 'ServiceConnectionInfo', () => {
} );
test( 'displays description if connection cannot be disconnected', () => {
- renderComponent( { can_disconnect: false } );
+ setup( { canUserManageConnection: false } );
+ renderComponent();
+
expect(
screen.getByText( 'This connection is added by a site administrator.' )
).toBeInTheDocument();
diff --git a/projects/js-packages/publicize-components/src/components/social-post-modal/post-preview.tsx b/projects/js-packages/publicize-components/src/components/social-post-modal/post-preview.tsx
index 55cb3717cf7e0..a89ddc5ad3f75 100644
--- a/projects/js-packages/publicize-components/src/components/social-post-modal/post-preview.tsx
+++ b/projects/js-packages/publicize-components/src/components/social-post-modal/post-preview.tsx
@@ -33,9 +33,9 @@ export type PostPreviewProps = {
export function PostPreview( { connection }: PostPreviewProps ) {
const user = useMemo(
() => ( {
- displayName: connection.display_name || connection.external_display,
+ displayName: connection.display_name,
profileImage: connection.profile_picture,
- externalName: connection.external_name,
+ externalName: connection.external_handle,
} ),
[ connection ]
);
diff --git a/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx b/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx
index 18e656f04082b..cbb0bef242bd5 100644
--- a/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx
+++ b/projects/js-packages/publicize-components/src/components/social-post-modal/preview-section.tsx
@@ -33,7 +33,7 @@ export function PreviewSection() {
// to avoid errors for old connections like Twitter
.filter( ( { service_name } ) => getService( service_name ) )
.map( connection => {
- const title = connection.display_name || connection.external_display;
+ const title = connection.display_name;
const name = `${ connection.service_name }-${ connection.connection_id }`;
const icon = (
- conn.connection_id
- ? conn.connection_id === freshConnection.connection_id
- : conn.id === freshConnection.id
+ const prevConnection = prevConnections.find(
+ conn => conn.connection_id === freshConnection.connection_id
);
const connection = {
...defaults,
...prevConnection,
...freshConnection,
- shared: prevConnection?.shared,
- is_healthy: freshConnection.test_success,
};
connections.push( connection );
}
@@ -288,7 +282,7 @@ export function deleteConnectionById( { connectionId, showSuccessNotice = true }
const { createErrorNotice, createSuccessNotice } = coreDispatch( globalNoticesStore );
try {
- const path = `/jetpack/v4/social/connections/${ connectionId }`;
+ const path = `/wpcom/v2/publicize/connections/${ connectionId }`;
// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );
@@ -347,7 +341,7 @@ export function createConnection( data, optimisticData = {} ) {
const tempId = `new-${ ++uniqueId }`;
try {
- const path = `/jetpack/v4/social/connections/`;
+ const path = `/wpcom/v2/publicize/connections/`;
dispatch(
addConnection( {
@@ -368,7 +362,6 @@ export function createConnection( data, optimisticData = {} ) {
// Updating the connection will also override the connection_id.
updateConnection( tempId, {
...connection,
- can_disconnect: true,
// For editor, we always enable the connection by default.
enabled: true,
} )
@@ -378,7 +371,7 @@ export function createConnection( data, optimisticData = {} ) {
sprintf(
/* translators: %s is the name of the social media platform e.g. "Facebook" */
__( '%s account connected successfully.', 'jetpack-publicize-components' ),
- connection.label
+ connection.service_label
),
{
type: 'snackbar',
@@ -467,7 +460,7 @@ export function updateConnectionById( connectionId, data ) {
const prevConnection = select.getConnectionById( connectionId );
try {
- const path = `/jetpack/v4/social/connections/${ connectionId }`;
+ const path = `/wpcom/v2/publicize/connections/${ connectionId }`;
// Abort the refresh connections request.
dispatch( abortRefreshConnectionsRequest() );
diff --git a/projects/js-packages/publicize-components/src/social-store/actions/test/connection-data.js b/projects/js-packages/publicize-components/src/social-store/actions/test/connection-data.js
index 86b84bc27c9b3..2e6efa6235e99 100644
--- a/projects/js-packages/publicize-components/src/social-store/actions/test/connection-data.js
+++ b/projects/js-packages/publicize-components/src/social-store/actions/test/connection-data.js
@@ -119,22 +119,14 @@ describe( 'Social store actions: connectionData', () => {
const freshConnections = connections.map( connection => ( {
...connection,
- test_success: false,
+ status: 'broken',
} ) );
registry.dispatch( socialStore ).mergeConnections( freshConnections );
const connectionsAfterMerge = registry.select( socialStore ).getConnections();
- expect( connectionsAfterMerge ).toEqual(
- freshConnections.map( connection => ( {
- ...connection,
- // These 3 are added while merging
- done: false,
- toggleable: true,
- is_healthy: false,
- } ) )
- );
+ expect( connectionsAfterMerge ).toEqual( freshConnections );
} );
} );
@@ -156,10 +148,7 @@ describe( 'Social store actions: connectionData', () => {
if ( path.startsWith( refreshConnections ) ) {
return connections.map( connection => ( {
...connection,
- can_refresh: false,
- refresh_url: '',
- test_message: 'Some message',
- test_success: true,
+ status: 'broken',
} ) );
}
@@ -184,14 +173,7 @@ describe( 'Social store actions: connectionData', () => {
expect( connectionsAfterRefresh ).toEqual(
connections.map( connection => ( {
...connection,
- can_refresh: false,
- refresh_url: '',
- test_message: 'Some message',
- test_success: true,
- // These 3 are added while merging
- done: false,
- toggleable: true,
- is_healthy: true,
+ status: 'broken',
} ) )
);
diff --git a/projects/js-packages/publicize-components/src/social-store/reducer/connection-data.ts b/projects/js-packages/publicize-components/src/social-store/reducer/connection-data.ts
index 29fd84f077177..237d93331c0ea 100644
--- a/projects/js-packages/publicize-components/src/social-store/reducer/connection-data.ts
+++ b/projects/js-packages/publicize-components/src/social-store/reducer/connection-data.ts
@@ -133,11 +133,7 @@ const connectionData = ( state: ConnectionData = { connections: [] }, action ) =
return {
...state,
connections: state.connections.map( connection => {
- // If the connection has a connection_id, then give it priority.
- // Otherwise, use the id.
- const isTargetConnection = connection.connection_id
- ? connection.connection_id === action.connectionId
- : connection.id === action.connectionId;
+ const isTargetConnection = connection.connection_id === action.connectionId;
if ( isTargetConnection ) {
return {
diff --git a/projects/js-packages/publicize-components/src/social-store/selectors/connection-data.js b/projects/js-packages/publicize-components/src/social-store/selectors/connection-data.ts
similarity index 77%
rename from projects/js-packages/publicize-components/src/social-store/selectors/connection-data.js
rename to projects/js-packages/publicize-components/src/social-store/selectors/connection-data.ts
index 25b675d301c4d..ea1a0dc0f6795 100644
--- a/projects/js-packages/publicize-components/src/social-store/selectors/connection-data.js
+++ b/projects/js-packages/publicize-components/src/social-store/selectors/connection-data.ts
@@ -1,5 +1,8 @@
-import { checkConnectionCode } from '../../utils/connections';
+import { getScriptData } from '@automattic/jetpack-script-data';
+import { store as coreStore } from '@wordpress/core-data';
+import { createRegistrySelector } from '@wordpress/data';
import { REQUEST_TYPE_DEFAULT } from '../actions/constants';
+import { Connection, SocialStoreState } from '../types';
/**
* Returns the connections list from the store.
@@ -8,7 +11,7 @@ import { REQUEST_TYPE_DEFAULT } from '../actions/constants';
*
* @return {Array} The connections list
*/
-export function getConnections( state ) {
+export function getConnections( state: SocialStoreState ) {
return state.connectionData?.connections ?? [];
}
@@ -32,13 +35,7 @@ export function getConnectionById( state, connectionId ) {
*/
export function getBrokenConnections( state ) {
return getConnections( state ).filter( connection => {
- return (
- connection.status === 'broken' ||
- // This is a legacy check for connections that are not healthy.
- // TODO remove this check when we are sure that all connections have
- // the status property (same schema for connections endpoints), e.g. on Simple/Atomic sites
- checkConnectionCode( connection, 'broken' )
- );
+ return connection.status === 'broken';
} );
}
@@ -48,7 +45,7 @@ export function getBrokenConnections( state ) {
* @param {import("../types").SocialStoreState} state - State object.
* @param {string} serviceName - The service name.
*
- * @return {Array} The connections.
+ * @return {Array} The connections.
*/
export function getConnectionsByService( state, serviceName ) {
return getConnections( state ).filter( ( { service_name } ) => service_name === serviceName );
@@ -72,12 +69,12 @@ export function hasConnections( state ) {
export function getFailedConnections( state ) {
const connections = getConnections( state );
- return connections.filter( connection => false === connection.test_success );
+ return connections.filter( connection => 'broken' === connection.status );
}
/**
* Returns a list of Publicize connection service names that require reauthentication from users.
- * iFor example, when LinkedIn switched its API from v1 to v2.
+ * For example, when LinkedIn switched its API from v1 to v2.
*
* @param {import("../types").SocialStoreState} state - State object.
* @return {Array} List of service names that need reauthentication.
@@ -85,7 +82,7 @@ export function getFailedConnections( state ) {
export function getMustReauthConnections( state ) {
const connections = getConnections( state );
return connections
- .filter( connection => 'must_reauth' === connection.test_success )
+ .filter( connection => 'must_reauth' === connection.status )
.map( connection => connection.service_name );
}
@@ -132,22 +129,11 @@ export function getConnectionProfileDetails( state, service, { forceDefaults = f
);
if ( connection ) {
- const {
- display_name,
- profile_display_name,
- profile_picture,
- external_display,
- external_name,
- } = connection;
-
- displayName = 'twitter' === service ? profile_display_name : display_name || external_display;
- username = 'twitter' === service ? display_name : connection.username;
- profileImage = profile_picture;
+ const { display_name, profile_picture, external_handle } = connection;
- // Connections schema is a mess
- if ( 'bluesky' === service ) {
- username = external_name;
- }
+ displayName = display_name;
+ username = external_handle;
+ profileImage = profile_picture;
}
}
@@ -199,14 +185,14 @@ export function getAbortControllers( state, requestType = REQUEST_TYPE_DEFAULT )
/**
* Whether a mastodon account is already connected.
*
- * @param {import("../types").SocialStoreState} state - State object.
- * @param {string} username - The mastodon username.
+ * @param {import("../types").SocialStoreState} state - State object.
+ * @param {string} handle - The mastodon handle.
*
* @return {boolean} Whether the mastodon account is already connected.
*/
-export function isMastodonAccountAlreadyConnected( state, username ) {
+export function isMastodonAccountAlreadyConnected( state, handle ) {
return getConnectionsByService( state, 'mastodon' ).some( connection => {
- return connection.external_display === username;
+ return connection.external_handle === handle;
} );
}
@@ -220,7 +206,7 @@ export function isMastodonAccountAlreadyConnected( state, username ) {
*/
export function isBlueskyAccountAlreadyConnected( state, handle ) {
return getConnectionsByService( state, 'bluesky' ).some( connection => {
- return connection.external_name === handle;
+ return connection.external_handle === handle;
} );
}
@@ -244,3 +230,32 @@ export function getKeyringResult( state ) {
export function isConnectionsModalOpen( state ) {
return state.connectionData?.isConnectionsModalOpen ?? false;
}
+
+/**
+ * Whether the current user can manage the connection.
+ */
+export const canUserManageConnection = createRegistrySelector(
+ select =>
+ ( state: SocialStoreState, connectionOrId: Connection | string ): boolean => {
+ const connection =
+ typeof connectionOrId === 'string'
+ ? getConnectionById( state, connectionOrId )
+ : connectionOrId;
+
+ const { current_user } = getScriptData().user;
+
+ // If the current user is the connection owner.
+ if ( current_user.wpcom?.ID === connection.wpcom_user_id ) {
+ return true;
+ }
+
+ const {
+ // @ts-expect-error getUser exists but `core-data` entities are not typed properly.
+ // Should work fine after https://github.com/WordPress/gutenberg/pull/67668 is released to npm.
+ getUser,
+ } = select( coreStore );
+
+ // The user has to be at least an editor to manage the connection.
+ return getUser( current_user.id )?.capabilities?.edit_others_posts ?? false;
+ }
+);
diff --git a/projects/js-packages/publicize-components/src/social-store/selectors/index.ts b/projects/js-packages/publicize-components/src/social-store/selectors/index.ts
index b7d1c985dda21..4d2ca69c41288 100644
--- a/projects/js-packages/publicize-components/src/social-store/selectors/index.ts
+++ b/projects/js-packages/publicize-components/src/social-store/selectors/index.ts
@@ -8,8 +8,6 @@ import * as utmSelectors from './utm-settings';
/**
* Returns whether the site settings are being saved.
- *
- * @type {() => boolean} Whether the site settings are being saved.
*/
export const isSavingSiteSettings = createRegistrySelector( select => () => {
return select( coreStore ).isSavingEntityRecord( 'root', 'site', undefined );
diff --git a/projects/js-packages/publicize-components/src/social-store/selectors/test/connection-data.test.js b/projects/js-packages/publicize-components/src/social-store/selectors/test/connection-data.test.js
index b73f4d62f1c49..fdb3c21209930 100644
--- a/projects/js-packages/publicize-components/src/social-store/selectors/test/connection-data.test.js
+++ b/projects/js-packages/publicize-components/src/social-store/selectors/test/connection-data.test.js
@@ -12,34 +12,31 @@ const state = {
connectionData: {
connections: [
{
- id: '123456789',
service_name: 'facebook',
display_name: 'Some name',
profile_picture: 'https://wordpress.com/some-url-of-a-picture',
- username: 'username',
+ external_handle: 'external_handle',
enabled: false,
connection_id: '987654321',
- test_success: true,
+ status: 'ok',
},
{
- id: '234567891',
service_name: 'tumblr',
display_name: 'Some name',
profile_picture: 'https://wordpress.com/some-url-of-another-picture',
- username: 'username',
+ external_handle: 'external_handle',
enabled: true,
connection_id: '198765432',
- test_success: false,
+ status: 'broken',
},
{
- id: '345678912',
service_name: 'mastodon',
display_name: 'somename',
profile_picture: 'https://wordpress.com/some-url-of-one-more-picture',
- username: '@somename@mastodon.social',
+ external_handle: '@somename@mastodon.social',
enabled: false,
connection_id: '219876543',
- test_success: 'must_reauth',
+ status: 'must_reauth',
},
],
},
@@ -140,7 +137,7 @@ describe( 'Social store selectors: connectionData', () => {
expect( getConnectionProfileDetails( state, 'facebook' ) ).toEqual( {
displayName: connection.display_name,
profileImage: connection.profile_picture,
- username: connection.username,
+ username: connection.external_handle,
} );
} );
diff --git a/projects/js-packages/publicize-components/src/social-store/types.ts b/projects/js-packages/publicize-components/src/social-store/types.ts
index fb5701967342a..2be401380fd57 100644
--- a/projects/js-packages/publicize-components/src/social-store/types.ts
+++ b/projects/js-packages/publicize-components/src/social-store/types.ts
@@ -1,25 +1,60 @@
-export type ConnectionStatus = 'ok' | 'broken';
+export type ConnectionStatus = 'ok' | 'broken' | 'must_reauth';
export type Connection = {
- id: string;
- service_name: string;
- label?: string;
+ connection_id: string;
display_name: string;
- external_display?: string;
- external_id: string;
- external_name?: string;
- username: string;
enabled: boolean;
- done: boolean;
- toggleable: boolean;
- connection_id: string;
- is_healthy?: boolean;
- error_code?: string;
- can_disconnect: boolean;
- profile_picture: string;
+ external_handle: string;
+ external_id: string;
profile_link: string;
+ profile_picture: string;
+ service_label: string;
+ service_name: string;
shared: boolean;
status: ConnectionStatus;
+ wpcom_user_id: number;
+
+ /* DEPRECATED FIELDS */
+ /**
+ * @deprecated
+ */
+ done?: boolean;
+ /**
+ * @deprecated Use `status` instead.
+ */
+ error_code?: string;
+ /**
+ * @deprecated Use `display_name` instead.
+ */
+ external_display?: string;
+ /**
+ * @deprecated Use `external_handle` instead.
+ */
+ external_name?: string;
+ /**
+ * @deprecated Use `connection_id` instead.
+ */
+ id?: string;
+ /**
+ * @deprecated Use `status` instead.
+ */
+ is_healthy?: boolean;
+ /**
+ * @deprecated Use `service_label` instead.
+ */
+ label?: string;
+ /**
+ * @deprecated Use `status` instead.
+ */
+ test_success?: boolean;
+ /**
+ * @deprecated
+ */
+ toggleable?: boolean;
+ /**
+ * @deprecated Use `external_handle` instead.
+ */
+ username?: string;
};
export type ConnectionData = {
diff --git a/projects/js-packages/publicize-components/src/utils/connections.ts b/projects/js-packages/publicize-components/src/utils/connections.ts
deleted file mode 100644
index 5ca5e3daf8266..0000000000000
--- a/projects/js-packages/publicize-components/src/utils/connections.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { Connection } from '../social-store/types';
-
-export const checkConnectionCode = ( connection: Connection, code: string ) => {
- return false === connection.is_healthy && code === ( connection.error_code ?? 'broken' );
-};
diff --git a/projects/js-packages/publicize-components/src/utils/test-factory.js b/projects/js-packages/publicize-components/src/utils/test-factory.js
index 7c2b310f63807..f65a22f6fe1d8 100644
--- a/projects/js-packages/publicize-components/src/utils/test-factory.js
+++ b/projects/js-packages/publicize-components/src/utils/test-factory.js
@@ -13,16 +13,16 @@ jest.mock( '../hooks/use-social-media-connections', () => ( {
export const setup = ( {
connections = [
- { service_name: 'twitter', connection_id: '1', display_name: 'Twitter', can_disconnect: true },
+ { service_name: 'twitter', connection_id: '1', display_name: 'Twitter' },
{
service_name: 'facebook',
connection_id: '2',
display_name: 'Facebook',
- can_disconnect: true,
},
],
getDeletingConnections = [],
getUpdatingConnections = [],
+ canUserManageConnection = true,
} = {} ) => {
let storeSelect;
renderHook( () => useSelect( select => ( storeSelect = select( store ) ) ) );
@@ -36,6 +36,10 @@ export const setup = ( {
.mockReset()
.mockReturnValue( getUpdatingConnections );
const stubGetKeyringResult = jest.spyOn( storeSelect, 'getKeyringResult' ).mockReset();
+ jest
+ .spyOn( storeSelect, 'canUserManageConnection' )
+ .mockReset()
+ .mockReturnValue( canUserManageConnection );
const { result: dispatch } = renderHook( () => useDispatch( store ) );
const stubDeleteConnectionById = jest
diff --git a/projects/js-packages/publicize-components/src/utils/test-utils.js b/projects/js-packages/publicize-components/src/utils/test-utils.js
index fa2f326be7bc6..b502817d8f05c 100644
--- a/projects/js-packages/publicize-components/src/utils/test-utils.js
+++ b/projects/js-packages/publicize-components/src/utils/test-utils.js
@@ -33,34 +33,28 @@ export const testPost = {
export const connections = [
{
- id: '123456789',
service_name: 'facebook',
display_name: 'Some name',
profile_picture: 'https://wordpress.com/some-url-of-a-picture',
- username: 'username',
+ external_handle: 'username',
enabled: false,
connection_id: '987654321',
- test_success: true,
},
{
- id: '234567891',
service_name: 'tumblr',
display_name: 'Some name',
profile_picture: 'https://wordpress.com/some-url-of-another-picture',
- username: 'username',
+ external_handle: 'username',
enabled: false,
connection_id: '198765432',
- test_success: false,
},
{
- id: '345678912',
service_name: 'mastodon',
display_name: 'somename',
profile_picture: 'https://wordpress.com/some-url-of-one-more-picture',
- username: '@somename@mastodon.social',
+ external_handle: '@somename@mastodon.social',
enabled: false,
connection_id: '219876543',
- test_success: 'must_reauth',
},
];
diff --git a/projects/js-packages/script-data/changelog/add-wpcom-data-for-current-user b/projects/js-packages/script-data/changelog/add-wpcom-data-for-current-user
new file mode 100644
index 0000000000000..1c4d1fd6232c3
--- /dev/null
+++ b/projects/js-packages/script-data/changelog/add-wpcom-data-for-current-user
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Added wpcom data for current user
diff --git a/projects/js-packages/script-data/src/types.ts b/projects/js-packages/script-data/src/types.ts
index 03d420fdfe7cd..596cdc642eb97 100644
--- a/projects/js-packages/script-data/src/types.ts
+++ b/projects/js-packages/script-data/src/types.ts
@@ -33,6 +33,10 @@ export interface SiteData extends PublicSiteData, Partial< AdminSiteData > {}
export interface CurrentUserData {
id: number;
display_name: string;
+ wpcom?: {
+ ID: number;
+ login: string;
+ };
}
export interface UserData {
diff --git a/projects/packages/publicize/.phan/baseline.php b/projects/packages/publicize/.phan/baseline.php
index 73ac3608fa9a5..b819017a9a146 100644
--- a/projects/packages/publicize/.phan/baseline.php
+++ b/projects/packages/publicize/.phan/baseline.php
@@ -11,13 +11,13 @@
// # Issue statistics:
// PhanPluginDuplicateConditionalNullCoalescing : 6 occurrences
// PhanTypeMismatchArgument : 6 occurrences
+ // PhanPluginMixedKeyNoKey : 3 occurrences
// PhanTypeMismatchArgumentNullable : 3 occurrences
+ // PhanUndeclaredClassMethod : 3 occurrences
// PhanDeprecatedFunction : 2 occurrences
- // PhanPluginMixedKeyNoKey : 2 occurrences
// PhanPossiblyUndeclaredVariable : 2 occurrences
// PhanTypeMismatchReturnProbablyReal : 2 occurrences
// PhanTypeMissingReturn : 2 occurrences
- // PhanUndeclaredClassMethod : 2 occurrences
// PhanImpossibleCondition : 1 occurrence
// PhanNoopNew : 1 occurrence
// PhanParamSignatureMismatch : 1 occurrence
@@ -29,19 +29,19 @@
// PhanTypeMismatchDefault : 1 occurrence
// PhanTypeMismatchDimFetch : 1 occurrence
// PhanTypeMismatchReturn : 1 occurrence
- // PhanUndeclaredFunction : 1 occurrence
+ // PhanTypeSuspiciousNonTraversableForeach : 1 occurrence
// PhanUndeclaredMethod : 1 occurrence
// Currently, file_suppressions and directory_suppressions are the only supported suppressions
'file_suppressions' => [
+ 'src/class-connections.php' => ['PhanUndeclaredClassMethod', 'PhanUndeclaredMethod'],
'src/class-keyring-helper.php' => ['PhanTypeMismatchArgumentProbablyReal', 'PhanTypeMismatchDefault'],
'src/class-publicize-base.php' => ['PhanImpossibleCondition', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginSimplifyExpressionBool', 'PhanSuspiciousMagicConstant', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchArgumentNullableInternal', 'PhanTypeMismatchDimFetch', 'PhanTypeMismatchReturn'],
'src/class-publicize-setup.php' => ['PhanNoopNew', 'PhanTypeMismatchArgument'],
'src/class-publicize-ui.php' => ['PhanPluginDuplicateExpressionAssignmentOperation', 'PhanTypeMismatchReturnProbablyReal'],
'src/class-publicize.php' => ['PhanParamSignatureMismatch', 'PhanPossiblyUndeclaredVariable', 'PhanTypeMismatchArgument', 'PhanTypeMissingReturn'],
'src/class-rest-controller.php' => ['PhanPluginDuplicateConditionalNullCoalescing', 'PhanTypeMismatchReturnProbablyReal'],
- 'src/rest-api/class-base-controller.php' => ['PhanUndeclaredClassMethod', 'PhanUndeclaredFunction'],
- 'src/rest-api/class-connections-controller.php' => ['PhanPluginMixedKeyNoKey', 'PhanUndeclaredMethod'],
+ 'src/rest-api/class-connections-controller.php' => ['PhanPluginMixedKeyNoKey', 'PhanTypeSuspiciousNonTraversableForeach'],
'src/rest-api/class-connections-post-field.php' => ['PhanPluginDuplicateConditionalNullCoalescing'],
'src/social-image-generator/class-post-settings.php' => ['PhanPluginDuplicateConditionalNullCoalescing'],
'src/social-image-generator/class-rest-settings-controller.php' => ['PhanPluginMixedKeyNoKey'],
diff --git a/projects/packages/publicize/.phan/config.php b/projects/packages/publicize/.phan/config.php
index 075dd16643b9e..d60576709a738 100644
--- a/projects/packages/publicize/.phan/config.php
+++ b/projects/packages/publicize/.phan/config.php
@@ -13,6 +13,7 @@
return make_phan_config(
dirname( __DIR__ ),
array(
+ '+stubs' => array( 'wpcom' ),
'parse_file_list' => array(
// Reference files to handle code checking for stuff from Jetpack-the-plugin or other in-monorepo plugins.
// Wherever feasible we should really clean up this sort of thing instead of adding stuff here.
diff --git a/projects/packages/publicize/changelog/fix-social-connections-list-feature-check b/projects/packages/publicize/changelog/fix-social-connections-list-feature-check
new file mode 100644
index 0000000000000..db871891de374
--- /dev/null
+++ b/projects/packages/publicize/changelog/fix-social-connections-list-feature-check
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Social | Fix feature check for social connections list initial state
diff --git a/projects/packages/publicize/changelog/social-unified-connections-management b/projects/packages/publicize/changelog/social-unified-connections-management
new file mode 100644
index 0000000000000..03f5d101125bf
--- /dev/null
+++ b/projects/packages/publicize/changelog/social-unified-connections-management
@@ -0,0 +1,4 @@
+Significance: minor
+Type: changed
+
+Social | Unify connections management API schema
diff --git a/projects/packages/publicize/src/class-connections.php b/projects/packages/publicize/src/class-connections.php
new file mode 100644
index 0000000000000..6d186ed0e09c0
--- /dev/null
+++ b/projects/packages/publicize/src/class-connections.php
@@ -0,0 +1,405 @@
+ 'blog' ) );
+ } else {
+
+ $ignore_cache = $args['ignore_cache'] ?? false;
+
+ $connections = get_transient( self::CONNECTIONS_TRANSIENT );
+
+ if ( $ignore_cache || false === $connections ) {
+ $connections = self::fetch_and_cache_connections();
+ }
+ }
+
+ // Let us add the deprecated fields for now.
+ // TODO: Remove this after https://github.com/Automattic/jetpack/pull/40539 is merged.
+ $connections = self::retain_deprecated_fields( $connections );
+
+ return $connections;
+ }
+
+ /**
+ * Get a connection by connection_id.
+ *
+ * @param string $connection_id Connection ID.
+ *
+ * @return array|null
+ */
+ public static function get_by_id( $connection_id ) {
+
+ $connections = self::get_all();
+
+ foreach ( $connections as $connection ) {
+ if ( $connection['connection_id'] === $connection_id ) {
+ return $connection;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all connections for the current user.
+ *
+ * @param array $args Arguments. Same as self::get_all().
+ *
+ * @see Automattic\Jetpack\Publicize\Connections::get_all()
+ *
+ * @return array
+ */
+ public static function get_all_for_user( $args = array() ) {
+ $connections = self::get_all( $args );
+
+ $connections_for_user = array();
+
+ foreach ( $connections as $connection ) {
+
+ if ( $connection['shared'] || self::user_owns_connection( $connection ) ) {
+ $connections_for_user[] = $connection;
+ }
+ }
+
+ return $connections_for_user;
+ }
+
+ /**
+ * Whether the current user owns a connection.
+ *
+ * @param array $connection The connection.
+ * @param int $user_id The user ID. Defaults to the current user.
+ *
+ * @return bool
+ */
+ public static function user_owns_connection( $connection, $user_id = null ) {
+ if ( Publicize_Utils::is_wpcom() ) {
+ $wpcom_user_id = get_current_user_id();
+ } else {
+
+ $wpcom_user_data = ( new Connection\Manager() )->get_connected_user_data( $user_id );
+
+ $wpcom_user_id = ! empty( $wpcom_user_data['ID'] ) ? $wpcom_user_data['ID'] : null;
+ }
+
+ return $wpcom_user_id && $connection['wpcom_user_id'] === $wpcom_user_id;
+ }
+
+ /**
+ * Retain deprecated fields.
+ *
+ * @param array $connections Connections.
+ * @return array
+ */
+ private static function retain_deprecated_fields( $connections ) {
+ return array_map(
+ function ( $connection ) {
+
+ $owns_connection = self::user_owns_connection( $connection );
+
+ $connection = array_merge(
+ $connection,
+ array(
+ 'external_display' => $connection['display_name'],
+ 'can_disconnect' => current_user_can( 'edit_others_posts' ) || $owns_connection,
+ 'label' => $connection['service_label'],
+ )
+ );
+
+ if ( 'bluesky' === $connection['service_name'] ) {
+ $connection['external_name'] = $connection['external_handle'];
+ }
+
+ return $connection;
+ },
+ $connections
+ );
+ }
+
+ /**
+ * Fetch connections from the REST API and cache them.
+ *
+ * @return array
+ */
+ public static function fetch_and_cache_connections() {
+ $connections = self::fetch_site_connections();
+
+ if ( is_array( $connections ) ) {
+ if ( ! set_transient( self::CONNECTIONS_TRANSIENT, $connections, HOUR_IN_SECONDS * 4 ) ) {
+ // If the transient has beeen set in another request, the call to set_transient can fail.
+ // If so, we can delete the transient and try again.
+ self::clear_cache();
+
+ set_transient( self::CONNECTIONS_TRANSIENT, $connections, HOUR_IN_SECONDS * 4 );
+ }
+ }
+
+ return $connections;
+ }
+
+ /**
+ * Fetch connections for the site from WPCOM REST API.
+ *
+ * @return array
+ */
+ public static function fetch_site_connections() {
+ $proxy = new Proxy_Requests( 'publicize/connections' );
+
+ $request = new WP_REST_Request( 'GET', '/wpcom/v2/publicize/connections' );
+
+ $connections = $proxy->proxy_request_to_wpcom_as_blog( $request );
+
+ if ( is_wp_error( $connections ) ) {
+ // @todo log error.
+ return array();
+ }
+
+ return $connections;
+ }
+
+ /**
+ * Get all connections. Meant to be called directly only on WPCOM.
+ *
+ * @param array $args Arguments
+ * - 'test_connections': bool Whether to run connection tests.
+ * - 'context': enum('blog', 'user') Whether to include connections for the current blog or user.
+ *
+ * @return array
+ */
+ public static function wpcom_get_connections( $args = array() ) {
+ // Ensure that we are on WPCOM.
+ Publicize_Utils::assert_is_wpcom( __METHOD__ );
+
+ /**
+ * Publicize instance.
+ */
+ global $publicize;
+
+ $items = array();
+
+ $run_tests = $args['test_connections'] ?? false;
+
+ $test_results = $run_tests ? self::get_test_status() : array();
+
+ $service_connections = $publicize->get_all_connections_for_blog_id( get_current_blog_id() );
+
+ $context = $args['context'] ?? 'user';
+
+ foreach ( $service_connections as $service_name => $connections ) {
+ foreach ( $connections as $connection ) {
+ $connection_id = $publicize->get_connection_id( $connection );
+
+ $item = self::wpcom_prepare_connection_data( $connection, $service_name );
+
+ $item['status'] = $test_results[ $connection_id ] ?? null;
+
+ // For blog context, return all connections.
+ // Otherwise, return only connections owned by the user and the shared ones.
+ if ( 'blog' === $context || $item['shared'] || self::user_owns_connection( $item ) ) {
+ $items[] = $item;
+ }
+ }
+ }
+
+ return $items;
+ }
+
+ /**
+ * Filters out data based on ?_fields= request parameter
+ *
+ * @param mixed $connection Connection to prepare.
+ * @param string $service_name Service name.
+ *
+ * @return array
+ */
+ public static function wpcom_prepare_connection_data( $connection, $service_name ) {
+ // Ensure that we are on WPCOM.
+ Publicize_Utils::assert_is_wpcom( __METHOD__ );
+
+ /**
+ * Publicize instance.
+ */
+ global $publicize;
+
+ $connection_id = $publicize->get_connection_id( $connection );
+
+ $connection_meta = $publicize->get_connection_meta( $connection );
+ $connection_data = $connection_meta['connection_data'];
+
+ return array(
+ 'connection_id' => (string) $connection_id,
+ 'display_name' => (string) $publicize->get_display_name( $service_name, $connection ),
+ 'external_handle' => (string) $publicize->get_external_handle( $service_name, $connection ),
+ 'external_id' => $connection_meta['external_id'] ?? '',
+ 'profile_link' => (string) $publicize->get_profile_link( $service_name, $connection ),
+ 'profile_picture' => (string) $publicize->get_profile_picture( $connection ),
+ 'service_label' => (string) Publicize::get_service_label( $service_name ),
+ 'service_name' => $service_name,
+ 'shared' => ! $connection_data['user_id'],
+ 'wpcom_user_id' => (int) $connection_data['user_id'],
+
+ // Deprecated fields.
+ 'id' => (string) $publicize->get_connection_unique_id( $connection ),
+ 'username' => $publicize->get_username( $service_name, $connection ),
+ 'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '',
+ // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored.
+ 'global' => 0 == $connection_data['user_id'],
+
+ );
+ }
+
+ /**
+ * Create a connection. Meant to be called directly only on WPCOM.
+ *
+ * @param mixed $input Input data.
+ *
+ * @return string|WP_Error Connection ID or WP_Error.
+ */
+ public static function wpcom_create_connection( $input ) {
+ // Ensure that we are on WPCOM.
+ Publicize_Utils::assert_is_wpcom( __METHOD__ );
+
+ require_lib( 'social-connections-rest-helper' );
+
+ $connections_helper = \Social_Connections_Rest_Helper::init();
+
+ $result = $connections_helper->create_publicize_connection( $input );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ if ( ! isset( $result['ID'] ) ) {
+ return new WP_Error(
+ 'wpcom_connection_creation_failed',
+ __( 'Something went wrong while creating a connection.', 'jetpack-publicize-pkg' )
+ );
+ }
+
+ return (string) $result['ID'];
+ }
+
+ /**
+ * Update a connection. Meant to be called directly only on WPCOM.
+ *
+ * @param string $connection_id Connection ID.
+ * @param mixed $input Input data.
+ *
+ * @return string|WP_Error Connection ID or WP_Error.
+ */
+ public static function wpcom_update_connection( $connection_id, $input ) {
+ // Ensure that we are on WPCOM.
+ Publicize_Utils::assert_is_wpcom( __METHOD__ );
+
+ require_lib( 'social-connections-rest-helper' );
+ $connections_helper = \Social_Connections_Rest_Helper::init();
+
+ $result = $connections_helper->update_connection( $connection_id, $input );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ if ( ! $result ) {
+ return new WP_Error(
+ 'wpcom_connection_updation_failed',
+ __( 'Something went wrong while updating the connection.', 'jetpack-publicize-pkg' )
+ );
+ }
+
+ return (string) $connection_id;
+ }
+
+ /**
+ * Delete a connection. Meant to be called directly only on WPCOM.
+ *
+ * @param string $connection_id Connection ID.
+ *
+ * @return bool|WP_Error
+ */
+ public static function wpcom_delete_connection( $connection_id ) {
+ // Ensure that we are on WPCOM.
+ Publicize_Utils::assert_is_wpcom( __METHOD__ );
+
+ require_lib( 'social-connections-rest-helper' );
+ $connections_helper = \Social_Connections_Rest_Helper::init();
+
+ $result = $connections_helper->delete_publicize_connection( $connection_id );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ if ( ! $result ) {
+ return new WP_Error(
+ 'wpcom_connection_deletion_failed',
+ __( 'Something went wrong while deleting the connection.', 'jetpack-publicize-pkg' )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Get the connections test status.
+ *
+ * @return array
+ */
+ public static function get_test_status() {
+ /**
+ * Publicize instance.
+ *
+ * @var \Automattic\Jetpack\Publicize\Publicize $publicize
+ */
+ global $publicize;
+
+ $test_results = $publicize->get_publicize_conns_test_results();
+
+ $test_results_map = array();
+
+ foreach ( $test_results as $test_result ) {
+ $result = $test_result['connectionTestPassed'];
+ if ( 'must_reauth' !== $result ) {
+ $result = $test_result['connectionTestPassed'] ? 'ok' : 'broken';
+ }
+ $test_results_map[ $test_result['connectionID'] ] = $result;
+ }
+
+ return $test_results_map;
+ }
+
+ /**
+ * Clear the connections cache.
+ */
+ public static function clear_cache() {
+ delete_transient( self::CONNECTIONS_TRANSIENT );
+ }
+}
diff --git a/projects/packages/publicize/src/class-publicize-base.php b/projects/packages/publicize/src/class-publicize-base.php
index 076abd34acdc6..d707968be7b2f 100644
--- a/projects/packages/publicize/src/class-publicize-base.php
+++ b/projects/packages/publicize/src/class-publicize-base.php
@@ -556,8 +556,8 @@ public function get_profile_link( $service_name, $connection ) {
public function get_display_name( $service_name, $connection ) {
$cmeta = $this->get_connection_meta( $connection );
- if ( 'mastodon' === $service_name && isset( $cmeta['external_name'] ) ) {
- return $cmeta['external_name'];
+ if ( 'mastodon' === $service_name && isset( $cmeta['external_display'] ) ) {
+ return $cmeta['external_display'];
}
if ( isset( $cmeta['connection_data']['meta']['display_name'] ) ) {
@@ -1015,7 +1015,6 @@ public function get_filtered_connection_data( $selected_post_id = null ) {
'service_name' => $service_name,
'shared' => ! $connection_data['user_id'],
'status' => null,
- 'user_id' => (int) $connection_data['user_id'],
// Deprecated fields.
'id' => $connection_id,
@@ -1024,6 +1023,7 @@ public function get_filtered_connection_data( $selected_post_id = null ) {
'done' => $done,
'toggleable' => $toggleable,
'global' => 0 == $connection_data['user_id'], // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual,WordPress.PHP.StrictComparisons.LooseComparison -- Other types can be used at times.
+ 'user_id' => (int) $connection_data['user_id'],
);
}
}
diff --git a/projects/packages/publicize/src/class-publicize-script-data.php b/projects/packages/publicize/src/class-publicize-script-data.php
index 3035e3e6b1d71..c40d5f51a31c9 100644
--- a/projects/packages/publicize/src/class-publicize-script-data.php
+++ b/projects/packages/publicize/src/class-publicize-script-data.php
@@ -21,8 +21,6 @@
*/
class Publicize_Script_Data {
- const SERVICES_TRANSIENT = 'jetpack_social_services_list';
-
/**
* Get the publicize instance - properly typed
*
@@ -75,9 +73,32 @@ public static function set_admin_script_data( $data ) {
$data['site']['host'] = ( new Host() )->get_known_host_guess();
}
+ self::set_wpcom_user_data( $data['user']['current_user'] );
+
return $data;
}
+ /**
+ * Set wpcom user data.
+ *
+ * @param array $user_data The user data.
+ */
+ private static function set_wpcom_user_data( &$user_data ) {
+ if ( ( new Host() )->is_wpcom_simple() ) {
+ $wpcom_user_data = array(
+ 'ID' => get_current_user_id(),
+ 'login' => wp_get_current_user()->user_login,
+ );
+ } else {
+ $wpcom_user_data = ( new Manager() )->get_connected_user_data();
+ }
+
+ $user_data['wpcom'] = array_merge(
+ $user_data['wpcom'] ?? array(),
+ $wpcom_user_data ? $wpcom_user_data : array()
+ );
+ }
+
/**
* Get the script data for admin UI.
*
@@ -163,8 +184,7 @@ public static function get_store_initial_state() {
return array(
'connectionData' => array(
- // We do not have this method on WPCOM Publicize class yet.
- 'connections' => ! $is_wpcom ? self::publicize()->get_all_connections_for_user() : array(),
+ 'connections' => self::has_feature_flag( 'connections-management' ) ? Connections::get_all_for_user() : array(),
),
'shareStatus' => $share_status,
);
@@ -247,17 +267,24 @@ public static function get_api_paths() {
$is_wpcom = ( new Host() )->is_wpcom_platform();
+ $commom_paths = array(
+ 'refreshConnections' => '/wpcom/v2/publicize/connections?test_connections=1',
+ );
+
+ $specific_paths = array();
+
if ( $is_wpcom ) {
- return array(
- 'refreshConnections' => '/wpcom/v2/publicize/connection-test-results',
- 'resharePost' => '/wpcom/v2/posts/{postId}/publicize',
+
+ $specific_paths = array(
+ 'resharePost' => '/wpcom/v2/posts/{postId}/publicize',
+ );
+ } else {
+ $specific_paths = array(
+ 'resharePost' => '/jetpack/v4/publicize/{postId}',
);
}
- return array(
- 'refreshConnections' => '/jetpack/v4/publicize/connections?test_connections=1',
- 'resharePost' => '/jetpack/v4/publicize/{postId}',
- );
+ return array_merge( $commom_paths, $specific_paths );
}
/**
diff --git a/projects/packages/publicize/src/class-publicize-utils.php b/projects/packages/publicize/src/class-publicize-utils.php
index 77f5272f0ba93..ef31f3c8ee24d 100644
--- a/projects/packages/publicize/src/class-publicize-utils.php
+++ b/projects/packages/publicize/src/class-publicize-utils.php
@@ -87,4 +87,26 @@ public static function is_connected() {
public static function is_publicize_active() {
return ( new Modules() )->is_active( 'publicize' );
}
+
+ /**
+ * Check if we are on WPCOM.
+ *
+ * @return bool
+ */
+ public static function is_wpcom() {
+ return ( new Host() )->is_wpcom_simple();
+ }
+
+ /**
+ * Assert that the method is only called on WPCOM.
+ *
+ * @param string $method The method name.
+ *
+ * @throws \Exception If the method is not called on WPCOM.
+ */
+ public static function assert_is_wpcom( $method ) {
+ if ( ! self::is_wpcom() ) {
+ throw new \Exception( esc_html( "Method $method can only be called on WordPress.com." ) );
+ }
+ }
}
diff --git a/projects/packages/publicize/src/class-publicize.php b/projects/packages/publicize/src/class-publicize.php
index beb043db17f16..0a444a86bf5a8 100644
--- a/projects/packages/publicize/src/class-publicize.php
+++ b/projects/packages/publicize/src/class-publicize.php
@@ -145,6 +145,10 @@ public function disconnect( $service_name, $connection_id, $_blog_id = false, $_
* @return true
*/
public function receive_updated_publicize_connections( $publicize_connections ) {
+
+ // Populate the cache with the new data.
+ Connections::get_all( array( 'ignore_cache' => true ) );
+
$expiry = 3600 * 4;
if ( ! set_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT, $publicize_connections, $expiry ) ) {
// If the transient has beeen set in another request, the call to set_transient can fail. If so,
diff --git a/projects/packages/publicize/src/class-services.php b/projects/packages/publicize/src/class-services.php
index 26e49b096b40b..b43c6af0aae0a 100644
--- a/projects/packages/publicize/src/class-services.php
+++ b/projects/packages/publicize/src/class-services.php
@@ -26,11 +26,9 @@ class Services {
public static function get_all( $force_refresh = false ) {
if ( defined( 'IS_WPCOM' ) && constant( 'IS_WPCOM' ) ) {
if ( function_exists( 'require_lib' ) ) {
- // @phan-suppress-next-line PhanUndeclaredFunction - phan is dumb not to see the function_exists check.
require_lib( 'external-connections' );
}
- // @phan-suppress-next-line PhanUndeclaredClassMethod - We are here because we are on WPCOM.
$external_connections = \WPCOM_External_Connections::init();
$services = array_values( $external_connections->get_external_services_list( 'publicize', get_current_blog_id() ) );
diff --git a/projects/packages/publicize/src/rest-api/class-base-controller.php b/projects/packages/publicize/src/rest-api/class-base-controller.php
index 86ca5c8e40a52..fd0f7a870a759 100644
--- a/projects/packages/publicize/src/rest-api/class-base-controller.php
+++ b/projects/packages/publicize/src/rest-api/class-base-controller.php
@@ -7,7 +7,8 @@
namespace Automattic\Jetpack\Publicize\REST_API;
-use Automattic\Jetpack\Status\Host;
+use Automattic\Jetpack\Publicize\Connections;
+use Automattic\Jetpack\Publicize\Publicize_Utils;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
@@ -32,22 +33,13 @@ public function __construct() {
$this->wpcom_is_wpcom_only_endpoint = true;
}
- /**
- * Check if we are on WPCOM.
- *
- * @return bool
- */
- public static function is_wpcom() {
- return ( new Host() )->is_wpcom_simple();
- }
-
/**
* Check if the request is authorized for the blog.
*
* @return bool
*/
protected static function is_authorized_blog_request() {
- if ( self::is_wpcom() && is_jetpack_site( get_current_blog_id() ) ) {
+ if ( Publicize_Utils::is_wpcom() && is_jetpack_site( get_current_blog_id() ) ) {
$jp_auth_endpoint = new \WPCOM_REST_API_V2_Endpoint_Jetpack_Auth();
@@ -82,10 +74,9 @@ public function prepare_item_for_response( $item, $request ) {
/**
* Verify that user can access Publicize data
*
- * @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error
*/
- public function get_items_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
+ protected function publicize_permissions_check() {
global $publicize;
@@ -111,4 +102,23 @@ public function get_items_permissions_check( $request ) {// phpcs:ignore Variabl
array( 'status' => rest_authorization_required_code() )
);
}
+
+ /**
+ * Check whether the request is allowed to manage (update/delete) a connection.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return bool True if the request can manage connection, false otherwise.
+ */
+ protected function manage_connection_permission_check( $request ) {
+ // Editors and above can manage any connection.
+ if ( current_user_can( 'edit_others_posts' ) ) {
+ return true;
+ }
+
+ $connection_id = $request->get_param( 'connection_id' );
+
+ $connection = Connections::get_by_id( $connection_id );
+
+ return Connections::user_owns_connection( $connection );
+ }
}
diff --git a/projects/packages/publicize/src/rest-api/class-connections-controller.php b/projects/packages/publicize/src/rest-api/class-connections-controller.php
index 23725b12a33a1..0e33f29499292 100644
--- a/projects/packages/publicize/src/rest-api/class-connections-controller.php
+++ b/projects/packages/publicize/src/rest-api/class-connections-controller.php
@@ -7,9 +7,10 @@
namespace Automattic\Jetpack\Publicize\REST_API;
-use Automattic\Jetpack\Connection\Client;
-use Automattic\Jetpack\Connection\Manager;
-use Automattic\Jetpack\Publicize\Publicize;
+use Automattic\Jetpack\Connection\Traits\WPCOM_REST_API_Proxy_Request;
+use Automattic\Jetpack\Publicize\Connections;
+use Automattic\Jetpack\Publicize\Publicize_Utils;
+use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
@@ -19,12 +20,18 @@
*/
class Connections_Controller extends Base_Controller {
+ use WPCOM_REST_API_Proxy_Request;
+
/**
* Constructor.
*/
public function __construct() {
parent::__construct();
- $this->namespace = 'wpcom/v2';
+
+ $this->base_api_path = 'wpcom';
+ $this->version = 'v2';
+
+ $this->namespace = "{$this->base_api_path}/{$this->version}";
$this->rest_base = 'publicize/connections';
$this->allow_requests_as_blog = true;
@@ -51,6 +58,58 @@ public function register_routes() {
),
),
),
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'create_item' ),
+ 'permission_callback' => array( $this, 'create_item_permissions_check' ),
+ 'args' => array(
+ 'keyring_connection_ID' => array(
+ 'description' => __( 'Keyring connection ID.', 'jetpack-publicize-pkg' ),
+ 'type' => 'integer',
+ 'required' => true,
+ ),
+ 'external_user_ID' => array(
+ 'description' => __( 'External User Id - in case of services like Facebook.', 'jetpack-publicize-pkg' ),
+ 'type' => 'string',
+ ),
+ 'shared' => array(
+ 'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
+ 'type' => 'boolean',
+ ),
+ ),
+ ),
+ 'schema' => array( $this, 'get_public_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/(?P[0-9]+)',
+ array(
+ 'args' => array(
+ 'connection_id' => array(
+ 'description' => __( 'Unique identifier for the connection.', 'jetpack-publicize-pkg' ),
+ 'type' => 'string',
+ 'required' => true,
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::EDITABLE,
+ 'callback' => array( $this, 'update_item' ),
+ 'permission_callback' => array( $this, 'update_item_permissions_check' ),
+ 'args' => array(
+ 'shared' => array(
+ 'description' => __( 'Whether the connection is shared with other users.', 'jetpack-publicize-pkg' ),
+ 'type' => 'boolean',
+ ),
+ ),
+ ),
+ array(
+ 'methods' => WP_REST_Server::DELETABLE,
+ 'callback' => array( $this, 'delete_item' ),
+ 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
+
+ ),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
@@ -164,173 +223,267 @@ public static function get_the_item_schema() {
'enum' => array(
'ok',
'broken',
+ 'must_reauth',
null,
),
),
- 'user_id' => array(
+ 'wpcom_user_id' => array(
'type' => 'integer',
- 'description' => __( 'ID of the user the connection belongs to. It is the user ID on wordpress.com', 'jetpack-publicize-pkg' ),
+ 'description' => __( 'wordpress.com ID of the user the connection belongs to.', 'jetpack-publicize-pkg' ),
),
);
}
/**
- * Get all connections. Meant to be called directly only on WPCOM.
+ * Verify that the request has access to connectoins list.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return true|WP_Error
+ */
+ public function get_items_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
+ return $this->publicize_permissions_check();
+ }
+
+ /**
+ * Get list of connected Publicize connections.
*
- * @param array $args Arguments
- * - 'test_connections': bool Whether to run connection tests.
- * - 'scope': enum('site', 'user') Which connections to include.
+ * @param WP_REST_Request $request Full details about the request.
*
- * @return array
+ * @return WP_REST_Response suitable for 1-page collection
*/
- protected static function get_all_connections( $args = array() ) {
- /**
- * Publicize instance.
- */
- global $publicize;
+ public function get_items( $request ) {
+ if ( Publicize_Utils::is_wpcom() ) {
+ $args = array(
+ 'context' => self::is_authorized_blog_request() ? 'blog' : 'user',
+ 'test_connections' => $request->get_param( 'test_connections' ),
+ );
- $items = array();
+ $connections = Connections::wpcom_get_connections( $args );
+ } else {
+ $connections = $this->proxy_request_to_wpcom_as_user( $request );
+ }
- $run_tests = $args['test_connections'] ?? false;
+ if ( is_wp_error( $connections ) ) {
+ return $connections;
+ }
- $test_results = $run_tests ? self::get_connections_test_status() : array();
+ $items = array();
- // If a (Jetpack) blog request, return all the connections for that site.
- if ( self::is_authorized_blog_request() ) {
- $service_connections = $publicize->get_all_connections_for_blog_id( get_current_blog_id() );
- } else {
- $service_connections = (array) $publicize->get_services( 'connected' );
- }
+ foreach ( $connections as $item ) {
+ $data = $this->prepare_item_for_response( $item, $request );
- foreach ( $service_connections as $service_name => $connections ) {
- foreach ( $connections as $connection ) {
-
- $connection_id = $publicize->get_connection_id( $connection );
-
- $connection_meta = $publicize->get_connection_meta( $connection );
- $connection_data = $connection_meta['connection_data'];
-
- $items[] = array(
- 'connection_id' => (string) $connection_id,
- 'display_name' => (string) $publicize->get_display_name( $service_name, $connection ),
- 'external_handle' => (string) $publicize->get_external_handle( $service_name, $connection ),
- 'external_id' => $connection_meta['external_id'] ?? '',
- 'profile_link' => (string) $publicize->get_profile_link( $service_name, $connection ),
- 'profile_picture' => (string) $publicize->get_profile_picture( $connection ),
- 'service_label' => (string) Publicize::get_service_label( $service_name ),
- 'service_name' => $service_name,
- 'shared' => ! $connection_data['user_id'],
- 'status' => $test_results[ $connection_id ] ?? null,
- 'user_id' => (int) $connection_data['user_id'],
-
- // Deprecated fields.
- 'id' => (string) $publicize->get_connection_unique_id( $connection ),
- 'username' => $publicize->get_username( $service_name, $connection ),
- 'profile_display_name' => ! empty( $connection_meta['profile_display_name'] ) ? $connection_meta['profile_display_name'] : '',
- // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual -- We expect an integer, but do loose comparison below in case some other type is stored.
- 'global' => 0 == $connection_data['user_id'],
-
- );
- }
+ $items[] = $this->prepare_response_for_collection( $data );
}
- return $items;
+ $response = rest_ensure_response( $items );
+ $response->header( 'X-WP-Total', (string) count( $items ) );
+ $response->header( 'X-WP-TotalPages', '1' );
+
+ return $response;
}
/**
- * Get a list of publicize connections.
- *
- * @param array $args Arguments.
+ * Checks if a given request has access to create a connection.
*
- * @see Automattic\Jetpack\Publicize\REST_API\Connections_Controller::get_all_connections()
+ * @param WP_REST_Request $request Full details about the request.
+ * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
+ */
+ public function create_item_permissions_check( $request ) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
+ $permissions = parent::publicize_permissions_check();
+
+ if ( is_wp_error( $permissions ) ) {
+ return $permissions;
+ }
+
+ return current_user_can( 'publish_posts' );
+ }
+
+ /**
+ * Creates a new connection.
*
- * @return array
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
- public static function get_connections( $args = array() ) {
- if ( self::is_wpcom() ) {
- return self::get_all_connections( $args );
+ public function create_item( $request ) {
+ if ( Publicize_Utils::is_wpcom() ) {
+
+ $input = array(
+ 'keyring_connection_ID' => $request->get_param( 'keyring_connection_ID' ),
+ 'shared' => $request->get_param( 'shared' ),
+ );
+
+ $external_user_id = $request->get_param( 'external_user_ID' );
+ if ( ! empty( $external_user_id ) ) {
+ $input['external_user_ID'] = $external_user_id;
+ }
+
+ $result = Connections::wpcom_create_connection( $input );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ $connection = Connections::get_by_id( $result );
+
+ $response = $this->prepare_item_for_response( $connection, $request );
+ $response = rest_ensure_response( $response );
+
+ $response->set_status( 201 );
+
+ return $response;
+
}
- $site_id = Manager::get_site_id( true );
- if ( ! $site_id ) {
- return array();
+ $response = $this->proxy_request_to_wpcom_as_user( $request, '', array( 'timeout' => 120 ) );
+
+ if ( is_wp_error( $response ) ) {
+ return new WP_Error(
+ 'jp_connection_update_failed',
+ __( 'Something went wrong while creating a connection.', 'jetpack-publicize-pkg' ),
+ $response->get_error_message()
+ );
}
- $path = add_query_arg(
- array(
- 'test_connections' => $args['test_connections'] ?? false,
- ),
- sprintf( '/sites/%d/publicize/connections', $site_id )
- );
+ $response = rest_ensure_response( $response );
- $blog_or_user = ( $args['scope'] ?? '' ) === 'site' ? 'blog' : 'user';
+ $response->set_status( 201 );
- $callback = array( Client::class, "wpcom_json_api_request_as_{$blog_or_user}" );
+ return $response;
+ }
- $response = call_user_func( $callback, $path, 'v2', array( 'method' => 'GET' ), null, 'wpcom' );
+ /**
+ * Checks if a given request has access to update a connection.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
+ */
+ public function update_item_permissions_check( $request ) {
+ $permissions = parent::publicize_permissions_check();
- if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
- // TODO log error.
- return array();
+ if ( is_wp_error( $permissions ) ) {
+ return $permissions;
}
- $body = wp_remote_retrieve_body( $response );
+ // If the user cannot manage the connection, they can't update it either.
+ if ( ! $this->manage_connection_permission_check( $request ) ) {
+ return new WP_Error(
+ 'rest_cannot_edit',
+ __( 'Sorry, you are not allowed to update this connection.', 'jetpack-publicize-pkg' ),
+ array( 'status' => rest_authorization_required_code() )
+ );
+ }
- $items = json_decode( $body, true );
+ // If the connection is being marked/unmarked as shared.
+ if ( $request->has_param( 'shared' ) ) {
+ // Only editors and above can mark a connection as shared.
+ return current_user_can( 'edit_others_posts' );
+ }
- return $items ? $items : array();
+ return current_user_can( 'publish_posts' );
}
/**
- * Get list of connected Publicize connections.
+ * Update a connection.
*
* @param WP_REST_Request $request Full details about the request.
- *
- * @return WP_REST_Response suitable for 1-page collection
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
- public function get_items( $request ) {
- $items = array();
+ public function update_item( $request ) {
+ $connection_id = $request->get_param( 'connection_id' );
- // On Jetpack, we don't want to pass the 'scope' param to get_connections().
- $args = array(
- 'test_connections' => $request->get_param( 'test_connections' ),
- );
+ if ( Publicize_Utils::is_wpcom() ) {
- foreach ( self::get_connections( $args ) as $item ) {
- $data = $this->prepare_item_for_response( $item, $request );
+ $input = array(
+ 'shared' => $request->get_param( 'shared' ),
+ );
- $items[] = $this->prepare_response_for_collection( $data );
+ $result = Connections::wpcom_update_connection( $connection_id, $input );
+
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ $connection = Connections::get_by_id( $connection_id );
+
+ $response = $this->prepare_item_for_response( $connection, $request );
+ $response = rest_ensure_response( $response );
+
+ $response->set_status( 201 );
+
+ return $response;
}
- $response = rest_ensure_response( $items );
- $response->header( 'X-WP-Total', (string) count( $items ) );
- $response->header( 'X-WP-TotalPages', '1' );
+ $response = $this->proxy_request_to_wpcom_as_user( $request, $connection_id, array( 'timeout' => 120 ) );
+
+ if ( is_wp_error( $response ) ) {
+ return new WP_Error(
+ 'jp_connection_updation_failed',
+ __( 'Something went wrong while updating the connection.', 'jetpack-publicize-pkg' ),
+ $response->get_error_message()
+ );
+ }
+
+ $response = rest_ensure_response( $response );
+
+ $response->set_status( 201 );
return $response;
}
/**
- * Get the connections test status.
+ * Checks if a given request has access to delete a connection.
*
- * @return array
+ * @param WP_REST_Request $request Full details about the request.
+ * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
+ */
+ public function delete_item_permissions_check( $request ) {
+ $permissions = parent::publicize_permissions_check();
+
+ if ( is_wp_error( $permissions ) ) {
+ return $permissions;
+ }
+
+ return $this->manage_connection_permission_check( $request );
+ }
+
+ /**
+ * Delete a connection.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
- protected static function get_connections_test_status() {
- /**
- * Publicize instance.
- *
- * @var \Automattic\Jetpack\Publicize\Publicize $publicize
- */
- global $publicize;
+ public function delete_item( $request ) {
+ $connection_id = $request->get_param( 'connection_id' );
+
+ if ( Publicize_Utils::is_wpcom() ) {
+
+ $result = Connections::wpcom_delete_connection( $connection_id );
- $test_results = $publicize->get_publicize_conns_test_results();
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+
+ $response = rest_ensure_response( $result );
+
+ $response->set_status( 201 );
+
+ return $response;
+ }
- $test_results_map = array();
+ $response = $this->proxy_request_to_wpcom_as_user( $request, $connection_id, array( 'timeout' => 120 ) );
- foreach ( $test_results as $test_result ) {
- // Compare to `true` because the API returns a 'must_reauth' for LinkedIn.
- $test_results_map[ $test_result['connectionID'] ] = true === $test_result['connectionTestPassed'] ? 'ok' : 'broken';
+ if ( is_wp_error( $response ) ) {
+ return new WP_Error(
+ 'jp_connection_deletion_failed',
+ __( 'Something went wrong while deleting the connection.', 'jetpack-publicize-pkg' ),
+ $response->get_error_message()
+ );
}
- return $test_results_map;
+ $response = rest_ensure_response( $response );
+
+ $response->set_status( 201 );
+
+ return $response;
}
}
diff --git a/projects/packages/publicize/src/rest-api/class-connections-post-field.php b/projects/packages/publicize/src/rest-api/class-connections-post-field.php
index a12047e30d912..647655b211d37 100644
--- a/projects/packages/publicize/src/rest-api/class-connections-post-field.php
+++ b/projects/packages/publicize/src/rest-api/class-connections-post-field.php
@@ -7,6 +7,7 @@
namespace Automattic\Jetpack\Publicize\REST_API;
+use Automattic\Jetpack\Publicize\Connections;
use WP_Error;
use WP_Post;
use WP_REST_Request;
@@ -179,6 +180,16 @@ public function get( $post_array, $field_name, $request, $object_type ) { // php
$properties = array_keys( $schema['properties'] );
$connections = $publicize->get_filtered_connection_data( $post_id );
+ $connections_id_map = array_reduce(
+ Connections::get_all(),
+ function ( $map, $connection ) {
+ $map[ $connection['connection_id'] ] = $connection;
+
+ return $map;
+ },
+ array()
+ );
+
$output_connections = array();
foreach ( $connections as $connection ) {
$output_connection = array();
@@ -190,6 +201,7 @@ public function get( $post_array, $field_name, $request, $object_type ) { // php
$output_connection['id'] = (string) $connection['unique_id'];
$output_connection['can_disconnect'] = current_user_can( 'edit_others_posts' ) || get_current_user_id() === (int) $connection['user_id'];
+ $output_connection['wpcom_user_id'] = $connections_id_map[ $connection['connection_id'] ]['wpcom_user_id'] ?? 0;
$output_connections[] = $output_connection;
}
diff --git a/projects/packages/publicize/src/rest-api/class-proxy-requests.php b/projects/packages/publicize/src/rest-api/class-proxy-requests.php
new file mode 100644
index 0000000000000..58839e5010796
--- /dev/null
+++ b/projects/packages/publicize/src/rest-api/class-proxy-requests.php
@@ -0,0 +1,31 @@
+rest_base = $rest_base;
+ $this->base_api_path = $base_api_path;
+ $this->version = $version;
+ }
+}
diff --git a/projects/plugins/jetpack/changelog/fix-social-connections-list-feature-check b/projects/plugins/jetpack/changelog/fix-social-connections-list-feature-check
new file mode 100644
index 0000000000000..76c3a4215d02f
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/fix-social-connections-list-feature-check
@@ -0,0 +1,4 @@
+Significance: patch
+Type: bugfix
+
+Social | Fix publicize error in the editor due to malformed connections data
diff --git a/projects/plugins/social/changelog/fix-social-connections-list-feature-check b/projects/plugins/social/changelog/fix-social-connections-list-feature-check
new file mode 100644
index 0000000000000..d4868fdf6e0a2
--- /dev/null
+++ b/projects/plugins/social/changelog/fix-social-connections-list-feature-check
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fix publicize error in the editor due to malformed connections data