Skip to content

Commit

Permalink
Block Directory: Use plugin API for installing & deleting block-plugi…
Browse files Browse the repository at this point in the history
…ns (#23219)

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

* Remove the install & uninstall endpoints from the block-directory

* Set the endpoint for the block by using the self link property

* Remove erroneous route tests

The install and uninstall routes no longer exist on this controller, as they've moved to the plugin controller.

* Update e2e install mock url to fix tests.

Co-authored-by: tellyworth <alex@automattic.com>
Co-authored-by: dufresnesteven <steve.dufresne@automattic.com>
  • Loading branch information
3 people committed Jun 24, 2020
1 parent 81fe567 commit bacb520
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 206 deletions.
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

0 comments on commit bacb520

Please sign in to comment.