diff --git a/plugins/woocommerce-admin/docs/page-controller.md b/plugins/woocommerce-admin/docs/page-controller.md new file mode 100644 index 00000000000..82f6c0580e8 --- /dev/null +++ b/plugins/woocommerce-admin/docs/page-controller.md @@ -0,0 +1,134 @@ +WooCommerce Admin Page Controller +================================= + +Pages rendered with React and pages that include the WooCommmerce Admin header (containing the Activity Panel) need to be registered with the WooCommerce Admin Page Controller. + +This is the API you will use to add your own React-powered page, or to include the WooCommerce Admin header on your plugin pages. + +### Connecting a PHP-powered Page + +To show the WooCommerce Admin header on existing PHP-powered admin pages (most plugin pages), use the `wc_admin_connect_page()` function. + +Connecting pages uses five parameters to `wc_admin_connect_page()`: + +* `id` - Identifies the page with the controller. Required. +* `parent` - Denotes the page as a child of `parent`. Used for breadcrumbs. Optional. +* `screen_id` - Corresponds to [`WC_Admin_Page_Controller::get_current_screen_id()`](../includes/page-controller/class-wc-admin-page-controller.php#L219) to determine the current page. Required. +* `title` - Page title. Used to build breadcrumbs. String or array of breadcrumb pieces. Required. +* `path` - Page path (relative). Used for linking breadcrumb pieces when this page is a `parent`. Optional. + +#### Examples + +```php +// WooCommerce > Settings > General (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-general', + 'title' => array( + __( 'Settings', 'woocommerce-admin' ), + __( 'General', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( 'page', 'wc-settings', 'admin.php' ), + ) +); +``` + +The `WooCommerce > Settings > General` example shows how to set up multiple breadcrumb pieces for a page. When building the breadcrumbs, WooCommerce will attach a link to `path` to the first piece in the `title` array. All subsequent pieces are plain text (not linked). + +```php +// WooCommerce > Settings > Payments. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-payments', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-checkout', + 'title' => __( 'Payments', 'woocommerce-admin' ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'checkout', + ), + 'admin.php' + ), + ) +); + +// WooCommerce > Orders. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-orders', + 'screen_id' => 'edit-shop_order', + 'title' => __( 'Orders', 'woocommerce-admin' ), + 'path' => add_query_arg( 'post_type', 'shop_order', 'edit.php' ), + ) +); +``` + +### Determining Screen ID + +WooCommerce Admin implements it's own version of `get_current_screen()` to allow for more precise identification of admin pages. + +Some screen ID formats that the function will generate are: + +* - `{$current_screen->action}-{$current_screen->action}-tab-section` +* - `{$current_screen->action}-{$current_screen->action}-tab` +* - `{$current_screen->action}-{$current_screen->action}` if no tab is present +* - `{$current_screen->action}` if no action or tab is present + +WooCommerce Admin can recognize WooCommerce pages that have both tabs and sub sections. For example, `woocommerce_page_wc-settings-products-inventory` is the `WooCommerce > Settings > Products > Inventory` page. + +If your plugin adds new pages with tabs and sub sections, use the `wc_admin_pages_with_tabs` and `wc_admin_page_tab_sections` filters to have WooCommerce Admin generate accurate screen IDs for them. + +You can also use the `wc_admin_current_screen_id` filter to make any changes necessary to the behavior. + +### Registering a React-powered Page + +Registering a React-powered page is similar to connecting a PHP page, but with some key differences. Registering pages will automatically create WordPress menu items for them, with the appropriate hierarchy based on `parent`. + +Register pages with `wc_admin_register_page()` using these parameters: + +* `id` - Identifies the page with the controller. Required. +* `parent` - Denotes the page as a child of `parent`. Used for breadcrumbs. Optional. +* `title` - Page title. Used to build breadcrumbs. String or array of breadcrumb pieces. Required. +* `path` - Page path (relative to `#wc-admin`). Used for identifying this page and for linking breadcrumb pieces when this page is a `parent`. Required. +* `capability` - User capability needed to access this page. Optional (defaults to `manage_options`). +* `icon` - Dashicons helper class or base64-encoded SVG. Optional. +* `position` - Menu item position for parent pages. Optional. See: `add_menu_page()`. + +#### Example - Adding a New Analytics Report + +Add our new report using the appropriate filter: + +```javascript +import { addFilter } from '@wordpress/hooks'; + +addFilter( 'woocommerce_admin_reports_list', 'my-namespace', ( reports ) => { + reports.push( { + report: 'example', + title: __( 'Example', 'my-textdomain' ), + component: ExampleReportComponent, + } ); + + return reports; +} ); +``` + +Register the report page with the controller: + +```php +wc_admin_register_page( + array( + 'id' => 'woocommerce-analytics-example', + 'title' => __( 'Example', 'my-textdomain' ), + 'parent' => 'woocommerce-analytics', + 'path' => '/analytics/example', + ) +); +``` + +### Further Reading + +* Check out the [`WC_Admin_Page_Controller`](../includes/page-controller/class-wc-admin-page-controller.php) class. +* See how we're [connecting existing WooCommerce pages](../includes/page-controller/connect-existing-pages.php). +* See how we're [registering Analytics Reports](../includes/features/analytics/class-wc-admin-analytics.php#L75). \ No newline at end of file diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-loader.php b/plugins/woocommerce-admin/includes/class-wc-admin-loader.php index d06f2e83641..447671c1bb1 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-loader.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-loader.php @@ -6,38 +6,14 @@ * @package Woocommerce Admin */ -if ( ! function_exists( 'wc_admin_register_page' ) ) { - /** - * Add a single page to a given parent top-level-item. - * - * @param array $options { - * Array describing the menu item. - * - * @type string $title Menu title - * @type string $parent Parent path or menu ID - * @type string $path Path for this page, full path in app context; ex /analytics/report - * } - */ - function wc_admin_register_page( $options ) { - $defaults = array( - 'parent' => '/analytics', - ); - $options = wp_parse_args( $options, $defaults ); - add_submenu_page( - '/' === $options['parent'][0] ? "wc-admin#{$options['parent']}" : $options['parent'], - $options['title'], - $options['title'], - 'manage_options', - "wc-admin#{$options['path']}", - array( 'WC_Admin_Loader', 'page_wrapper' ) - ); - } -} - /** * WC_Admin_Loader Class. */ class WC_Admin_Loader { + /** + * App entry point. + */ + const APP_ENTRY_POINT = 'wc-admin'; /** * Class instance. @@ -84,7 +60,7 @@ class WC_Admin_Loader { add_action( 'admin_notices', array( 'WC_Admin_Loader', 'inject_after_notices' ), PHP_INT_MAX ); // priority is 20 to run after https://github.com/woocommerce/woocommerce/blob/a55ae325306fc2179149ba9b97e66f32f84fdd9c/includes/admin/class-wc-admin-menus.php#L165. - add_action( 'admin_head', array( 'WC_Admin_Loader', 'update_link_structure' ), 20 ); + add_action( 'admin_head', array( 'WC_Admin_Loader', 'remove_app_entry_page_menu_item' ), 20 ); } /** @@ -145,6 +121,9 @@ class WC_Admin_Loader { * Class loader for enabled WooCommerce Admin features/sections. */ public static function load_features() { + require_once WC_ADMIN_ABSPATH . 'includes/page-controller/class-wc-admin-page-controller.php'; + require_once WC_ADMIN_ABSPATH . 'includes/page-controller/page-controller-functions.php'; + $features = self::get_features(); foreach ( $features as $feature ) { $feature = strtolower( $feature ); @@ -163,28 +142,23 @@ class WC_Admin_Loader { * @todo The entry point for the embed needs moved to this class as well. */ public static function register_page_handler() { - $page_title = null; - $menu_title = null; - - if ( self::is_feature_enabled( 'analytics-dashboard' ) ) { - $page_title = __( 'WooCommerce Dashboard', 'woocommerce-admin' ); - $menu_title = __( 'Dashboard', 'woocommerce-admin' ); - } - - add_submenu_page( - 'woocommerce', - $page_title, - $menu_title, - 'manage_options', - 'wc-admin', - array( 'WC_Admin_Loader', 'page_wrapper' ) + wc_admin_register_page( + array( + 'id' => 'woocommerce-dashboard', // Expected to be overridden if dashboard is enabled. + 'parent' => 'woocommerce', + 'title' => null, + 'path' => self::APP_ENTRY_POINT, + ) ); + + // Connect existing WooCommerce pages. + require_once WC_ADMIN_ABSPATH . 'includes/page-controller/connect-existing-pages.php'; } /** - * Update the WooCommerce menu structure to make our main dashboard/handler the top level link for 'WooCommerce'. + * Remove the menu item for the app entry point page. */ - public static function update_link_structure() { + public static function remove_app_entry_page_menu_item() { global $submenu; // User does not have capabilites to see the submenu. if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu['woocommerce'] ) ) { @@ -193,7 +167,8 @@ class WC_Admin_Loader { $wc_admin_key = null; foreach ( $submenu['woocommerce'] as $submenu_key => $submenu_item ) { - if ( 'wc-admin' === $submenu_item[2] ) { + // Our app entry page menu item has no title. + if ( is_null( $submenu_item[0] ) && self::APP_ENTRY_POINT === $submenu_item[2] ) { $wc_admin_key = $submenu_key; break; } @@ -203,11 +178,7 @@ class WC_Admin_Loader { return; } - $menu = $submenu['woocommerce'][ $wc_admin_key ]; - - // Move menu item to top of array. unset( $submenu['woocommerce'][ $wc_admin_key ] ); - array_unshift( $submenu['woocommerce'], $menu ); } /** @@ -351,11 +322,7 @@ class WC_Admin_Loader { * Returns true if we are on a JS powered admin page. */ public static function is_admin_page() { - $current_screen = get_current_screen(); - if ( '_page_wc-admin' === substr( $current_screen->id, -14 ) ) { - return true; - } - return false; + return wc_admin_is_registered_page(); } /** @@ -364,248 +331,14 @@ class WC_Admin_Loader { * @todo See usage in `admin.php`. This needs refactored and implemented properly in core. */ public static function is_embed_page() { - $is_embed = false; - $screen_id = self::get_current_screen_id(); - if ( ! $screen_id ) { - return false; - } - - $screens = self::get_embed_enabled_screen_ids(); - - if ( in_array( $screen_id, $screens, true ) ) { - $is_embed = true; - } - - return apply_filters( 'woocommerce_page_is_embed_page', $is_embed ); - } - - /** - * Returns the current screen ID. - * This is slightly different from WP's get_current_screen, in that it attaches an action, - * so certain pages like 'add new' pages can have different breadcrumbs or handling. - * It also catches some more unique dynamic pages like taxonomy/attribute management. - * - * Format: {$current_screen->action}-{$current_screen->action}, or just {$current_screen->action} if no action is found - * - * @todo Refactor: https://github.com/woocommerce/woocommerce-admin/issues/1432. - * @return string Current screen ID. - */ - public static function get_current_screen_id() { - $current_screen = get_current_screen(); - if ( ! $current_screen ) { - return false; - } - $current_screen_id = $current_screen->action ? $current_screen->action . '-' . $current_screen->id : $current_screen->id; - - if ( ! empty( $_GET['taxonomy'] ) && ! empty( $_GET['post_type'] ) && 'product' === $_GET['post_type'] ) { - $current_screen_id = 'product_page_product_attributes'; - } - - return $current_screen_id; - } - - /** - * `WC_Admin_Loader::get_embed_enabled_screen_ids`, `WC_Admin_Loader::get_embed_enabled_plugin_screen_ids`, - * `WC_Admin_Loader::get_embed_enabled_screen_ids` should be considered temporary functions for the feature plugin. - * This is separate from WC's screen_id functions so that extensions explictly have to opt-in to the feature plugin. - * - * @todo Refactor: https://github.com/woocommerce/woocommerce-admin/issues/1432. - */ - public static function get_embed_enabled_core_screen_ids() { - $screens = array( - 'edit-shop_order', - 'shop_order', - 'add-shop_order', - 'edit-shop_coupon', - 'shop_coupon', - 'add-shop_coupon', - 'woocommerce_page_wc-reports', - 'woocommerce_page_wc-settings', - 'woocommerce_page_wc-status', - 'woocommerce_page_wc-addons', - 'edit-product', - 'product_page_product_importer', - 'product_page_product_exporter', - 'add-product', - 'product', - 'edit-product_cat', - 'edit-product_tag', - 'product_page_product_attributes', - ); - return apply_filters( 'wc_admin_get_embed_enabled_core_screens_ids', $screens ); - } - - /** - * If any extensions want to show the new header, they can register their screen ids. - * Separate so extensions can register support for the feature plugin separately. - * - * @todo Refactor: https://github.com/woocommerce/woocommerce-admin/issues/1432. - */ - public static function get_embed_enabled_plugin_screen_ids() { - $screens = array(); - return apply_filters( 'wc_admin_get_embed_enabled_plugin_screens_ids', $screens ); - } - - /** - * Returns core and plugin screen IDs for a list of screens the new header should be enabled on. - */ - public static function get_embed_enabled_screen_ids() { - return array_merge( self::get_embed_enabled_core_screen_ids(), self::get_embed_enabled_plugin_screen_ids() ); + return wc_admin_is_connected_page(); } /** * Returns breadcrumbs for the current page. - * - * @todo Refactor: https://github.com/woocommerce/woocommerce-admin/issues/1432. */ private static function get_embed_breadcrumbs() { - $current_screen_id = self::get_current_screen_id(); - - // If a page has a tab, we can append that to the screen ID and show another pagination level. - $pages_with_tabs = array( - 'wc-reports' => 'orders', - 'wc-settings' => 'general', - 'wc-status' => 'status', - ); - $tab = ''; - $get_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : ''; - if ( isset( $_GET['page'] ) ) { - $page = sanitize_text_field( wp_unslash( $_GET['page'] ) ); - if ( in_array( $page, array_keys( $pages_with_tabs ) ) ) { - $tab = ! empty( $get_tab ) ? $get_tab . '-' : $pages_with_tabs[ $page ] . '-'; - } - } - - $breadcrumbs = apply_filters( - 'wc_admin_get_breadcrumbs', - array( - 'edit-shop_order' => __( 'Orders', 'woocommerce-admin' ), - 'add-shop_order' => array( - array( '/edit.php?post_type=shop_order', __( 'Orders', 'woocommerce-admin' ) ), - __( 'Add New', 'woocommerce-admin' ), - ), - 'shop_order' => array( - array( '/edit.php?post_type=shop_order', __( 'Orders', 'woocommerce-admin' ) ), - __( 'Edit Order', 'woocommerce-admin' ), - ), - 'edit-shop_coupon' => __( 'Coupons', 'woocommerce-admin' ), - 'add-shop_coupon' => array( - array( 'edit.php?post_type=shop_coupon', __( 'Coupons', 'woocommerce-admin' ) ), - __( 'Add New', 'woocommerce-admin' ), - ), - 'shop_coupon' => array( - array( 'edit.php?post_type=shop_coupon', __( 'Coupons', 'woocommerce-admin' ) ), - __( 'Edit Coupon', 'woocommerce-admin' ), - ), - 'woocommerce_page_wc-reports' => array( - array( 'admin.php?page=wc-reports', __( 'Reports', 'woocommerce-admin' ) ), - ), - 'orders-woocommerce_page_wc-reports' => array( - array( 'admin.php?page=wc-reports', __( 'Reports', 'woocommerce-admin' ) ), - __( 'Orders', 'woocommerce-admin' ), - ), - 'customers-woocommerce_page_wc-reports' => array( - array( 'admin.php?page=wc-reports', __( 'Reports', 'woocommerce-admin' ) ), - __( 'Customers', 'woocommerce-admin' ), - ), - 'stock-woocommerce_page_wc-reports' => array( - array( 'admin.php?page=wc-reports', __( 'Reports', 'woocommerce-admin' ) ), - __( 'Stock', 'woocommerce-admin' ), - ), - 'taxes-woocommerce_page_wc-reports' => array( - array( 'admin.php?page=wc-reports', __( 'Reports', 'woocommerce-admin' ) ), - __( 'Taxes', 'woocommerce-admin' ), - ), - 'woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - ), - 'general-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'General', 'woocommerce-admin' ), - ), - 'products-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'Products', 'woocommerce-admin' ), - ), - 'tax-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'Tax', 'woocommerce-admin' ), - ), - 'shipping-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'Shipping', 'woocommerce-admin' ), - ), - 'checkout-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'Payments', 'woocommerce-admin' ), - ), - 'email-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'Emails', 'woocommerce-admin' ), - ), - 'advanced-woocommerce_page_wc-settings' => array( - array( 'admin.php?page=wc-settings', __( 'Settings', 'woocommerce-admin' ) ), - __( 'Advanced', 'woocommerce-admin' ), - ), - 'woocommerce_page_wc-status' => array( - __( 'Status', 'woocommerce-admin' ), - ), - 'status-woocommerce_page_wc-status' => array( - array( 'admin.php?page=wc-status', __( 'Status', 'woocommerce-admin' ) ), - __( 'System Status', 'woocommerce-admin' ), - ), - 'tools-woocommerce_page_wc-status' => array( - array( 'admin.php?page=wc-status', __( 'Status', 'woocommerce-admin' ) ), - __( 'Tools', 'woocommerce-admin' ), - ), - 'logs-woocommerce_page_wc-status' => array( - array( 'admin.php?page=wc-status', __( 'Status', 'woocommerce-admin' ) ), - __( 'Logs', 'woocommerce-admin' ), - ), - 'connect-woocommerce_page_wc-status' => array( - array( 'admin.php?page=wc-status', __( 'Status', 'woocommerce-admin' ) ), - __( 'WooCommerce Services Status', 'woocommerce-admin' ), - ), - 'woocommerce_page_wc-addons' => __( 'Extensions', 'woocommerce-admin' ), - 'edit-product' => __( 'Products', 'woocommerce-admin' ), - 'product_page_product_importer' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Import', 'woocommerce-admin' ), - ), - 'product_page_product_exporter' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Export', 'woocommerce-admin' ), - ), - 'add-product' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Add New', 'woocommerce-admin' ), - ), - 'product' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Edit Product', 'woocommerce-admin' ), - ), - 'edit-product_cat' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Categories', 'woocommerce-admin' ), - ), - 'edit-product_tag' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Tags', 'woocommerce-admin' ), - ), - 'product_page_product_attributes' => array( - array( 'edit.php?post_type=product', __( 'Products', 'woocommerce-admin' ) ), - __( 'Attributes', 'woocommerce-admin' ), - ), - ) - ); - - if ( ! empty( $breadcrumbs[ $tab . $current_screen_id ] ) ) { - return $breadcrumbs[ $tab . $current_screen_id ]; - } elseif ( ! empty( $breadcrumbs[ $current_screen_id ] ) ) { - return $breadcrumbs[ $current_screen_id ]; - } else { - return ''; - } + return wc_admin_get_breadcrumbs(); } /** @@ -617,9 +350,7 @@ class WC_Admin_Loader { ?> - - - + @@ -643,7 +374,9 @@ class WC_Admin_Loader {

