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' ) ); } else { add_action( 'wp_dashboard_setup', array( $this, 'init' ) ); } } } /** * Init dashboard widgets. */ public function init() { // Reviews Widget. if ( current_user_can( 'publish_shop_orders' ) && post_type_supports( 'product', 'comments' ) ) { wp_add_dashboard_widget( 'woocommerce_dashboard_recent_reviews', __( 'WooCommerce Recent Reviews', 'woocommerce' ), array( $this, 'recent_reviews' ) ); } wp_add_dashboard_widget( 'woocommerce_dashboard_status', __( 'WooCommerce Status', 'woocommerce' ), array( $this, 'status_widget' ) ); // Network Order Widget. if ( is_multisite() && is_main_site() ) { $this->register_network_order_widget(); } } /** * Register the network order dashboard widget. */ public function register_network_order_widget() { 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() { if ( ! WC()->is_wc_admin_active() ) { return false; } $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. * * @return object */ private function get_top_seller() { global $wpdb; $hpos_enabled = OrderUtil::custom_orders_table_usage_is_enabled(); $orders_table = OrderUtil::get_table_for_orders(); $orders_column_id = $hpos_enabled ? 'id' : 'ID'; $orders_column_type = $hpos_enabled ? 'type' : 'post_type'; $orders_column_status = $hpos_enabled ? 'status' : 'post_status'; $orders_column_date = $hpos_enabled ? 'date_created_gmt' : 'post_date_gmt'; $query = array(); $query['fields'] = "SELECT SUM( order_item_meta.meta_value ) as qty, order_item_meta_2.meta_value as product_id FROM {$orders_table} AS orders"; $query['join'] = "INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON orders.{$orders_column_id} = order_id "; $query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id "; $query['join'] .= "INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_2 ON order_items.order_item_id = order_item_meta_2.order_item_id "; $query['where'] = "WHERE orders.{$orders_column_type} IN ( '" . implode( "','", wc_get_order_types( 'order-count' ) ) . "' ) "; /** * Allows modifying the order statuses used in the top seller query inside the dashboard status widget. * * @since 2.2.0 * * @param string[] $order_statuses Order statuses. */ $order_statuses = apply_filters( 'woocommerce_reports_order_statuses', array( OrderStatus::COMPLETED, OrderStatus::PROCESSING, OrderStatus::ON_HOLD ) ); $query['where'] .= "AND orders.{$orders_column_status} IN ( 'wc-" . implode( "','wc-", $order_statuses ) . "' ) "; $query['where'] .= "AND order_item_meta.meta_key = '_qty' "; $query['where'] .= "AND order_item_meta_2.meta_key = '_product_id' "; $query['where'] .= "AND orders.{$orders_column_date} >= '" . gmdate( 'Y-m-01', current_time( 'timestamp' ) ) . "' "; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested $query['where'] .= "AND orders.{$orders_column_date} <= '" . gmdate( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) . "' "; // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested $query['groupby'] = 'GROUP BY product_id'; $query['orderby'] = 'ORDER BY qty DESC'; $query['limits'] = 'LIMIT 1'; /** * Allows modification of the query to determine the top seller product in the dashboard status widget. * * @since 2.2.0 * * @param array $query SQL query parts. */ $query = apply_filters( 'woocommerce_dashboard_status_widget_top_seller_query', $query ); $sql = implode( ' ', $query ); return $wpdb->get_row( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared } /** * Show status widget. */ public function status_widget() { $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; $version = Constants::get_constant( 'WC_VERSION' ); wp_enqueue_script( 'wc-status-widget', WC()->plugin_url() . '/assets/js/admin/wc-status-widget' . $suffix . '.js', array( 'jquery', 'wc-flot' ), $version, true ); wp_enqueue_script( 'wc-status-widget-async', WC()->plugin_url() . '/assets/js/admin/wc-status-widget-async' . $suffix . '.js', array( 'jquery' ), $version, true ); wp_localize_script( 'wc-status-widget-async', 'wc_status_widget_params', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'security' => wp_create_nonce( 'wc-status-widget' ), 'error_message' => esc_html__( 'Error loading widget', 'woocommerce' ), ) ); // Display loading placeholder. echo '
'; echo ''; } /** * Generate the actual status widget content. * This contains the original content of the status_widget() method. */ public function status_widget_content() { //phpcs:ignore $is_wc_admin_disabled = apply_filters( 'woocommerce_admin_disabled', false ) || ! Features::is_enabled( 'analytics' ); $status_widget_reports = array( 'net_sales_link' => 'admin.php?page=wc-admin&path=%2Fanalytics%2Frevenue&chart=net_revenue&orderby=net_revenue&period=month&compare=previous_period', 'top_seller_link' => 'admin.php?page=wc-admin&filter=single_product&path=%2Fanalytics%2Fproducts&products=', 'lowstock_link' => 'admin.php?page=wc-admin&type=lowstock&path=%2Fanalytics%2Fstock', 'outofstock_link' => 'admin.php?page=wc-admin&type=outofstock&path=%2Fanalytics%2Fstock', 'report_data' => null, 'get_sales_sparkline' => array( $this, 'get_sales_sparkline' ), ); if ( $is_wc_admin_disabled ) { /** * Filter to change the reports of the status widget on the Dashboard page. * * Please note that this filter is mainly for backward compatibility with the legacy reports. * It's not recommended to use this filter to change the data of this widget. * * @since 9.5.0 */ $status_widget_reports = apply_filters( 'woocommerce_dashboard_status_widget_reports', $status_widget_reports ); } else { $status_widget_reports['report_data'] = $this->get_wc_admin_performance_data(); } echo '' . wp_kses_data( $comment->comment_content ) . '
' . esc_html__( 'There are no product reviews yet.', 'woocommerce' ) . '
'; } } /** * Recent reviews widget: placeholder. */ public function recent_reviews() { $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; $version = Constants::get_constant( 'WC_VERSION' ); wp_enqueue_script( 'wc-recent-reviews-widget-async', WC()->plugin_url() . '/assets/js/admin/wc-recent-reviews-widget-async' . $suffix . '.js', array( 'jquery' ), $version, true ); wp_localize_script( 'wc-recent-reviews-widget-async', 'wc_recent_reviews_widget_params', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'security' => wp_create_nonce( 'wc-recent-reviews-widget' ), 'error_message' => esc_html__( 'Error loading widget', 'woocommerce' ), ) ); // Display loading placeholder. echo ''; echo ''; } /** * Recent reviews widget: content. */ public function recent_reviews_content(): void { // Backward compatibility mode: if any of the checked below hooks are in use, use the legacy implementation. $has_legacy_query_filter = has_filter( 'woocommerce_report_recent_reviews_query_from' ); $has_legacy_product_title_filter = has_filter( 'woocommerce_admin_dashboard_recent_reviews' ); $use_legacy_implementation = $has_legacy_query_filter || $has_legacy_product_title_filter; if ( $use_legacy_implementation ) { if ( $has_legacy_query_filter ) { wc_deprecated_hook( 'woocommerce_report_recent_reviews_query_from', '10.5.0' ); } if ( $has_legacy_product_title_filter ) { wc_deprecated_hook( 'woocommerce_admin_dashboard_recent_reviews', '10.5.0', 'dashboard-widget-reviews.php template' ); } $this->legacy_recent_reviews(); return; } // Optimized version of the widget: faster SQL queries and templates-based rendering for customization. /** @var \WP_Comment[] $comments */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort $comments = get_comments( array( 'type' => 'review', 'status' => 'approve', 'parent' => 0, 'number' => 25, 'update_comment_post_cache' => true, ) ); $comments = array_filter( $comments, static fn( \WP_Comment $comment ) => current_user_can( 'read_product', $comment->comment_post_ID ) && ! post_password_required( (int) $comment->comment_post_ID ) ); if ( $comments ) { echo '' . esc_html__( 'There are no product reviews yet.', 'woocommerce' ) . '
'; } } /** * Network orders widget. */ public function network_orders() { $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; $version = Constants::get_constant( 'WC_VERSION' ); wp_enqueue_style( 'wc-network-orders', WC()->plugin_url() . '/assets/css/network-order-widget.css', array(), $version ); wp_enqueue_script( 'wc-network-orders', WC()->plugin_url() . '/assets/js/admin/network-orders' . $suffix . '.js', array( 'jquery', 'underscore' ), $version, true ); $user = wp_get_current_user(); $blogs = get_blogs_of_user( $user->ID ); $blog_ids = wp_list_pluck( $blogs, 'userblog_id' ); wp_localize_script( 'wc-network-orders', 'woocommerce_network_orders', array( 'nonce' => wp_create_nonce( 'wp_rest' ), 'sites' => array_values( $blog_ids ), 'order_endpoint' => get_rest_url( null, 'wc/v3/orders/network' ), ) ); ?>