diff --git a/changelog/change-setup-wizard-redirect b/changelog/change-setup-wizard-redirect new file mode 100644 index 0000000000..fdd3fa30a7 --- /dev/null +++ b/changelog/change-setup-wizard-redirect @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Open Setup Wizard when navigating through relevant pages on admin if it didn't open yet diff --git a/includes/admin/class-sensei-setup-wizard.php b/includes/admin/class-sensei-setup-wizard.php index 88e995890b..2be3d0312e 100644 --- a/includes/admin/class-sensei-setup-wizard.php +++ b/includes/admin/class-sensei-setup-wizard.php @@ -93,7 +93,7 @@ public function __construct() { add_action( 'current_screen', [ $this, 'remove_notices_from_setup_wizard' ] ); add_action( 'admin_notices', [ $this, 'setup_wizard_notice' ] ); add_action( 'admin_init', [ $this, 'skip_setup_wizard' ] ); - add_action( 'admin_init', [ $this, 'activation_redirect' ] ); + add_action( 'current_screen', [ $this, 'activation_redirect' ] ); add_action( 'current_screen', [ $this, 'add_setup_wizard_help_tab' ] ); // Maybe prevent WooCommerce help tab. @@ -189,18 +189,20 @@ public function prepare_wizard_page() { public function activation_redirect() { if ( // Check if activation redirect is needed. - ! get_transient( 'sensei_activation_redirect' ) + ! get_option( 'sensei_activation_redirect', false ) // Test whether the context of execution comes from async action scheduler. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Arguments used for comparison. || ( isset( $_REQUEST['action'] ) && 'as_async_request_queue_runner' === $_REQUEST['action'] ) // On these pages, or during these events, postpone the redirect. - || wp_doing_ajax() || wp_doing_cron() || is_network_admin() || ! current_user_can( 'manage_sensei' ) + || wp_doing_ajax() || wp_doing_cron() || is_network_admin() + // Only redirects for admin users. + || ! current_user_can( 'manage_sensei' ) + // Check if it's an admin screen that should redirect. + || ! $this->should_current_page_redirect_to_wizard() ) { return; } - delete_transient( 'sensei_activation_redirect' ); - // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Arguments used for comparison. if ( isset( $_GET['activate-multi'] ) ) { return; @@ -221,6 +223,8 @@ protected function redirect_to_setup_wizard() { * Render app container for setup wizard. */ public function render_wizard_page() { + // Delete option when the Setup Wizard is loaded, so it doesn't redirect anymore. + delete_option( 'sensei_activation_redirect' ); ?>
@@ -234,16 +238,39 @@ public function render_wizard_page() { * * @return boolean */ - private function should_current_page_display_wizard() { - $screen = get_current_screen(); + private function should_current_page_display_wizard_notice() { + // Dashboard, plugins, and Sensei pages, except Sensei Home. + $screens_without_sensei_prefix = [ + 'dashboard', + 'plugins', + 'edit-sensei_message', + 'edit-course', + 'edit-course-category', + 'admin_page_course-order', + 'edit-module', + 'admin_page_module-order', + 'edit-lesson', + 'edit-lesson-tag', + 'admin_page_lesson-order', + 'edit-question', + 'question', + 'edit-question-category', + ]; - if ( false !== strpos( $screen->id, 'sensei-lms_page_sensei' ) ) { - return true; - } + return $this->check_sensei_screen( $screens_without_sensei_prefix ); + } + /** + * Check if current screen is selected to redirect to the wizard. + * + * @return boolean + */ + private function should_current_page_redirect_to_wizard() { + // Dashboard, plugins, and Sensei pages. $screens_without_sensei_prefix = [ 'dashboard', 'plugins', + 'toplevel_page_sensei', 'edit-sensei_message', 'edit-course', 'edit-course-category', @@ -258,7 +285,26 @@ private function should_current_page_display_wizard() { 'edit-question-category', ]; - return in_array( $screen->id, $screens_without_sensei_prefix, true ); + return $this->check_sensei_screen( $screens_without_sensei_prefix ); + } + + /** + * Check if current screen is a Sensei screen. + * The default check verifies if the screen ID contains 'sensei-lms_page_sensei'. + * For more screens to be checked, pass the IDs as an array. + * + * @param array $other_screens Other screens to check. + * + * @return boolean + */ + private function check_sensei_screen( $other_screens = [] ) { + $screen = get_current_screen(); + + if ( false !== strpos( $screen->id, 'sensei-lms_page_sensei' ) ) { + return true; + } + + return in_array( $screen->id, $other_screens, true ); } /** @@ -268,7 +314,7 @@ private function should_current_page_display_wizard() { */ public function setup_wizard_notice() { if ( - ! $this->should_current_page_display_wizard() + ! $this->should_current_page_display_wizard_notice() || ! get_option( self::SUGGEST_SETUP_WIZARD_OPTION, 0 ) || ! current_user_can( 'manage_sensei' ) ) { @@ -446,7 +492,7 @@ public function get_woocommerce_connect_data() { * * @return stdClass Extension with status. */ - private function get_feature_with_status( $extension, $installing_plugins, $selected_plugins ) { + private function get_feature_with_status( $extension, $installing_plugins, $selected_plugins ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- Called by a public deprecated method. _deprecated_function( __METHOD__, '4.8.0' ); $installing_key = array_search( $extension->product_slug, wp_list_pluck( $installing_plugins, 'product_slug' ), true ); @@ -496,7 +542,7 @@ public function get_sensei_extensions( $clear_active_plugins_cache = false ) { } $extensions = array_map( - function( $extension ) use ( $installing_plugins, $selected_plugins ) { + function ( $extension ) use ( $installing_plugins, $selected_plugins ) { // Decode price. if ( isset( $extension->price ) && 0 !== $extension->price ) { $extension->price = html_entity_decode( $extension->price ); @@ -545,7 +591,7 @@ public static function close_wccom_install() { if ( isset( $_SERVER['HTTP_REFERER'] ) && - 0 === strpos( $_SERVER['HTTP_REFERER'], 'https://woocommerce.com/checkout' ) && // phpcs:ignore sanitization ok. + 0 === strpos( $_SERVER['HTTP_REFERER'], 'https://woocommerce.com/checkout' ) && // phpcs:ignore -- sanitization ok. false !== get_transient( $wccom_installing_transient ) ) { delete_transient( $wccom_installing_transient ); diff --git a/includes/class-sensei-data-cleaner.php b/includes/class-sensei-data-cleaner.php index 99a45e32e0..a05e3692b2 100644 --- a/includes/class-sensei-data-cleaner.php +++ b/includes/class-sensei-data-cleaner.php @@ -99,6 +99,7 @@ class Sensei_Data_Cleaner { 'sensei_home_tasks_dismissed', 'sensei_home_tasks_list_is_completed', 'sensei-home-task-pro-upsell', + 'sensei_activation_redirect', ); /** @@ -233,7 +234,7 @@ class Sensei_Data_Cleaner { 'sensei_answers_feedback_[0-9]+_[0-9]+', 'quiz_grades_[0-9]+_[0-9]+', 'sensei_comment_counts_[0-9]+', - 'sensei_activation_redirect', + 'sensei_activation_redirect', // @deprecated $$next-version$$ Changed to an option. 'sensei_woocommerce_plugin_information', 'sensei_extensions_.*', 'sensei_background_job_.*', diff --git a/includes/class-sensei.php b/includes/class-sensei.php index a8e46617bb..d14d95d8b8 100644 --- a/includes/class-sensei.php +++ b/includes/class-sensei.php @@ -1166,7 +1166,7 @@ public function activate_sensei() { // Do not enable the wizard for sites that are created with the onboarding flow. if ( 'sensei' !== get_option( 'site_intent' ) ) { - set_transient( 'sensei_activation_redirect', 1, 30 ); + update_option( 'sensei_activation_redirect', 1 ); update_option( Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 1 ); } else { diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 3c96bd0323..07e443962a 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -81,4 +81,13 @@ **/views/* tests/bootstrap.php + + + + + + + + + diff --git a/tests/unit-tests/admin/test-class-sensei-setup-wizard.php b/tests/unit-tests/admin/test-class-sensei-setup-wizard.php index 08cc20c349..cdc03f6254 100644 --- a/tests/unit-tests/admin/test-class-sensei-setup-wizard.php +++ b/tests/unit-tests/admin/test-class-sensei-setup-wizard.php @@ -12,6 +12,8 @@ * @covers Sensei_Setup_Wizard */ class Sensei_Setup_Wizard_Test extends WP_UnitTestCase { + use Sensei_Test_Redirect_Helpers; + /** * Set up before the class. */ @@ -58,14 +60,16 @@ public function tearDown(): void { /** * Testing the setup wizard class to make sure it is loaded. */ - public function testClassInstance() { + public function testClassInstance_Always_Exists() { + // Assert. $this->assertTrue( class_exists( 'Sensei_Setup_Wizard' ), 'Sensei Setup Wizard class does not exist' ); } /** * Test setup wizard notice in dashboard. */ - public function testSetupWizardNoticeInDashboard() { + public function testSetupWizardNotice_WhenSuggestSetupWizardOptionIsOneAndScreenIsDashboard_DisplaysNotice() { + // Arrange. // Create and login as admin. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -73,19 +77,22 @@ public function testSetupWizardNoticeInDashboard() { set_current_screen( 'dashboard' ); update_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 1 ); + // Act. ob_start(); Sensei()->setup_wizard->setup_wizard_notice(); $html = ob_get_clean(); $pos_setup_button = strpos( $html, 'Run the Setup Wizard' ); + // Assert. $this->assertNotFalse( $pos_setup_button, 'Should return the notice HTML' ); } /** * Test setup wizard notice in screen with Sensei prefix. */ - public function testSetupWizardNoticeInSenseiScreen() { + public function testSetupWizardNotice_WhenSuggestSetupWizardOptionIsOneAndScreenIsSenseiPage_DisplaysNotice() { + // Arrange. // Create and login as admin. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -93,19 +100,22 @@ public function testSetupWizardNoticeInSenseiScreen() { set_current_screen( 'sensei-lms_page_sensei_test' ); update_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 1 ); + // Act. ob_start(); Sensei()->setup_wizard->setup_wizard_notice(); $html = ob_get_clean(); $pos_setup_button = strpos( $html, 'Run the Setup Wizard' ); + // Assert. $this->assertNotFalse( $pos_setup_button, 'Should return the notice HTML' ); } /** * Test setup wizard notice in no Sensei screen. */ - public function testSetupWizardNoticeInOtherScreen() { + public function testSetupWizardNotice_WhenInOtherScreen_DoesNotDisplayNotice() { + // Arrange. // Create and login as admin. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -113,17 +123,20 @@ public function testSetupWizardNoticeInOtherScreen() { set_current_screen( 'other' ); update_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 1 ); + // Act. ob_start(); Sensei()->setup_wizard->setup_wizard_notice(); $html = ob_get_clean(); + // Assert. $this->assertEmpty( $html, 'Should return empty string' ); } /** * Test setup wizard notice with suggest option as 0. */ - public function testSetupWizardNoticeSuggestOptionAsZero() { + public function testSetupWizardNotice_WhenSuggestOptionIsZero_DoesNotDisplayNotice() { + // Arrange. // Create and login as admin. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -131,52 +144,61 @@ public function testSetupWizardNoticeSuggestOptionAsZero() { set_current_screen( 'dashboard' ); update_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 0 ); + // Act. ob_start(); Sensei()->setup_wizard->setup_wizard_notice(); $html = ob_get_clean(); + // Assert. $this->assertEmpty( $html, 'Should return empty string' ); } /** * Test setup wizard notice with suggest option empty. */ - public function testSetupWizardNoticeSuggestOptionEmpty() { + public function testSetupWizardNotice_WhenSuggestOptionIsEmpty_DoesNotDisplayNotice() { + // Arrange. // Create and login as admin. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); set_current_screen( 'dashboard' ); + // Act. ob_start(); Sensei()->setup_wizard->setup_wizard_notice(); $html = ob_get_clean(); + // Assert. $this->assertEmpty( $html, 'Should return empty string' ); } /** * Test setup wizard notice for no admin user. */ - public function testSetupWizardNoticeNoAdmin() { + public function testSetupWizardNotice_WhenUserIsNoAdmin_DoesNotDisplayNotice() { + // Arrange. // Create and login as teacher. $teacher_id = $this->factory->user->create( array( 'role' => 'teacher' ) ); wp_set_current_user( $teacher_id ); set_current_screen( 'dashboard' ); - update_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 0 ); + update_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, 1 ); + // Act. ob_start(); Sensei()->setup_wizard->setup_wizard_notice(); $html = ob_get_clean(); + // Assert. $this->assertEmpty( $html, 'Should return empty string' ); } /** * Test skip setup wizard. */ - public function testSkipSetupWizard() { + public function testSkipSetupWizard_WhenArgumentsAreSet_UpdatesOptionToZero() { + // Arrange. // Create and login as admin. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -184,16 +206,19 @@ public function testSkipSetupWizard() { $_GET['sensei_skip_setup_wizard'] = '1'; $_GET['_wpnonce'] = wp_create_nonce( 'sensei_skip_setup_wizard' ); + // Act. Sensei()->setup_wizard->skip_setup_wizard(); $option_value = get_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, false ); + // Assert. $this->assertEquals( '0', $option_value, 'Should update option to 0' ); } /** * Test skip setup wizard. */ - public function testSkipSetupWizardNoAdmin() { + public function testSkipSetupWizard_WhenUserIsNoAdmin_DoesNotUpdateOption() { + // Arrange. // Create and login as teacher. $teacher_id = $this->factory->user->create( array( 'role' => 'teacher' ) ); wp_set_current_user( $teacher_id ); @@ -201,80 +226,133 @@ public function testSkipSetupWizardNoAdmin() { $_GET['sensei_skip_setup_wizard'] = '1'; $_GET['_wpnonce'] = wp_create_nonce( 'sensei_skip_setup_wizard' ); + // Act. Sensei()->setup_wizard->skip_setup_wizard(); $option_value = get_option( \Sensei_Setup_Wizard::SUGGEST_SETUP_WIZARD_OPTION, false ); + // Assert. $this->assertFalse( $option_value, 'Should not update option' ); } /* * Testing if activation redirect works properly. */ - public function testActivationRedirect() { + public function testActivationRedirect_WhenRedirectOptionIsOne_CallsRedirect() { + // Arrange. // Create and login as administrator. - $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + $expected_redirect = admin_url( 'admin.php?page=sensei_setup_wizard' ); + $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + $this->prevent_wp_redirect(); wp_set_current_user( $admin_id ); + set_current_screen( 'dashboard' ); + + update_option( 'sensei_activation_redirect', 1 ); - set_transient( 'sensei_activation_redirect', 1, 30 ); + // Act. + $redirect_location = ''; + try { + Sensei()->setup_wizard->activation_redirect(); + } catch ( Sensei_WP_Redirect_Exception $e ) { + $redirect_location = $e->getMessage(); + } - $setup_wizard_mock = $this->getMockBuilder( 'Sensei_Setup_Wizard' ) - ->setMethods( [ 'redirect_to_setup_wizard' ] ) - ->getMock(); + // Assert. + $this->assertSame( $expected_redirect, $redirect_location ); + } - $setup_wizard_mock->expects( $this->once() ) - ->method( 'redirect_to_setup_wizard' ); + /* + * Testing if activation doesn't redirect for no Sensei screens. + */ + public function testActivationRedirect_WhenInAPageNotRelatedToSensei_DoesNotCallRedirect() { + // Arrange. + // Create and login as administrator. + $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + $this->prevent_wp_redirect(); + wp_set_current_user( $admin_id ); + set_current_screen( 'any_other' ); - $setup_wizard_mock->activation_redirect(); + update_option( 'sensei_activation_redirect', 1 ); - $this->assertFalse( get_transient( 'sensei_activation_redirect' ), 'Transient should be removed' ); + // Act. + $redirect_location = ''; + try { + Sensei()->setup_wizard->activation_redirect(); + } catch ( Sensei_WP_Redirect_Exception $e ) { + $redirect_location = $e->getMessage(); + } + + // Assert. + $this->assertEmpty( $redirect_location ); } /** * Testing if activation doesn't redirect for no admin user. */ - public function testActivationRedirectNoAdmin() { + public function testActivationRedirect_WhenUserIsNoAdmin_DoesNotCallRedirect() { + // Arrange. // Create and login as subscriber. $subscriber_id = $this->factory->user->create( array( 'role' => 'subscriber' ) ); + $this->prevent_wp_redirect(); wp_set_current_user( $subscriber_id ); - set_transient( 'sensei_activation_redirect', 1, 30 ); - - $setup_wizard_mock = $this->getMockBuilder( 'Sensei_Setup_Wizard' ) - ->setMethods( [ 'redirect_to_setup_wizard' ] ) - ->getMock(); - - $setup_wizard_mock->expects( $this->never() ) - ->method( 'redirect_to_setup_wizard' ); + update_option( 'sensei_activation_redirect', 1 ); - $setup_wizard_mock->activation_redirect(); + // Act. + $redirect_location = ''; + try { + Sensei()->setup_wizard->activation_redirect(); + } catch ( Sensei_WP_Redirect_Exception $e ) { + $redirect_location = $e->getMessage(); + } - $this->assertNotFalse( get_transient( 'sensei_activation_redirect' ), 'Transient should not be removed' ); + // Assert. + $this->assertEmpty( $redirect_location ); } /** - * Testing if activation doesn't redirect when transient is not defined. + * Testing if activation doesn't redirect when option does not exist. */ - public function testActivationRedirectWithoutTransient() { + public function testActivationRedirect_WhenRedirectOptionDoesNotExist_DoesNotCallRedirect() { + // Arrange. // Create and login as administrator. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); + $this->prevent_wp_redirect(); wp_set_current_user( $admin_id ); - $setup_wizard_mock = $this->getMockBuilder( 'Sensei_Setup_Wizard' ) - ->setMethods( [ 'redirect_to_setup_wizard' ] ) - ->getMock(); + // Act. + $redirect_location = ''; + try { + Sensei()->setup_wizard->activation_redirect(); + } catch ( Sensei_WP_Redirect_Exception $e ) { + $redirect_location = $e->getMessage(); + } - $setup_wizard_mock->expects( $this->never() ) - ->method( 'redirect_to_setup_wizard' ); + // Assert. + $this->assertEmpty( $redirect_location ); + } - $setup_wizard_mock->activation_redirect(); + /** + * Testing if redirect option is cleared on setup wizard rendering. + */ + public function testRenderWizardPage_WhenRendered_ClearsRedirectOption() { + // Arrange. + update_option( 'sensei_activation_redirect', 1 ); + + // Act. + Sensei()->setup_wizard->render_wizard_page(); + + // Assert. + $this->assertFalse( get_option( 'sensei_activation_redirect', false ) ); } /** * Test if WooCommerce help tab is being prevented in the Sensei pages. */ - public function testShouldEnableWooCommerceHelpTab() { + public function testWooCommerceHelpTab_WhenOnCoursePage_ShouldNotPreventTab() { + // Arrange. $_GET['post_type'] = 'course'; + // Act & Assert. $this->assertFalse( Sensei()->setup_wizard->should_enable_woocommerce_help_tab( true ), 'Should not allow WooCommerce help tab for course post type' @@ -284,9 +362,11 @@ public function testShouldEnableWooCommerceHelpTab() { /** * Test if WooCommerce help tab is being untouched in no Sensei pages. */ - public function testShouldEnableWooCommerceHelpTabNoSenseiPage() { + public function testWooCommerceHelpTab_WhenOnNoSenseiPage_ShouldNotChangeValue() { + // Arrange. $_GET['post_type'] = 'woocommerce'; + // Act & Assert. $this->assertTrue( Sensei()->setup_wizard->should_enable_woocommerce_help_tab( true ), 'Should not touch WooCommerce help tab for no Sensei pages' @@ -296,7 +376,8 @@ public function testShouldEnableWooCommerceHelpTabNoSenseiPage() { /** * Test add setup wizard help tab to edit course screen. */ - public function testAddSetupWizardHelpTab() { + public function testAddSetupWizardHelpTab_WhenInEditCourse_ShouldAddTab() { + // Arrange. // Create and login as administrator. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -305,16 +386,20 @@ public function testAddSetupWizardHelpTab() { $screen = get_current_screen(); $screen->remove_help_tab( 'sensei_lms_setup_wizard_tab' ); + + // Act. Sensei()->setup_wizard->add_setup_wizard_help_tab( $screen ); $created_tab = $screen->get_help_tab( 'sensei_lms_setup_wizard_tab' ); + // Assert. $this->assertNotNull( $created_tab, 'Should create the setup wizard tab to edit course screens.' ); } /** * Test add setup wizard help tab in non edit course screens. */ - public function testAddSetupWizardHelpTabNonEditCourseScreen() { + public function testAddSetupWizardHelpTab_WhenNotInEditCourse_ShouldNotAddTab() { + // Arrange. // Create and login as administrator. $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) ); wp_set_current_user( $admin_id ); @@ -323,16 +408,20 @@ public function testAddSetupWizardHelpTabNonEditCourseScreen() { $screen = get_current_screen(); $screen->remove_help_tab( 'sensei_lms_setup_wizard_tab' ); + + // Act. Sensei()->setup_wizard->add_setup_wizard_help_tab( $screen ); $created_tab = $screen->get_help_tab( 'sensei_lms_setup_wizard_tab' ); + // Assert. $this->assertNull( $created_tab, 'Should not create the setup wizard tab to non edit course screens.' ); } /** * Test add setup wizard help tab for no admin user. */ - public function testAddSetupWizardHelpTabNoAdmin() { + public function testAddSetupWizardHelpTab_WhenUserIsNoAdmin_ShouldNotAddTab() { + // Arrange. // Create and login as teacher. $teacher_id = $this->factory->user->create( array( 'role' => 'teacher' ) ); wp_set_current_user( $teacher_id ); @@ -341,9 +430,12 @@ public function testAddSetupWizardHelpTabNoAdmin() { $screen = get_current_screen(); $screen->remove_help_tab( 'sensei_lms_setup_wizard_tab' ); + + // Act. Sensei()->setup_wizard->add_setup_wizard_help_tab( $screen ); $created_tab = $screen->get_help_tab( 'sensei_lms_setup_wizard_tab' ); + // Assert. $this->assertNull( $created_tab, 'Should not create the setup wizard tab to no admin user.' ); }