- WooCommerce + + + @@ -731,22 +464,18 @@ class WC_Admin_Loader { return $admin_title; } - if ( self::is_embed_page() ) { - $sections = self::get_embed_breadcrumbs(); - $sections = is_array( $sections ) ? $sections : array( $sections ); - $pieces = array(); + $sections = self::get_embed_breadcrumbs(); + $pieces = array(); - foreach ( $sections as $section ) { - $pieces[] = is_array( $section ) ? $section[1] : $section; - } - - $pieces = array_reverse( $pieces ); - $title = implode( ' ‹ ', $pieces ); - } else { - $title = __( 'Dashboard', 'woocommerce-admin' ); + foreach ( $sections as $section ) { + $pieces[] = is_array( $section ) ? $section[1] : $section; } + + $pieces = array_reverse( $pieces ); + $title = implode( ' ‹ ', $pieces ); + /* translators: %1$s: updated title, %2$s: blog info name */ - return sprintf( __( '%1$s ‹ %2$s — WooCommerce', 'woocommerce-admin' ), $title, get_bloginfo( 'name' ) ); + return sprintf( __( '%1$s ‹ %2$s', 'woocommerce-admin' ), $title, get_bloginfo( 'name' ) ); } /** diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php index caa97c42543..36645d7e3b0 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-data-store.php @@ -337,24 +337,29 @@ class WC_Admin_Reports_Orders_Data_Store extends WC_Admin_Reports_Data_Store imp } /** - * Get customer data from order IDs. + * Get customer data from Order data. * - * @param array $orders Array of orders. + * @param array $orders Array of orders data. * @return array */ protected function get_customers_by_orders( $orders ) { global $wpdb; - $customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup'; - $customer_ids = array(); + $customer_lookup_table = $wpdb->prefix . 'wc_customer_lookup'; + $customer_ids = array(); + foreach ( $orders as $order ) { if ( $order['customer_id'] ) { - $customer_ids[] = $order['customer_id']; + $customer_ids[] = intval( $order['customer_id'] ); } } - $customer_ids = implode( ',', $customer_ids ); - $customers = $wpdb->get_results( + if ( empty( $customer_ids ) ) { + return array(); + } + + $customer_ids = implode( ',', $customer_ids ); + $customers = $wpdb->get_results( "SELECT * FROM {$customer_lookup_table} WHERE customer_id IN ({$customer_ids})", ARRAY_A ); // WPCS: cache ok, DB call ok, unprepared SQL ok. diff --git a/plugins/woocommerce-admin/includes/features/analytics-dashboard/class-wc-admin-analytics-dashboard.php b/plugins/woocommerce-admin/includes/features/analytics-dashboard/class-wc-admin-analytics-dashboard.php index 518f01d654f..fcda49a22de 100644 --- a/plugins/woocommerce-admin/includes/features/analytics-dashboard/class-wc-admin-analytics-dashboard.php +++ b/plugins/woocommerce-admin/includes/features/analytics-dashboard/class-wc-admin-analytics-dashboard.php @@ -10,6 +10,11 @@ * Contains backend logic for the dashboard feature. */ class WC_Admin_Analytics_Dashboard { + /** + * Menu slug. + */ + const MENU_SLUG = 'wc-admin'; + /** * Class instance. * @@ -33,6 +38,9 @@ class WC_Admin_Analytics_Dashboard { public function __construct() { add_filter( 'woocommerce_component_settings_preload_endpoints', array( $this, 'add_preload_endpoints' ) ); add_filter( 'wc_admin_get_user_data_fields', array( $this, 'add_user_data_fields' ) ); + add_action( 'admin_menu', array( $this, 'register_page' ) ); + // priority is 20 to run after https://github.com/woocommerce/woocommerce/blob/a55ae325306fc2179149ba9b97e66f32f84fdd9c/includes/admin/class-wc-admin-menus.php#L165. + add_action( 'admin_head', array( $this, 'update_link_structure' ), 20 ); } /** @@ -64,6 +72,50 @@ class WC_Admin_Analytics_Dashboard { ) ); } + + /** + * Registers dashboard page. + */ + public function register_page() { + wc_admin_register_page( + array( + 'id' => 'woocommerce-dashboard', + 'title' => __( 'Dashboard', 'woocommerce-admin' ), + 'parent' => 'woocommerce', + 'path' => self::MENU_SLUG, + ) + ); + } + + /** + * Update the WooCommerce menu structure to make our main dashboard/handler + * the top level link for 'WooCommerce'. + */ + public function update_link_structure() { + global $submenu; + // User does not have capabilites to see the submenu. + if ( ! current_user_can( 'manage_woocommerce' ) || empty( $submenu['woocommerce'] ) ) { + return; + } + + $wc_admin_key = null; + foreach ( $submenu['woocommerce'] as $submenu_key => $submenu_item ) { + if ( self::MENU_SLUG === $submenu_item[2] ) { + $wc_admin_key = $submenu_key; + break; + } + } + + if ( ! $wc_admin_key ) { + return; + } + + $menu = $submenu['woocommerce'][ $wc_admin_key ]; + + // Move menu item to top of array. + unset( $submenu['woocommerce'][ $wc_admin_key ] ); + array_unshift( $submenu['woocommerce'], $menu ); + } } new WC_Admin_Analytics_Dashboard(); diff --git a/plugins/woocommerce-admin/includes/features/analytics/class-wc-admin-analytics.php b/plugins/woocommerce-admin/includes/features/analytics/class-wc-admin-analytics.php index 8baac3660c7..62fe08f28ac 100644 --- a/plugins/woocommerce-admin/includes/features/analytics/class-wc-admin-analytics.php +++ b/plugins/woocommerce-admin/includes/features/analytics/class-wc-admin-analytics.php @@ -73,65 +73,72 @@ class WC_Admin_Analytics { * Registers report pages. */ public function register_pages() { - add_menu_page( - __( 'WooCommerce Analytics', 'woocommerce-admin' ), - __( 'Analytics', 'woocommerce-admin' ), - 'manage_options', - 'wc-admin#/analytics/revenue', - array( 'WC_Admin_Loader', 'page_wrapper' ), - 'dashicons-chart-bar', - 56 // After WooCommerce & Product menu items. - ); - $report_pages = array( array( + 'id' => 'woocommerce-analytics', + 'title' => __( 'Analytics', 'woocommerce-admin' ), + 'path' => '/analytics/revenue', + 'icon' => 'dashicons-chart-bar', + 'position' => 56, // After WooCommerce & Product menu items. + ), + array( + 'id' => 'woocommerce-analytics-revenue', 'title' => __( 'Revenue', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/revenue', ), array( + 'id' => 'woocommerce-analytics-orders', 'title' => __( 'Orders', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/orders', ), array( + 'id' => 'woocommerce-analytics-products', 'title' => __( 'Products', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/products', ), array( + 'id' => 'woocommerce-analytics-categories', 'title' => __( 'Categories', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/categories', ), array( + 'id' => 'woocommerce-analytics-coupons', 'title' => __( 'Coupons', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/coupons', ), array( + 'id' => 'woocommerce-analytics-taxes', 'title' => __( 'Taxes', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/taxes', ), array( + 'id' => 'woocommerce-analytics-downloads', 'title' => __( 'Downloads', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/downloads', ), 'yes' === get_option( 'woocommerce_manage_stock' ) ? array( + 'id' => 'woocommerce-analytics-stock', 'title' => __( 'Stock', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/stock', ) : null, array( + 'id' => 'woocommerce-analytics-customers', 'title' => __( 'Customers', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/customers', ), array( + 'id' => 'woocommerce-analytics-settings', 'title' => __( 'Settings', 'woocommerce-admin' ), - 'parent' => '/analytics/revenue', + 'parent' => 'woocommerce-analytics', 'path' => '/analytics/settings', ), ); diff --git a/plugins/woocommerce-admin/includes/notes/class-wc-admin-notes-order-milestones.php b/plugins/woocommerce-admin/includes/notes/class-wc-admin-notes-order-milestones.php index 2c804138c22..a798b5360c4 100644 --- a/plugins/woocommerce-admin/includes/notes/class-wc-admin-notes-order-milestones.php +++ b/plugins/woocommerce-admin/includes/notes/class-wc-admin-notes-order-milestones.php @@ -13,16 +13,6 @@ defined( 'ABSPATH' ) || exit; * WC_Admin_Notes_Order_Milestones */ class WC_Admin_Notes_Order_Milestones { - /** - * Name of the "first order" note. - */ - const FIRST_ORDER_NOTE_NAME = 'wc-admin-first-order'; - - /** - * Name of the "ten orders" note. - */ - const TEN_ORDERS_NOTE_NAME = 'wc-admin-ten-orders'; - /** * Name of the "other milestones" note. */ @@ -62,6 +52,8 @@ class WC_Admin_Notes_Order_Milestones { * @var array */ protected $milestones = array( + 1, + 10, 100, 250, 500, @@ -101,10 +93,6 @@ class WC_Admin_Notes_Order_Milestones { add_action( 'wc_admin_installed', array( $this, 'backfill_last_milestone' ) ); - if ( $this->get_orders_count() <= 10 ) { - add_action( 'woocommerce_new_order', array( $this, 'first_two_milestones' ) ); - } - add_action( self::PROCESS_ORDERS_MILESTONE_HOOK, array( $this, 'other_milestones' ) ); } @@ -130,59 +118,6 @@ class WC_Admin_Notes_Order_Milestones { return $this->orders_count; } - /** - * Add a milestone notes for the store's first order and first 10 orders. - * - * @param int $order_id WC_Order ID. - */ - public function first_two_milestones( $order_id ) { - $order = wc_get_order( $order_id ); - - // Make sure this is the first pending/processing/completed order. - if ( ! in_array( $order->get_status(), $this->allowed_statuses ) ) { - return; - } - - // Retrieve the orders count, forcing a cache refresh. - $orders_count = $this->get_orders_count( true ); - - if ( 1 === $orders_count ) { - // Add the first order note. - $note = new WC_Admin_Note(); - $note->set_title( __( 'First order', 'woocommerce-admin' ) ); - $note->set_content( - __( 'Congratulations on getting your first order from a customer! Learn how to manage your orders.', 'woocommerce-admin' ) - ); - $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); - $note->set_icon( 'trophy' ); - $note->set_name( self::FIRST_ORDER_NOTE_NAME ); - $note->set_source( 'woocommerce-admin' ); - $note->add_action( 'learn-more', __( 'Learn more', 'woocommerce-admin' ), 'https://docs.woocommerce.com/document/managing-orders/' ); - $note->save(); - } - - if ( 10 === $orders_count ) { - // Add the first ten orders note. - $note = new WC_Admin_Note(); - $note->set_title( - sprintf( - /* translators: Number of orders processed. */ - __( 'Congratulations on processing %s orders!', 'woocommerce-admin' ), - wc_format_decimal( 10 ) - ) - ); - $note->set_content( - __( "You've hit the 10 orders milestone! Look at you go. Browse some WooCommerce success stories for inspiration.", 'woocommerce-admin' ) - ); - $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); - $note->set_icon( 'trophy' ); - $note->set_name( self::TEN_ORDERS_NOTE_NAME ); - $note->set_source( 'woocommerce-admin' ); - $note->add_action( 'browse', __( 'Browse', 'woocommerce-admin' ), 'https://woocommerce.com/success-stories/' ); - $note->save(); - } - } - /** * Backfill the store's current milestone. * @@ -230,6 +165,103 @@ class WC_Admin_Notes_Order_Milestones { return $milestone_reached; } + /** + * Get the appropriate note title for a given milestone. + * + * @param int $milestone Order milestone. + * @return string Note title for the milestone. + */ + public function get_note_title_for_milestone( $milestone ) { + switch ( $milestone ) { + case 1: + return __( 'First order', 'woocommerce-admin' ); + case 10: + case 100: + case 250: + case 500: + case 1000: + case 5000: + case 10000: + case 500000: + case 1000000: + return sprintf( + /* translators: Number of orders processed. */ + __( 'Congratulations on processing %s orders!', 'woocommerce-admin' ), + wc_format_decimal( $milestone ) + ); + default: + return ''; + } + } + + /** + * Get the appropriate note content for a given milestone. + * + * @param int $milestone Order milestone. + * @return string Note content for the milestone. + */ + public function get_note_content_for_milestone( $milestone ) { + switch ( $milestone ) { + case 1: + return __( 'Congratulations on getting your first order from a customer! Learn how to manage your orders.', 'woocommerce-admin' ); + case 10: + return __( "You've hit the 10 orders milestone! Look at you go. Browse some WooCommerce success stories for inspiration.", 'woocommerce-admin' ); + case 100: + case 250: + case 500: + case 1000: + case 5000: + case 10000: + case 500000: + case 1000000: + return __( 'Another order milestone! Take a look at your Orders Report to review your orders to date.', 'woocommerce-admin' ); + default: + return ''; + } + } + + /** + * Get the appropriate note action for a given milestone. + * + * @param int $milestone Order milestone. + * @return array Note actoion (name, label, query) for the milestone. + */ + public function get_note_action_for_milestone( $milestone ) { + switch ( $milestone ) { + case 1: + return array( + 'name' => 'learn-more', + 'label' => __( 'Learn more', 'woocommerce-admin' ), + 'query' => 'https://docs.woocommerce.com/document/managing-orders/', + ); + case 10: + return array( + 'name' => 'browse', + 'label' => __( 'Browse', 'woocommerce-admin' ), + 'query' => 'https://woocommerce.com/success-stories/', + ); + case 100: + case 250: + case 500: + case 1000: + case 5000: + case 10000: + case 500000: + case 1000000: + return array( + 'name' => 'review-orders', + 'label' => __( 'Review your orders', 'woocommerce-admin' ), + 'query' => '?page=wc-admin#/analytics/orders', + ); + default: + return array( + 'name' => '', + 'label' => '', + 'query' => '', + ); + } + } + /** * Add milestone notes for other significant thresholds. */ @@ -248,21 +280,14 @@ class WC_Admin_Notes_Order_Milestones { // Add the milestone note. $note = new WC_Admin_Note(); - $note->set_title( - sprintf( - /* translators: Number of orders processed. */ - __( 'Congratulations on processing %s orders!', 'woocommerce-admin' ), - wc_format_decimal( $current_milestone ) - ) - ); - $note->set_content( - __( 'Another order milestone! Take a look at your Orders Report to review your orders to date.', 'woocommerce-admin' ) - ); + $note->set_title( $this->get_note_title_for_milestone( $current_milestone ) ); + $note->set_content( $this->get_note_content_for_milestone( $current_milestone ) ); + $note_action = $this->get_note_action_for_milestone( $current_milestone ); + $note->add_action( $note_action['name'], $note_action['label'], $note_action['query'] ); $note->set_type( WC_Admin_Note::E_WC_ADMIN_NOTE_INFORMATIONAL ); $note->set_icon( 'trophy' ); $note->set_name( self::ORDERS_MILESTONE_NOTE_NAME ); $note->set_source( 'woocommerce-admin' ); - $note->add_action( 'review-orders', __( 'Review your orders', 'woocommerce-admin' ), '?page=wc-admin#/analytics/orders' ); $note->save(); } } diff --git a/plugins/woocommerce-admin/includes/page-controller/class-wc-admin-page-controller.php b/plugins/woocommerce-admin/includes/page-controller/class-wc-admin-page-controller.php new file mode 100644 index 00000000000..f197e7c5269 --- /dev/null +++ b/plugins/woocommerce-admin/includes/page-controller/class-wc-admin-page-controller.php @@ -0,0 +1,450 @@ +pages[ $options['id'] ] = $options; + } + + /** + * Determine the current page ID, if it was registered with this controller. + */ + public function determine_current_page() { + $current_url = ''; + $current_screen_id = $this->get_current_screen_id(); + + if ( isset( $_SERVER['REQUEST_URI'] ) ) { + $current_url = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); + } + + $current_path = wp_parse_url( $current_url, PHP_URL_PATH ); + $current_query = wp_parse_url( $current_url, PHP_URL_QUERY ); + $current_fragment = wp_parse_url( $current_url, PHP_URL_FRAGMENT ); + + foreach ( $this->pages as $page ) { + if ( isset( $page['js_page'] ) && $page['js_page'] ) { + // Check registered admin pages. + $full_page_path = add_query_arg( 'page', $page['path'], admin_url( 'admin.php' ) ); + $page_path = wp_parse_url( $full_page_path, PHP_URL_PATH ); + $page_query = wp_parse_url( $full_page_path, PHP_URL_QUERY ); + $page_fragment = wp_parse_url( $full_page_path, PHP_URL_FRAGMENT ); + + if ( + $page_path === $current_path && + $page_query === $current_query && + $page_fragment === $current_fragment + ) { + $this->current_page = $page; + return; + } + } else { + // Check connected admin pages. + if ( + isset( $page['screen_id'] ) && + $page['screen_id'] === $current_screen_id + ) { + $this->current_page = $page; + return; + } + } + } + + $this->current_page = false; + } + + + /** + * Get breadcrumbs for WooCommerce Admin Page navigation. + * + * @return array Navigation pieces (breadcrumbs). + */ + public function get_breadcrumbs() { + $current_page = $this->get_current_page(); + + // Bail if this isn't a page registered with this controller. + if ( false === $current_page ) { + // Filter documentation below. + return apply_filters( 'wc_admin_get_breadcrumbs', array( '' ), $current_page ); + } + + if ( 1 === count( $current_page['title'] ) ) { + $breadcrumbs = $current_page['title']; + } else { + // If this page has multiple title pieces, only link the first one. + $breadcrumbs = array_merge( + array( + array( $current_page['path'], reset( $current_page['title'] ) ), + ), + array_slice( $current_page['title'], 1 ) + ); + } + + if ( isset( $current_page['parent'] ) ) { + $parent_id = $current_page['parent']; + + while ( $parent_id ) { + if ( isset( $this->pages[ $parent_id ] ) ) { + $parent = $this->pages[ $parent_id ]; + array_unshift( $breadcrumbs, array( $parent['path'], reset( $parent['title'] ) ) ); + $parent_id = isset( $parent['parent'] ) ? $parent['parent'] : false; + } else { + $parent_id = false; + } + } + } + + /** + * The navigation breadcrumbs for the current page. + * + * @param array $breadcrumbs Navigation pieces (breadcrumbs). + * @param array|boolean $current_page The connected page data or false if not identified. + */ + return apply_filters( 'wc_admin_get_breadcrumbs', $breadcrumbs, $current_page ); + } + + /** + * Get the current page. + * + * @return array|boolean Current page or false if not registered with this controller. + */ + public function get_current_page() { + if ( is_null( $this->current_page ) ) { + $this->determine_current_page(); + } + + return $this->current_page; + } + + + /** + * Returns the current screen ID. + * + * This is slightly different from WP's get_current_screen, in that it attaches an action, + * so certain pages like 'add new' pages can have different breadcrumbs or handling. + * It also catches some more unique dynamic pages like taxonomy/attribute management. + * + * Format: + * - {$current_screen->action}-{$current_screen->action}-tab-section + * - {$current_screen->action}-{$current_screen->action}-tab + * - {$current_screen->action}-{$current_screen->action} if no tab is present + * - {$current_screen->action} if no action or tab is present + * + * @return string Current screen ID. + */ + public function get_current_screen_id() { + $current_screen = get_current_screen(); + if ( ! $current_screen ) { + // Filter documentation below. + return apply_filters( 'wc_admin_current_screen_id', false, $current_screen ); + } + + $screen_pieces = array( $current_screen->id ); + + if ( $current_screen->action ) { + $screen_pieces[] = $current_screen->action; + } + + if ( + ! empty( $current_screen->taxonomy ) && + isset( $current_screen->post_type ) && + 'product' === $current_screen->post_type + ) { + // Editing a product attribute. + if ( 0 === strpos( $current_screen->taxonomy, 'pa_' ) ) { + $screen_pieces = array( 'product_page_product_attribute-edit' ); + } + + // Editing a product taxonomy term. + if ( ! empty( $_GET['tag_ID'] ) ) { + $screen_pieces = array( $current_screen->taxonomy ); + } + } + + // Pages with default tab values. + $pages_with_tabs = apply_filters( + 'wc_admin_pages_with_tabs', + array( + 'wc-reports' => 'orders', + 'wc-settings' => 'general', + 'wc-status' => 'status', + 'wc-addons' => 'browse-extensions', + ) + ); + + // Tabs that have sections as well. + $wc_emails = WC_Emails::instance(); + $wc_email_ids = array_map( 'sanitize_title', array_keys( $wc_emails->get_emails() ) ); + + $tabs_with_sections = apply_filters( + 'wc_admin_page_tab_sections', + array( + 'products' => array( '', 'inventory', 'downloadable' ), + 'shipping' => array( '', 'options', 'classes' ), + 'checkout' => array( 'bacs', 'cheque', 'cod', 'paypal' ), + 'email' => $wc_email_ids, + 'advanced' => array( + '', + 'keys', + 'webhooks', + 'legacy_api', + 'woocommerce_com', + ), + 'browse-extensions' => array( 'helper' ), + ) + ); + + if ( ! empty( $_GET['page'] ) ) { + if ( in_array( $_GET['page'], array_keys( $pages_with_tabs ) ) ) { // WPCS: sanitization ok. + if ( ! empty( $_GET['tab'] ) ) { + $tab = wc_clean( wp_unslash( $_GET['tab'] ) ); + } else { + $tab = $pages_with_tabs[ $_GET['page'] ]; // WPCS: sanitization ok. + } + + $screen_pieces[] = $tab; + + if ( ! empty( $_GET['section'] ) ) { + if ( + isset( $tabs_with_sections[ $tab ] ) && + in_array( $_GET['section'], array_keys( $tabs_with_sections[ $tab ] ) ) // WPCS: sanitization ok. + ) { + $screen_pieces[] = wc_clean( wp_unslash( $_GET['section'] ) ); + } + } + + // Editing a shipping zone. + if ( ( 'shipping' === $tab ) && isset( $_GET['zone_id'] ) ) { + $screen_pieces[] = 'edit_zone'; + } + } + } + + /** + * The current screen id. + * + * Used for identifying pages to render the WooCommerce Admin header. + * + * @param string|boolean $screen_id The screen id or false if not identified. + * @param WP_Screen $current_screen The current WP_Screen. + */ + return apply_filters( 'wc_admin_current_screen_id', implode( '-', $screen_pieces ), $current_screen ); + } + + /** + * Returns the path from an ID. + * + * @param string $id ID to get path for. + * @return string Path for the given ID, or the ID on lookup miss. + */ + public function get_path_from_id( $id ) { + if ( isset( $this->pages[ $id ] ) && isset( $this->pages[ $id ]['path'] ) ) { + return $this->pages[ $id ]['path']; + } + return $id; + } + + /** + * Returns true if we are on a page connected to this controller. + * + * @return boolean + */ + public function is_connected_page() { + $current_page = $this->get_current_page(); + + if ( false === $current_page ) { + $is_connected_page = false; + } else { + $is_connected_page = isset( $current_page['js_page'] ) ? ! $current_page['js_page'] : true; + } + + /** + * Whether or not the current page is an existing page connected to this controller. + * + * Used to determine if the WooCommerce Admin header should be rendered. + * + * @param boolean $is_connected_page True if the current page is connected. + * @param array|boolean $current_page The connected page data or false if not identified. + */ + return apply_filters( 'woocommerce_page_is_connected_page', $is_connected_page, $current_page ); + } + + /** + * Returns true if we are on a page registed with this controller. + * + * @return boolean + */ + public function is_registered_page() { + $current_page = $this->get_current_page(); + + if ( false === $current_page ) { + $is_registered_page = false; + } else { + $is_registered_page = isset( $current_page['js_page'] ) && $current_page['js_page']; + } + + /** + * Whether or not the current page was registered with this controller. + * + * Used to determine if this is a JS-powered WooCommerce Admin page. + * + * @param boolean $is_registered_page True if the current page was registered with this controller. + * @param array|boolean $current_page The registered page data or false if not identified. + */ + return apply_filters( 'woocommerce_page_is_registered_page', $is_registered_page, $current_page ); + } + + /** + * Adds a JS powered page to wc-admin. + * + * @param array $options { + * Array describing the page. + * + * @type string id Id to reference the page. + * @type string title Page title. Used in menus and breadcrumbs. + * @type string|null parent Parent ID. Null for new top level page. + * @type string path Path for this page, full path in app context; ex /analytics/report + * @type string capability Capability needed to access the page. + * @type string icon Icon. Dashicons helper class, base64-encoded SVG, or 'none'. + * @type int position Menu item position. + * } + */ + public function register_page( $options ) { + $defaults = array( + 'id' => null, + 'parent' => null, + 'title' => '', + 'capability' => 'manage_options', + 'path' => '', + 'icon' => '', + 'position' => null, + 'js_page' => true, + ); + + $options = wp_parse_args( $options, $defaults ); + + if ( 0 !== strpos( $options['path'], self::PAGE_ROOT ) ) { + $options['path'] = self::PAGE_ROOT . '#' . $options['path']; + } + + if ( is_null( $options['parent'] ) ) { + add_menu_page( + $options['title'], + $options['title'], + $options['capability'], + $options['path'], + array( __CLASS__, 'page_wrapper' ), + $options['icon'], + $options['position'] + ); + } else { + $parent_path = $this->get_path_from_id( $options['parent'] ); + // @todo check for null path. + add_submenu_page( + $parent_path, + $options['title'], + $options['title'], + $options['capability'], + $options['path'], + array( __CLASS__, 'page_wrapper' ) + ); + } + + $this->connect_page( $options ); + } + + /** + * Set up a div for the app to render into. + */ + public static function page_wrapper() { + ?> +
+
+
+ Settings > General (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-general', + 'title' => array( + __( 'Settings', 'woocommerce-admin' ), + __( 'General', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( 'page', 'wc-settings', $admin_page_base ), + ) +); + +// WooCommerce > Settings > Products > General (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-products', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-products', + 'title' => array( + __( 'Products', 'woocommerce-admin' ), + __( 'General', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'products', + ), + $admin_page_base + ), + ) +); + +// WooCommerce > Settings > Products > Inventory. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-products-inventory', + 'parent' => 'woocommerce-settings-products', + 'screen_id' => 'woocommerce_page_wc-settings-products-inventory', + 'title' => __( 'Inventory', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Products > Downloadable products. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-products-downloadable', + 'parent' => 'woocommerce-settings-products', + 'screen_id' => 'woocommerce_page_wc-settings-products-downloadable', + 'title' => __( 'Downloadable products', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Shipping > Shipping zones (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-shipping', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-shipping', + 'title' => array( + __( 'Shipping', 'woocommerce-admin' ), + __( 'Shipping zones', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'shipping', + ), + $admin_page_base + ), + ) +); + +// WooCommerce > Settings > Shipping > Shipping zones > Edit zone. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-edit-shipping-zone', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-shipping-edit_zone', + 'title' => array( + __( 'Shipping zones', 'woocommerce-admin' ), + __( 'Edit zone', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'shipping', + ), + $admin_page_base + ), + ) +); + +// WooCommerce > Settings > Shipping > Shipping options. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-shipping-options', + 'parent' => 'woocommerce-settings-shipping', + 'screen_id' => 'woocommerce_page_wc-settings-shipping-options', + 'title' => __( 'Shipping options', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Shipping > Shipping classes. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-shipping-classes', + 'parent' => 'woocommerce-settings-shipping', + 'screen_id' => 'woocommerce_page_wc-settings-shipping-classes', + 'title' => __( 'Shipping classes', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Payments. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-payments', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-checkout', + 'title' => __( 'Payments', 'woocommerce-admin' ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'checkout', + ), + $admin_page_base + ), + ) +); + +// WooCommerce > Settings > Payments > Direct bank transfer. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-payments-bacs', + 'parent' => 'woocommerce-settings-payments', + 'screen_id' => 'woocommerce_page_wc-settings-checkout-bacs', + 'title' => __( 'Direct bank transfer', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Payments > Check payments. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-payments-cheque', + 'parent' => 'woocommerce-settings-payments', + 'screen_id' => 'woocommerce_page_wc-settings-checkout-cheque', + 'title' => __( 'Check payments', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Payments > Cash on delivery. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-payments-cod', + 'parent' => 'woocommerce-settings-payments', + 'screen_id' => 'woocommerce_page_wc-settings-checkout-cod', + 'title' => __( 'Cash on delivery', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Payments > PayPal. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-payments-paypal', + 'parent' => 'woocommerce-settings-payments', + 'screen_id' => 'woocommerce_page_wc-settings-checkout-paypal', + 'title' => __( 'PayPal', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Accounts & Privacy. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-accounts-privacy', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-account', + 'title' => __( 'Accounts & Privacy', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Emails. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-email', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-email', + 'title' => __( 'Emails', 'woocommerce-admin' ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'email', + ), + $admin_page_base + ), + ) +); + +// WooCommerce > Settings > Emails > Edit email (all email editing). +$wc_emails = WC_Emails::instance(); +$wc_email_ids = array_map( 'sanitize_title', array_keys( $wc_emails->get_emails() ) ); + +foreach ( $wc_email_ids as $email_id ) { + wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-email-' . $email_id, + 'parent' => 'woocommerce-settings-email', + 'screen_id' => 'woocommerce_page_wc-settings-email-' . $email_id, + 'title' => __( 'Edit email', 'woocommerce-admin' ), + ) + ); +} + +// WooCommerce > Settings > Advanced > Page setup (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-advanced', + 'parent' => 'woocommerce-settings', + 'screen_id' => 'woocommerce_page_wc-settings-advanced', + 'title' => array( + __( 'Advanced', 'woocommerce-admin' ), + __( 'Page setup', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( + array( + 'page' => 'wc-settings', + 'tab' => 'advanced', + ), + $admin_page_base + ), + ) +); + +// WooCommerce > Settings > Advanced > REST API. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-advanced-rest-api', + 'parent' => 'woocommerce-settings-advanced', + 'screen_id' => 'woocommerce_page_wc-settings-advanced-keys', + 'title' => __( 'REST API', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Advanced > Webhooks. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-advanced-webhooks', + 'parent' => 'woocommerce-settings-advanced', + 'screen_id' => 'woocommerce_page_wc-settings-advanced-webhooks', + 'title' => __( 'Webhooks', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Advanced > Legacy API. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-advanced-legacy-api', + 'parent' => 'woocommerce-settings-advanced', + 'screen_id' => 'woocommerce_page_wc-settings-advanced-legacy_api', + 'title' => __( 'Legacy API', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Settings > Advanced > WooCommerce.com. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-settings-advanced-woocommerce-com', + 'parent' => 'woocommerce-settings-advanced', + 'screen_id' => 'woocommerce_page_wc-settings-advanced-woocommerce_com', + 'title' => __( 'WooCommerce.com', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Orders. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-orders', + 'screen_id' => 'edit-shop_order', + 'title' => __( 'Orders', 'woocommerce-admin' ), + 'path' => add_query_arg( 'post_type', 'shop_order', $posttype_list_base ), + ) +); + +// WooCommerce > Orders > Add New. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-add-order', + 'parent' => 'woocommerce-orders', + 'screen_id' => 'shop_order-add', + 'title' => __( 'Add New', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Orders > Edit Order. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-edit-order', + 'parent' => 'woocommerce-orders', + 'screen_id' => 'shop_order', + 'title' => __( 'Edit Order', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Coupons. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-coupons', + 'screen_id' => 'edit-shop_coupon', + 'title' => __( 'Coupons', 'woocommerce-admin' ), + 'path' => add_query_arg( 'post_type', 'shop_coupon', $posttype_list_base ), + ) +); + +// WooCommerce > Coupons > Add New. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-add-coupon', + 'parent' => 'woocommerce-coupons', + 'screen_id' => 'shop_coupon-add', + 'title' => __( 'Add New', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Coupons > Edit Coupon. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-edit-coupon', + 'parent' => 'woocommerce-coupons', + 'screen_id' => 'shop_coupon', + 'title' => __( 'Edit Coupon', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Reports > Orders (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-reports', + 'screen_id' => 'woocommerce_page_wc-reports-orders', + 'title' => array( + __( 'Reports', 'woocommerce-admin' ), + __( 'Orders', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( 'page', 'wc-reports', $admin_page_base ), + ) +); + +// WooCommerce > Reports > Customers. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-reports-customers', + 'parent' => 'woocommerce-reports', + 'screen_id' => 'woocommerce_page_wc-reports-customers', + 'title' => __( 'Customers', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Reports > Stock. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-reports-stock', + 'parent' => 'woocommerce-reports', + 'screen_id' => 'woocommerce_page_wc-reports-stock', + 'title' => __( 'Stock', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Reports > Taxes. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-reports-taxes', + 'parent' => 'woocommerce-reports', + 'screen_id' => 'woocommerce_page_wc-reports-taxes', + 'title' => __( 'Taxes', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Status > System status (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-status', + 'screen_id' => 'woocommerce_page_wc-status-status', + 'title' => array( + __( 'Status', 'woocommerce-admin' ), + __( 'System status', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( 'page', 'wc-status', $admin_page_base ), + ) +); + +// WooCommerce > Status > Tools. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-status-tools', + 'parent' => 'woocommerce-status', + 'screen_id' => 'woocommerce_page_wc-status-tools', + 'title' => __( 'Tools', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Status > Logs. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-status-logs', + 'parent' => 'woocommerce-status', + 'screen_id' => 'woocommerce_page_wc-status-tools', + 'title' => __( 'Tools', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Status > Scheduled Actions. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-status-action-scheduler', + 'parent' => 'woocommerce-status', + 'screen_id' => 'woocommerce_page_wc-status-action-scheduler', + 'title' => __( 'Scheduled Actions', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Extensions > Browse Extensions (default tab). +wc_admin_connect_page( + array( + 'id' => 'woocommerce-addons', + 'screen_id' => 'woocommerce_page_wc-addons-browse-extensions', + 'title' => array( + __( 'Extensions', 'woocommerce-admin' ), + __( 'Browse Extensions', 'woocommerce-admin' ), + ), + 'path' => add_query_arg( 'page', 'wc-addons', $admin_page_base ), + ) +); + +// WooCommerce > Extensions > WooCommerce.com Subscriptions. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-addons-subscriptions', + 'parent' => 'woocommerce-addons', + 'screen_id' => 'woocommerce_page_wc-addons-browse-extensions-helper', + 'title' => __( 'WooCommerce.com Subscriptions', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-products', + 'screen_id' => 'edit-product', + 'title' => __( 'Products', 'woocommerce-admin' ), + 'path' => add_query_arg( 'post_type', 'product', $posttype_list_base ), + ) +); + +// WooCommerce > Products > Add New. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-add-product', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product-add', + 'title' => __( 'Add New', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Edit Order. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-edit-product', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product', + 'title' => __( 'Edit Product', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Import Products. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-import-products', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product_page_product_importer', + 'title' => __( 'Import Products', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Export Products. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-export-products', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product_page_product_exporter', + 'title' => __( 'Export Products', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Product categories. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-product-categories', + 'parent' => 'woocommerce-products', + 'screen_id' => 'edit-product_cat', + 'title' => __( 'Product categories', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Edit category. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-product-edit-category', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product_cat', + 'title' => __( 'Edit category', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Product tags. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-product-tags', + 'parent' => 'woocommerce-products', + 'screen_id' => 'edit-product_tag', + 'title' => __( 'Product tags', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Edit tag. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-product-edit-tag', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product_tag', + 'title' => __( 'Edit tag', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Attributes. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-product-attributes', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product_page_product_attributes', + 'title' => __( 'Attributes', 'woocommerce-admin' ), + ) +); + +// WooCommerce > Products > Edit attribute. +wc_admin_connect_page( + array( + 'id' => 'woocommerce-product-edit-attribute', + 'parent' => 'woocommerce-products', + 'screen_id' => 'product_page_product_attribute-edit', + 'title' => __( 'Edit attribute', 'woocommerce-admin' ), + ) +); diff --git a/plugins/woocommerce-admin/includes/page-controller/page-controller-functions.php b/plugins/woocommerce-admin/includes/page-controller/page-controller-functions.php new file mode 100644 index 00000000000..1f55296953d --- /dev/null +++ b/plugins/woocommerce-admin/includes/page-controller/page-controller-functions.php @@ -0,0 +1,61 @@ +connect_page( $options ); +} + +/** + * Register JS-powered WooCommerce Admin Page. + * Passthrough to WC_Admin_Page_Controller::register_page(). + * + * @param array $options Options for WC_Admin_Page_Controller::register_page(). + */ +function wc_admin_register_page( $options ) { + $controller = WC_Admin_Page_Controller::get_instance(); + $controller->register_page( $options ); +} + +/** + * Is this page connected to WooCommerce Admin? + * Passthrough to WC_Admin_Page_Controller::is_connected_page(). + * + * @return boolean True if the page is connected to WooCommerce Admin. + */ +function wc_admin_is_connected_page() { + $controller = WC_Admin_Page_Controller::get_instance(); + return $controller->is_connected_page(); +} + +/** + * Is this a WooCommerce Admin Page? + * Passthrough to WC_Admin_Page_Controller::is_registered_page(). + * + * @return boolean True if the page is a WooCommerce Admin page. + */ +function wc_admin_is_registered_page() { + $controller = WC_Admin_Page_Controller::get_instance(); + return $controller->is_registered_page(); +} + +/** + * Get breadcrumbs for WooCommerce Admin Page navigation. + * Passthrough to WC_Admin_Page_Controller::get_breadcrumbs(). + * + * @return array Navigation pieces (breadcrumbs). + */ +function wc_admin_get_breadcrumbs() { + $controller = WC_Admin_Page_Controller::get_instance(); + return $controller->get_breadcrumbs(); +} diff --git a/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/style.scss b/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/style.scss index 3d29895c878..31e0e522b08 100644 --- a/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/style.scss +++ b/plugins/woocommerce-admin/packages/components/src/ellipsis-menu/style.scss @@ -1,4 +1,7 @@ /** @format */ +.woocommerce-ellipsis-menu { + text-align: center; +} .woocommerce-ellipsis-menu__toggle { height: 24px; diff --git a/plugins/woocommerce-admin/packages/components/src/summary/style.scss b/plugins/woocommerce-admin/packages/components/src/summary/style.scss index 3c5cbca5c2c..96f86b8e868 100644 --- a/plugins/woocommerce-admin/packages/components/src/summary/style.scss +++ b/plugins/woocommerce-admin/packages/components/src/summary/style.scss @@ -11,6 +11,13 @@ $inner-border: $core-grey-light-500; .woocommerce-summary__item-container:nth-of-type(#{$i}n) .woocommerce-summary__item { border-right-color: $outer-border; } + + .woocommerce-summary__item-container:nth-of-type(#{$i}n+1):nth-last-of-type(-n+#{$i}) { + .woocommerce-summary__item, + & ~ .woocommerce-summary__item-container .woocommerce-summary__item { + border-bottom-color: $outer-border; + } + } } @mixin wrap-contents() { @@ -31,24 +38,6 @@ $inner-border: $core-grey-light-500; } } -@mixin reset-wrap() { - .woocommerce-summary__item-value, - .woocommerce-summary__item-delta { - min-width: auto; - } - - .woocommerce-summary__item-prev-label, - .woocommerce-summary__item-prev-value { - display: inline; - } - - &.is-placeholder { - .woocommerce-summary__item-prev-label { - margin-right: 0; - } - } -} - .woocommerce-summary { margin: $gap 0; display: grid; @@ -149,16 +138,22 @@ $inner-border: $core-grey-light-500; @include make-cols( 4 ); } - &.has-5-items, - &.has-9-items, - &.has-10-items { + &.has-5-items { @include make-cols( 5 ); @include wrap-contents; } - &.has-6-items { - @include make-cols( 6 ); - @include wrap-contents; + @include breakpoint( '>1440px' ) { + &.has-6-items { + @include make-cols( 6 ); + @include wrap-contents; + } + + &.has-9-items, + &.has-10-items { + @include make-cols( 5 ); + @include wrap-contents; + } } @include breakpoint( '<1440px' ) { @@ -171,11 +166,11 @@ $inner-border: $core-grey-light-500; &.has-6-items, &.has-9-items { @include make-cols( 3 ); - @include reset-wrap; } &.has-10-items { @include make-cols( 4 ); + @include wrap-contents; } &.has-9-items, @@ -221,7 +216,7 @@ $inner-border: $core-grey-light-500; &:last-of-type .woocommerce-summary__item { // Make sure the last item always uses the outer-border color. - border-right-color: $outer-border !important; + border-bottom-color: $outer-border !important; } &.is-dropdown-button { @@ -285,10 +280,12 @@ $inner-border: $core-grey-light-500; } .woocommerce-summary__item { - display: block; + display: flex; + flex-direction: column; + height: 100%; padding: $spacing; background-color: $core-grey-light-100; - border-bottom: 1px solid $outer-border; + border-bottom: 1px solid $inner-border; border-right: 1px solid $inner-border; line-height: 1.4em; text-decoration: none; @@ -324,6 +321,10 @@ $inner-border: $core-grey-light-500; } } + .woocommerce-summary__item-data { + margin-top: auto; + } + .woocommerce-summary__item-label { display: block; margin-bottom: $gap;