Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create infrastructure for detecting & installing WP Consent API. #8305

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions assets/js/googlesitekit/datastore/site/consent-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,22 @@ const fetchSaveConsentModeSettingsStore = createFetchStore( {
},
} );

const fetchGetConsentAPIInfoStore = createFetchStore( {
baseName: 'getConsentAPIInfo',
controlCallback: () => {
return API.get( 'core', 'site', 'consent-api-info', null, {
useCache: false,
} );
},
reducerCallback: createReducer( ( state, apiInfo ) => {
state.consentMode.apiInfo = apiInfo;
} ),
} );

const baseInitialState = {
consentMode: {
settings: undefined,
apiInfo: undefined,
},
};

Expand Down Expand Up @@ -139,6 +152,18 @@ const baseSelectors = {

return enabled;
} ),

/**
* Gets the WP Consent Mode API info.
*
* @since n.e.x.t
*
* @param {Object} state Data store's state.
* @return {Object|undefined} WP Consent Mode API info, or `undefined` if not loaded.
*/
getConsentAPIInfo: ( state ) => {
return state.consentMode.apiInfo;
},
};

const baseResolvers = {
Expand All @@ -151,11 +176,22 @@ const baseResolvers = {

yield fetchGetConsentModeSettingsStore.actions.fetchGetConsentModeSettings();
},

*getConsentAPIInfo() {
const { select } = yield getRegistry();

if ( select( CORE_SITE ).getConsentAPIInfo() ) {
return;
}

yield fetchGetConsentAPIInfoStore.actions.fetchGetConsentAPIInfo();
},
};

const store = Data.combineStores(
fetchGetConsentModeSettingsStore,
fetchSaveConsentModeSettingsStore,
fetchGetConsentAPIInfoStore,
{
initialState: baseInitialState,
actions: baseActions,
Expand Down
82 changes: 82 additions & 0 deletions assets/js/googlesitekit/datastore/site/consent-mode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,22 @@ describe( 'core/site Consent Mode', () => {
status: 500,
} );

const initialSettings = registry
.select( CORE_SITE )
.getConsentModeSettings();

expect( initialSettings ).toBeUndefined();

await untilResolved(
registry,
CORE_SITE
).getConsentModeSettings();

const settings = registry
.select( CORE_SITE )
.getConsentModeSettings();

// Verify the settings are still undefined after the selector is resolved.
expect( settings ).toBeUndefined();

await waitForDefaultTimeouts();
Expand All @@ -216,5 +228,75 @@ describe( 'core/site Consent Mode', () => {
).toBe( true );
} );
} );

