Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: removed

Remove the deprecated start-writing launchpad checklist and its remaining task handling.
Original file line number Diff line number Diff line change
Expand Up @@ -1833,9 +1833,9 @@ function wpcom_track_site_launch_task() {
wpcom_launchpad_mark_launchpad_task_complete_if_active( 'videopress_launched' );
wpcom_launchpad_mark_launchpad_task_complete_if_active( 'blog_launched' );

// Remove site intent for blog onboarding flows and disable launchpad.
// Remove site intent for the blog onboarding flow and disable launchpad.
$site_intent = get_option( 'site_intent' );
if ( in_array( $site_intent, array( 'start-writing', 'design-first' ), true ) ) {
if ( 'design-first' === $site_intent ) {
update_option( 'site_intent', '' );
update_option( 'launchpad_screen', 'off' );
}
Expand Down Expand Up @@ -1996,15 +1996,6 @@ function wpcom_launchpad_is_blog_launched_task_disabled() {
}
return true;
}
if ( 'start-writing' === get_option( 'site_intent' ) ) {
if ( wpcom_is_checklist_task_complete( 'plan_completed' )
&& wpcom_is_checklist_task_complete( 'domain_upsell' )
&& wpcom_is_checklist_task_complete( 'setup_blog' )
&& wpcom_is_checklist_task_complete( 'first_post_published' ) ) {
return false;
}
return true;
}
return false;
}

Expand Down Expand Up @@ -2819,7 +2810,7 @@ function wpcom_trigger_email_campaign() {
return;
}

