diff --git a/lib/class-wp-rest-block-directory-controller.php b/lib/class-wp-rest-block-directory-controller.php index c668bca24ff1d..9e911d28c9bd0 100644 --- a/lib/class-wp-rest-block-directory-controller.php +++ b/lib/class-wp-rest-block-directory-controller.php @@ -10,6 +10,8 @@ /** * Controller which provides REST endpoint for the blocks. * + * This class can be removed when plugin support requires WordPress 5.5.0+. + * * @since 5.5.0 * * @see WP_REST_Controller @@ -20,7 +22,7 @@ class WP_REST_Block_Directory_Controller extends WP_REST_Controller { * Constructs the controller. */ public function __construct() { - $this->namespace = '__experimental'; + $this->namespace = 'wp/v2'; $this->rest_base = 'block-directory'; } @@ -177,7 +179,7 @@ public function prepare_item_for_response( $plugin, $request ) { protected function prepare_links( $plugin ) { $links = array( 'https://api.w.org/install-plugin' => array( - 'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( '__experimental/plugins' ) ), + 'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ), ), ); @@ -185,7 +187,7 @@ protected function prepare_links( $plugin ) { if ( $plugin_file ) { $links['https://api.w.org/plugin'] = array( - 'href' => rest_url( '__experimental/plugins/' . substr( $plugin_file, 0, - 4 ) ), + 'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ), 'embeddable' => true, ); } @@ -322,8 +324,7 @@ public function get_item_schema() { public function get_collection_params() { $query_params = parent::get_collection_params(); - $query_params['context']['default'] = 'view'; - $query_params['per_page']['default'] = 3; + $query_params['context']['default'] = 'view'; $query_params['term'] = array( 'description' => __( 'Limit result set to blocks matching the search term.', 'gutenberg' ), diff --git a/lib/class-wp-rest-plugins-controller.php b/lib/class-wp-rest-plugins-controller.php index a4ec355b8c0d5..49a55192dad20 100644 --- a/lib/class-wp-rest-plugins-controller.php +++ b/lib/class-wp-rest-plugins-controller.php @@ -10,6 +10,8 @@ /** * Core class to access plugins via the REST API. * + * This class can be removed when plugin support requires WordPress 5.5.0+. + * * @since 5.5.0 * * @see WP_REST_Controller @@ -24,7 +26,7 @@ class WP_REST_Plugins_Controller extends WP_REST_Controller { * @since 5.5.0 */ public function __construct() { - $this->namespace = '__experimental'; + $this->namespace = 'wp/v2'; $this->rest_base = 'plugins'; } diff --git a/package-lock.json b/package-lock.json index 0be9133345c10..589041da45289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10940,6 +10940,7 @@ "@wordpress/icons": "file:packages/icons", "@wordpress/notices": "file:packages/notices", "@wordpress/plugins": "file:packages/plugins", + "@wordpress/url": "file:packages/url", "lodash": "^4.17.15" } }, diff --git a/packages/block-directory/package.json b/packages/block-directory/package.json index 9326f4efe4078..4141e7bb8db9f 100644 --- a/packages/block-directory/package.json +++ b/packages/block-directory/package.json @@ -39,6 +39,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/notices": "file:../notices", "@wordpress/plugins": "file:../plugins", + "@wordpress/url": "file:../url", "lodash": "^4.17.15" }, "publishConfig": { diff --git a/packages/block-directory/src/store/actions.js b/packages/block-directory/src/store/actions.js index a2da6b8d9794e..7dc899ebf7e1d 100644 --- a/packages/block-directory/src/store/actions.js +++ b/packages/block-directory/src/store/actions.js @@ -67,7 +67,7 @@ export function* installBlockType( block ) { } yield setIsInstalling( block.id, true ); const response = yield apiFetch( { - path: '__experimental/plugins', + path: 'wp/v2/plugins', data: { slug: block.id, status: 'active', diff --git a/packages/block-directory/src/store/controls.js b/packages/block-directory/src/store/controls.js index 19885bb224e7a..f3f58536fa641 100644 --- a/packages/block-directory/src/store/controls.js +++ b/packages/block-directory/src/store/controls.js @@ -1,3 +1,8 @@ +/** + * WordPress dependencies + */ +import { getPath } from '@wordpress/url'; + /** * Loads a JavaScript file. * @@ -6,7 +11,7 @@ * @return {Promise} Promise which will resolve when the asset is loaded. */ export const loadScript = ( asset ) => { - if ( ! asset || ! /\.js$/.test( asset ) ) { + if ( ! asset || ! /\.js$/.test( getPath( asset ) ) ) { return Promise.reject( new Error( 'No script found.' ) ); } return new Promise( ( resolve, reject ) => { @@ -30,7 +35,7 @@ export const loadScript = ( asset ) => { * @return {Promise} Promise which will resolve when the asset is added. */ export const loadStyle = ( asset ) => { - if ( ! asset || ! /\.css$/.test( asset ) ) { + if ( ! asset || ! /\.css$/.test( getPath( asset ) ) ) { return Promise.reject( new Error( 'No style found.' ) ); } return new Promise( ( resolve, reject ) => { @@ -60,7 +65,7 @@ export function loadAssets( assets ) { const controls = { LOAD_ASSETS( { assets } ) { const scripts = assets.map( ( asset ) => - asset.match( /\.js$/ ) !== null + getPath( asset ).match( /\.js$/ ) !== null ? loadScript( asset ) : loadStyle( asset ) ); diff --git a/packages/block-directory/src/store/resolvers.js b/packages/block-directory/src/store/resolvers.js index 5ca5f025e791a..e5724e87ff76c 100644 --- a/packages/block-directory/src/store/resolvers.js +++ b/packages/block-directory/src/store/resolvers.js @@ -26,7 +26,7 @@ export default { try { yield fetchDownloadableBlocks( filterValue ); const results = yield apiFetch( { - path: `__experimental/block-directory/search?term=${ filterValue }`, + path: `wp/v2/block-directory/search?term=${ filterValue }`, } ); const blocks = results.map( ( result ) => mapKeys( result, ( value, key ) => { @@ -45,7 +45,7 @@ export default { try { const response = yield apiFetch( { method: 'OPTIONS', - path: `__experimental/block-directory/search`, + path: `wp/v2/block-directory/search`, parse: false, } ); diff --git a/packages/block-directory/src/store/test/actions.js b/packages/block-directory/src/store/test/actions.js index a301a5ab28308..09069c2e4d422 100644 --- a/packages/block-directory/src/store/test/actions.js +++ b/packages/block-directory/src/store/test/actions.js @@ -4,7 +4,7 @@ import { installBlockType, uninstallBlockType } from '../actions'; describe( 'actions', () => { - const endpoint = '/wp-json/__experimental/plugins/block/block'; + const endpoint = '/wp-json/wp/v2/plugins/block/block'; const item = { id: 'block/block', name: 'Test Block', @@ -43,7 +43,7 @@ describe( 'actions', () => { expect( generator.next().value ).toMatchObject( { type: 'API_FETCH', request: { - path: '__experimental/plugins', + path: 'wp/v2/plugins', method: 'POST', }, } ); @@ -122,7 +122,7 @@ describe( 'actions', () => { expect( generator.next().value ).toMatchObject( { type: 'API_FETCH', request: { - path: '__experimental/plugins', + path: 'wp/v2/plugins', method: 'POST', }, } ); diff --git a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js index af9d615086779..697d51fb2863b 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js +++ b/packages/e2e-tests/specs/editor/plugins/block-directory-add.test.js @@ -11,15 +11,13 @@ import { // Urls to mock const SEARCH_URLS = [ - '/__experimental/block-directory/search', - `rest_route=${ encodeURIComponent( - '/__experimental/block-directory/search' - ) }`, + '/wp/v2/block-directory/search', + `rest_route=${ encodeURIComponent( '/wp/v2/block-directory/search' ) }`, ]; const INSTALL_URLS = [ - '/__experimental/plugins', - `rest_route=${ encodeURIComponent( '/__experimental/plugins' ) }`, + '/wp/v2/plugins', + `rest_route=${ encodeURIComponent( '/wp/v2/plugins' ) }`, ]; // Example Blocks @@ -36,7 +34,7 @@ const MOCK_BLOCK1 = { author: 'No Author', icon: 'block-default', assets: [ - 'fake_url.com/block.js', // we will mock this + 'https://fake_url.com/block.js', // we will mock this ], humanized_updated: '5 months ago', }; @@ -86,7 +84,7 @@ const block = `( function() { } )();`; const MOCK_OPTIONS = { - namespace: '__experimental', + namespace: 'wp/v2', methods: [ 'GET' ], endpoints: [ { diff --git a/phpunit/class-wp-rest-block-directory-controller-test.php b/phpunit/class-wp-rest-block-directory-controller-test.php deleted file mode 100644 index e3ffb1df71793..0000000000000 --- a/phpunit/class-wp-rest-block-directory-controller-test.php +++ /dev/null @@ -1,181 +0,0 @@ -user->create( - array( - 'role' => 'administrator', - ) - ); - - if ( is_multisite() ) { - grant_super_admin( self::$admin_id ); - } - } - - public static function wpTearDownAfterClass() { - self::delete_user( self::$admin_id ); - } - - public function test_register_routes() { - $routes = rest_get_server()->get_routes(); - - $this->assertArrayHasKey( '/__experimental/block-directory/search', $routes ); - } - - public function test_context_param() { - // Collection. - $request = new WP_REST_Request( 'OPTIONS', '/__experimental/block-directory/search' ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); - $this->assertEquals( array( 'view' ), $data['endpoints'][0]['args']['context']['enum'] ); - } - - public function test_get_items() { - wp_set_current_user( self::$admin_id ); - - $request = new WP_REST_Request( 'GET', '/__experimental/block-directory/search' ); - $request->set_query_params( array( 'term' => 'foo' ) ); - - $result = rest_do_request( $request ); - $this->assertNotWPError( $result->as_error() ); - $this->assertEquals( 200, $result->status ); - } - - public function test_get_items_wdotorg_unavailable() { - wp_set_current_user( self::$admin_id ); - - $request = new WP_REST_Request( 'GET', '/__experimental/block-directory/search' ); - $request->set_query_params( array( 'term' => 'foo' ) ); - - $this->prevent_requests_to_host( 'api.wordpress.org' ); - - $this->expectException( 'PHPUnit_Framework_Error_Warning' ); - $response = rest_do_request( $request ); - $this->assertErrorResponse( 'plugins_api_failed', $response, 500 ); - } - - public function test_get_items_logged_out() { - $request = new WP_REST_Request( 'GET', '/__experimental/block-directory/search' ); - $request->set_query_params( array( 'term' => 'foo' ) ); - $response = rest_do_request( $request ); - $this->assertErrorResponse( 'rest_block_directory_cannot_view', $response ); - } - - public function test_get_items_no_results() { - wp_set_current_user( self::$admin_id ); - - $request = new WP_REST_Request( 'GET', '/__experimental/block-directory/search' ); - $request->set_query_params( array( 'term' => '0c4549ee68f24eaaed46a49dc983ecde' ) ); - $response = rest_do_request( $request ); - $data = $response->get_data(); - - // Should produce a 200 status with an empty array. - $this->assertEquals( 200, $response->status ); - $this->assertEquals( array(), $data ); - } - - public function test_get_item() { - $this->markTestSkipped( 'Controller does not have get_item route.' ); - } - - public function test_create_item() { - $this->markTestSkipped( 'Controller does not have create_item route.' ); - } - - public function test_update_item() { - $this->markTestSkipped( 'Controller does not have update_item route.' ); - } - - public function test_delete_item() { - $this->markTestSkipped( 'Controller does not have delete_item route.' ); - } - - public function test_prepare_item() { - wp_set_current_user( self::$admin_id ); - - // This will hit the live API. We're searching for `block` which should definitely return at least one result. - $request = new WP_REST_Request( 'GET', '/__experimental/block-directory/search' ); - $request->set_query_params( array( 'term' => 'block' ) ); - $response = rest_do_request( $request ); - $data = $response->get_data(); - - $this->assertEquals( 200, $response->status ); - // At least one result - $this->assertGreaterThanOrEqual( 1, count( $data ) ); - // Each result should be an object with important attributes set - foreach ( $data as $plugin ) { - $this->assertArrayHasKey( 'name', $plugin ); - $this->assertArrayHasKey( 'title', $plugin ); - $this->assertArrayHasKey( 'id', $plugin ); - $this->assertArrayHasKey( 'author_block_rating', $plugin ); - $this->assertArrayHasKey( 'assets', $plugin ); - $this->assertArrayHasKey( 'humanized_updated', $plugin ); - - // Assets should be fully qualified https URLs - foreach ( $plugin['assets'] as $asset ) { - $this->assertEquals( 'https', wp_parse_url( $asset, PHP_URL_SCHEME ) ); - } - } - } - - public function test_get_item_schema() { - wp_set_current_user( self::$admin_id ); - - $request = new WP_REST_Request( 'OPTIONS', '/__experimental/block-directory/search' ); - $request->set_query_params( array( 'term' => 'foo' ) ); - $response = rest_do_request( $request ); - $data = $response->get_data(); - - // Check endpoints - $this->assertEquals( [ 'GET' ], $data['endpoints'][0]['methods'] ); - $this->assertTrue( $data['endpoints'][0]['args']['term'][ 'required'] ); - - $properties = $data['schema']['properties']; - - $this->assertCount( 13, $properties ); - $this->assertArrayHasKey( 'name', $properties ); - $this->assertArrayHasKey( 'title', $properties ); - $this->assertArrayHasKey( 'description', $properties ); - $this->assertArrayHasKey( 'id', $properties ); - $this->assertArrayHasKey( 'rating', $properties ); - $this->assertArrayHasKey( 'rating_count', $properties ); - $this->assertArrayHasKey( 'active_installs', $properties ); - $this->assertArrayHasKey( 'author_block_rating', $properties ); - $this->assertArrayHasKey( 'author_block_count', $properties ); - $this->assertArrayHasKey( 'author', $properties ); - $this->assertArrayHasKey( 'icon', $properties ); - $this->assertArrayHasKey( 'humanized_updated', $properties ); - $this->assertArrayHasKey( 'assets', $properties ); - } - - /** - * Simulate a network failure on outbound http requests to a given hostname. - * - * @param string $blocked_host The host to block connections to. - */ - private function prevent_requests_to_host( $blocked_host = 'api.wordpress.org' ) { - add_filter( - 'pre_http_request', - static function ( $return, $args, $url ) use ( $blocked_host ) { - if ( @parse_url( $url, PHP_URL_HOST ) === $blocked_host ) { - return new WP_Error( 'plugins_api_failed', "An expected error occurred connecting to $blocked_host because of a unit test", "cURL error 7: Failed to connect to $blocked_host port 80: Connection refused" ); - - } - - return $return; - }, - 10, - 3 - ); - } -} diff --git a/phpunit/class-wp-rest-plugins-controller-test.php b/phpunit/class-wp-rest-plugins-controller-test.php deleted file mode 100644 index 619f1f517ac27..0000000000000 --- a/phpunit/class-wp-rest-plugins-controller-test.php +++ /dev/null @@ -1,1015 +0,0 @@ -user->create( - array( - 'role' => 'subscriber', - ) - ); - self::$super_admin = $factory->user->create( - array( - 'role' => 'administrator', - ) - ); - self::$admin = $factory->user->create( - array( - 'role' => 'administrator', - ) - ); - - if ( is_multisite() ) { - grant_super_admin( self::$super_admin ); - } - - if ( ! defined( 'FS_METHOD' ) ) { - define( 'FS_METHOD', 'direct' ); - } - } - - /** - * Clean up test fixtures. - * - * @since 5.5.0 - */ - public static function wpTearDownAfterClass() { - self::delete_user( self::$subscriber_id ); - self::delete_user( self::$super_admin ); - } - - public function tearDown() { - parent::tearDown(); - - if ( file_exists( WP_PLUGIN_DIR . '/test-plugin/test-plugin.php' ) ) { - $this->rmdir( WP_PLUGIN_DIR . '/test-plugin' ); - } - } - - public function test_register_routes() { - $routes = rest_get_server()->get_routes(); - $this->assertArrayHasKey( self::BASE, $routes ); - $this->assertArrayHasKey( self::BASE . '/(?P[^.\/]+(?:\/[^.\/]+)?)', $routes ); - } - - public function test_context_param() { - // Collection. - $request = new WP_REST_Request( 'OPTIONS', self::BASE ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); - $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); - // Single. - $request = new WP_REST_Request( 'OPTIONS', self::BASE . '/' . self::PLUGIN ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] ); - $this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] ); - } - - public function test_get_items() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $response = rest_do_request( self::BASE ); - $this->assertEquals( 200, $response->get_status() ); - - $items = wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ); - - $this->assertCount( 1, $items ); - $this->check_get_plugin_data( array_shift( $items ) ); - } - - public function test_get_items_search() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'search' => 'testeroni' ) ); - $response = rest_do_request( $request ); - $this->assertCount( 0, $response->get_data() ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'search' => 'Cool' ) ); - $response = rest_do_request( $request ); - $this->assertCount( 1, wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ) ); - } - - public function test_get_items_status() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'status' => 'inactive' ) ); - $response = rest_do_request( $request ); - $this->assertCount( 1, wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ) ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'status' => 'active' ) ); - $response = rest_do_request( $request ); - $this->assertCount( 0, wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ) ); - } - - public function test_get_items_status_multiple() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'status' => array( 'inactive', 'active' ) ) ); - $response = rest_do_request( $request ); - - $this->assertGreaterThan( 1, count( wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ), 'NOT' ) ) ); - $this->assertCount( 1, wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ) ); - } - - /** - * @group ms-required - */ - public function test_get_items_status_network_active() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - $this->assertCount( 0, wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ) ); - - activate_plugin( self::PLUGIN_FILE, '', true ); - $request = new WP_REST_Request( 'GET', self::BASE ); - $request->set_query_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - $this->assertCount( 1, wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ) ); - } - - public function test_get_items_logged_out() { - $response = rest_do_request( self::BASE ); - $this->assertEquals( 401, $response->get_status() ); - } - - public function test_get_items_insufficient_permissions() { - wp_set_current_user( self::$subscriber_id ); - $response = rest_do_request( self::BASE ); - $this->assertequals( 403, $response->get_status() ); - } - - /** - * @group ms-required - */ - public function test_cannot_get_items_if_plugins_menu_not_available() { - $this->create_test_plugin(); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'GET', self::BASE ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_view_plugins', $response->as_error(), 403 ); - } - - /** - * @group ms-required - */ - public function test_get_items_if_plugins_menu_available() { - $this->create_test_plugin(); - $this->enable_plugins_menu_item(); - wp_set_current_user( self::$admin ); - - $response = rest_do_request( self::BASE ); - $this->assertEquals( 200, $response->get_status() ); - } - - /** - * @group ms-required - */ - public function test_get_items_excludes_network_only_plugin_if_not_active() { - $this->create_test_plugin( true ); - $this->enable_plugins_menu_item(); - wp_set_current_user( self::$admin ); - - $response = rest_do_request( self::BASE ); - $this->assertEquals( 200, $response->get_status() ); - - $items = wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ); - $this->assertCount( 0, $items ); - } - - /** - * @group ms-excluded - */ - public function test_get_items_does_not_exclude_network_only_plugin_if_not_active_on_single_site() { - $this->create_test_plugin( true ); - wp_set_current_user( self::$admin ); - - $response = rest_do_request( self::BASE ); - $this->assertEquals( 200, $response->get_status() ); - - $items = wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ); - $this->assertCount( 1, $items ); - $this->check_get_plugin_data( array_shift( $items ), true ); - } - - /** - * @group ms-required - */ - public function test_get_items_does_not_exclude_network_only_plugin_if_not_active_but_has_network_caps() { - $this->create_test_plugin( true ); - $this->enable_plugins_menu_item(); - wp_set_current_user( self::$super_admin ); - - $response = rest_do_request( self::BASE ); - $this->assertEquals( 200, $response->get_status() ); - - $items = wp_list_filter( $response->get_data(), array( 'plugin' => self::PLUGIN ) ); - $this->assertCount( 1, $items ); - $this->check_get_plugin_data( array_shift( $items ), true ); - } - - public function test_get_item() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $response = rest_do_request( self::BASE . '/' . self::PLUGIN ); - $this->assertEquals( 200, $response->get_status() ); - $this->check_get_plugin_data( $response->get_data() ); - } - - public function test_get_item_logged_out() { - $response = rest_do_request( self::BASE . '/' . self::PLUGIN ); - $this->assertEquals( 401, $response->get_status() ); - } - - public function test_get_item_insufficient_permissions() { - wp_set_current_user( self::$subscriber_id ); - $response = rest_do_request( self::BASE . '/' . self::PLUGIN ); - $this->assertEquals( 403, $response->get_status() ); - } - - /** - * @group ms-required - */ - public function test_cannot_get_item_if_plugins_menu_not_available() { - $this->create_test_plugin(); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'GET', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_view_plugin', $response->as_error(), 403 ); - } - - /** - * @group ms-required - */ - public function test_get_item_if_plugins_menu_available() { - $this->create_test_plugin(); - $this->enable_plugins_menu_item(); - wp_set_current_user( self::$admin ); - - $response = rest_do_request( self::BASE . '/' . self::PLUGIN ); - $this->assertEquals( 200, $response->get_status() ); - } - - public function test_get_item_invalid_plugin() { - wp_set_current_user( self::$super_admin ); - $response = rest_do_request( self::BASE . '/' . self::PLUGIN ); - $this->assertEquals( 404, $response->get_status() ); - } - - public function test_create_item() { - if ( isset( get_plugins()['hello-dolly/hello.php'] ) ) { - delete_plugins( array( 'hello-dolly/hello.php' ) ); - } - - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( array( 'slug' => 'hello-dolly' ) ); - - $response = rest_do_request( $request ); - $this->skip_on_filesystem_error( $response ); - $this->assertNotWPError( $response->as_error() ); - $this->assertEquals( 201, $response->get_status() ); - $this->assertEquals( 'Hello Dolly', $response->get_data()['name'] ); - } - - public function test_create_item_and_activate() { - if ( isset( get_plugins()['hello-dolly/hello.php'] ) ) { - delete_plugins( array( 'hello-dolly/hello.php' ) ); - } - - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( - array( - 'slug' => 'hello-dolly', - 'status' => 'active', - ) - ); - - $response = rest_do_request( $request ); - $this->skip_on_filesystem_error( $response ); - $this->assertNotWPError( $response->as_error() ); - $this->assertEquals( 201, $response->get_status() ); - $this->assertEquals( 'Hello Dolly', $response->get_data()['name'] ); - $this->assertTrue( is_plugin_active( 'hello-dolly/hello.php' ) ); - } - - public function test_create_item_and_activate_errors_if_no_permission_to_activate_plugin() { - if ( isset( get_plugins()['hello-dolly/hello.php'] ) ) { - delete_plugins( array( 'hello-dolly/hello.php' ) ); - } - - wp_set_current_user( self::$super_admin ); - $this->disable_activate_permission( 'hello-dolly/hello.php' ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( - array( - 'slug' => 'hello-dolly', - 'status' => 'active', - ) - ); - - $response = rest_do_request( $request ); - $this->skip_on_filesystem_error( $response ); - $this->assertErrorResponse( 'rest_cannot_activate_plugin', $response ); - $this->assertFalse( is_plugin_active( 'hello-dolly/hello.php' ) ); - } - - /** - * @group ms-excluded - */ - public function test_create_item_and_network_activate_rejected_if_not_multisite() { - if ( isset( get_plugins()['hello-dolly/hello.php'] ) ) { - delete_plugins( array( 'hello-dolly/hello.php' ) ); - } - - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( - array( - 'slug' => 'hello-dolly', - 'status' => 'network-active', - ) - ); - - $response = rest_do_request( $request ); - $this->skip_on_filesystem_error( $response ); - $this->assertErrorResponse( 'rest_invalid_param', $response ); - } - - /** - * @group ms-required - */ - public function test_create_item_and_network_activate() { - if ( isset( get_plugins()['hello-dolly/hello.php'] ) ) { - delete_plugins( array( 'hello-dolly/hello.php' ) ); - } - - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( - array( - 'slug' => 'hello-dolly', - 'status' => 'network-active', - ) - ); - - $response = rest_do_request( $request ); - $this->skip_on_filesystem_error( $response ); - $this->assertNotWPError( $response->as_error() ); - $this->assertEquals( 201, $response->get_status() ); - $this->assertEquals( 'Hello Dolly', $response->get_data()['name'] ); - $this->assertTrue( is_plugin_active_for_network( 'hello-dolly/hello.php' ) ); - } - - public function test_create_item_logged_out() { - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( array( 'slug' => 'hello-dolly' ) ); - - $response = rest_do_request( $request ); - $this->assertEquals( 401, $response->get_status() ); - } - - public function test_create_item_insufficient_permissions() { - wp_set_current_user( self::$subscriber_id ); - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( array( 'slug' => 'hello-dolly' ) ); - - $response = rest_do_request( $request ); - $this->assertEquals( 403, $response->get_status() ); - } - - /** - * @group ms-required - */ - public function test_cannot_create_item_if_not_super_admin() { - $this->create_test_plugin(); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( array( 'slug' => 'hello-dolly' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_install_plugin', $response->as_error(), 403 ); - } - - public function test_create_item_wdotorg_unreachable() { - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( array( 'slug' => 'foo' ) ); - - $this->prevent_requests_to_host( 'api.wordpress.org' ); - - $this->expectException( 'PHPUnit_Framework_Error_Warning' ); - $response = rest_do_request( $request ); - $this->skip_on_filesystem_error( $response ); - $this->assertErrorResponse( 'plugins_api_failed', $response, 500 ); - } - - public function test_create_item_unknown_plugin() { - wp_set_current_user( self::$super_admin ); - - // This will hit the live API. - $request = new WP_REST_Request( 'POST', self::BASE ); - $request->set_body_params( array( 'slug' => 'alex-says-this-block-definitely-doesnt-exist' ) ); - $response = rest_do_request( $request ); - - $this->skip_on_filesystem_error( $response ); - // Is this an appropriate status? - $this->assertErrorResponse( 'plugins_api_failed', $response, 404 ); - } - - public function test_update_item() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - } - - public function test_update_item_logged_out() { - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertEquals( 401, $response->get_status() ); - } - - public function test_update_item_insufficient_permissions() { - wp_set_current_user( self::$subscriber_id ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertEquals( 403, $response->get_status() ); - } - - /** - * @group ms-required - */ - public function test_cannot_update_item_if_plugins_menu_not_available() { - $this->create_test_plugin(); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_manage_plugins', $response->as_error(), 403 ); - } - - public function test_update_item_activate_plugin() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'active' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_active( self::PLUGIN_FILE ) ); - } - - public function test_update_item_activate_plugin_fails_if_no_activate_cap() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - $this->disable_activate_permission( self::PLUGIN_FILE ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'active' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_activate_plugin', $response, 403 ); - } - - /** - * @group ms-excluded - */ - public function test_update_item_network_activate_plugin_rejected_if_not_multisite() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_invalid_param', $response ); - } - - /** - * @group ms-required - */ - public function test_update_item_network_activate_plugin() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_active_for_network( self::PLUGIN_FILE ) ); - } - - /** - * @group ms-required - */ - public function test_update_item_network_activate_plugin_that_was_active_on_single_site() { - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_active_for_network( self::PLUGIN_FILE ) ); - } - - /** - * @group ms-required - */ - public function test_update_item_activate_network_only_plugin() { - $this->create_test_plugin( true ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'active' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_network_only_plugin', $response, 400 ); - } - - /** - * @group ms-required - */ - public function test_update_item_network_activate_network_only_plugin() { - $this->create_test_plugin( true ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_active_for_network( self::PLUGIN_FILE ) ); - } - - /** - * @group ms-excluded - */ - public function test_update_item_activate_network_only_plugin_on_non_multisite() { - $this->create_test_plugin( true ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'active' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_active( self::PLUGIN_FILE ) ); - } - - /** - * @group ms-required - */ - public function test_update_item_activate_plugin_for_site_if_menu_item_available() { - $this->create_test_plugin(); - $this->enable_plugins_menu_item(); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'active' ) ); - $response = rest_do_request( $request ); - - $this->assertNotWPError( $response->as_error() ); - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_active( self::PLUGIN_FILE ) ); - } - - /** - * @group ms-required - */ - public function test_update_item_network_activate_plugin_for_site_if_menu_item_available() { - $this->create_test_plugin(); - $this->enable_plugins_menu_item(); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'network-active' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_manage_network_plugins', $response, 403 ); - } - - public function test_update_item_deactivate_plugin() { - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'inactive' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_inactive( self::PLUGIN_FILE ) ); - } - - public function test_update_item_deactivate_plugin_fails_if_no_deactivate_cap() { - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE ); - wp_set_current_user( self::$super_admin ); - $this->disable_deactivate_permission( self::PLUGIN_FILE ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'inactive' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_deactivate_plugin', $response, 403 ); - } - - /** - * @group ms-required - */ - public function test_update_item_deactivate_network_active_plugin() { - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE, '', true ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'inactive' ) ); - $response = rest_do_request( $request ); - - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( is_plugin_inactive( self::PLUGIN_FILE ) ); - } - - /** - * @group ms-required - */ - public function test_update_item_deactivate_network_active_plugin_if_not_super_admin() { - $this->enable_plugins_menu_item(); - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE, '', true ); - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'PUT', self::BASE . '/' . self::PLUGIN ); - $request->set_body_params( array( 'status' => 'inactive' ) ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_manage_network_plugins', $response, 403 ); - } - - public function test_delete_item() { - $this->create_test_plugin(); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'DELETE', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->skip_on_filesystem_error( $response ); - $this->assertNotWPError( $response->as_error() ); - $this->assertEquals( 200, $response->get_status() ); - $this->assertTrue( $response->get_data()['deleted'] ); - $this->assertEquals( self::PLUGIN, $response->get_data()['previous']['plugin'] ); - $this->assertFileNotExists( WP_PLUGIN_DIR . '/' . self::PLUGIN_FILE ); - } - - public function test_delete_item_logged_out() { - $request = new WP_REST_Request( 'DELETE', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertEquals( 401, $response->get_status() ); - } - - public function test_delete_item_insufficient_permissions() { - wp_set_current_user( self::$subscriber_id ); - - $request = new WP_REST_Request( 'DELETE', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertEquals( 403, $response->get_status() ); - } - - /** - * @group ms-required - */ - public function test_cannot_delete_item_if_plugins_menu_not_available() { - wp_set_current_user( self::$admin ); - - $request = new WP_REST_Request( 'DELETE', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_manage_plugins', $response->as_error(), 403 ); - } - - /** - * @group ms-required - */ - public function test_cannot_delete_item_if_plugins_menu_is_available() { - wp_set_current_user( self::$admin ); - $this->enable_plugins_menu_item(); - - $request = new WP_REST_Request( 'DELETE', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->assertErrorResponse( 'rest_cannot_manage_plugins', $response->as_error(), 403 ); - } - - public function test_delete_item_active_plugin() { - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE ); - wp_set_current_user( self::$super_admin ); - - $request = new WP_REST_Request( 'DELETE', self::BASE . '/' . self::PLUGIN ); - $response = rest_do_request( $request ); - - $this->skip_on_filesystem_error( $response ); - $this->assertErrorResponse( 'rest_cannot_delete_active_plugin', $response ); - } - - public function test_prepare_item() { - $this->create_test_plugin(); - - $item = get_plugins()[ self::PLUGIN_FILE ]; - $item['_file'] = self::PLUGIN_FILE; - - $endpoint = new WP_REST_Plugins_Controller(); - $response = $endpoint->prepare_item_for_response( $item, new WP_REST_Request( 'GET', self::BASE . '/' . self::PLUGIN ) ); - - $this->check_get_plugin_data( $response->get_data() ); - $links = $response->get_links(); - $this->assertArrayHasKey( 'self', $links ); - $this->assertEquals( rest_url( self::BASE . '/' . self::PLUGIN ), $links['self'][0]['href'] ); - } - - /** - * @group ms-required - */ - public function test_prepare_item_network_active() { - $this->create_test_plugin(); - activate_plugin( self::PLUGIN_FILE, '', true ); - - $item = get_plugins()[ self::PLUGIN_FILE ]; - $item['_file'] = self::PLUGIN_FILE; - - $endpoint = new WP_REST_Plugins_Controller(); - $response = $endpoint->prepare_item_for_response( $item, new WP_REST_Request( 'GET', self::BASE . '/' . self::PLUGIN ) ); - - $this->assertEquals( 'network-active', $response->get_data()['status'] ); - } - - /** - * @group ms-required - */ - public function test_prepare_item_network_only() { - $this->create_test_plugin( true ); - - $item = get_plugins()[ self::PLUGIN_FILE ]; - $item['_file'] = self::PLUGIN_FILE; - - $endpoint = new WP_REST_Plugins_Controller(); - $response = $endpoint->prepare_item_for_response( $item, new WP_REST_Request( 'GET', self::BASE . '/' . self::PLUGIN ) ); - - $this->check_get_plugin_data( $response->get_data(), true ); - } - - public function test_get_item_schema() { - $request = new WP_REST_Request( 'OPTIONS', self::BASE ); - $response = rest_get_server()->dispatch( $request ); - $data = $response->get_data(); - $properties = $data['schema']['properties']; - - $this->assertCount( 12, $properties ); - $this->assertArrayHasKey( 'plugin', $properties ); - $this->assertArrayHasKey( 'status', $properties ); - $this->assertArrayHasKey( 'name', $properties ); - $this->assertArrayHasKey( 'plugin_uri', $properties ); - $this->assertArrayHasKey( 'description', $properties ); - $this->assertArrayHasKey( 'author', $properties ); - $this->assertArrayHasKey( 'author_uri', $properties ); - $this->assertArrayHasKey( 'version', $properties ); - $this->assertArrayHasKey( 'network_only', $properties ); - $this->assertArrayHasKey( 'requires_wp', $properties ); - $this->assertArrayHasKey( 'requires_php', $properties ); - $this->assertArrayHasKey( 'text_domain', $properties ); - } - - /** - * Checks the response data. - * - * @since 5.5.0 - * - * @param array $data Prepared plugin data. - * @param bool $network_only Whether the plugin is network only. - */ - protected function check_get_plugin_data( $data, $network_only = false ) { - $this->assertEquals( 'test-plugin/test-plugin', $data['plugin'] ); - $this->assertEquals( '1.5.4', $data['version'] ); - $this->assertEquals( 'inactive', $data['status'] ); - $this->assertEquals( 'Test Plugin', $data['name'] ); - $this->assertEquals( 'https://wordpress.org/plugins/test-plugin/', $data['plugin_uri'] ); - $this->assertEquals( 'WordPress.org', $data['author'] ); - $this->assertEquals( 'https://wordpress.org/', $data['author_uri'] ); - $this->assertEquals( "My 'Cool' Plugin", $data['description']['raw'] ); - $this->assertEquals( 'My ‘Cool’ Plugin By WordPress.org.', $data['description']['rendered'] ); - $this->assertEquals( $network_only, $data['network_only'] ); - $this->assertEquals( '5.6.0', $data['requires_php'] ); - $this->assertEquals( '5.4.0', $data['requires_wp'] ); - $this->assertEquals( 'test-plugin', $data['text_domain'] ); - } - - /** - * Skips the test if the response is an error due to the filesystem being unavailable. - * - * @since 5.5.0 - * - * @param WP_REST_Response $response The response object to inspect. - */ - protected function skip_on_filesystem_error( WP_REST_Response $response ) { - if ( ! $response->is_error() ) { - return; - } - - $code = $response->as_error()->get_error_code(); - - if ( 'fs_unavailable' === $code || false !== strpos( $code, 'mkdir_failed' ) ) { - $this->markTestSkipped( 'Filesystem is unavailable.' ); - } - } - - /** - * Disables permission for activating a specific plugin. - * - * @param string $plugin The plugin file to disable. - */ - protected function disable_activate_permission( $plugin ) { - add_filter( - 'map_meta_cap', - static function ( $caps, $cap, $user, $args ) use ( $plugin ) { - if ( 'activate_plugin' === $cap && $plugin === $args[0] ) { - $caps = array( 'do_not_allow' ); - } - - return $caps; - }, - 10, - 4 - ); - } - - /** - * Disables permission for deactivating a specific plugin. - * - * @param string $plugin The plugin file to disable. - */ - protected function disable_deactivate_permission( $plugin ) { - add_filter( - 'map_meta_cap', - static function ( $caps, $cap, $user, $args ) use ( $plugin ) { - if ( 'deactivate_plugin' === $cap && $plugin === $args[0] ) { - $caps = array( 'do_not_allow' ); - } - - return $caps; - }, - 10, - 4 - ); - } - - /** - * Enables the "plugins" as an available menu item. - * - * @since 5.5.0 - */ - protected function enable_plugins_menu_item() { - $menu_perms = get_site_option( 'menu_items', array() ); - $menu_perms['plugins'] = true; - update_site_option( 'menu_items', $menu_perms ); - } - - /** - * Creates a test plugin. - * - * @since 5.5.0 - * - * @param bool $network_only Whether to make this a network only plugin. - */ - private function create_test_plugin( $network_only = false ) { - $network = $network_only ? PHP_EOL . ' * Network: true' . PHP_EOL : ''; - - $php = <<markTestSkipped( 'Filesystem is unavailable.' ); - } - - file_put_contents( WP_PLUGIN_DIR . '/test-plugin/test-plugin.php', $php ); - } - - /** - * Simulate a network failure on outbound http requests to a given hostname. - * - * @param string $blocked_host The host to block connections to. - */ - private function prevent_requests_to_host( $blocked_host = 'api.wordpress.org' ) { - add_filter( - 'pre_http_request', - static function ( $return, $args, $url ) use ( $blocked_host ) { - if ( @parse_url( $url, PHP_URL_HOST ) === $blocked_host ) { - return new WP_Error( 'plugins_api_failed', "An expected error occurred connecting to $blocked_host because of a unit test", "cURL error 7: Failed to connect to $blocked_host port 80: Connection refused" ); - - } - - return $return; - }, - 10, - 3 - ); - } -}