Skip to content

REST API: Add /themes/activate endpoint #9304

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

Open
wants to merge 3 commits into
base: trunk
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ public function register_routes() {
'schema' => array( $this, 'get_public_item_schema' ),
)
);

register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/activate',
array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'activate_theme' ),
'permission_callback' => array( $this, 'activate_theme_permissions_check' ),
'args' => array(
'stylesheet' => array(
'description' => __( 'Unique identifier for the theme.' ),
'type' => 'string',
'required' => true,
'validate_callback' => array( $this, 'validate_theme' ),
),
),
),
)
);
}

/**
Expand Down Expand Up @@ -164,6 +184,55 @@ protected function check_read_active_theme_permission() {
);
}

/**
* Checks if a given request has access to activate the theme.
*
* @since 6.9.0
*
* @return true|WP_Error True if the request has activate access for the item, otherwise WP_Error object.
*/
public function activate_theme_permissions_check() {
if ( current_user_can( 'switch_themes' ) ) {
return true;
}

return new WP_Error(
'rest_cannot_activate_themes',
__( 'Sorry, you are not allowed to activate themes.' ),
array( 'status' => rest_authorization_required_code() )
);
}

/**
* Validates the theme.
*
* @since 6.9.0
*
* @param string $stylesheet The stylesheet of the theme.
* @return true|WP_Error True if the theme is valid, otherwise WP_Error object.
*/
public function validate_theme( $stylesheet ) {
$theme = wp_get_theme( $stylesheet );

if ( ! $theme->exists() ) {
return new WP_Error(
'rest_theme_not_found',
__( 'The requested theme does not exist.' ),
array( 'status' => 404 )
);
}

if ( ! $theme->is_allowed() ) {
return new WP_Error(
'rest_theme_not_allowed',
__( 'The requested theme is not network enabled.' ),
array( 'status' => 400 )
);
}

return true;
}

/**
* Retrieves a single theme.
*
Expand Down Expand Up @@ -750,4 +819,25 @@ public function sanitize_theme_status( $statuses, $request, $parameter ) {

return $statuses;
}

/**
* Activates the theme.
*
* @since 6.9.0
*
* @param WP_REST_Request $request The request object.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error on failure.
*/
public function activate_theme( WP_REST_Request $request ) {
$theme = wp_get_theme( $request['stylesheet'] );

switch_theme( $theme->get_stylesheet() );

return rest_ensure_response(
array(
'message' => __( 'Theme activated successfully.' ),
'theme' => $theme->get_stylesheet(),
)
);
}
}
1 change: 1 addition & 0 deletions tests/phpunit/tests/rest-api/rest-schema-setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public function test_expected_routes_in_schema() {
'/wp/v2/templates/lookup',
'/wp/v2/themes',
'/wp/v2/themes/(?P<stylesheet>[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
'/wp/v2/themes/activate',
'/wp/v2/plugins',
'/wp/v2/plugins/(?P<plugin>[^.\/]+(?:\/[^.\/]+)?)',
'/wp/v2/block-directory/search',
Expand Down
93 changes: 93 additions & 0 deletions tests/phpunit/tests/rest-api/rest-themes-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ public function set_up() {

wp_set_current_user( self::$contributor_id );
switch_theme( 'rest-api' );

// In multisite, grant super admin privileges and network-enable the theme
if ( is_multisite() ) {
grant_super_admin( self::$admin_id );

// Network-enable the twentytwentyfive theme
$allowed_themes = get_site_option( 'allowedthemes' );
if ( ! is_array( $allowed_themes ) ) {
$allowed_themes = array();
}
$allowed_themes['twentytwentyfive'] = true;
update_site_option( 'allowedthemes', $allowed_themes );
}
}

/**
Expand Down Expand Up @@ -1557,4 +1570,84 @@ public function test_delete_item() {
public function test_context_param() {
// Controller does not use get_context_param().
}

/**
* Test that the activate_theme_permissions_check method returns an error for users without switch_themes capability.
*
* @covers WP_REST_Themes_Controller::activate_theme_permissions_check
*/
public function test_activate_theme_permissions_check() {
wp_set_current_user( self::$contributor_id );

$request = new WP_REST_Request(
'POST',
self::$themes_route . '/activate'
);
$request->set_param( 'stylesheet', 'twentytwentyfive' );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 403, $response->get_status() );
$this->assertSame( 'rest_cannot_activate_themes', $response->get_data()['code'] );
}

/**
* Test that the validate_theme method returns an error for non-existent themes.
*
* @covers WP_REST_Themes_Controller::validate_theme
*/
public function test_activate_theme_validate_theme_returns_error_on_non_existent_theme() {
wp_set_current_user( self::$admin_id );

$request = new WP_REST_Request(
'POST',
self::$themes_route . '/activate'
);
$request->set_param( 'stylesheet', 'non-existent-theme' );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 400, $response->get_status() );
$this->assertSame( 'rest_invalid_param', $response->get_data()['code'] );
$this->assertSame( 'rest_theme_not_found', $response->get_data()['data']['details']['stylesheet']['code'] );
}

/**
* Test that the validate_theme method returns an error for themes that are not network enabled
* when on a multisite installation.
*
* @group ms-required
*/
public function test_activate_theme_validate_theme_returns_error_on_not_network_enabled_theme() {
wp_set_current_user( self::$admin_id );

$request = new WP_REST_Request(
'POST',
self::$themes_route . '/activate'
);
$request->set_param( 'stylesheet', 'twentytwentyfour' );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 400, $response->get_status() );
$this->assertSame( 'rest_invalid_param', $response->get_data()['code'] );
$this->assertSame( 'rest_theme_not_allowed', $response->get_data()['data']['details']['stylesheet']['code'] );
}

/**
* Test that the activate_theme method activates a valid theme.
*
* @covers WP_REST_Themes_Controller::activate_theme
*/
public function test_activate_theme() {
wp_set_current_user( self::$admin_id );

$request = new WP_REST_Request(
'POST',
self::$themes_route . '/activate'
);
$request->set_param( 'stylesheet', 'twentytwentyfive' );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 200, $response->get_status() );
$this->assertSame( 'Theme activated successfully.', $response->get_data()['message'] );
$this->assertSame( 'twentytwentyfive', $response->get_data()['theme'] );
}
}
27 changes: 27 additions & 0 deletions tests/qunit/fixtures/wp-api-generated.js
Original file line number Diff line number Diff line change
Expand Up @@ -11071,6 +11071,33 @@ mockedApiResponse.Schema = {
}
]
},
"/wp/v2/themes/activate": {
"namespace": "wp/v2",
"methods": [
"POST"
],
"endpoints": [
{
"methods": [
"POST"
],
"args": {
"stylesheet": {
"description": "Unique identifier for the theme.",
"type": "string",
"required": true
}
}
}
],
"_links": {
"self": [
{
"href": "http://example.org/index.php?rest_route=/wp/v2/themes/activate"
}
]
}
},
"/wp/v2/plugins": {
"namespace": "wp/v2",
"methods": [
Expand Down
Loading