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

Block Directory: Use plugin API for installing & deleting block-plugins #23219

Merged
merged 5 commits into from
Jun 24, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
143 changes: 1 addition & 142 deletions lib/class-wp-rest-block-directory-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,6 @@ public function register_routes() {
'schema' => array( $this, 'get_public_item_schema' ),
)
);

register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/install',
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array(
'slug' => array(
'required' => true,
),
),
)
);

register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/uninstall',
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'slug' => array(
'required' => true,
),
),
)
);
}

/**
Expand Down Expand Up @@ -136,117 +106,6 @@ public function get_items( $request ) {
return rest_ensure_response( $result );
}

/**
* Checks whether a given request has permission to install and activate plugins.
*
* @since 5.5.0
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|bool True if the request has permission, WP_Error object otherwise.
*/
public function create_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
return new WP_Error(
'rest_block_directory_cannot_create',
__( 'Sorry, you are not allowed to install blocks.', 'gutenberg' ),
array( 'status' => rest_authorization_required_code() )
);
}

return true;
}

/**
* Installs and activates a plugin
*
* @since 5.5.0
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function create_item( $request ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';

$existing = $this->find_plugin_for_slug( $request['slug'] );

if ( $existing ) {
$activate = new WP_REST_Request( 'PUT', '/__experimental/plugins/' . substr( $existing, 0, - 4 ) );
$activate->set_body_params( array( 'status' => 'active' ) );

return rest_do_request( $activate );
}

$inner_request = new WP_REST_Request( 'POST', '/__experimental/plugins' );
$inner_request->set_body_params(
array(
'slug' => $request['slug'],
'status' => 'active',
)
);

return rest_do_request( $inner_request );
}

/**
* Checks whether a given request has permission to remove/deactivate plugins.
*
* @since 5.5.0
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|bool True if the request has permission, WP_Error object otherwise.
*/
public function delete_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
if ( ! current_user_can( 'delete_plugins' ) || ! current_user_can( 'deactivate_plugins' ) ) {
return new WP_Error(
'rest_block_directory_cannot_delete',
__( 'Sorry, you are not allowed to uninstall blocks.', 'gutenberg' ),
array( 'status' => rest_authorization_required_code() )
);
}

return true;
}

/**
* Deactivates and deletes a plugin
*
* @since 5.5.0
*
* @param WP_REST_Request $request Full details about the request.
*
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
*/
public function delete_item( $request ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';

$slug = trim( $request->get_param( 'slug' ) );

if ( ! $slug ) {
return new WP_Error( 'slug_not_provided', 'Valid slug not provided.', array( 'status' => 400 ) );
}

$plugin_file = $this->find_plugin_for_slug( $slug );

if ( ! $plugin_file ) {
return new WP_Error( 'block_not_found', 'Valid slug not provided.', array( 'status' => 400 ) );
}

$route = '/__experimental/plugins/' . substr( $plugin_file, 0, - 4 );
$deactivate = new WP_REST_Request( 'PUT', $route );
$deactivate->set_body_params( array( 'status' => 'inactive' ) );

$deactivated = rest_do_request( $deactivate );

if ( $deactivated->is_error() ) {
return $deactivated->as_error();
}

return rest_do_request( new WP_REST_Request( 'DELETE', $route ) );
}

