helper
5 years ago
importers
5 years ago
list-tables
5 years ago
marketplace-suggestions
5 years ago
meta-boxes
5 years ago
notes
5 years ago
plugin-updates
5 years ago
reports
5 years ago
settings
5 years ago
views
5 years ago
class-wc-admin-addons.php
5 years ago
class-wc-admin-api-keys-table-list.php
6 years ago
class-wc-admin-api-keys.php
6 years ago
class-wc-admin-assets.php
5 years ago
class-wc-admin-attributes.php
5 years ago
class-wc-admin-customize.php
5 years ago
class-wc-admin-dashboard.php
5 years ago
class-wc-admin-duplicate-product.php
5 years ago
class-wc-admin-exporters.php
5 years ago
class-wc-admin-help.php
5 years ago
class-wc-admin-importers.php
5 years ago
class-wc-admin-log-table-list.php
5 years ago
class-wc-admin-menus.php
5 years ago
class-wc-admin-meta-boxes.php
5 years ago
class-wc-admin-notices.php
5 years ago
class-wc-admin-permalink-settings.php
5 years ago
class-wc-admin-pointers.php
5 years ago
class-wc-admin-post-types.php
5 years ago
class-wc-admin-profile.php
5 years ago
class-wc-admin-reports.php
5 years ago
class-wc-admin-settings.php
5 years ago
class-wc-admin-setup-wizard.php
5 years ago
class-wc-admin-status.php
5 years ago
class-wc-admin-taxonomies.php
5 years ago
class-wc-admin-webhooks-table-list.php
7 years ago
class-wc-admin-webhooks.php
5 years ago
class-wc-admin.php
5 years ago
wc-admin-functions.php
5 years ago
wc-meta-box-functions.php
5 years ago
class-wc-admin-dashboard.php
424 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Admin Dashboard |
| 4 | * |
| 5 | * @package WooCommerce\Admin |
| 6 | * @version 2.1.0 |
| 7 | */ |
| 8 | |
| 9 | use Automattic\Jetpack\Constants; |
| 10 | |
| 11 | if ( ! defined( 'ABSPATH' ) ) { |
| 12 | exit; // Exit if accessed directly. |
| 13 | } |
| 14 | |
| 15 | if ( ! class_exists( 'WC_Admin_Dashboard', false ) ) : |
| 16 | |
| 17 | /** |
| 18 | * WC_Admin_Dashboard Class. |
| 19 | */ |
| 20 | class WC_Admin_Dashboard { |
| 21 | |
| 22 | /** |
| 23 | * Hook in tabs. |
| 24 | */ |
| 25 | public function __construct() { |
| 26 | // Only hook in admin parts if the user has admin access. |
| 27 | if ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'manage_woocommerce' ) || current_user_can( 'publish_shop_orders' ) ) { |
| 28 | // If on network admin, only load the widget that works in that context and skip the rest. |
| 29 | if ( is_multisite() && is_network_admin() ) { |
| 30 | add_action( 'wp_network_dashboard_setup', array( $this, 'register_network_order_widget' ) ); |
| 31 | } else { |
| 32 | add_action( 'wp_dashboard_setup', array( $this, 'init' ) ); |
| 33 | } |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | /** |
| 38 | * Init dashboard widgets. |
| 39 | */ |
| 40 | public function init() { |
| 41 | // Reviews Widget. |
| 42 | if ( current_user_can( 'publish_shop_orders' ) && post_type_supports( 'product', 'comments' ) ) { |
| 43 | wp_add_dashboard_widget( 'woocommerce_dashboard_recent_reviews', __( 'WooCommerce Recent Reviews', 'woocommerce' ), array( $this, 'recent_reviews' ) ); |
| 44 | } |
| 45 | wp_add_dashboard_widget( 'woocommerce_dashboard_status', __( 'WooCommerce Status', 'woocommerce' ), array( $this, 'status_widget' ) ); |
| 46 | |
| 47 | // Network Order Widget. |
| 48 | if ( is_multisite() && is_main_site() ) { |
| 49 | $this->register_network_order_widget(); |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * Register the network order dashboard widget. |
| 55 | */ |
| 56 | public function register_network_order_widget() { |
| 57 | wp_add_dashboard_widget( 'woocommerce_network_orders', __( 'WooCommerce Network Orders', 'woocommerce' ), array( $this, 'network_orders' ) ); |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * Get top seller from DB. |
| 62 | * |
| 63 | * @return object |
| 64 | */ |
| 65 | private function get_top_seller() { |
| 66 | global $wpdb; |
| 67 | |
| 68 | $query = array(); |
| 69 | $query['fields'] = "SELECT SUM( order_item_meta.meta_value ) as qty, order_item_meta_2.meta_value as product_id |
| 70 | FROM {$wpdb->posts} as posts"; |
| 71 | $query['join'] = "INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_id "; |
| 72 | $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 "; |
| 73 | $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 "; |
| 74 | $query['where'] = "WHERE posts.post_type IN ( '" . implode( "','", wc_get_order_types( 'order-count' ) ) . "' ) "; |
| 75 | $query['where'] .= "AND posts.post_status IN ( 'wc-" . implode( "','wc-", apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) ) ) . "' ) "; |
| 76 | $query['where'] .= "AND order_item_meta.meta_key = '_qty' "; |
| 77 | $query['where'] .= "AND order_item_meta_2.meta_key = '_product_id' "; |
| 78 | $query['where'] .= "AND posts.post_date >= '" . gmdate( 'Y-m-01', current_time( 'timestamp' ) ) . "' "; |
| 79 | $query['where'] .= "AND posts.post_date <= '" . gmdate( 'Y-m-d H:i:s', current_time( 'timestamp' ) ) . "' "; |
| 80 | $query['groupby'] = 'GROUP BY product_id'; |
| 81 | $query['orderby'] = 'ORDER BY qty DESC'; |
| 82 | $query['limits'] = 'LIMIT 1'; |
| 83 | |
| 84 | return $wpdb->get_row( implode( ' ', apply_filters( 'woocommerce_dashboard_status_widget_top_seller_query', $query ) ) ); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Get sales report data. |
| 89 | * |
| 90 | * @return object |
| 91 | */ |
| 92 | private function get_sales_report_data() { |
| 93 | include_once dirname( __FILE__ ) . '/reports/class-wc-report-sales-by-date.php'; |
| 94 | |
| 95 | $sales_by_date = new WC_Report_Sales_By_Date(); |
| 96 | $sales_by_date->start_date = strtotime( gmdate( 'Y-m-01', current_time( 'timestamp' ) ) ); |
| 97 | $sales_by_date->end_date = strtotime( gmdate( 'Y-m-d', current_time( 'timestamp' ) ) ); |
| 98 | $sales_by_date->chart_groupby = 'day'; |
| 99 | $sales_by_date->group_by_query = 'YEAR(posts.post_date), MONTH(posts.post_date), DAY(posts.post_date)'; |
| 100 | |
| 101 | return $sales_by_date->get_report_data(); |
| 102 | } |
| 103 | |
| 104 | /** |
| 105 | * Show status widget. |
| 106 | */ |
| 107 | public function status_widget() { |
| 108 | include_once dirname( __FILE__ ) . '/reports/class-wc-admin-report.php'; |
| 109 | |
| 110 | $reports = new WC_Admin_Report(); |
| 111 | |
| 112 | echo '<ul class="wc_status_list">'; |
| 113 | |
| 114 | if ( current_user_can( 'view_woocommerce_reports' ) ) { |
| 115 | $report_data = $this->get_sales_report_data(); |
| 116 | if ( $report_data ) { |
| 117 | ?> |
| 118 | <li class="sales-this-month"> |
| 119 | <a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=orders&range=month' ) ); ?>"> |
| 120 | <?php echo $reports->sales_sparkline( '', max( 7, gmdate( 'd', current_time( 'timestamp' ) ) ) ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?> |
| 121 | <?php |
| 122 | printf( |
| 123 | /* translators: %s: net sales */ |
| 124 | esc_html__( '%s net sales this month', 'woocommerce' ), |
| 125 | '<strong>' . wc_price( $report_data->net_sales ) . '</strong>' |
| 126 | ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped |
| 127 | ?> |
| 128 | </a> |
| 129 | </li> |
| 130 | <?php |
| 131 | } |
| 132 | |
| 133 | $top_seller = $this->get_top_seller(); |
| 134 | if ( $top_seller && $top_seller->qty ) { |
| 135 | ?> |
| 136 | <li class="best-seller-this-month"> |
| 137 | <a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=orders&report=sales_by_product&range=month&product_ids=' . $top_seller->product_id ) ); ?>"> |
| 138 | <?php echo $reports->sales_sparkline( $top_seller->product_id, max( 7, gmdate( 'd', current_time( 'timestamp' ) ) ), 'count' ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped ?> |
| 139 | <?php |
| 140 | printf( |
| 141 | /* translators: 1: top seller product title 2: top seller quantity */ |
| 142 | esc_html__( '%1$s top seller this month (sold %2$d)', 'woocommerce' ), |
| 143 | '<strong>' . get_the_title( $top_seller->product_id ) . '</strong>', |
| 144 | $top_seller->qty |
| 145 | ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped |
| 146 | ?> |
| 147 | </a> |
| 148 | </li> |
| 149 | <?php |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | $this->status_widget_order_rows(); |
| 154 | $this->status_widget_stock_rows(); |
| 155 | |
| 156 | do_action( 'woocommerce_after_dashboard_status_widget', $reports ); |
| 157 | echo '</ul>'; |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * Show order data is status widget. |
| 162 | */ |
| 163 | private function status_widget_order_rows() { |
| 164 | if ( ! current_user_can( 'edit_shop_orders' ) ) { |
| 165 | return; |
| 166 | } |
| 167 | $on_hold_count = 0; |
| 168 | $processing_count = 0; |
| 169 | |
| 170 | foreach ( wc_get_order_types( 'order-count' ) as $type ) { |
| 171 | $counts = (array) wp_count_posts( $type ); |
| 172 | $on_hold_count += isset( $counts['wc-on-hold'] ) ? $counts['wc-on-hold'] : 0; |
| 173 | $processing_count += isset( $counts['wc-processing'] ) ? $counts['wc-processing'] : 0; |
| 174 | } |
| 175 | ?> |
| 176 | <li class="processing-orders"> |
| 177 | <a href="<?php echo esc_url( admin_url( 'edit.php?post_status=wc-processing&post_type=shop_order' ) ); ?>"> |
| 178 | <?php |
| 179 | printf( |
| 180 | /* translators: %s: order count */ |
| 181 | _n( '<strong>%s order</strong> awaiting processing', '<strong>%s orders</strong> awaiting processing', $processing_count, 'woocommerce' ), |
| 182 | $processing_count |
| 183 | ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped |
| 184 | ?> |
| 185 | </a> |
| 186 | </li> |
| 187 | <li class="on-hold-orders"> |
| 188 | <a href="<?php echo esc_url( admin_url( 'edit.php?post_status=wc-on-hold&post_type=shop_order' ) ); ?>"> |
| 189 | <?php |
| 190 | printf( |
| 191 | /* translators: %s: order count */ |
| 192 | _n( '<strong>%s order</strong> on-hold', '<strong>%s orders</strong> on-hold', $on_hold_count, 'woocommerce' ), |
| 193 | $on_hold_count |
| 194 | ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped |
| 195 | ?> |
| 196 | </a> |
| 197 | </li> |
| 198 | <?php |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Show stock data is status widget. |
| 203 | */ |
| 204 | private function status_widget_stock_rows() { |
| 205 | global $wpdb; |
| 206 | |
| 207 | // Requires lookup table added in 3.6. |
| 208 | if ( version_compare( get_option( 'woocommerce_db_version', null ), '3.6', '<' ) ) { |
| 209 | return; |
| 210 | } |
| 211 | |
| 212 | $stock = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) ); |
| 213 | $nostock = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) ); |
| 214 | |
| 215 | $transient_name = 'wc_low_stock_count'; |
| 216 | $lowinstock_count = get_transient( $transient_name ); |
| 217 | |
| 218 | if ( false === $lowinstock_count ) { |
| 219 | /** |
| 220 | * Status widget low in stock count pre query. |
| 221 | * |
| 222 | * @since 4.3.0 |
| 223 | * @param null|string $low_in_stock_count Low in stock count, by default null. |
| 224 | * @param int $stock Low stock amount. |
| 225 | * @param int $nostock No stock amount |
| 226 | */ |
| 227 | $lowinstock_count = apply_filters( 'woocommerce_status_widget_low_in_stock_count_pre_query', null, $stock, $nostock ); |
| 228 | |
| 229 | if ( is_null( $lowinstock_count ) ) { |
| 230 | $lowinstock_count = $wpdb->get_var( |
| 231 | $wpdb->prepare( |
| 232 | "SELECT COUNT( product_id ) |
| 233 | FROM {$wpdb->wc_product_meta_lookup} AS lookup |
| 234 | INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID |
| 235 | WHERE stock_quantity <= %d |
| 236 | AND stock_quantity > %d |
| 237 | AND posts.post_status = 'publish'", |
| 238 | $stock, |
| 239 | $nostock |
| 240 | ) |
| 241 | ); |
| 242 | } |
| 243 | |
| 244 | set_transient( $transient_name, (int) $lowinstock_count, DAY_IN_SECONDS * 30 ); |
| 245 | } |
| 246 | |
| 247 | $transient_name = 'wc_outofstock_count'; |
| 248 | $outofstock_count = get_transient( $transient_name ); |
| 249 | |
| 250 | if ( false === $outofstock_count ) { |
| 251 | /** |
| 252 | * Status widget out of stock count pre query. |
| 253 | * |
| 254 | * @since 4.3.0 |
| 255 | * @param null|string $outofstock_count Out of stock count, by default null. |
| 256 | * @param int $nostock No stock amount |
| 257 | */ |
| 258 | $outofstock_count = apply_filters( 'woocommerce_status_widget_out_of_stock_count_pre_query', null, $nostock ); |
| 259 | |
| 260 | if ( is_null( $outofstock_count ) ) { |
| 261 | $outofstock_count = (int) $wpdb->get_var( |
| 262 | $wpdb->prepare( |
| 263 | "SELECT COUNT( product_id ) |
| 264 | FROM {$wpdb->wc_product_meta_lookup} AS lookup |
| 265 | INNER JOIN {$wpdb->posts} as posts ON lookup.product_id = posts.ID |
| 266 | WHERE stock_quantity <= %d |
| 267 | AND posts.post_status = 'publish'", |
| 268 | $nostock |
| 269 | ) |
| 270 | ); |
| 271 | } |
| 272 | |
| 273 | set_transient( $transient_name, (int) $outofstock_count, DAY_IN_SECONDS * 30 ); |
| 274 | } |
| 275 | ?> |
| 276 | <li class="low-in-stock"> |
| 277 | <a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=stock&report=low_in_stock' ) ); ?>"> |
| 278 | <?php |
| 279 | printf( |
| 280 | /* translators: %s: order count */ |
| 281 | _n( '<strong>%s product</strong> low in stock', '<strong>%s products</strong> low in stock', $lowinstock_count, 'woocommerce' ), |
| 282 | $lowinstock_count |
| 283 | ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped |
| 284 | ?> |
| 285 | </a> |
| 286 | </li> |
| 287 | <li class="out-of-stock"> |
| 288 | <a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-reports&tab=stock&report=out_of_stock' ) ); ?>"> |
| 289 | <?php |
| 290 | printf( |
| 291 | /* translators: %s: order count */ |
| 292 | _n( '<strong>%s product</strong> out of stock', '<strong>%s products</strong> out of stock', $outofstock_count, 'woocommerce' ), |
| 293 | $outofstock_count |
| 294 | ); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped |
| 295 | ?> |
| 296 | </a> |
| 297 | </li> |
| 298 | <?php |
| 299 | } |
| 300 | |
| 301 | /** |
| 302 | * Recent reviews widget. |
| 303 | */ |
| 304 | public function recent_reviews() { |
| 305 | global $wpdb; |
| 306 | |
| 307 | $query_from = apply_filters( |
| 308 | 'woocommerce_report_recent_reviews_query_from', |
| 309 | "FROM {$wpdb->comments} comments |
| 310 | LEFT JOIN {$wpdb->posts} posts ON (comments.comment_post_ID = posts.ID) |
| 311 | WHERE comments.comment_approved = '1' |
| 312 | AND comments.comment_type = 'review' |
| 313 | AND posts.post_password = '' |
| 314 | AND posts.post_type = 'product' |
| 315 | AND comments.comment_parent = 0 |
| 316 | ORDER BY comments.comment_date_gmt DESC |
| 317 | LIMIT 5" |
| 318 | ); |
| 319 | |
| 320 | $comments = $wpdb->get_results( |
| 321 | "SELECT posts.ID, posts.post_title, comments.comment_author, comments.comment_author_email, comments.comment_ID, comments.comment_content {$query_from};" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared |
| 322 | ); |
| 323 | |
| 324 | if ( $comments ) { |
| 325 | echo '<ul>'; |
| 326 | foreach ( $comments as $comment ) { |
| 327 | |
| 328 | echo '<li>'; |
| 329 | |
| 330 | echo get_avatar( $comment->comment_author_email, '32' ); |
| 331 | |
| 332 | $rating = intval( get_comment_meta( $comment->comment_ID, 'rating', true ) ); |
| 333 | |
| 334 | /* translators: %s: rating */ |
| 335 | echo '<div class="star-rating"><span style="width:' . esc_attr( $rating * 20 ) . '%">' . sprintf( esc_html__( '%s out of 5', 'woocommerce' ), esc_html( $rating ) ) . '</span></div>'; |
| 336 | |
| 337 | /* translators: %s: review author */ |
| 338 | echo '<h4 class="meta"><a href="' . esc_url( get_permalink( $comment->ID ) ) . '#comment-' . esc_attr( absint( $comment->comment_ID ) ) . '">' . esc_html( apply_filters( 'woocommerce_admin_dashboard_recent_reviews', $comment->post_title, $comment ) ) . '</a> ' . sprintf( esc_html__( 'reviewed by %s', 'woocommerce' ), esc_html( $comment->comment_author ) ) . '</h4>'; |
| 339 | echo '<blockquote>' . wp_kses_data( $comment->comment_content ) . '</blockquote></li>'; |
| 340 | |
| 341 | } |
| 342 | echo '</ul>'; |
| 343 | } else { |
| 344 | echo '<p>' . esc_html__( 'There are no product reviews yet.', 'woocommerce' ) . '</p>'; |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | /** |
| 349 | * Network orders widget. |
| 350 | */ |
| 351 | public function network_orders() { |
| 352 | $suffix = Constants::is_true( 'SCRIPT_DEBUG' ) ? '' : '.min'; |
| 353 | $version = Constants::get_constant( 'WC_VERSION' ); |
| 354 | |
| 355 | wp_enqueue_style( 'wc-network-orders', WC()->plugin_url() . '/assets/css/network-order-widget.css', array(), $version ); |
| 356 | |
| 357 | wp_enqueue_script( 'wc-network-orders', WC()->plugin_url() . '/assets/js/admin/network-orders' . $suffix . '.js', array( 'jquery', 'underscore' ), $version, true ); |
| 358 | |
| 359 | $user = wp_get_current_user(); |
| 360 | $blogs = get_blogs_of_user( $user->ID ); |
| 361 | $blog_ids = wp_list_pluck( $blogs, 'userblog_id' ); |
| 362 | |
| 363 | wp_localize_script( |
| 364 | 'wc-network-orders', |
| 365 | 'woocommerce_network_orders', |
| 366 | array( |
| 367 | 'nonce' => wp_create_nonce( 'wp_rest' ), |
| 368 | 'sites' => array_values( $blog_ids ), |
| 369 | 'order_endpoint' => get_rest_url( null, 'wc/v3/orders/network' ), |
| 370 | ) |
| 371 | ); |
| 372 | ?> |
| 373 | <div class="post-type-shop_order"> |
| 374 | <div id="woocommerce-network-order-table-loading" class="woocommerce-network-order-table-loading is-active"> |
| 375 | <p> |
| 376 | <span class="spinner is-active"></span> <?php esc_html_e( 'Loading network orders', 'woocommerce' ); ?> |
| 377 | </p> |
| 378 | |
| 379 | </div> |
| 380 | <table id="woocommerce-network-order-table" class="woocommerce-network-order-table"> |
| 381 | <thead> |
| 382 | <tr> |
| 383 | <td><?php esc_html_e( 'Order', 'woocommerce' ); ?></td> |
| 384 | <td><?php esc_html_e( 'Status', 'woocommerce' ); ?></td> |
| 385 | <td><?php esc_html_e( 'Total', 'woocommerce' ); ?></td> |
| 386 | </tr> |
| 387 | </thead> |
| 388 | <tbody id="network-orders-tbody"> |
| 389 | |
| 390 | </tbody> |
| 391 | </table> |
| 392 | <div id="woocommerce-network-orders-no-orders" class="woocommerce-network-orders-no-orders"> |
| 393 | <p> |
| 394 | <?php esc_html_e( 'No orders found', 'woocommerce' ); ?> |
| 395 | </p> |
| 396 | </div> |
| 397 | <?php // @codingStandardsIgnoreStart ?> |
| 398 | <script type="text/template" id="network-orders-row-template"> |
| 399 | <tr> |
| 400 | <td> |
| 401 | <a href="<%- edit_url %>" class="order-view"><strong>#<%- number %> <%- customer %></strong></a> |
| 402 | <br> |
| 403 | <em> |
| 404 | <%- blog.blogname %> |
| 405 | </em> |
| 406 | </td> |
| 407 | <td> |
| 408 | <mark class="order-status status-<%- status %>"><span><%- status_name %></span></mark> |
| 409 | </td> |
| 410 | <td> |
| 411 | <%= formatted_total %> |
| 412 | </td> |
| 413 | </tr> |
| 414 | </script> |
| 415 | <?php // @codingStandardsIgnoreEnd ?> |
| 416 | </div> |
| 417 | <?php |
| 418 | } |
| 419 | } |
| 420 | |
| 421 | endif; |
| 422 | |
| 423 | return new WC_Admin_Dashboard(); |
| 424 |