if ( ! in_array( $site_intent, array( 'start-writing', 'design-first' ), true ) ) {
if ( 'design-first' !== $site_intent ) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,19 +151,6 @@ function wpcom_launchpad_get_task_list_definitions() {
),
'is_enabled_callback' => 'wpcom_launchpad_get_fullscreen_enabled',
),
'start-writing' => array(
'get_title' => function () {
return __( 'Next steps for your site', 'jetpack-mu-wpcom' );
},
'task_ids' => array(
'first_post_published',
'setup_blog',
'domain_upsell',
'plan_completed',
'blog_launched',
),
'is_enabled_callback' => 'wpcom_launchpad_get_fullscreen_enabled',
),
'design-first' => array(
'get_title' => function () {
return __( 'Next steps for your site', 'jetpack-mu-wpcom' );
Expand Down Expand Up @@ -440,6 +427,20 @@ function wpcom_register_default_launchpad_checklists() {
}
add_action( 'init', 'wpcom_register_default_launchpad_checklists', 11 );

/**
* The site intents of retired onboarding flows.
*
* These no longer have a checklist, but sites created under them still carry
* the value, and clients still pass it to the launchpad endpoint as a checklist
* slug. The endpoint keeps accepting these so such a request degrades to an
* empty checklist rather than a 400. Empty this list once no site carries one.
*
* @return string[] Retired site intents.
*/
function wpcom_launchpad_get_retired_site_intents() {
return array( 'start-writing' );
}

/**
* Clears the site intent for a retired onboarding flow.
*
Expand All @@ -452,9 +453,7 @@ function wpcom_register_default_launchpad_checklists() {
* once no site carries one of these values.
*/
function wpcom_launchpad_clear_retired_site_intents() {
$retired_site_intents = array( 'start-writing' );

if ( ! in_array( get_option( 'site_intent' ), $retired_site_intents, true ) ) {
if ( ! in_array( get_option( 'site_intent' ), wpcom_launchpad_get_retired_site_intents(), true ) ) {
return;
}

Expand Down Expand Up @@ -1007,24 +1006,34 @@ function wpcom_launchpad_navigator_remove_checklist( $checklist_slug ) {
);
}

$current_active_checklist = wpcom_launchpad_get_active_checklist();

$checklists = $wpcom_launchpad_config['navigator_checklists'];
// Find if $checklist_slug is in the checklists array. If it is, remove it.
$key = array_search( $checklist_slug, $checklists, true );
if ( $key === false ) {
return array(
'updated' => false,
'new_active_checklist' => $current_active_checklist,
'new_active_checklist' => wpcom_launchpad_get_active_checklist(),
);
}

unset( $checklists[ $key ] );

$new_active_checklist = $current_active_checklist;
if ( $current_active_checklist === $checklist_slug ) {
// get last item on $checklists array, if there is one; otherwise set to null
$new_active_checklist = end( $checklists ) ? end( $checklists ) : null;
// Compare against the raw stored slug, not the getter: a retired active checklist
// reads as null there, which would otherwise hide that it was the one removed.
$stored_active_slug = $wpcom_launchpad_config['active_checklist_slug'] ?? null;

$new_active_checklist = wpcom_launchpad_get_active_checklist();
if ( $stored_active_slug === $checklist_slug ) {
// The active checklist was removed. Promote the most recently added one that
// is still registered, skipping any other retired slugs, or clear it.
$registered_checklists = wpcom_launchpad_checklists()->get_all_task_lists();
$new_active_checklist = null;
foreach ( array_reverse( $checklists ) as $remaining_slug ) {
if ( is_string( $remaining_slug ) && array_key_exists( $remaining_slug, $registered_checklists ) ) {
$new_active_checklist = $remaining_slug;
break;
}
}
wpcom_launchpad_set_current_active_checklist( $new_active_checklist );
}

Expand Down Expand Up @@ -1073,13 +1082,28 @@ function wpcom_launchpad_get_active_checklist() {
return null;
}

return $wpcom_launchpad_config['active_checklist_slug'];
$active_checklist_slug = $wpcom_launchpad_config['active_checklist_slug'];

// This repairs persisted state, so it cannot assume a well-formed value: legacy
// or custom code may have stored a non-string, which would fatal below.
if ( ! is_string( $active_checklist_slug ) ) {
return null;
}

// A retired checklist can linger here from before it was unregistered. Treat it
// as unset, matching how the available list drops unregistered slugs and how the
// setter refuses them, so the Navigator never reports a checklist that is gone.
if ( ! array_key_exists( $active_checklist_slug, wpcom_launchpad_checklists()->get_all_task_lists() ) ) {
return null;
}

return $active_checklist_slug;
}

/**
* Helper function to set the current active checklist in the navigator context.
*
* @param string $checklist_slug The slug of the launchpad task list to mark as active.
* @param string|null $checklist_slug The slug of the launchpad task list to mark as active, or null to clear it.
* @return bool Whether the option update succeeded.
*/
function wpcom_launchpad_set_current_active_checklist( $checklist_slug ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function register_routes() {
'remove_checklist_slug' => array(
'description' => 'The slug of the checklist to remove from the active list.',
'type' => 'string',
'enum' => $this->get_checklist_slug_enums(),
'enum' => $this->get_readable_checklist_slug_enums(),
),
),
),
Expand All @@ -61,7 +61,7 @@ public function register_routes() {
/**
* Validates that the argument sent to the active_checklist_slug parameter is a valid checklist slug or empty.
*
* @param string $value The value of the active_checklist_slug parameter.
* @param string|null $value The value of the active_checklist_slug parameter.
* @return bool
*/
public function validate_checklist_slug_param( $value ) {
Expand All @@ -83,6 +83,21 @@ public function get_checklist_slug_enums() {
return array_keys( $checklists );
}

/**
* Returns the checklist slugs a read or removal request may reference.
*
* Retired flow slugs are included so a stale client can still read or remove
* one. Setting a retired slug as active still uses the registered slugs from
* get_checklist_slug_enums(), so it is rejected at the boundary.
*
* @return array Array of checklist slugs.
*/
public function get_readable_checklist_slug_enums() {
return array_values(
array_unique( array_merge( $this->get_checklist_slug_enums(), wpcom_launchpad_get_retired_site_intents() ) )
);
}

/**
* Updates Launchpad navigator-related options and returns the result
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function register_routes() {
'checklist_slug' => array(
'description' => 'Checklist slug',
'type' => 'string',
'enum' => $this->get_checklist_slug_enums(),
'enum' => $this->get_readable_checklist_slug_enums(),
),
'launchpad_context' => array(
'description' => 'Screen where Launchpand instance is loaded.',
Expand Down Expand Up @@ -122,6 +122,21 @@ public function get_checklist_slug_enums() {
return array_keys( $checklists );
}

/**
* Returns the checklist slugs a read request may reference.
*
* Retired flow slugs are included so a stale client request degrades to an
* empty checklist rather than a 400. Read-only: mutations use the registered
* slugs from get_checklist_slug_enums() so they cannot act on a retired slug.
*
* @return array Array of checklist slugs.
*/
public function get_readable_checklist_slug_enums() {
return array_values(
array_unique( array_merge( $this->get_checklist_slug_enums(), wpcom_launchpad_get_retired_site_intents() ) )
);
}

/**
* Returns all registered checklist statuses.
*
Expand Down Expand Up @@ -190,7 +205,7 @@ public function get_data( $request ) {
'launchpad_screen' => get_option( 'launchpad_screen' ),
'checklist_statuses' => get_option( 'launchpad_checklist_tasks_statuses', array() ),
'checklist' => wpcom_get_launchpad_checklist_by_checklist_slug( $checklist_slug, $launchpad_context ),
'is_enabled' => wpcom_get_launchpad_task_list_is_enabled( $checklist_slug ),
'is_enabled' => (bool) wpcom_get_launchpad_task_list_is_enabled( $checklist_slug ),
'is_dismissed' => wpcom_launchpad_is_task_list_dismissed( $checklist_slug ),
'is_dismissible' => wpcom_launchpad_is_task_list_dismissible( $checklist_slug ),
'title' => wpcom_get_launchpad_checklist_title_by_checklist_slug( $checklist_slug ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* @package automattic/jetpack-mu-wpcom
*/

//phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath
require_once \Automattic\Jetpack\Jetpack_Mu_Wpcom::PKG_DIR . 'src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad-navigator.php';

use PHPUnit\Framework\Attributes\DataProvider;

/**
Expand Down Expand Up @@ -208,4 +211,91 @@ public function test_cleanup_ignores_the_intent_seen_before_the_blog_switch() {
$this->assertSame( 'design-first', get_option( 'site_intent' ), 'the switched-to blog must survive' );
$this->assertSame( 'full', get_option( 'launchpad_screen' ), 'the switched-to blog must survive' );
}

/**
* A Navigator config left pointing at a retired checklist reports no active
* checklist, rather than one that no longer exists. This can outlive the
* intent cleanup, so it must not depend on `site_intent` still matching.
*/
public function test_retired_active_checklist_slug_reads_as_none() {
wpcom_register_default_launchpad_checklists();
// Cleared already, to prove the repair does not depend on the intent still matching.
update_option( 'site_intent', '' );
update_option(
'wpcom_launchpad_config',
array(
'active_checklist_slug' => 'start-writing',
'navigator_checklists' => array( 'start-writing', 'design-first' ),
)
);

$this->assertNull( wpcom_launchpad_get_active_checklist(), 'retired active slug must read as none' );
}

/**
* A live active checklist is still reported.
*/
public function test_live_active_checklist_slug_is_reported() {
wpcom_register_default_launchpad_checklists();
update_option(
'wpcom_launchpad_config',
array( 'active_checklist_slug' => 'design-first' )
);

$this->assertSame( 'design-first', wpcom_launchpad_get_active_checklist() );
}

/**
* Removing a retired active checklist promotes the next registered one, rather
* than leaving the retired slug stored with no active checklist reported.
*/
public function test_removing_retired_active_checklist_promotes_next_registered() {
wpcom_register_default_launchpad_checklists();
update_option(
'wpcom_launchpad_config',
array(
'active_checklist_slug' => 'start-writing',
'navigator_checklists' => array( 'start-writing', 'design-first' ),
)
);

$result = wpcom_launchpad_navigator_remove_checklist( 'start-writing' );

$this->assertSame( 'design-first', $result['new_active_checklist'], 'the next registered checklist must be promoted' );
$this->assertSame( 'design-first', wpcom_launchpad_get_active_checklist(), 'the promotion must be persisted' );
}

/**
* A non-string active checklist slug from malformed legacy state reads as none
* rather than fataling on the registered-checklist lookup.
*/
public function test_malformed_active_checklist_slug_reads_as_none() {
wpcom_register_default_launchpad_checklists();
update_option(
'wpcom_launchpad_config',
array( 'active_checklist_slug' => array( 'unexpected' ) )
);

$this->assertNull( wpcom_launchpad_get_active_checklist() );
}

/**
* Retired slugs are accepted for reads and removal, but not for setting the
* active checklist, so a retired flow cannot be re-selected.
*/
public function test_navigator_enums_separate_reads_from_mutations() {
wpcom_register_default_launchpad_checklists();
$navigator = new WPCOM_REST_API_V2_Endpoint_Launchpad_Navigator();

$registered = $navigator->get_checklist_slug_enums();
$this->assertContains( 'design-first', $registered );
$this->assertNotContains( 'start-writing', $registered, 'mutations must not accept a retired slug' );

$readable = $navigator->get_readable_checklist_slug_enums();
$this->assertContains( 'start-writing', $readable, 'reads and removal must accept a retired slug' );

$this->assertFalse( $navigator->validate_checklist_slug_param( 'start-writing' ), 'setting a retired slug active must be rejected' );
$this->assertTrue( $navigator->validate_checklist_slug_param( 'design-first' ) );
$this->assertTrue( $navigator->validate_checklist_slug_param( null ), 'clearing the active checklist must stay allowed' );
}
}
Loading