describe( 'getConsentAPIInfo', () => {
const consentAPIInfoEndpointRegExp = new RegExp(
'^/google-site-kit/v1/core/site/data/consent-api-info'
);

it( 'uses a resolver to make a network request', async () => {
const consentAPIInfo = {
hasConsentAPI: false,
wpConsentPlugin: {
installed: false,
activateURL:
'http://example.com/wp-admin/plugins.php?action=activate&plugin=some-plugin',
installURL:
'http://example.com/wp-admin/update.php?action=install-plugin&plugin=some-plugin',
},
};

fetchMock.getOnce( consentAPIInfoEndpointRegExp, {
body: consentAPIInfo,
status: 200,
} );

const initialAPIInfo = registry
.select( CORE_SITE )
.getConsentAPIInfo();

expect( initialAPIInfo ).toBeUndefined();

await untilResolved( registry, CORE_SITE ).getConsentAPIInfo();

const apiInfo = registry
.select( CORE_SITE )
.getConsentAPIInfo();

expect( apiInfo ).toEqual( consentAPIInfo );

expect( fetchMock ).toHaveFetched(
consentAPIInfoEndpointRegExp
);
} );

it( 'returns undefined if the request fails', async () => {
fetchMock.getOnce( consentAPIInfoEndpointRegExp, {
body: { error: 'something went wrong' },
status: 500,
} );

const initialAPIInfo = registry
.select( CORE_SITE )
.getConsentAPIInfo();

expect( initialAPIInfo ).toBeUndefined();

await untilResolved( registry, CORE_SITE ).getConsentAPIInfo();

const apiInfo = registry
.select( CORE_SITE )
.getConsentAPIInfo();

// Verify the API info is still undefined after the selector is resolved.
expect( apiInfo ).toBeUndefined();

expect( fetchMock ).toHaveFetched(
consentAPIInfoEndpointRegExp
);

expect( console ).toHaveErrored();
} );
} );
} );
} );
46 changes: 46 additions & 0 deletions includes/Core/Consent_Mode/REST_Consent_Mode_Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,52 @@ protected function get_rest_routes() {
),
)
),
new REST_Route(
'core/site/data/consent-api-info',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => function () {
$is_active = function_exists( 'wp_set_consent' );
$installed = $is_active;
$slug = 'wp-consent-api';
$plugin = "$slug/$slug.php";
Comment on lines +143 to +144
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could actually be different if it were installed via Composer. See https://packagist.org/packages/rlankhorst/wp-consent-level-api

Definitely an edge case for most of our users I think, but worth keeping in mind that the $plugin can't always be assumed to be $slug/$slug. Not just because of Composer, but the directory can be renamed, or sometimes even the plugin itself can change the name of its entry point php file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @aaemnnosttv, that's a good point. I've created #8307 to address this later. Seems like a post-MVP issue to me.


$response = array(
'hasConsentAPI' => $is_active,
);

if ( ! $is_active ) {
if ( ! function_exists( 'get_plugins' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
foreach ( array_keys( get_plugins() ) as $installed_plugin ) {
if ( $installed_plugin === $plugin ) {
$installed = true;
break;
}
}

// Alternate wp_nonce_url without esc_html breaking query parameters.
$nonce_url = function ( $action_url, $action ) {
return add_query_arg( '_wpnonce', wp_create_nonce( $action ), $action_url );
};
$activate_url = $nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=' . $plugin ), 'activate-plugin_' . $plugin );
$install_url = $nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $slug ), 'install-plugin_' . $slug );

$response['wpConsentPlugin'] = array(
'installed' => $installed,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that there's no need to include the active field specified in the AC. This is because hasConsentAPI indicates the active status.

'activateURL' => current_user_can( 'activate_plugin', $plugin ) ? esc_url_raw( $activate_url ) : false,
'installURL' => current_user_can( 'install_plugins' ) ? esc_url_raw( $install_url ) : false,
);
}

return new WP_REST_Response( $response );
},
'permission_callback' => $can_manage_options,
),
)
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public function test_set_settings__requires_authenticated_admin() {
}

/**
* @dataProvider provider_wrong_data
* @dataProvider provider_wrong_settings_data
*/
public function test_set_settings__wrong_data( $settings ) {
remove_all_filters( 'googlesitekit_rest_routes' );
Expand All @@ -206,7 +206,7 @@ public function test_set_settings__wrong_data( $settings ) {
$this->assertEquals( 'rest_invalid_param', $response->get_data()['code'] );
}

public function provider_wrong_data() {
public function provider_wrong_settings_data() {
return array(
'wrong data type' => array(
'{}',
Expand All @@ -223,6 +223,41 @@ public function provider_wrong_data() {
);
}

public function test_get_api_info() {
remove_all_filters( 'googlesitekit_rest_routes' );
$this->controller->register();
$this->register_rest_routes();
// Setup the site and admin user to make a successful REST request.
$this->grant_manage_options_permission();

$request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/consent-api-info' );
$response = rest_get_server()->dispatch( $request );

$response_data = $response->get_data();

$this->assertFalse( $response_data['hasConsentAPI'] );
$this->assertIsArray( $response_data['wpConsentPlugin'] );

$wp_consent_plugin = $response_data['wpConsentPlugin'];

$this->assertFalse( $wp_consent_plugin['installed'] );
$this->assertStringStartsWith( 'http://example.org/wp-admin/plugins.php?action=activate&plugin=wp-consent-api%2Fwp-consent-api.php&_wpnonce=', $wp_consent_plugin['activateURL'] );
$this->assertStringStartsWith( 'http://example.org/wp-admin/update.php?action=install-plugin&plugin=wp-consent-api&_wpnonce=', $wp_consent_plugin['installURL'] );
}

public function test_get_api_info__requires_authenticated_admin() {
remove_all_filters( 'googlesitekit_rest_routes' );
$this->controller->register();
$this->register_rest_routes();

$request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/consent-api-info' );
$response = rest_get_server()->dispatch( $request );

// This admin hasn't authenticated with the Site Kit proxy service yet,
// so they aren't allowed to modify Dashboard Sharing settings.
$this->assertEquals( 'rest_forbidden', $response->get_data()['code'] );
}

private function grant_manage_options_permission() {
// Setup SiteKit.
$this->fake_proxy_site_connection();
Expand Down
Loading