/**
* Parse block metadata for a block, and prepare it for an API repsonse.
*
Expand Down Expand Up @@ -277,7 +136,7 @@ public function prepare_item_for_response( $plugin, $request ) {
'assets' => array(),
'last_updated' => $plugin['last_updated'],
'humanized_updated' => sprintf(
/* translators: %s: Human-readable time difference. */
/* translators: %s: Human-readable time difference. */
__( '%s ago', 'gutenberg' ),
human_time_diff( strtotime( $plugin['last_updated'] ) )
),
Expand Down
22 changes: 12 additions & 10 deletions packages/block-directory/src/store/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,16 @@ export function* installBlockType( block ) {
throw new Error( __( 'Block has no assets.' ) );
}
yield setIsInstalling( block.id, true );
yield apiFetch( {
path: '__experimental/block-directory/install',
const response = yield apiFetch( {
path: '__experimental/plugins',
data: {
slug: block.id,
status: 'active',
},
method: 'POST',
} );
yield addInstalledBlockType( block );
const endpoint = response?._links?.self[ 0 ]?.href;
yield addInstalledBlockType( { ...block, endpoint } );

yield loadAssets( assets );
const registeredBlocks = yield select( 'core/blocks', 'getBlockTypes' );
Expand All @@ -94,18 +96,18 @@ export function* installBlockType( block ) {
* @param {Object} block The blockType object.
*/
export function* uninstallBlockType( block ) {
const { id } = block;
try {
const response = yield apiFetch( {
path: '__experimental/block-directory/uninstall',
yield apiFetch( {
url: block.endpoint,
data: {
slug: id,
status: 'inactive',
},
method: 'PUT',
} );
yield apiFetch( {
url: block.endpoint,
method: 'DELETE',
} );
if ( response !== true ) {
throw new Error( __( 'Unable to uninstall this block.' ) );
}
yield removeInstalledBlockType( block );
} catch ( error ) {
yield dispatch(
Expand Down
72 changes: 62 additions & 10 deletions packages/block-directory/src/store/test/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@
import { installBlockType, uninstallBlockType } from '../actions';

describe( 'actions', () => {
const endpoint = '/wp-json/__experimental/plugins/block/block';
const item = {
id: 'block/block',
name: 'Test Block',
assets: [ 'script.js' ],
};
const plugin = {
plugin: 'block/block.php',
status: 'active',
name: 'Test Block',
version: '1.0.0',
_links: {
self: [
{
href: endpoint,
},
],
},
};

describe( 'installBlockType', () => {
it( 'should install a block successfully', () => {
Expand All @@ -28,11 +42,16 @@ describe( 'actions', () => {

expect( generator.next().value ).toMatchObject( {
type: 'API_FETCH',
request: {
path: '__experimental/plugins',
method: 'POST',
},
} );

expect( generator.next( { success: true } ).value ).toEqual( {
const itemWithEndpoint = { ...item, endpoint };
expect( generator.next( plugin ).value ).toEqual( {
type: 'ADD_INSTALLED_BLOCK_TYPE',
item,
item: itemWithEndpoint,
} );

expect( generator.next().value ).toEqual( {
Expand Down Expand Up @@ -102,6 +121,10 @@ describe( 'actions', () => {

expect( generator.next().value ).toMatchObject( {
type: 'API_FETCH',
request: {
path: '__experimental/plugins',
method: 'POST',
},
} );

const apiError = {
Expand All @@ -128,16 +151,32 @@ describe( 'actions', () => {
} );

describe( 'uninstallBlockType', () => {
const itemWithEndpoint = { ...item, endpoint };

it( 'should uninstall a block successfully', () => {
const generator = uninstallBlockType( item );
const generator = uninstallBlockType( itemWithEndpoint );

// First the deactivation step
expect( generator.next().value ).toMatchObject( {
type: 'API_FETCH',
request: {
url: endpoint,
method: 'PUT',
},
} );

// Then the deletion step
expect( generator.next().value ).toMatchObject( {
type: 'API_FETCH',
request: {
url: endpoint,
method: 'DELETE',
},
} );

expect( generator.next( true ).value ).toEqual( {
expect( generator.next().value ).toEqual( {
type: 'REMOVE_INSTALLED_BLOCK_TYPE',
item,
item: itemWithEndpoint,
} );

expect( generator.next() ).toEqual( {
Expand All @@ -146,19 +185,32 @@ describe( 'actions', () => {
} );
} );

it( "should set a global notice if the plugin can't uninstall", () => {
const generator = uninstallBlockType( item );
it( "should set a global notice if the plugin can't be deleted", () => {
const generator = uninstallBlockType( itemWithEndpoint );

expect( generator.next().value ).toMatchObject( {
type: 'API_FETCH',
request: {
url: endpoint,
method: 'PUT',
},
} );

expect( generator.next().value ).toMatchObject( {
type: 'API_FETCH',
request: {
url: endpoint,
method: 'DELETE',
},
} );

const apiError = {
code: 'could_not_remove_plugin',
message: 'Could not fully remove the plugin .',
code: 'rest_cannot_delete_active_plugin',
message:
'Cannot delete an active plugin. Please deactivate it first.',
data: null,
};
expect( generator.next( apiError ).value ).toMatchObject( {
expect( generator.throw( apiError ).value ).toMatchObject( {
type: 'DISPATCH',
actionName: 'createErrorNotice',
storeKey: 'core/notices',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ const SEARCH_URLS = [
];

const INSTALL_URLS = [
'/__experimental/block-directory/install',
`rest_route=${ encodeURIComponent(
'/__experimental/block-directory/install'
) }`,
'/__experimental/plugins',
`rest_route=${ encodeURIComponent( '/__experimental/plugins' ) }`,
];

// Example Blocks
Expand Down
Loading