diff --git a/assets/css/dashboard-setup.scss b/assets/css/dashboard-setup.scss new file mode 100644 index 00000000000..cee2344428e --- /dev/null +++ b/assets/css/dashboard-setup.scss @@ -0,0 +1,52 @@ +/** + * dashboard-setup.scss + * Styles for WooCommerce dashboard finish setup widgets + * only loaded on the dashboard itself. + */ + +/** + * Styling begins + */ + +.dashboard-widget-finish-setup { + + .progress-wrapper { + border: 1px solid #757575; + border-radius: 16px; + font-size: 0.9em; + padding: 2px 8px 2px 8px; + display: inline-block; + box-sizing: border-box; + } + + .progress-wrapper span { + position: relative; + top: -3px; + color: #757575; + } + + .description div { + margin-top: 11px; + float: left; + width: 70%; + } + + .description img { + float: right; + width: 30%; + } + + .circle-progress { + margin-top: 1px; + margin-left: -3px; + + circle { + stroke: #f0f0f0; + stroke-width: 1px; + } + + .bar { + stroke: #949494; + } + } +} diff --git a/assets/images/dashboard-widget-setup.png b/assets/images/dashboard-widget-setup.png new file mode 100644 index 00000000000..fcba8f5532a Binary files /dev/null and b/assets/images/dashboard-widget-setup.png differ diff --git a/includes/admin/class-wc-admin-dashboard-setup.php b/includes/admin/class-wc-admin-dashboard-setup.php new file mode 100644 index 00000000000..8fdbc1e94de --- /dev/null +++ b/includes/admin/class-wc-admin-dashboard-setup.php @@ -0,0 +1,211 @@ + array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&path=%2Fsetup-wizard', + ), + 'products' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=products', + ), + 'woocommerce-payments' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&path=%2Fpayments%2Fconnect', + ), + 'payments' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=payments', + ), + 'tax' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=tax', + ), + 'shipping' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=shipping', + ), + 'appearance' => array( + 'completed' => false, + 'button_link' => 'admin.php?page=wc-admin&task=appearance', + ), + ); + + /** + * # of completed tasks. + * + * @var int + */ + private $completed_tasks_count = 0; + + /** + * WC_Admin_Dashboard_Setup constructor. + */ + public function __construct() { + if ( $this->should_display_widget() ) { + $this->populate_general_tasks(); + $this->populate_payment_tasks(); + $this->completed_tasks_count = $this->get_completed_tasks_count(); + add_meta_box( + 'wc_admin_dashboard_setup', + __( 'WooCommerce Setup', 'woocommerce' ), + array( $this, 'render' ), + 'dashboard', + 'normal', + 'high' + ); + } + } + + /** + * Render meta box output. + */ + public function render() { + $version = Constants::get_constant( 'WC_VERSION' ); + wp_enqueue_style( 'wc-dashboard-setup', WC()->plugin_url() . '/assets/css/dashboard-setup.css', array(), $version ); + + $task = $this->get_next_task(); + if ( ! $task ) { + return; + } + + $button_link = $task['button_link']; + $completed_tasks_count = $this->completed_tasks_count; + $tasks_count = count( $this->tasks ); + + // Given 'r' (circle element's r attr), dashoffset = ((100-$desired_percentage)/100) * PI * (r*2). + $progress_percentage = ( $completed_tasks_count / $tasks_count ) * 100; + $circle_r = 6.5; + $circle_dashoffset = ( ( 100 - $progress_percentage ) / 100 ) * ( pi() * ( $circle_r * 2 ) ); + + include __DIR__ . '/views/html-admin-dashboard-setup.php'; + } + + /** + * Populate tasks from the database. + */ + private function populate_general_tasks() { + $tasks = get_option( 'woocommerce_task_list_tracked_completed_tasks', array() ); + foreach ( $tasks as $task ) { + if ( isset( $this->tasks[ $task ] ) ) { + $this->tasks[ $task ]['completed'] = true; + $this->tasks[ $task ]['button_link'] = wc_admin_url( $this->tasks[ $task ]['button_link'] ); + } + } + } + + /** + * Getter for $tasks + * + * @return array + */ + public function get_tasks() { + return $this->tasks; + } + + /** + * Return # of completed tasks + */ + public function get_completed_tasks_count() { + $completed_tasks = array_filter( + $this->tasks, + function( $task ) { + return $task['completed']; + } + ); + + return count( $completed_tasks ); + } + + /** + * Get the next task. + * + * @return array|null + */ + private function get_next_task() { + foreach ( $this->get_tasks() as $task ) { + if ( false === $task['completed'] ) { + return $task; + } + } + + return null; + } + + /** + * Check to see if we should display the widget + * + * @return bool + */ + private function should_display_widget() { + return 'yes' !== get_option( 'woocommerce_task_list_complete' ) && 'yes' !== get_option( 'woocommerce_task_list_hidden' ); + } + + /** + * Populate payment tasks's visibility and completion + */ + private function populate_payment_tasks() { + $is_woo_payment_installed = is_plugin_active( 'woocommerce-payments/woocommerce-payments.php' ); + $country = explode( ':', get_option( 'woocommerce_default_country', '' ) )[0]; + + // woocommerce-payments requires its plugin activated and country must be US. + if ( ! $is_woo_payment_installed || 'US' !== $country ) { + unset( $this->tasks['woocommerce-payments'] ); + } + + // payments can't be used when woocommerce-payments exists and country is US. + if ( $is_woo_payment_installed || 'US' === $country ) { + unset( $this->tasks['payments'] ); + } + + if ( isset( $this->tasks['payments'] ) ) { + $gateways = WC()->payment_gateways->get_available_payment_gateways(); + $enabled_gateways = array_filter( + $gateways, + function ( $gateway ) { + return 'yes' === $gateway->enabled; + } + ); + $this->tasks['payments']['completed'] = ! empty( $enabled_gateways ); + } + + if ( isset( $this->tasks['woocommerce-payments'] ) ) { + $wc_pay_is_connected = false; + if ( class_exists( '\WC_Payments' ) ) { + $wc_payments_gateway = \WC_Payments::get_gateway(); + $wc_pay_is_connected = method_exists( $wc_payments_gateway, 'is_connected' ) + ? $wc_payments_gateway->is_connected() + : false; + } + $this->tasks['woocommerce-payments']['completed'] = $wc_pay_is_connected; + } + } + } + +endif; + +return new WC_Admin_Dashboard_Setup(); diff --git a/includes/admin/class-wc-admin-dashboard.php b/includes/admin/class-wc-admin-dashboard.php index 2bc03a07edb..498eeee78ae 100644 --- a/includes/admin/class-wc-admin-dashboard.php +++ b/includes/admin/class-wc-admin-dashboard.php @@ -24,7 +24,7 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : */ public function __construct() { // Only hook in admin parts if the user has admin access. - if ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ) ) { + if ( $this->should_display_widget() ) { // If on network admin, only load the widget that works in that context and skip the rest. if ( is_multisite() && is_network_admin() ) { add_action( 'wp_network_dashboard_setup', array( $this, 'register_network_order_widget' ) ); @@ -57,6 +57,17 @@ if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : wp_add_dashboard_widget( 'woocommerce_network_orders', __( 'WooCommerce Network Orders', 'woocommerce' ), array( $this, 'network_orders' ) ); } + /** + * Check to see if we should display the widget. + * + * @return bool + */ + private function should_display_widget() { + $has_permission = current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ); + $task_completed_or_hidden = 'yes' === get_option( 'woocommerce_task_list_complete' ) || 'yes' === get_option( 'woocommerce_task_list_hidden' ); + return $task_completed_or_hidden && $has_permission; + } + /** * Get top seller from DB. * diff --git a/includes/admin/class-wc-admin.php b/includes/admin/class-wc-admin.php index e136974d539..0c2acf037f0 100644 --- a/includes/admin/class-wc-admin.php +++ b/includes/admin/class-wc-admin.php @@ -94,6 +94,7 @@ class WC_Admin { switch ( $screen->id ) { case 'dashboard': case 'dashboard-network': + include __DIR__ . '/class-wc-admin-dashboard-setup.php'; include __DIR__ . '/class-wc-admin-dashboard.php'; break; case 'options-permalink': diff --git a/includes/admin/views/html-admin-dashboard-setup.php b/includes/admin/views/html-admin-dashboard-setup.php new file mode 100644 index 00000000000..ddc7b6b85f9 --- /dev/null +++ b/includes/admin/views/html-admin-dashboard-setup.php @@ -0,0 +1,29 @@ + +
+ + + + + + + + +
+
+ +
+
+ +
+
+
diff --git a/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php b/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php new file mode 100644 index 00000000000..82ebf87cc05 --- /dev/null +++ b/tests/php/includes/admin/class-wc-admin-dashboard-setup-test.php @@ -0,0 +1,136 @@ +get_widget()->render(); + return ob_get_clean(); + } + + /** + * Tests widget does not get rendered when woocommerce_task_list_hidden or woocommerce_task_list_hidden + * is true. + * + * @dataProvider should_display_widget_data_provider + * + * @param array $options a set of options. + */ + public function test_widget_does_not_get_rendered( array $options ) { + global $wp_meta_boxes; + + foreach ( $options as $name => $value ) { + update_option( $name, $value ); + } + + $this->get_widget(); + $this->assertNull( $wp_meta_boxes ); + } + + /** + * Given both woocommerce_task_list_hidden and woocommerce_task_list_complete are false + * Then the widget should be added to the $wp_meta_boxes + */ + public function test_widget_gets_rendered_when_both_options_are_false() { + global $wp_meta_boxes; + update_option( 'woocommerce_task_list_complete', false ); + update_option( 'woocommerce_task_list_hidden', false ); + + $this->get_widget(); + $this->assertArrayHasKey( 'wc_admin_dashboard_setup', $wp_meta_boxes['dashboard']['normal']['high'] ); + } + + /** + * Tests the widget output when 0 task has been completed. + */ + public function test_initial_widget_output() { + $html = $this->get_widget_output(); + + $required_strings = array( + 'Step 0 of 5', + 'You're almost there! Once you complete store setup you can start receiving orders.', + 'Start selling', + 'admin.php\?page=wc-admin&path=%2Fsetup-wizard', + ); + + foreach ( $required_strings as $required_string ) { + $this->assertRegexp( "/${required_string}/", $html ); + } + } + + /** + * Tests completed task count as it completes one by one + */ + public function test_widget_renders_completed_task_count() { + $completed_tasks = array(); + $tasks = $this->get_widget()->get_tasks(); + $tasks_count = count( $tasks ); + foreach ( $tasks as $key => $task ) { + array_push( $completed_tasks, $key ); + update_option( 'woocommerce_task_list_tracked_completed_tasks', $completed_tasks ); + $completed_tasks_count = count( $completed_tasks ); + // When all tasks are completed, assert that the widget output is empty. + // As widget won't be rendered when tasks are completed. + if ( $completed_tasks_count === $tasks_count ) { + $this->assertEmpty( $this->get_widget_output() ); + } else { + $this->assertRegexp( "/Step ${completed_tasks_count} of 5/", $this->get_widget_output() ); + } + } + } + + + /** + * Provides dataset that controls output of `should_display_widget` + */ + public function should_display_widget_data_provider() { + return array( + array( + array( + 'woocommerce_task_list_complete' => 'yes', + 'woocommerce_task_list_hidden' => 'no', + ), + ), + array( + array( + 'woocommerce_task_list_complete' => 'no', + 'woocommerce_task_list_hidden' => 'yes', + ), + ), + ); + } +}