Merge branch 'Charts/Reports' Closes #3281

This commit is contained in:
Mike Jolley 2013-07-18 14:08:27 +01:00
commit f295a5d568
41 changed files with 7666 additions and 4061 deletions

View File

@ -0,0 +1,213 @@
<?php
/**
* Admin Reports
*
* Functions used for displaying sales and customer reports in admin.
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Admin/Reports
* @version 2.0.0
*/
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
/**
* WC_Admin_Reports Class
*/
class WC_Admin_Reports {
private $start_date;
private $end_date;
/**
* Constructor
*/
public function __construct() {
add_filter( 'admin_menu', array( $this, 'add_menu_item' ), 20 );
add_filter( 'woocommerce_screen_ids', array( $this, 'add_screen_id' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'scripts_and_styles' ) );
}
/**
* Add menu item
*/
public function add_menu_item() {
add_submenu_page( 'woocommerce', __( 'Reports', 'woocommerce' ), __( 'Reports', 'woocommerce' ) , 'view_woocommerce_reports', 'wc_reports', array( $this, 'admin_page' ) );
}
/**
* Add screen ID
* @param array $ids
*/
public function add_screen_id( $ids ) {
$wc_screen_id = strtolower( __( 'WooCommerce', 'woocommerce' ) );
$ids[] = $wc_screen_id . '_page_wc_reports';
return $ids;
}
/**
* Script and styles
*/
public function scripts_and_styles() {
$screen = get_current_screen();
$wc_screen_id = strtolower( __( 'WooCommerce', 'woocommerce' ) );
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
if ( in_array( $screen->id, apply_filters( 'woocommerce_reports_screen_ids', array( $wc_screen_id . '_page_wc_reports' ) ) ) ) {
wp_enqueue_script( 'wc-reports', WC()->plugin_url() . '/assets/js/admin/reports' . $suffix . '.js', array( 'jquery', 'jquery-ui-datepicker' ), '1.0' );
wp_enqueue_script( 'flot', WC()->plugin_url() . '/assets/js/admin/jquery.flot' . $suffix . '.js', array( 'jquery' ), '1.0' );
wp_enqueue_script( 'flot-resize', WC()->plugin_url() . '/assets/js/admin/jquery.flot.resize' . $suffix . '.js', array('jquery', 'flot'), '1.0' );
wp_enqueue_script( 'flot-time', WC()->plugin_url() . '/assets/js/admin/jquery.flot.time' . $suffix . '.js', array( 'jquery', 'flot' ), '1.0' );
wp_enqueue_script( 'flot-pie', WC()->plugin_url() . '/assets/js/admin/jquery.flot.pie' . $suffix . '.js', array( 'jquery', 'flot' ), '1.0' );
wp_enqueue_script( 'flot-stack', WC()->plugin_url() . '/assets/js/admin/jquery.flot.stack' . $suffix . '.js', array( 'jquery', 'flot' ), '1.0' );
}
}
/**
* Returns the definitions for the reports to show in admin.
*
* @return array
*/
public function get_reports() {
$reports = array(
'orders' => array(
'title' => __( 'Orders', 'woocommerce' ),
'reports' => array(
"sales_by_date" => array(
'title' => __( 'Sales by date', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"sales_by_product" => array(
'title' => __( 'Sales by product', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"sales_by_category" => array(
'title' => __( 'Sales by category', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"coupon_usage" => array(
'title' => __( 'Coupons by date', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
)
)
),
'customers' => array(
'title' => __( 'Customers', 'woocommerce' ),
'reports' => array(
"customers" => array(
'title' => __( 'Customers vs. Guests', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"customer_list" => array(
'title' => __( 'Customer List', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
)
),
'stock' => array(
'title' => __( 'Stock', 'woocommerce' ),
'reports' => array(
"low_in_stock" => array(
'title' => __( 'Low in stock', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"out_of_stock" => array(
'title' => __( 'Out of stock', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"most_stocked" => array(
'title' => __( 'Most Stocked', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
)
)
);
if ( get_option( 'woocommerce_calc_taxes' ) == 'yes' ) {
$reports['taxes'] = array(
'title' => __( 'Taxes', 'woocommerce' ),
'reports' => array(
"taxes_by_code" => array(
'title' => __( 'Taxes by code', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
"taxes_by_date" => array(
'title' => __( 'Taxes by date', 'woocommerce' ),
'description' => '',
'hide_title' => true,
'callback' => array( $this, 'get_report' )
),
)
);
}
$reports = apply_filters( 'woocommerce_admin_reports', $reports );
// Backwards compat
$reports = apply_filters( 'woocommerce_reports_charts', $reports );
foreach ( $reports as $key => $report_group ) {
if ( isset( $reports[ $key ]['charts'] ) )
$reports[ $key ]['reports'] = $reports[ $key ]['charts'];
foreach ( $reports[ $key ]['reports'] as $report_key => $report ) {
if ( isset( $reports[ $key ]['reports'][ $report_key ]['function'] ) )
$reports[ $key ]['reports'][ $report_key ]['callback'] = $reports[ $key ]['reports'][ $report_key ]['function'];
}
}
return $reports;
}
/**
* Handles output of the reports page in admin.
*/
public function admin_page() {
$reports = $this->get_reports();
$first_tab = array_keys( $reports );
$current_tab = ! empty( $_GET['tab'] ) ? sanitize_title( urldecode( $_GET['tab'] ) ) : $first_tab[0];
$current_report = isset( $_GET['report'] ) ? sanitize_title( urldecode( $_GET['report'] ) ) : current( array_keys( $reports[ $current_tab ]['reports'] ) );
include_once( 'reports/class-wc-admin-report.php' );
include_once( 'views/html-admin-page-reports.php' );
}
/**
* Get a report from our reports subfolder
*/
public function get_report( $name ) {
$name = sanitize_title( str_replace( '_', '-', $name ) );
$class = 'WC_Report_' . str_replace( '-', '_', $name );
include_once( 'reports/class-wc-report-' . $name . '.php' );
if ( ! class_exists( $class ) )
return;
$report = new $class();
$report->output_report();
}
}
new WC_Admin_Reports();

View File

@ -947,6 +947,8 @@ function woocommerce_process_shop_order_meta( $post_id, $post ) {
}
delete_transient( 'woocommerce_processing_order_count' );
$wpdb->query( "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE ('_transient_wc_report_%') OR `option_name` LIKE ('_transient_timeout_wc_report_%')" );
}
add_action( 'woocommerce_process_shop_order_meta', 'woocommerce_process_shop_order_meta', 10, 2 );

View File

@ -0,0 +1,388 @@
<?php
/**
* Admin Report
*
* Extended by reports to show charts and stats in admin.
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Admin/Reports
* @version 2.1.0
*/
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
/**
* WC_Admin_Report Class
*/
class WC_Admin_Report {
public $start_date;
public $end_date;
/**
* Get report totals such as order totals and discount amounts.
*
* Data example:
*
* '_order_total' => array(
* 'type' => 'meta',
* 'function' => 'SUM',
* 'name' => 'total_sales'
* )
*
* @param array $args
* @return array of results
*/
public function get_order_report_data( $args = array() ) {
global $wpdb;
$defaults = array(
'data' => array(),
'where' => array(),
'where_meta' => array(),
'query_type' => 'get_row',
'group_by' => '',
'order_by' => '',
'limit' => '',
'filter_range' => false,
'nocache' => false,
'debug' => false
);
$args = wp_parse_args( $args, $defaults );
extract( $args );
if ( empty( $data ) )
return false;
$select = array();
foreach ( $data as $key => $value ) {
$distinct = '';
if ( isset( $value['distinct'] ) )
$distinct = 'DISTINCT';
if ( $value['type'] == 'meta' )
$get_key = "meta_{$key}.meta_value";
elseif( $value['type'] == 'post_data' )
$get_key = "posts.{$key}";
elseif( $value['type'] == 'order_item_meta' )
$get_key = "order_item_meta_{$key}.meta_value";
elseif( $value['type'] == 'order_item' )
$get_key = "order_items.{$key}";
if ( $value['function'] )
$get = "{$value['function']}({$distinct} {$get_key})";
else
$get = "{$distinct} {$get_key}";
$select[] = "{$get} as {$value['name']}";
}
$query['select'] = "SELECT " . implode( ',', $select );
$query['from'] = "FROM {$wpdb->posts} AS posts";
// Joins
$joins = array();
$joins['rel'] = "LEFT JOIN {$wpdb->term_relationships} AS rel ON posts.ID=rel.object_ID";
$joins['tax'] = "LEFT JOIN {$wpdb->term_taxonomy} AS tax USING( term_taxonomy_id )";
$joins['term'] = "LEFT JOIN {$wpdb->terms} AS term USING( term_id )";
foreach ( $data as $key => $value ) {
if ( $value['type'] == 'meta' ) {
$joins["meta_{$key}"] = "LEFT JOIN {$wpdb->postmeta} AS meta_{$key} ON posts.ID = meta_{$key}.post_id";
} elseif ( $value['type'] == 'order_item_meta' ) {
$joins["order_items"] = "LEFT JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_id";
$joins["order_item_meta_{$key}"] = "LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_{$key} ON order_items.order_item_id = order_item_meta_{$key}.order_item_id";
} elseif ( $value['type'] == 'order_item' ) {
$joins["order_items"] = "LEFT JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_id";
}
}
if ( ! empty( $where_meta ) ) {
foreach ( $where_meta as $value ) {
if ( ! is_array( $value ) )
continue;
if ( isset( $value['type'] ) && $value['type'] == 'order_item_meta' ) {
$joins["order_items"] = "LEFT JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON posts.ID = order_id";
$joins["order_item_meta_{$value['meta_key']}"] = "LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS order_item_meta_{$value['meta_key']} ON order_items.order_item_id = order_item_meta_{$value['meta_key']}.order_item_id";
} else {
// If we have a where clause for meta, join the postmeta table
$joins["meta_{$value['meta_key']}"] = "LEFT JOIN {$wpdb->postmeta} AS meta_{$value['meta_key']} ON posts.ID = meta_{$value['meta_key']}.post_id";
}
}
}
$query['join'] = implode( ' ', $joins );
$query['where'] = "
WHERE posts.post_type = 'shop_order'
AND posts.post_status = 'publish'
AND tax.taxonomy = 'shop_order_status'
AND term.slug IN ('" . implode( "','", apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) ) ) . "')
";
if ( $filter_range ) {
$query['where'] .= "
AND post_date > '" . date('Y-m-d', $this->start_date ) . "'
AND post_date < '" . date('Y-m-d', strtotime( '+1 DAY', $this->end_date ) ) . "'
";
}
foreach ( $data as $key => $value ) {
if ( $value['type'] == 'meta' ) {
$query['where'] .= " AND meta_{$key}.meta_key = '{$key}'";
} elseif ( $value['type'] == 'order_item_meta' ) {
$query['where'] .= " AND order_items.order_item_type = '{$value['order_item_type']}'";
$query['where'] .= " AND order_item_meta_{$key}.meta_key = '{$key}'";
}
}
if ( ! empty( $where_meta ) ) {
$relation = isset( $where_meta['relation'] ) ? $where_meta['relation'] : 'AND';
$query['where'] .= " AND (";
foreach ( $where_meta as $index => $value ) {
if ( ! is_array( $value ) )
continue;
if ( strtolower( $value['operator'] ) == 'in' ) {
if ( is_array( $value['meta_value'] ) )
$value['meta_value'] = implode( "','", $value['meta_value'] );
if ( ! empty( $value['meta_value'] ) )
$where_value = "IN ('{$value['meta_value']}')";
} else {
$where_value = "{$value['operator']} '{$value['meta_value']}'";
}
if ( ! empty( $where_value ) ) {
if ( $index > 0 )
$query['where'] .= ' ' . $relation;
if ( isset( $value['type'] ) && $value['type'] == 'order_item_meta' ) {
$query['where'] .= " ( order_item_meta_{$value['meta_key']}.meta_key = '{$value['meta_key']}'";
$query['where'] .= " AND order_item_meta_{$value['meta_key']}.meta_value {$where_value} )";
} else {
$query['where'] .= " ( meta_{$value['meta_key']}.meta_key = '{$value['meta_key']}'";
$query['where'] .= " AND meta_{$value['meta_key']}.meta_value {$where_value} )";
}
}
}
$query['where'] .= ")";
}
if ( ! empty( $where ) ) {
foreach ( $where as $value ) {
if ( strtolower( $value['operator'] ) == 'in' ) {
if ( is_array( $value['value'] ) )
$value['value'] = implode( "','", $value['value'] );
if ( ! empty( $value['value'] ) )
$where_value = "IN ('{$value['value']}')";
} else {
$where_value = "{$value['operator']} '{$value['value']}'";
}
if ( ! empty( $where_value ) )
$query['where'] .= " AND {$value['key']} {$where_value}";
}
}
if ( $group_by ) {
$query['group_by'] = "GROUP BY {$group_by}";
}
if ( $order_by ) {
$query['order_by'] = "ORDER BY {$order_by}";
}
if ( $limit ) {
$query['limit'] = "LIMIT {$limit}";
}
$query = implode( ' ', $query );
$query_hash = md5( $query_type . $query );
if ( $debug )
var_dump( $query );
if ( $debug || $nocache || ( false === ( $result = get_transient( 'wc_report_' . $query_hash ) ) ) ) {
$result = apply_filters( 'woocommerce_reports_get_order_report_data', $wpdb->$query_type( $query ), $data );
if ( $filter_range ) {
if ( date('Y-m-d', strtotime( $this->end_date ) ) == date('Y-m-d', current_time( 'timestamp' ) ) ) {
$expiration = 60 * 60 * 1; // 1 hour
} else {
$expiration = 60 * 60 * 24; // 24 hour
}
} else {
$expiration = 60 * 60 * 24; // 24 hour
}
set_transient( 'wc_report_' . $query_hash, $result, $expiration );
}
return $result;
}
/**
* Put data with post_date's into an array of times
*
* @param array $data array of your data
* @param string $date_key key for the 'date' field. e.g. 'post_date'
* @param string $data_key key for the data you are charting
* @param int $interval
* @param string $start_date
* @param string $group_by
* @return string
*/
public function prepare_chart_data( $data, $date_key, $data_key, $interval, $start_date, $group_by ) {
$prepared_data = array();
// Ensure all days (or months) have values first in this range
for ( $i = 0; $i <= $interval; $i ++ ) {
switch ( $group_by ) {
case 'day' :
$time = strtotime( date( 'Ymd', strtotime( "+{$i} DAY", $start_date ) ) ) * 1000;
break;
case 'month' :
$time = strtotime( date( 'Ym', strtotime( "+{$i} MONTH", $start_date ) ) . '01' ) * 1000;
break;
}
if ( ! isset( $prepared_data[ $time ] ) )
$prepared_data[ $time ] = array( esc_js( $time ), 0 );
}
foreach ( $data as $d ) {
switch ( $group_by ) {
case 'day' :
$time = strtotime( date( 'Ymd', strtotime( $d->$date_key ) ) ) * 1000;
break;
case 'month' :
$time = strtotime( date( 'Ym', strtotime( $d->$date_key ) ) . '01' ) * 1000;
break;
}
if ( ! isset( $prepared_data[ $time ] ) )
continue;
if ( $data_key )
$prepared_data[ $time ][1] += $d->$data_key;
else
$prepared_data[ $time ][1] ++;
}
return $prepared_data;
}
/**
* Prepares a sparkline to show sales in the last X days
*
* @param int $id
* @param int $days
*/
public function sales_sparkline( $id, $days, $type ) {
$meta_key = $type == 'sales' ? '_line_total' : '_qty';
$data = $this->get_order_report_data( array(
'data' => array(
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
),
$meta_key => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_value'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where' => array(
array(
'key' => 'post_date',
'value' => date( 'Y-m-d', strtotime( 'midnight -7 days', current_time( 'timestamp' ) ) ),
'operator' => '>'
),
array(
'key' => 'order_item_meta__product_id.meta_value',
'value' => $id,
'operator' => '='
)
),
'group_by' => 'YEAR(post_date), MONTH(post_date), DAY(post_date)',
'query_type' => 'get_results',
'filter_range' => false
) );
$total = 0;
foreach ( $data as $d )
$total += $d->order_item_value;
if ( $type == 'sales' ) {
$tooltip = sprintf( __( 'Sold %s worth in the last %d days', 'woocommerce' ), strip_tags( woocommerce_price( $total ) ), $days );
} else {
$tooltip = sprintf( _n( 'Sold 1 time in the last %d days', 'Sold %d times in the last %d days', $total, 'woocommerce' ), $total, $days );
}
$sparkline_data = array_values( $this->prepare_chart_data( $data, 'post_date', 'order_item_value', $days - 1, strtotime( 'midnight -' . ( $days - 1 ) . ' days', current_time( 'timestamp' ) ), 'day' ) );
return '<span class="wc_sparkline ' . ( $type == 'sales' ? 'lines' : 'bars' ) . ' tips" data-color="#777" data-tip="' . $tooltip . '" data-barwidth="' . 60*60*16*1000 . '" data-sparkline="' . esc_attr( json_encode( $sparkline_data ) ) . '"></span>';
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {}
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
return array();
}
/**
* [get_chart_widgets description]
* @return array
*/
public function get_chart_widgets() {
return array();
}
/**
* Get an export link if needed
*/
public function get_export_button() {}
/**
* Output the report
*/
public function output_report() {}
}

View File

@ -0,0 +1,558 @@
<?php
/**
* WC_Report_Coupon_Usage class
*/
class WC_Report_Coupon_Usage extends WC_Admin_Report {
public $coupon_codes = array();
/**
* Constructor
*/
public function __construct() {
if ( isset( $_GET['coupon_codes'] ) && is_array( $_GET['coupon_codes'] ) )
$this->coupon_codes = array_filter( array_map( 'sanitize_text_field', $_GET['coupon_codes'] ) );
elseif ( isset( $_GET['coupon_codes'] ) )
$this->coupon_codes = array_filter( array( sanitize_text_field( $_GET['coupon_codes'] ) ) );
}
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
$legend = array();
$total_discount = $this->get_order_report_data( array(
'data' => array(
'discount_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'coupon',
'function' => 'SUM',
'name' => 'discount_amount'
)
),
'where' => array(
array(
'type' => 'order_item',
'key' => 'order_item_name',
'value' => $this->coupon_codes,
'operator' => 'IN'
)
),
'query_type' => 'get_var',
'filter_range' => true
) );
$total_coupons = absint( $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'order_item_type' => 'coupon',
'function' => 'COUNT',
'name' => 'order_coupon_count'
)
),
'where' => array(
array(
'type' => 'order_item',
'key' => 'order_item_name',
'value' => $this->coupon_codes,
'operator' => 'IN'
)
),
'query_type' => 'get_var',
'filter_range' => true
) ) );
$legend[] = array(
'title' => sprintf( __( '%s discounts in total', 'woocommerce' ), '<strong>' . woocommerce_price( $total_discount ) . '</strong>' ),
'color' => $this->chart_colours['discount_amount'],
'highlight_series' => 1
);
$legend[] = array(
'title' => sprintf( __( '%s coupons used in total', 'woocommerce' ), '<strong>' . $total_coupons . '</strong>' ),
'color' => $this->chart_colours['coupon_count' ],
'highlight_series' => 0
);
return $legend;
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
'7day' => __( 'Last 7 Days', 'woocommerce' )
);
$this->chart_colours = array(
'discount_amount' => '#3498db',
'coupon_count' => '#d4d9dc',
);
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 3 months max for day view
if ( $interval > 3 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
case '7day' :
default :
$this->start_date = strtotime( '-6 days', current_time( 'timestamp' ) );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php');
}
/**
* [get_chart_widgets description]
* @return array
*/
public function get_chart_widgets() {
$widgets = array();
$widgets[] = array(
'title' => '',
'callback' => array( $this, 'coupons_widget' )
);
return $widgets;
}
/**
* Product selection
* @return void
*/
public function coupons_widget() {
?>
<h4 class="section_title"><span><?php _e( 'Filter by coupon', 'woocommerce' ); ?></span></h4>
<div class="section">
<form method="GET">
<div>
<select id="coupon_codes" name="coupon_codes" class="chosen_select" data-placeholder="<?php _e( 'Choose coupons&hellip;', 'woocommerce' ); ?>" style="width:100%;">
<?php
$used_coupons = $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'order_item_type' => 'coupon',
'function' => '',
'distinct' => true,
'name' => 'order_item_name'
)
),
'where' => array(
array(
'key' => 'order_item_type',
'value' => 'coupon',
'operator' => '='
)
),
'query_type' => 'get_col',
'filter_range' => false
) );
if ( $used_coupons ) {
echo '<option value="">' . __( 'All coupons', 'woocommerce' ) . '</option>';
foreach ( $used_coupons as $coupon ) {
echo '<option value="' . $coupon . '" ' . selected( in_array( $coupon, $this->coupon_codes ), true, false ) . '>' . $coupon . '</option>';
}
} else
echo '<option value="">' . __( 'No used coupons found', 'woocommerce' ) . '</option>';
?>
</select>
<input type="submit" class="submit button" value="<?php _e( 'Show', 'woocommerce' ); ?>" />
<input type="hidden" name="range" value="<?php if ( ! empty( $_GET['range'] ) ) echo esc_attr( $_GET['range'] ) ?>" />
<input type="hidden" name="start_date" value="<?php if ( ! empty( $_GET['start_date'] ) ) echo esc_attr( $_GET['start_date'] ) ?>" />
<input type="hidden" name="end_date" value="<?php if ( ! empty( $_GET['end_date'] ) ) echo esc_attr( $_GET['end_date'] ) ?>" />
<input type="hidden" name="page" value="<?php if ( ! empty( $_GET['page'] ) ) echo esc_attr( $_GET['page'] ) ?>" />
<input type="hidden" name="tab" value="<?php if ( ! empty( $_GET['tab'] ) ) echo esc_attr( $_GET['tab'] ) ?>" />
<input type="hidden" name="report" value="<?php if ( ! empty( $_GET['report'] ) ) echo esc_attr( $_GET['report'] ) ?>" />
</div>
<script type="text/javascript">
jQuery(function(){
jQuery("select.chosen_select").chosen();
});
</script>
</form>
</div>
<h4 class="section_title"><span><?php _e( 'Most Popular', 'woocommerce' ); ?></span></h4>
<div class="section">
<table cellspacing="0">
<?php
$most_popular = $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'order_item_type' => 'coupon',
'function' => '',
'name' => 'coupon_code'
),
'order_item_id' => array(
'type' => 'order_item',
'order_item_type' => 'coupon',
'function' => 'COUNT',
'name' => 'coupon_count'
),
),
'where' => array(
array(
'type' => 'order_item',
'key' => 'order_item_type',
'value' => 'coupon',
'operator' => '='
)
),
'order_by' => 'coupon_count DESC',
'group_by' => 'order_item_name',
'limit' => 12,
'query_type' => 'get_results',
'filter_range' => true
) );
if ( $most_popular ) {
foreach ( $most_popular as $coupon ) {
echo '<tr class="' . ( in_array( $coupon->coupon_code, $this->coupon_codes ) ? 'active' : '' ) . '">
<td class="count" width="1%">' . $coupon->coupon_count . '</td>
<td class="name"><a href="' . add_query_arg( 'coupon_codes', $coupon->coupon_code ) . '">' . $coupon->coupon_code . '</a></td>
</tr>';
}
} else {
echo '<tr><td colspan="2">' . __( 'No coupons found in range', 'woocommerce' ) . '</td></tr>';
}
?>
</table>
</div>
<h4 class="section_title"><span><?php _e( 'Most Discount', 'woocommerce' ); ?></span></h4>
<div class="section">
<table cellspacing="0">
<?php
$most_discount = $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'order_item_type' => 'coupon',
'function' => '',
'name' => 'coupon_code'
),
'discount_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'coupon',
'function' => 'SUM',
'name' => 'discount_amount'
)
),
'where' => array(
array(
'type' => 'order_item',
'key' => 'order_item_type',
'value' => 'coupon',
'operator' => '='
)
),
'order_by' => 'discount_amount DESC',
'group_by' => 'order_item_name',
'limit' => 12,
'query_type' => 'get_results',
'filter_range' => true
) );
if ( $most_discount ) {
foreach ( $most_discount as $coupon ) {
echo '<tr class="' . ( in_array( $coupon->coupon_code, $this->coupon_codes ) ? 'active' : '' ) . '">
<td class="count" width="1%">' . woocommerce_price( $coupon->discount_amount ) . '</td>
<td class="name"><a href="' . add_query_arg( 'coupon_codes', $coupon->coupon_code ) . '">' . $coupon->coupon_code . '</a></td>
</tr>';
}
} else {
echo '<tr><td colspan="3">' . __( 'No coupons found in range', 'woocommerce' ) . '</td></tr>';
}
?>
</table>
</div>
<script type="text/javascript">
jQuery('.section_title').click(function(){
var next_section = jQuery(this).next('.section');
if ( jQuery(next_section).is(':visible') )
return false;
jQuery('.section:visible').slideUp();
jQuery('.section_title').removeClass('open');
jQuery(this).addClass('open').next('.section').slideDown();
return false;
});
jQuery('.section').slideUp( 100, function() {
<?php if ( empty( $this->coupon_codes ) ) : ?>
jQuery('.section_title:eq(1)').click();
<?php else : ?>
jQuery('.section_title:eq(0)').click();
<?php endif; ?>
});
</script>
<?php
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="chart"
data-xaxes="<?php _e( 'Date', 'woocommerce' ); ?>"
data-groupby="<?php echo $this->chart_groupby; ?>"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wp_locale;
// Get orders and dates in range - we want the SUM of order totals, COUNT of order items, COUNT of orders, and the date
$order_coupon_counts = $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'order_item_type' => 'coupon',
'function' => 'COUNT',
'name' => 'order_coupon_count'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where' => array(
array(
'type' => 'order_item',
'key' => 'order_item_name',
'value' => $this->coupon_codes,
'operator' => 'IN'
)
),
'group_by' => $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
$order_discount_amounts = $this->get_order_report_data( array(
'data' => array(
'discount_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'coupon',
'function' => 'SUM',
'name' => 'discount_amount'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where' => array(
array(
'type' => 'order_item',
'key' => 'order_item_name',
'value' => $this->coupon_codes,
'operator' => 'IN'
)
),
'group_by' => $this->group_by_query . ', order_item_name',
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
// Prepare data for report
$order_coupon_counts = $this->prepare_chart_data( $order_coupon_counts, 'post_date', 'order_coupon_count' , $this->chart_interval, $this->start_date, $this->chart_groupby );
$order_discount_amounts = $this->prepare_chart_data( $order_discount_amounts, 'post_date', 'discount_amount', $this->chart_interval, $this->start_date, $this->chart_groupby );
// Encode in json format
$chart_data = json_encode( array(
'order_coupon_counts' => array_values( $order_coupon_counts ),
'order_discount_amounts' => array_values( $order_discount_amounts )
) );
?>
<div class="chart-container">
<div class="chart-placeholder main"></div>
</div>
<script type="text/javascript">
var main_chart;
jQuery(function(){
var order_data = jQuery.parseJSON( '<?php echo $chart_data; ?>' );
var drawGraph = function( highlight ) {
var series = [
{
label: "<?php echo esc_js( __( 'Number of coupons used', 'woocommerce' ) ) ?>",
data: order_data.order_coupon_counts,
color: '<?php echo $this->chart_colours['coupon_count' ]; ?>',
bars: { fillColor: '<?php echo $this->chart_colours['coupon_count' ]; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },
shadowSize: 0,
hoverable: false
},
{
label: "<?php echo esc_js( __( 'Discount amount', 'woocommerce' ) ) ?>",
data: order_data.order_discount_amounts,
yaxis: 2,
color: '<?php echo $this->chart_colours['discount_amount']; ?>',
points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },
lines: { show: true, lineWidth: 4, fill: false },
shadowSize: 0,
prepend_tooltip: "<?php echo get_woocommerce_currency_symbol(); ?>"
}
];
if ( highlight !== 'undefined' && series[ highlight ] ) {
highlight_series = series[ highlight ];
highlight_series.color = '#9c5d90';
if ( highlight_series.bars )
highlight_series.bars.fillColor = '#9c5d90';
if ( highlight_series.lines ) {
highlight_series.lines.lineWidth = 5;
}
}
main_chart = jQuery.plot(
jQuery('.chart-placeholder.main'),
series,
{
legend: {
show: false
},
grid: {
color: '#aaa',
borderColor: 'transparent',
borderWidth: 0,
hoverable: true
},
xaxes: [ {
color: '#aaa',
position: "bottom",
tickColor: 'transparent',
mode: "time",
timeformat: "<?php if ( $this->chart_groupby == 'day' ) echo '%d %b'; else echo '%b'; ?>",
monthNames: <?php echo json_encode( array_values( $wp_locale->month_abbrev ) ) ?>,
tickLength: 1,
minTickSize: [1, "<?php echo $this->chart_groupby; ?>"],
font: {
color: "#aaa"
}
} ],
yaxes: [
{
min: 0,
minTickSize: 1,
tickDecimals: 0,
color: '#ecf0f1',
font: { color: "#aaa" }
},
{
position: "right",
min: 0,
tickDecimals: 2,
alignTicksWithAxis: 1,
color: 'transparent',
font: { color: "#aaa" }
}
],
}
);
jQuery('.chart-placeholder').resize();
}
drawGraph();
jQuery('.highlight_series').hover(
function() {
drawGraph( jQuery(this).data('series') );
},
function() {
drawGraph();
}
);
});
</script>
<?php
}
}

View File

@ -6,16 +6,9 @@ if ( ! class_exists( 'WP_List_Table' ) )
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
/**
* Admin customers table
*
* Lists customers.
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Admin/Users
* @version 2.0.1
* WC_Report_Customer_List class
*/
class WC_Admin_Customers extends WP_List_Table {
class WC_Report_Customer_List extends WP_List_Table {
/**
* __construct function.
@ -32,6 +25,36 @@ class WC_Admin_Customers extends WP_List_Table {
) );
}
/**
* No items found text
*/
public function no_items() {
_e( 'No customers found.', 'woocommerce' );
}
/**
* Output the report
*/
public function output_report() {
$this->prepare_items();
echo '<div id="poststuff" class="woocommerce-reports-wide">';
if ( ! empty( $_GET['link_orders'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'link_orders' ) ) {
$linked = woocommerce_update_new_customer_past_orders( absint( $_GET['link_orders'] ) );
echo '<div class="updated"><p>' . sprintf( _n( '%s previous order linked', '%s previous orders linked', $linked, 'woocommerce' ), $linked ) . '</p></div>';
}
echo '<form method="post" id="woocommerce_customers">';
$this->search_box( __( 'Search customers', 'woocommerce' ), 'customer_search' );
$this->display();
echo '</form>';
echo '</div>';
}
/**
* column_default function.
*
@ -66,18 +89,13 @@ class WC_Admin_Customers extends WP_List_Table {
$value .= $country;
return $value;
if ( $value )
return $value;
else
return '-';
break;
case 'email' :
return '<a href="mailto:' . $user->user_email . '">' . $user->user_email . '</a>';
case 'paying' :
$paying_customer = get_user_meta( $user->ID, 'paying_customer', true );
if ( $paying_customer )
return '<img src="' . $woocommerce->plugin_url() . '/assets/images/success@2x.png" alt="yes" width="16px" />';
else
return ' - ';
break;
case 'spent' :
if ( ! $spent = get_user_meta( $user->ID, '_money_spent', true ) ) {
@ -148,7 +166,7 @@ class WC_Admin_Customers extends WP_List_Table {
$order = new WC_Order( $order_ids[0] );
echo '<a href="' . admin_url( 'post.php?post=' . $order->id . '&action=edit' ) . '">' . $order->get_order_number() . '</a> &ndash; ' . date_i18n( get_option( 'date_format', strtotime( $order->order_date ) ) );
}
} else echo '-';
break;
case 'user_actions' :
@ -218,11 +236,10 @@ class WC_Admin_Customers extends WP_List_Table {
$columns = array(
'customer_name' => __( 'Name (Last, First)', 'woocommerce' ),
'username' => __( 'Username', 'woocommerce' ),
'email' => __( 'Email address', 'woocommerce' ),
'email' => __( 'Email', 'woocommerce' ),
'location' => __( 'Location', 'woocommerce' ),
'paying' => __( 'Paying customer?', 'woocommerce' ),
'orders' => __( 'Complete orders', 'woocommerce' ),
'spent' => __( 'Money spent', 'woocommerce' ),
'orders' => __( 'Orders', 'woocommerce' ),
'spent' => __( 'Spent', 'woocommerce' ),
'last_order' => __( 'Last order', 'woocommerce' ),
'user_actions' => __( 'Actions', 'woocommerce' )
);
@ -272,8 +289,22 @@ class WC_Admin_Customers extends WP_List_Table {
/**
* Get users
*/
$admin_users = new WP_User_Query(
array(
'role' => 'administrator',
'fields' => 'ID'
)
);
$manager_users = new WP_User_Query(
array(
'role' => 'shop_manager',
'fields' => 'ID'
)
);
$query = new WP_User_Query( array(
'role' => 'customer',
'exclude' => array_merge( $admin_users->get_results(), $manager_users->get_results() ),
'number' => $per_page,
'offset' => ( $current_page - 1 ) * $per_page
) );

View File

@ -0,0 +1,445 @@
<?php
/**
* WC_Report_Customers class
*/
class WC_Report_Customers extends WC_Admin_Report {
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
$legend = array();
$legend[] = array(
'title' => sprintf( __( '%s signups in this period', 'woocommerce' ), '<strong>' . sizeof( $this->customers ) . '</strong>' ),
'color' => $this->chart_colours['signups'],
'highlight_series' => 2
);
return $legend;
}
/**
* [get_chart_widgets description]
* @return array
*/
public function get_chart_widgets() {
$widgets = array();
$widgets[] = array(
'title' => '',
'callback' => array( $this, 'customers_vs_guests' )
);
return $widgets;
}
/**
* customers_vs_guests
* @return void
*/
public function customers_vs_guests() {
$customer_order_totals = $this->get_order_report_data( array(
'data' => array(
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders'
)
),
'where_meta' => array(
array(
'meta_key' => '_customer_user',
'meta_value' => '0',
'operator' => '>'
)
),
'filter_range' => true
) );
$guest_order_totals = $this->get_order_report_data( array(
'data' => array(
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders'
)
),
'where_meta' => array(
array(
'meta_key' => '_customer_user',
'meta_value' => '0',
'operator' => '='
)
),
'filter_range' => true
) );
?>
<div class="chart-container">
<div class="chart-placeholder customers_vs_guests pie-chart" style="height:200px"></div>
<ul class="pie-chart-legend">
<li style="border-color: <?php echo $this->chart_colours['customers']; ?>"><?php _e( 'Customer Sales', 'woocommerce' ); ?></li>
<li style="border-color: <?php echo $this->chart_colours['guests']; ?>"><?php _e( 'Guest Sales', 'woocommerce' ); ?></li>
</ul>
</div>
<script type="text/javascript">
jQuery(function(){
jQuery.plot(
jQuery('.chart-placeholder.customers_vs_guests'),
[
{
label: '<?php _e( 'Customer Orders', 'woocommerce' ); ?>',
data: "<?php echo $customer_order_totals->total_orders ?>",
color: '<?php echo $this->chart_colours['customers']; ?>'
},
{
label: '<?php _e( 'Guest Orders', 'woocommerce' ); ?>',
data: "<?php echo $guest_order_totals->total_orders ?>",
color: '<?php echo $this->chart_colours['guests']; ?>'
}
],
{
grid: {
hoverable: true
},
series: {
pie: {
show: true,
radius: 1,
innerRadius: 0.6,
label: {
show: false
}
},
enable_tooltip: true,
append_tooltip: "<?php echo ' ' . __( 'orders', 'woocommerce' ); ?>",
},
legend: {
show: false
}
}
);
jQuery('.chart-placeholder.customers_vs_guests').resize();
});
</script>
<?php
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
'7day' => __( 'Last 7 Days', 'woocommerce' )
);
$this->chart_colours = array(
'signups' => '#3498db',
'customers' => '#1abc9c',
'guests' => '#8fdece'
);
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 3 months max for day view
if ( $interval > 3 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
case '7day' :
default :
$this->start_date = strtotime( '-6 days', current_time( 'timestamp' ) );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
$admin_users = new WP_User_Query(
array(
'role' => 'administrator',
'fields' => 'ID'
)
);
$manager_users = new WP_User_Query(
array(
'role' => 'shop_manager',
'fields' => 'ID'
)
);
$users_query = new WP_User_Query(
array(
'fields' => array( 'user_registered' ),
'exclude' => array_merge( $admin_users->get_results(), $manager_users->get_results() )
)
);
$this->customers = $users_query->get_results();
foreach ( $this->customers as $key => $customer ) {
if ( strtotime( $customer->user_registered ) < $this->start_date || strtotime( $customer->user_registered ) > $this->end_date )
unset( $this->customers[ $key ] );
}
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php' );
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="chart"
data-xaxes="<?php _e( 'Date', 'woocommerce' ); ?>"
data-groupby="<?php echo $this->chart_groupby; ?>"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wp_locale;
$customer_orders = $this->get_order_report_data( array(
'data' => array(
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where_meta' => array(
array(
'meta_key' => '_customer_user',
'meta_value' => '0',
'operator' => '>'
)
),
'group_by' => $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
$guest_orders = $this->get_order_report_data( array(
'data' => array(
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where_meta' => array(
array(
'meta_key' => '_customer_user',
'meta_value' => '0',
'operator' => '='
)
),
'group_by' => $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
$signups = $this->prepare_chart_data( $this->customers, 'user_registered', '', $this->chart_interval, $this->start_date, $this->chart_groupby );
$customer_orders = $this->prepare_chart_data( $customer_orders, 'post_date', 'total_orders', $this->chart_interval, $this->start_date, $this->chart_groupby );
$guest_orders = $this->prepare_chart_data( $guest_orders, 'post_date', 'total_orders', $this->chart_interval, $this->start_date, $this->chart_groupby );
// Encode in json format
$chart_data = json_encode( array(
'signups' => array_values( $signups ),
'customer_orders' => array_values( $customer_orders ),
'guest_orders' => array_values( $guest_orders )
) );
?>
<div class="chart-container">
<div class="chart-placeholder main"></div>
</div>
<script type="text/javascript">
var main_chart;
jQuery(function(){
var chart_data = jQuery.parseJSON( '<?php echo $chart_data; ?>' );
var drawGraph = function( highlight ) {
var series = [
{
label: "<?php echo esc_js( __( 'Customer Orders', 'woocommerce' ) ) ?>",
data: chart_data.customer_orders,
color: '<?php echo $this->chart_colours['customers']; ?>',
bars: { fillColor: '<?php echo $this->chart_colours['customers']; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },
shadowSize: 0,
enable_tooltip: true,
append_tooltip: "<?php echo ' ' . __( 'customer orders', 'woocommerce' ); ?>",
stack: true,
},
{
label: "<?php echo esc_js( __( 'Guest Orders', 'woocommerce' ) ) ?>",
data: chart_data.guest_orders,
color: '<?php echo $this->chart_colours['guests']; ?>',
bars: { fillColor: '<?php echo $this->chart_colours['guests']; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },
shadowSize: 0,
enable_tooltip: true,
append_tooltip: "<?php echo ' ' . __( 'guest orders', 'woocommerce' ); ?>",
stack: true,
},
{
label: "<?php echo esc_js( __( 'Signups', 'woocommerce' ) ) ?>",
data: chart_data.signups,
color: '<?php echo $this->chart_colours['signups']; ?>',
points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },
lines: { show: true, lineWidth: 4, fill: false },
shadowSize: 0,
enable_tooltip: true,
append_tooltip: "<?php echo ' ' . __( 'new users', 'woocommerce' ); ?>",
stack: false
},
];
if ( highlight !== 'undefined' && series[ highlight ] ) {
highlight_series = series[ highlight ];
highlight_series.color = '#9c5d90';
if ( highlight_series.bars )
highlight_series.bars.fillColor = '#9c5d90';
if ( highlight_series.lines ) {
highlight_series.lines.lineWidth = 5;
}
}
main_chart = jQuery.plot(
jQuery('.chart-placeholder.main'),
series,
{
legend: {
show: false
},
grid: {
color: '#aaa',
borderColor: 'transparent',
borderWidth: 0,
hoverable: true
},
xaxes: [ {
color: '#aaa',
position: "bottom",
tickColor: 'transparent',
mode: "time",
timeformat: "<?php if ( $this->chart_groupby == 'day' ) echo '%d %b'; else echo '%b'; ?>",
monthNames: <?php echo json_encode( array_values( $wp_locale->month_abbrev ) ) ?>,
tickLength: 1,
minTickSize: [1, "<?php echo $this->chart_groupby; ?>"],
tickSize: [1, "<?php echo $this->chart_groupby; ?>"],
font: {
color: "#aaa"
}
} ],
yaxes: [
{
min: 0,
minTickSize: 1,
tickDecimals: 0,
color: '#ecf0f1',
font: { color: "#aaa" }
}
],
}
);
jQuery('.chart-placeholder').resize();
}
drawGraph();
jQuery('.highlight_series').hover(
function() {
drawGraph( jQuery(this).data('series') );
},
function() {
drawGraph();
}
);
});
</script>
<?php
}
}

View File

@ -0,0 +1,54 @@
<?php
if ( ! defined( 'ABSPATH' ) )
exit; // Exit if accessed directly
if ( ! class_exists( 'WC_Report_Stock' ) )
require_once( 'class-wc-report-stock.php' );
/**
* WC_Report_Low_In_Stock class
*
* @extends WC_Report_Stock
*/
class WC_Report_Low_In_Stock extends WC_Report_Stock {
/**
* No items found text
*/
public function no_items() {
_e( 'No low in stock products found.', 'woocommerce' );
}
/**
* Get Products matching stock criteria
*
* @access public
*/
public function get_items( $current_page, $per_page ) {
global $wpdb;
$this->max_items = 0;
$this->items = array();
// Get products using a query - this is too advanced for get_posts :(
$stock = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
$nostock = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
$query_from = "FROM {$wpdb->posts} as posts
INNER JOIN {$wpdb->postmeta} AS postmeta ON posts.ID = postmeta.post_id
INNER JOIN {$wpdb->postmeta} AS postmeta2 ON posts.ID = postmeta2.post_id
WHERE 1=1
AND posts.post_type IN ('product', 'product_variation')
AND posts.post_status = 'publish'
AND (
postmeta.meta_key = '_stock' AND CAST(postmeta.meta_value AS SIGNED) <= '{$stock}' AND CAST(postmeta.meta_value AS SIGNED) > '{$nostock}' AND postmeta.meta_value != ''
)
AND (
( postmeta2.meta_key = '_manage_stock' AND postmeta2.meta_value = 'yes' ) OR ( posts.post_type = 'product_variation' )
)
";
$this->items = $wpdb->get_results( $wpdb->prepare( "SELECT posts.ID as id, posts.post_parent as parent {$query_from} GROUP BY posts.ID ORDER BY posts.post_title DESC LIMIT %d, %d;", ( $current_page - 1 ) * $per_page, $per_page ) );
$this->max_items = $wpdb->get_var( "SELECT COUNT( DISTINCT posts.ID ) {$query_from};" );
}
}

View File

@ -0,0 +1,46 @@
<?php
if ( ! defined( 'ABSPATH' ) )
exit; // Exit if accessed directly
if ( ! class_exists( 'WC_Report_Stock' ) )
require_once( 'class-wc-report-stock.php' );
/**
* WC_Report_Most_Stocked class
*
* @extends WC_Report_Stock
*/
class WC_Report_Most_Stocked extends WC_Report_Stock {
/**
* Get Products matching stock criteria
*
* @access public
*/
public function get_items( $current_page, $per_page ) {
global $wpdb;
$this->max_items = 0;
$this->items = array();
// Get products using a query - this is too advanced for get_posts :(
$stock = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 0 ) );
$query_from = "FROM {$wpdb->posts} as posts
INNER JOIN {$wpdb->postmeta} AS postmeta ON posts.ID = postmeta.post_id
INNER JOIN {$wpdb->postmeta} AS postmeta2 ON posts.ID = postmeta2.post_id
WHERE 1=1
AND posts.post_type IN ('product', 'product_variation')
AND posts.post_status = 'publish'
AND (
postmeta.meta_key = '_stock' AND CAST(postmeta.meta_value AS SIGNED) > '{$stock}' AND postmeta.meta_value != ''
)
AND (
( postmeta2.meta_key = '_manage_stock' AND postmeta2.meta_value = 'yes' ) OR ( posts.post_type = 'product_variation' )
)
";
$this->items = $wpdb->get_results( $wpdb->prepare( "SELECT posts.ID as id, posts.post_parent as parent {$query_from} GROUP BY posts.ID ORDER BY CAST(postmeta.meta_value AS SIGNED) DESC LIMIT %d, %d;", ( $current_page - 1 ) * $per_page, $per_page ) );
$this->max_items = $wpdb->get_var( "SELECT COUNT( DISTINCT posts.ID ) {$query_from};" );
}
}

View File

@ -0,0 +1,53 @@
<?php
if ( ! defined( 'ABSPATH' ) )
exit; // Exit if accessed directly
if ( ! class_exists( 'WC_Report_Stock' ) )
require_once( 'class-wc-report-stock.php' );
/**
* WC_Report_Out_Of_Stock class
*
* @extends WC_Report_Stock
*/
class WC_Report_Out_Of_Stock extends WC_Report_Stock {
/**
* No items found text
*/
public function no_items() {
_e( 'No out of stock products found.', 'woocommerce' );
}
/**
* Get Products matching stock criteria
*
* @access public
*/
public function get_items( $current_page, $per_page ) {
global $wpdb;
$this->max_items = 0;
$this->items = array();
// Get products using a query - this is too advanced for get_posts :(
$stock = absint( max( get_option( 'woocommerce_notify_no_stock_amount' ), 0 ) );
$query_from = "FROM {$wpdb->posts} as posts
INNER JOIN {$wpdb->postmeta} AS postmeta ON posts.ID = postmeta.post_id
INNER JOIN {$wpdb->postmeta} AS postmeta2 ON posts.ID = postmeta2.post_id
WHERE 1=1
AND posts.post_type IN ('product', 'product_variation')
AND posts.post_status = 'publish'
AND (
postmeta.meta_key = '_stock' AND CAST(postmeta.meta_value AS SIGNED) <= '{$stock}' AND postmeta.meta_value != ''
)
AND (
( postmeta2.meta_key = '_manage_stock' AND postmeta2.meta_value = 'yes' ) OR ( posts.post_type = 'product_variation' )
)
";
$this->items = $wpdb->get_results( $wpdb->prepare( "SELECT posts.ID as id, posts.post_parent as parent {$query_from} GROUP BY posts.ID ORDER BY posts.post_title DESC LIMIT %d, %d;", ( $current_page - 1 ) * $per_page, $per_page ) );
$this->max_items = $wpdb->get_var( "SELECT COUNT( DISTINCT posts.ID ) {$query_from};" );
}
}

View File

@ -0,0 +1,435 @@
<?php
/**
* WC_Report_Sales_By_Category class
*/
class WC_Report_Sales_By_Category extends WC_Admin_Report {
public $show_categories = array();
/**
* Constructor
*/
public function __construct() {
if ( isset( $_GET['show_categories'] ) && is_array( $_GET['show_categories'] ) )
$this->show_categories = array_map( 'absint', $_GET['show_categories'] );
elseif ( isset( $_GET['show_categories'] ) )
$this->show_categories = array( absint( $_GET['show_categories'] ) );
}
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
if ( ! $this->show_categories )
return array();
$legend = array();
$index = 0;
foreach( $this->show_categories as $category ) {
$category = get_term( $category, 'product_cat' );
$term_ids = get_term_children( $category->term_id, 'product_cat' );
$term_ids[] = $category->term_id;
$total = 0;
$product_ids = array_unique( get_objects_in_term( $term_ids, 'product_cat' ) );
foreach ( $product_ids as $id ) {
if ( isset( $this->item_sales[ $id ] ) ) {
$total += $this->item_sales[ $id ];
}
}
//if ( ! $total )
// continue;
$legend[] = array(
'title' => sprintf( __( '%s sales in %s', 'woocommerce' ), '<strong>' . woocommerce_price( $total ) . '</strong>', $category->name ),
'color' => isset( $this->chart_colours[ $index ] ) ? $this->chart_colours[ $index ] : $this->chart_colours[ 0 ],
'highlight_series' => $index
);
$index++;
}
return $legend;
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
'7day' => __( 'Last 7 Days', 'woocommerce' )
);
$this->chart_colours = array( '#3498db', '#34495e', '#1abc9c', '#2ecc71', '#f1c40f', '#e67e22', '#e74c3c', '#2980b9', '#8e44ad', '#2c3e50', '#16a085', '#27ae60', '#f39c12', '#d35400', '#c0392b' );
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 1 months max for day view
if ( $interval > 1 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
case '7day' :
default :
$this->start_date = strtotime( '-6 days', current_time( 'timestamp' ) );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
// Get item sales data
if ( $this->show_categories ) {
$order_items = $this->get_order_report_data( array(
'data' => array(
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
),
'_line_total' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'order_item_amount'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'group_by' => 'ID, product_id',
'query_type' => 'get_results',
'filter_range' => true
) );
$this->item_sales = array();
$this->item_sales_and_times = array();
if ( $order_items ) {
foreach ( $order_items as $order_item ) {
switch ( $this->chart_groupby ) {
case 'day' :
$time = strtotime( date( 'Ymd', strtotime( $order_item->post_date ) ) ) * 1000;
break;
case 'month' :
$time = strtotime( date( 'Ym', strtotime( $order_item->post_date ) ) . '01' ) * 1000;
break;
}
$this->item_sales_and_times[ $time ][ $order_item->product_id ] = isset( $this->item_sales_and_times[ $time ][ $order_item->product_id ] ) ? $this->item_sales_and_times[ $time ][ $order_item->product_id ] + $order_item->order_item_amount : $order_item->order_item_amount;
$this->item_sales[ $order_item->product_id ] = isset( $this->item_sales[ $order_item->product_id ] ) ? $this->item_sales[ $order_item->product_id ] + $order_item->order_item_amount : $order_item->order_item_amount;
}
}
}
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php');
}
/**
* [get_chart_widgets description]
* @return array
*/
public function get_chart_widgets() {
return array(
array(
'title' => __( 'Categories', 'woocommerce' ),
'callback' => array( $this, 'category_widget' )
)
);
}
/**
* Category selection
* @return void
*/
public function category_widget() {
$categories = get_terms( 'product_cat', array( 'orderby' => 'name' ) );
?>
<form method="GET">
<div>
<select multiple="multiple" class="chosen_select" id="show_categories" name="show_categories[]" style="width: 205px;">
<?php
$r = array();
$r['pad_counts'] = 1;
$r['hierarchical'] = 1;
$r['hide_empty'] = 1;
$r['value'] = 'id';
$r['selected'] = $this->show_categories;
include_once( WC()->plugin_path() . '/includes/walkers/class-product-cat-dropdown-walker.php' );
echo woocommerce_walk_category_dropdown_tree( $categories, 0, $r );
?>
</select>
<a href="#" class="select_none"><?php _e( 'None', 'woocommerce' ); ?></a>
<a href="#" class="select_all"><?php _e( 'All', 'woocommerce' ); ?></a>
<input type="submit" class="submit button" value="<?php _e( 'Show', 'woocommerce' ); ?>" />
<input type="hidden" name="range" value="<?php if ( ! empty( $_GET['range'] ) ) echo esc_attr( $_GET['range'] ) ?>" />
<input type="hidden" name="start_date" value="<?php if ( ! empty( $_GET['start_date'] ) ) echo esc_attr( $_GET['start_date'] ) ?>" />
<input type="hidden" name="end_date" value="<?php if ( ! empty( $_GET['end_date'] ) ) echo esc_attr( $_GET['end_date'] ) ?>" />
<input type="hidden" name="page" value="<?php if ( ! empty( $_GET['page'] ) ) echo esc_attr( $_GET['page'] ) ?>" />
<input type="hidden" name="tab" value="<?php if ( ! empty( $_GET['tab'] ) ) echo esc_attr( $_GET['tab'] ) ?>" />
<input type="hidden" name="report" value="<?php if ( ! empty( $_GET['report'] ) ) echo esc_attr( $_GET['report'] ) ?>" />
</div>
<script type="text/javascript">
jQuery(function(){
jQuery("select.chosen_select").chosen();
// Select all/none
jQuery('.select_all').live('click', function() {
jQuery(this).closest( 'div' ).find( 'select option' ).attr( "selected", "selected" );
jQuery(this).closest( 'div' ).find('select').trigger( 'liszt:updated' );
return false;
});
jQuery('.select_none').live('click', function() {
jQuery(this).closest( 'div' ).find( 'select option' ).removeAttr( "selected" );
jQuery(this).closest( 'div' ).find('select').trigger( 'liszt:updated' );
return false;
});
});
</script>
</form>
<?php
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="chart"
data-xaxes="<?php _e( 'Date', 'woocommerce' ); ?>"
data-groupby="<?php echo $this->chart_groupby; ?>"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wp_locale;
if ( ! $this->show_categories ) {
?>
<div class="chart-container">
<p class="chart-prompt"><?php _e( '&larr; Choose a category to view stats', 'woocommerce' ); ?></p>
</div>
<?php
} else {
$include_categories = array();
$chart_data = array();
$chart_ticks = array();
$index = 0;
foreach ( $this->show_categories as $category ) {
$category = get_term( $category, 'product_cat' );
$term_ids = get_term_children( $category->term_id, 'product_cat' );
$term_ids[] = $category->term_id;
$product_ids = array_unique( get_objects_in_term( $term_ids, 'product_cat' ) );
$category_total = 0;
$category_chart_data = array();
for ( $i = 0; $i <= $this->chart_interval; $i ++ ) {
$interval_total = 0;
switch ( $this->chart_groupby ) {
case 'day' :
$time = strtotime( date( 'Ymd', strtotime( "+{$i} DAY", $this->start_date ) ) ) * 1000;
break;
case 'month' :
$time = strtotime( date( 'Ym', strtotime( "+{$i} MONTH", $this->start_date ) ) . '01' ) * 1000;
break;
}
foreach ( $product_ids as $id ) {
if ( isset( $this->item_sales_and_times[ $time ][ $id ] ) ) {
$interval_total += $this->item_sales_and_times[ $time ][ $id ];
$category_total += $this->item_sales_and_times[ $time ][ $id ];
}
}
$category_chart_data[] = array( $time, $interval_total );
}
//if ( ! $category_total )
// continue;
$chart_data[ $category->term_id ]['category'] = $category->name;
$chart_data[ $category->term_id ]['data'] = $category_chart_data;
$index ++;
}
?>
<div class="chart-container">
<div class="chart-placeholder main"></div>
</div>
<script type="text/javascript">
var main_chart;
jQuery(function(){
var drawGraph = function( highlight ) {
var series = [
<?php
$index = 0;
foreach ( $chart_data as $data ) {
$color = isset( $this->chart_colours[ $index ] ) ? $this->chart_colours[ $index ] : $this->chart_colours[0];
$width = $this->barwidth / sizeof( $chart_data );
$offset = ( $width * $index );
$series = $data['data'];
foreach ( $series as $key => $series_data )
$series[ $key ][0] = $series_data[0] + $offset;
echo '{
label: "' . esc_js( $data['category'] ) . '",
data: jQuery.parseJSON( "' . json_encode( $series ) . '" ),
color: "' . $color . '",
bars: { fillColor: "' . $color . '", fill: true, show: true, lineWidth: 1, align: "center", barWidth: ' . $width * 0.75 . ', stack: false },
prepend_tooltip: "' . get_woocommerce_currency_symbol() . '",
enable_tooltip: true,
prepend_label: true
},';
$index++;
}
?>
];
if ( highlight !== 'undefined' && series[ highlight ] ) {
highlight_series = series[ highlight ];
highlight_series.color = '#9c5d90';
if ( highlight_series.bars )
highlight_series.bars.fillColor = '#9c5d90';
if ( highlight_series.lines ) {
highlight_series.lines.lineWidth = 5;
}
}
main_chart = jQuery.plot(
jQuery('.chart-placeholder.main'),
series,
{
legend: {
show: false
},
grid: {
color: '#aaa',
borderColor: 'transparent',
borderWidth: 0,
hoverable: true
},
xaxes: [ {
color: '#aaa',
reserveSpace: true,
position: "bottom",
tickColor: 'transparent',
mode: "time",
timeformat: "<?php if ( $this->chart_groupby == 'day' ) echo '%d %b'; else echo '%b'; ?>",
monthNames: <?php echo json_encode( array_values( $wp_locale->month_abbrev ) ) ?>,
tickLength: 1,
minTickSize: [1, "<?php echo $this->chart_groupby; ?>"],
tickSize: [1, "<?php echo $this->chart_groupby; ?>"],
font: {
color: "#aaa"
}
} ],
yaxes: [
{
min: 0,
tickDecimals: 2,
color: 'transparent',
font: { color: "#aaa" }
}
],
}
);
jQuery('.chart-placeholder').resize();
}
drawGraph();
jQuery('.highlight_series').hover(
function() {
drawGraph( jQuery(this).data('series') );
},
function() {
drawGraph();
}
);
});
</script>
<?php
}
}
}

View File

@ -0,0 +1,483 @@
<?php
/**
* WC_Report_Sales_By_Date class
*/
class WC_Report_Sales_By_Date extends WC_Admin_Report {
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
$legend = array();
$order_totals = $this->get_order_report_data( array(
'data' => array(
'_order_total' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'total_sales'
),
'_order_shipping' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'total_shipping'
),
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders'
)
),
'filter_range' => true
) );
$total_sales = $order_totals->total_sales;
$total_shipping = $order_totals->total_shipping;
$total_orders = absint( $order_totals->total_orders );
$total_items = absint( $this->get_order_report_data( array(
'data' => array(
'_qty' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_qty'
)
),
'query_type' => 'get_var',
'filter_range' => true
) ) );
// Get discount amounts in range
$total_coupons = $this->get_order_report_data( array(
'data' => array(
'discount_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'coupon',
'function' => 'SUM',
'name' => 'discount_amount'
)
),
'where' => array(
array(
'key' => 'order_item_type',
'value' => 'coupon',
'operator' => '='
)
),
'query_type' => 'get_var',
'filter_range' => true
) );
$this->average_sales = $total_sales / ( $this->chart_interval + 1 );
switch ( $this->chart_groupby ) {
case 'day' :
$average_sales_title = sprintf( __( '%s average daily sales', 'woocommerce' ), '<strong>' . woocommerce_price( $this->average_sales ) . '</strong>' );
break;
case 'month' :
$average_sales_title = sprintf( __( '%s average monthly sales', 'woocommerce' ), '<strong>' . woocommerce_price( $this->average_sales ) . '</strong>' );
break;
}
$legend[] = array(
'title' => sprintf( __( '%s sales in this period', 'woocommerce' ), '<strong>' . woocommerce_price( $total_sales ) . '</strong>' ),
'color' => $this->chart_colours['sales_amount'],
'highlight_series' => 5
);
$legend[] = array(
'title' => $average_sales_title,
'color' => $this->chart_colours['average'],
'highlight_series' => 2
);
$legend[] = array(
'title' => sprintf( __( '%s orders placed', 'woocommerce' ), '<strong>' . $total_orders . '</strong>' ),
'color' => $this->chart_colours['order_count'],
'highlight_series' => 1
);
$legend[] = array(
'title' => sprintf( __( '%s items purchased', 'woocommerce' ), '<strong>' . $total_items . '</strong>' ),
'color' => $this->chart_colours['item_count'],
'highlight_series' => 0
);
$legend[] = array(
'title' => sprintf( __( '%s charged for shipping', 'woocommerce' ), '<strong>' . woocommerce_price( $total_shipping ) . '</strong>' ),
'color' => $this->chart_colours['shipping_amount'],
'highlight_series' => 4
);
$legend[] = array(
'title' => sprintf( __( '%s worth of coupons used', 'woocommerce' ), '<strong>' . woocommerce_price( $total_coupons ) . '</strong>' ),
'color' => $this->chart_colours['coupon_amount'],
'highlight_series' => 3
);
return $legend;
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
'7day' => __( 'Last 7 Days', 'woocommerce' )
);
$this->chart_colours = array(
'sales_amount' => '#3498db',
'average' => '#75b9e7',
'order_count' => '#b8c0c5',
'item_count' => '#d4d9dc',
'coupon_amount' => '#e67e22',
'shipping_amount' => '#1abc9c'
);
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 3 months max for day view
if ( $interval > 3 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
case '7day' :
default :
$this->start_date = strtotime( '-6 days', current_time( 'timestamp' ) );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php');
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="chart"
data-xaxes="<?php _e( 'Date', 'woocommerce' ); ?>"
data-exclude_series="2"
data-groupby="<?php echo $this->chart_groupby; ?>"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wp_locale;
// Get orders and dates in range - we want the SUM of order totals, COUNT of order items, COUNT of orders, and the date
$orders = $this->get_order_report_data( array(
'data' => array(
'_order_total' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'total_sales'
),
'_order_shipping' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'total_shipping'
),
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders',
'distinct' => true,
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'group_by' => $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
// Order items
$order_items = $this->get_order_report_data( array(
'data' => array(
'_qty' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_count'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where' => array(
array(
'key' => 'order_item_type',
'value' => 'line_item',
'operator' => '='
)
),
'group_by' => $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
// Get discount amounts in range
$coupons = $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'function' => '',
'name' => 'order_item_name'
),
'discount_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'coupon',
'function' => 'SUM',
'name' => 'discount_amount'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'where' => array(
array(
'key' => 'order_item_type',
'value' => 'coupon',
'operator' => '='
)
),
'group_by' => $this->group_by_query . ', order_item_name',
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
// Prepare data for report
$order_counts = $this->prepare_chart_data( $orders, 'post_date', 'total_orders', $this->chart_interval, $this->start_date, $this->chart_groupby );
$order_item_counts = $this->prepare_chart_data( $order_items, 'post_date', 'order_item_count', $this->chart_interval, $this->start_date, $this->chart_groupby );
$order_amounts = $this->prepare_chart_data( $orders, 'post_date', 'total_sales', $this->chart_interval, $this->start_date, $this->chart_groupby );
$coupon_amounts = $this->prepare_chart_data( $coupons, 'post_date', 'discount_amount', $this->chart_interval, $this->start_date, $this->chart_groupby );
$shipping_amounts = $this->prepare_chart_data( $orders, 'post_date', 'total_shipping', $this->chart_interval, $this->start_date, $this->chart_groupby );
// Encode in json format
$chart_data = json_encode( array(
'order_counts' => array_values( $order_counts ),
'order_item_counts' => array_values( $order_item_counts ),
'order_amounts' => array_values( $order_amounts ),
'coupon_amounts' => array_values( $coupon_amounts ),
'shipping_amounts' => array_values( $shipping_amounts )
) );
?>
<div class="chart-container">
<div class="chart-placeholder main"></div>
</div>
<script type="text/javascript">
var main_chart;
jQuery(function(){
var order_data = jQuery.parseJSON( '<?php echo $chart_data; ?>' );
var drawGraph = function( highlight ) {
var series = [
{
label: "<?php echo esc_js( __( 'Number of items sold', 'woocommerce' ) ) ?>",
data: order_data.order_item_counts,
color: '<?php echo $this->chart_colours['item_count']; ?>',
bars: { fillColor: '<?php echo $this->chart_colours['item_count']; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },
shadowSize: 0,
hoverable: false
},
{
label: "<?php echo esc_js( __( 'Number of orders', 'woocommerce' ) ) ?>",
data: order_data.order_counts,
color: '<?php echo $this->chart_colours['order_count']; ?>',
bars: { fillColor: '<?php echo $this->chart_colours['order_count']; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },
shadowSize: 0,
hoverable: false
},
{
label: "<?php echo esc_js( __( 'Average sales amount', 'woocommerce' ) ) ?>",
data: [ [ <?php echo min( array_keys( $order_amounts ) ); ?>, <?php echo $this->average_sales; ?> ], [ <?php echo max( array_keys( $order_amounts ) ); ?>, <?php echo $this->average_sales; ?> ] ],
yaxis: 2,
color: '<?php echo $this->chart_colours['average']; ?>',
points: { show: false },
lines: { show: true, lineWidth: 2, fill: false },
shadowSize: 0,
hoverable: false
},
{
label: "<?php echo esc_js( __( 'Coupon amount', 'woocommerce' ) ) ?>",
data: order_data.coupon_amounts,
yaxis: 2,
color: '<?php echo $this->chart_colours['coupon_amount']; ?>',
points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },
lines: { show: true, lineWidth: 4, fill: false },
shadowSize: 0,
prepend_tooltip: "<?php echo get_woocommerce_currency_symbol(); ?>"
},
{
label: "<?php echo esc_js( __( 'Shipping amount', 'woocommerce' ) ) ?>",
data: order_data.shipping_amounts,
yaxis: 2,
color: '<?php echo $this->chart_colours['shipping_amount']; ?>',
points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },
lines: { show: true, lineWidth: 4, fill: false },
shadowSize: 0,
prepend_tooltip: "<?php echo get_woocommerce_currency_symbol(); ?>"
},
{
label: "<?php echo esc_js( __( 'Sales amount', 'woocommerce' ) ) ?>",
data: order_data.order_amounts,
yaxis: 2,
color: '<?php echo $this->chart_colours['sales_amount']; ?>',
points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },
lines: { show: true, lineWidth: 4, fill: false },
shadowSize: 0,
prepend_tooltip: "<?php echo get_woocommerce_currency_symbol(); ?>"
}
];
if ( highlight !== 'undefined' && series[ highlight ] ) {
highlight_series = series[ highlight ];
highlight_series.color = '#9c5d90';
if ( highlight_series.bars )
highlight_series.bars.fillColor = '#9c5d90';
if ( highlight_series.lines ) {
highlight_series.lines.lineWidth = 5;
}
}
main_chart = jQuery.plot(
jQuery('.chart-placeholder.main'),
series,
{
legend: {
show: false
},
grid: {
color: '#aaa',
borderColor: 'transparent',
borderWidth: 0,
hoverable: true
},
xaxes: [ {
color: '#aaa',
position: "bottom",
tickColor: 'transparent',
mode: "time",
timeformat: "<?php if ( $this->chart_groupby == 'day' ) echo '%d %b'; else echo '%b'; ?>",
monthNames: <?php echo json_encode( array_values( $wp_locale->month_abbrev ) ) ?>,
tickLength: 1,
minTickSize: [1, "<?php echo $this->chart_groupby; ?>"],
font: {
color: "#aaa"
}
} ],
yaxes: [
{
min: 0,
minTickSize: 1,
tickDecimals: 0,
color: '#d4d9dc',
font: { color: "#aaa" }
},
{
position: "right",
min: 0,
tickDecimals: 2,
alignTicksWithAxis: 1,
color: 'transparent',
font: { color: "#aaa" }
}
],
}
);
jQuery('.chart-placeholder').resize();
}
drawGraph();
jQuery('.highlight_series').hover(
function() {
drawGraph( jQuery(this).data('series') );
},
function() {
drawGraph();
}
);
});
</script>
<?php
}
}

View File

@ -0,0 +1,614 @@
<?php
/**
* WC_Report_Sales_By_Product class
*/
class WC_Report_Sales_By_Product extends WC_Admin_Report {
public $product_ids = array();
/**
* Constructor
*/
public function __construct() {
if ( isset( $_GET['product_ids'] ) && is_array( $_GET['product_ids'] ) )
$this->product_ids = array_map( 'absint', $_GET['product_ids'] );
elseif ( isset( $_GET['product_ids'] ) )
$this->product_ids = array( absint( $_GET['product_ids'] ) );
}
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
if ( ! $this->product_ids )
return array();
$legend = array();
$total_sales = $this->get_order_report_data( array(
'data' => array(
'_line_total' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_amount'
),
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
),
),
'where_meta' => array(
'relation' => 'OR',
array(
'type' => 'order_item_meta',
'meta_key' => '_product_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
),
array(
'type' => 'order_item_meta',
'meta_key' => '_variation_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
)
),
'query_type' => 'get_var',
'filter_range' => true
) );
$total_items = absint( $this->get_order_report_data( array(
'data' => array(
'_qty' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_count'
),
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
),
),
'where_meta' => array(
'relation' => 'OR',
array(
'type' => 'order_item_meta',
'meta_key' => '_product_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
),
array(
'type' => 'order_item_meta',
'meta_key' => '_variation_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
)
),
'group_by' => 'product_id',
'query_type' => 'get_var',
'filter_range' => true
) ) );
$legend[] = array(
'title' => sprintf( __( '%s sales for the selected items', 'woocommerce' ), '<strong>' . woocommerce_price( $total_sales ) . '</strong>' ),
'color' => $this->chart_colours['sales_amount'],
'highlight_series' => 1
);
$legend[] = array(
'title' => sprintf( __( '%s purchases for the selected items', 'woocommerce' ), '<strong>' . $total_items . '</strong>' ),
'color' => $this->chart_colours['item_count'],
'highlight_series' => 0
);
return $legend;
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
'7day' => __( 'Last 7 Days', 'woocommerce' )
);
$this->chart_colours = array(
'sales_amount' => '#3498db',
'item_count' => '#d4d9dc',
);
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 3 months max for day view
if ( $interval > 3 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
case '7day' :
default :
$this->start_date = strtotime( '-6 days', current_time( 'timestamp' ) );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php');
}
/**
* [get_chart_widgets description]
* @return array
*/
public function get_chart_widgets() {
$widgets = array();
if ( ! empty( $this->product_ids ) ) {
$widgets[] = array(
'title' => __( 'Showing reports for:', 'woocommerce' ),
'callback' => array( $this, 'current_filters' )
);
}
$widgets[] = array(
'title' => '',
'callback' => array( $this, 'products_widget' )
);
return $widgets;
}
/**
* Show current filters
* @return void
*/
public function current_filters() {
$this->product_ids_titles = array();
foreach ( $this->product_ids as $product_id ) {
$product = get_product( $product_id );
$this->product_ids_titles[] = $product->get_formatted_name();
}
echo '<p>' . ' <strong>' . implode( ', ', $this->product_ids_titles ) . '</strong></p>';
echo '<p><a class="button" href="' . remove_query_arg( 'product_ids' ) . '">' . __( 'Reset', 'woocommerce' ) . '</a></p>';
}
/**
* Product selection
* @return void
*/
public function products_widget() {
?>
<h4 class="section_title"><span><?php _e( 'Product Search', 'woocommerce' ); ?></span></h4>
<div class="section">
<form method="GET">
<div>
<select id="product_ids" name="product_ids[]" class="ajax_chosen_select_products" multiple="multiple" data-placeholder="<?php _e( 'Search for a product&hellip;', 'woocommerce' ); ?>" style="width:203px;"></select>
<input type="submit" class="submit button" value="<?php _e( 'Show', 'woocommerce' ); ?>" />
<input type="hidden" name="range" value="<?php if ( ! empty( $_GET['range'] ) ) echo esc_attr( $_GET['range'] ) ?>" />
<input type="hidden" name="start_date" value="<?php if ( ! empty( $_GET['start_date'] ) ) echo esc_attr( $_GET['start_date'] ) ?>" />
<input type="hidden" name="end_date" value="<?php if ( ! empty( $_GET['end_date'] ) ) echo esc_attr( $_GET['end_date'] ) ?>" />
<input type="hidden" name="page" value="<?php if ( ! empty( $_GET['page'] ) ) echo esc_attr( $_GET['page'] ) ?>" />
<input type="hidden" name="tab" value="<?php if ( ! empty( $_GET['tab'] ) ) echo esc_attr( $_GET['tab'] ) ?>" />
<input type="hidden" name="report" value="<?php if ( ! empty( $_GET['report'] ) ) echo esc_attr( $_GET['report'] ) ?>" />
</div>
<script type="text/javascript">
jQuery(function(){
// Ajax Chosen Product Selectors
jQuery("select.ajax_chosen_select_products").ajaxChosen({
method: 'GET',
url: '<?php echo admin_url('admin-ajax.php'); ?>',
dataType: 'json',
afterTypeDelay: 100,
data: {
action: 'woocommerce_json_search_products_and_variations',
security: '<?php echo wp_create_nonce("search-products"); ?>'
}
}, function (data) {
var terms = {};
jQuery.each(data, function (i, val) {
terms[i] = val;
});
return terms;
});
});
</script>
</form>
</div>
<h4 class="section_title"><span><?php _e( 'Top Sellers', 'woocommerce' ); ?></span></h4>
<div class="section">
<table cellspacing="0">
<?php
$top_sellers = $this->get_order_report_data( array(
'data' => array(
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
),
'_qty' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_qty'
)
),
'order_by' => 'order_item_qty DESC',
'group_by' => 'product_id',
'limit' => 12,
'query_type' => 'get_results',
'filter_range' => true
) );
if ( $top_sellers ) {
foreach ( $top_sellers as $product ) {
echo '<tr class="' . ( in_array( $product->product_id, $this->product_ids ) ? 'active' : '' ) . '">
<td class="count">' . $product->order_item_qty . '</td>
<td class="name"><a href="' . add_query_arg( 'product_ids', $product->product_id ) . '">' . get_the_title( $product->product_id ) . '</a></td>
<td class="sparkline">' . $this->sales_sparkline( $product->product_id, 14, 'count' ) . '</td>
</tr>';
}
} else {
echo '<tr><td colspan="3">' . __( 'No products found in range', 'woocommerce' ) . '</td></tr>';
}
?>
</table>
</div>
<h4 class="section_title"><span><?php _e( 'Top Earners', 'woocommerce' ); ?></span></h4>
<div class="section">
<table cellspacing="0">
<?php
$top_earners = $this->get_order_report_data( array(
'data' => array(
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
),
'_line_total' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_total'
)
),
'order_by' => 'order_item_total DESC',
'group_by' => 'product_id',
'limit' => 12,
'query_type' => 'get_results',
'filter_range' => true
) );
if ( $top_earners ) {
foreach ( $top_earners as $product ) {
echo '<tr class="' . ( in_array( $product->product_id, $this->product_ids ) ? 'active' : '' ) . '">
<td class="count">' . woocommerce_price( $product->order_item_total ) . '</td>
<td class="name"><a href="' . add_query_arg( 'product_ids', $product->product_id ) . '">' . get_the_title( $product->product_id ) . '</a></td>
<td class="sparkline">' . $this->sales_sparkline( $product->product_id, 14, 'sales' ) . '</td>
</tr>';
}
} else {
echo '<tr><td colspan="3">' . __( 'No products found in range', 'woocommerce' ) . '</td></tr>';
}
?>
</table>
</div>
<script type="text/javascript">
jQuery('.section_title').click(function(){
var next_section = jQuery(this).next('.section');
if ( jQuery(next_section).is(':visible') )
return false;
jQuery('.section:visible').slideUp();
jQuery('.section_title').removeClass('open');
jQuery(this).addClass('open').next('.section').slideDown();
return false;
});
jQuery('.section').slideUp( 100, function() {
<?php if ( empty( $this->product_ids ) ) : ?>
jQuery('.section_title:eq(1)').click();
<?php endif; ?>
});
</script>
<?php
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : '7day';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="chart"
data-xaxes="<?php _e( 'Date', 'woocommerce' ); ?>"
data-groupby="<?php echo $this->chart_groupby; ?>"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wp_locale;
if ( ! $this->product_ids ) {
?>
<div class="chart-container">
<p class="chart-prompt"><?php _e( '&larr; Choose a product to view stats', 'woocommerce' ); ?></p>
</div>
<?php
} else {
// Get orders and dates in range - we want the SUM of order totals, COUNT of order items, COUNT of orders, and the date
$order_item_counts = $this->get_order_report_data( array(
'data' => array(
'_qty' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_count'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
)
),
'where_meta' => array(
'relation' => 'OR',
array(
'type' => 'order_item_meta',
'meta_key' => '_product_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
),
array(
'type' => 'order_item_meta',
'meta_key' => '_variation_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
)
),
'group_by' => 'product_id,' . $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
$order_item_amounts = $this->get_order_report_data( array(
'data' => array(
'_line_total' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => 'SUM',
'name' => 'order_item_amount'
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
'_product_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'line_item',
'function' => '',
'name' => 'product_id'
)
),
'where_meta' => array(
'relation' => 'OR',
array(
'type' => 'order_item_meta',
'meta_key' => '_product_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
),
array(
'type' => 'order_item_meta',
'meta_key' => '_variation_id',
'meta_value' => $this->product_ids,
'operator' => 'IN'
)
),
'group_by' => 'product_id,' . $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
// Prepare data for report
$order_item_counts = $this->prepare_chart_data( $order_item_counts, 'post_date', 'order_item_count', $this->chart_interval, $this->start_date, $this->chart_groupby );
$order_item_amounts = $this->prepare_chart_data( $order_item_amounts, 'post_date', 'order_item_amount', $this->chart_interval, $this->start_date, $this->chart_groupby );
// Encode in json format
$chart_data = json_encode( array(
'order_item_counts' => array_values( $order_item_counts ),
'order_item_amounts' => array_values( $order_item_amounts )
) );
?>
<div class="chart-container">
<div class="chart-placeholder main"></div>
</div>
<script type="text/javascript">
var main_chart;
jQuery(function(){
var order_data = jQuery.parseJSON( '<?php echo $chart_data; ?>' );
var drawGraph = function( highlight ) {
var series = [
{
label: "<?php echo esc_js( __( 'Number of items sold', 'woocommerce' ) ) ?>",
data: order_data.order_item_counts,
color: '<?php echo $this->chart_colours['item_count']; ?>',
bars: { fillColor: '<?php echo $this->chart_colours['item_count']; ?>', fill: true, show: true, lineWidth: 0, barWidth: <?php echo $this->barwidth; ?> * 0.5, align: 'center' },
shadowSize: 0,
hoverable: false
},
{
label: "<?php echo esc_js( __( 'Sales amount', 'woocommerce' ) ) ?>",
data: order_data.order_item_amounts,
yaxis: 2,
color: '<?php echo $this->chart_colours['sales_amount']; ?>',
points: { show: true, radius: 5, lineWidth: 3, fillColor: '#fff', fill: true },
lines: { show: true, lineWidth: 4, fill: false },
shadowSize: 0,
prepend_tooltip: "<?php echo get_woocommerce_currency_symbol(); ?>"
}
];
if ( highlight !== 'undefined' && series[ highlight ] ) {
highlight_series = series[ highlight ];
highlight_series.color = '#9c5d90';
if ( highlight_series.bars )
highlight_series.bars.fillColor = '#9c5d90';
if ( highlight_series.lines ) {
highlight_series.lines.lineWidth = 5;
}
}
main_chart = jQuery.plot(
jQuery('.chart-placeholder.main'),
series,
{
legend: {
show: false
},
grid: {
color: '#aaa',
borderColor: 'transparent',
borderWidth: 0,
hoverable: true
},
xaxes: [ {
color: '#aaa',
position: "bottom",
tickColor: 'transparent',
mode: "time",
timeformat: "<?php if ( $this->chart_groupby == 'day' ) echo '%d %b'; else echo '%b'; ?>",
monthNames: <?php echo json_encode( array_values( $wp_locale->month_abbrev ) ) ?>,
tickLength: 1,
minTickSize: [1, "<?php echo $this->chart_groupby; ?>"],
font: {
color: "#aaa"
}
} ],
yaxes: [
{
min: 0,
minTickSize: 1,
tickDecimals: 0,
color: '#ecf0f1',
font: { color: "#aaa" }
},
{
position: "right",
min: 0,
tickDecimals: 2,
alignTicksWithAxis: 1,
color: 'transparent',
font: { color: "#aaa" }
}
],
}
);
jQuery('.chart-placeholder').resize();
}
drawGraph();
jQuery('.highlight_series').hover(
function() {
drawGraph( jQuery(this).data('series') );
},
function() {
drawGraph();
}
);
});
</script>
<?php
}
}
}

View File

@ -0,0 +1,168 @@
<?php
if ( ! defined( 'ABSPATH' ) )
exit; // Exit if accessed directly
if ( ! class_exists( 'WP_List_Table' ) )
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
/**
* WC_Report_Stock class
*/
class WC_Report_Stock extends WP_List_Table {
/**
* __construct function.
*
* @access public
*/
public function __construct(){
parent::__construct( array(
'singular' => __( 'Stock', 'woocommerce' ),
'plural' => __( 'Stock', 'woocommerce' ),
'ajax' => false
) );
}
/**
* No items found text
*/
public function no_items() {
_e( 'No products found.', 'woocommerce' );
}
/**
* Don't need this
*/
public function display_tablenav( $position ) {
if ( $position != 'top' )
parent::display_tablenav( $position );
}
/**
* Output the report
*/
public function output_report() {
$this->prepare_items();
echo '<div id="poststuff" class="woocommerce-reports-wide">';
$this->display();
echo '</div>';
}
/**
* column_default function.
*
* @access public
* @param mixed $item
* @param mixed $column_name
*/
function column_default( $item, $column_name ) {
global $woocommerce, $wpdb, $product;
if ( ! $product || $product->id !== $item->id )
$product = get_product( $item->id );
switch( $column_name ) {
case 'product' :
if ( $sku = $product->get_sku() )
echo $sku . ' - ';
echo $product->get_title();
// Get variation data
if ( $product->is_type( 'variation' ) ) {
$list_attributes = array();
$attributes = $product->get_variation_attributes();
foreach ( $attributes as $name => $attribute ) {
$list_attributes[] = $woocommerce->get_helper( 'attribute' )->attribute_label( str_replace( 'attribute_', '', $name ) ) . ': <strong>' . $attribute . '</strong>';
}
echo '<div class="description">' . implode( ', ', $list_attributes ) . '</div>';
}
break;
case 'parent' :
if ( $item->parent )
echo get_the_title( $item->parent );
else
echo '-';
break;
case 'stock_status' :
if ( $product->is_in_stock() ) {
echo '<mark class="instock">' . __( 'In stock', 'woocommerce' ) . '</mark>';
} else {
echo '<mark class="outofstock">' . __( 'Out of stock', 'woocommerce' ) . '</mark>';
}
break;
case 'stock_level' :
echo $product->get_stock_quantity();
break;
case 'wc_actions' :
?><p>
<?php
$actions = array();
$action_id = $product->is_type( 'variation' ) ? $item->parent : $item->id;
$actions['edit'] = array(
'url' => admin_url( 'post.php?post=' . $action_id . '&action=edit' ),
'name' => __( 'Edit', 'woocommerce' ),
'action' => "edit"
);
if ( $product->is_visible() )
$actions['view'] = array(
'url' => get_permalink( $action_id ),
'name' => __( 'View', 'woocommerce' ),
'action' => "view"
);
$actions = apply_filters( 'woocommerce_admin_stock_report_product_actions', $actions, $product );
foreach ( $actions as $action ) {
$image = ( isset( $action['image_url'] ) ) ? $action['image_url'] : $woocommerce->plugin_url() . '/assets/images/icons/' . $action['action'] . '.png';
printf( '<a class="button tips" href="%s" data-tip="%s"><img src="%s" alt="%s" width="14" /></a>', esc_url( $action['url'] ), esc_attr( $action['name'] ), esc_attr( $image ), esc_attr( $action['name'] ) );
}
?>
</p><?php
break;
}
}
/**
* get_columns function.
*
* @access public
*/
function get_columns(){
$columns = array(
'product' => __( 'Product', 'woocommerce' ),
'parent' => __( 'Parent', 'woocommerce' ),
'stock_level' => __( 'Units in stock', 'woocommerce' ),
'stock_status' => __( 'Stock status', 'woocommerce' ),
'wc_actions' => __( 'Actions', 'woocommerce' ),
);
return $columns;
}
/**
* prepare_items function.
*
* @access public
*/
public function prepare_items() {
$this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns() );
$current_page = absint( $this->get_pagenum() );
$per_page = 20;
$this->get_items( $current_page, $per_page );
/**
* Pagination
*/
$this->set_pagination_args( array(
'total_items' => $this->max_items,
'per_page' => $per_page,
'total_pages' => ceil( $this->max_items / $per_page )
) );
}
}

View File

@ -0,0 +1,213 @@
<?php
/**
* WC_Report_Taxes_By_Code class
*/
class WC_Report_Taxes_By_Code extends WC_Admin_Report {
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
$legend = array();
return array();
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : 'last_month';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="table"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
);
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : 'last_month';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 3 months max for day view
if ( $interval > 3 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
default :
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
$hide_sidebar = true;
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php');
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wpdb;
$tax_rows = $this->get_order_report_data( array(
'data' => array(
'order_item_name' => array(
'type' => 'order_item',
'function' => '',
'name' => 'tax_rate'
),
'tax_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'tax',
'function' => 'SUM',
'name' => 'tax_amount'
),
'shipping_tax_amount' => array(
'type' => 'order_item_meta',
'order_item_type' => 'tax',
'function' => 'SUM',
'name' => 'shipping_tax_amount'
),
'rate_id' => array(
'type' => 'order_item_meta',
'order_item_type' => 'tax',
'function' => '',
'name' => 'rate_id'
),
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders',
'distinct' => true,
),
),
'where' => array(
array(
'key' => 'order_item_type',
'value' => 'tax',
'operator' => '='
),
array(
'key' => 'order_item_name',
'value' => '',
'operator' => '!='
)
),
'group_by' => 'tax_rate',
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
?>
<table class="widefat">
<thead>
<tr>
<th><?php _e( 'Tax', 'woocommerce' ); ?></th>
<th><?php _e( 'Rate', 'woocommerce' ); ?></th>
<th class="total_row"><?php _e( 'Number of orders', 'woocommerce' ); ?></th>
<th class="total_row"><?php _e( 'Tax Amount', 'woocommerce' ); ?> <a class="tips" data-tip="<?php esc_attr_e( 'This is the sum of the "Tax Rows" tax amount within your orders.', 'woocommerce' ); ?>" href="#">[?]</a></th>
<th class="total_row"><?php _e( 'Shipping Tax Amount', 'woocommerce' ); ?> <a class="tips" data-tip="<?php esc_attr_e( 'This is the sum of the "Tax Rows" shipping tax amount within your orders.', 'woocommerce' ); ?>" href="#">[?]</a></th>
<th class="total_row"><?php _e( 'Total Tax', 'woocommerce' ); ?> <a class="tips" data-tip="<?php esc_attr_e( 'This is the total tax for the rate (shipping tax + product tax).', 'woocommerce' ); ?>" href="#">[?]</a></th>
</tr>
</thead>
<?php if ( $tax_rows ) : ?>
<tfoot>
<tr>
<th scope="row" colspan="3"><?php _e( 'Total', 'woocommerce' ); ?></th>
<th class="total_row"><?php echo woocommerce_price( array_sum( wp_list_pluck( (array) $tax_rows, 'tax_amount' ) ) ); ?></th>
<th class="total_row"><?php echo woocommerce_price( array_sum( wp_list_pluck( (array) $tax_rows, 'shipping_tax_amount' ) ) ); ?></th>
<th class="total_row"><strong><?php echo woocommerce_price( array_sum( wp_list_pluck( (array) $tax_rows, 'tax_amount' ) ) + array_sum( wp_list_pluck( (array) $tax_rows, 'shipping_tax_amount' ) ) ); ?></strong></th>
</tr>
</tfoot>
<tbody>
<?php
foreach ( $tax_rows as $tax_row ) {
$rate = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d;", $tax_row->rate_id ) );
?>
<tr>
<th scope="row"><?php echo $tax_row->tax_rate; ?></th>
<td><?php echo $rate; ?>%</td>
<td class="total_row"><?php echo $tax_row->total_orders; ?></td>
<td class="total_row"><?php echo woocommerce_price( $tax_row->tax_amount ); ?></td>
<td class="total_row"><?php echo woocommerce_price( $tax_row->shipping_tax_amount ); ?></td>
<td class="total_row"><?php echo woocommerce_price( $tax_row->tax_amount + $tax_row->shipping_tax_amount ); ?></td>
</tr>
<?php
}
?>
</tbody>
<?php else : ?>
<tbody>
<tr>
<td><?php _e( 'No taxes found in this period', 'woocommerce' ); ?></td>
</tr>
</tbody>
<?php endif; ?>
</table>
<?php
}
}

View File

@ -0,0 +1,214 @@
<?php
/**
* WC_Report_Taxes_By_Date class
*/
class WC_Report_Taxes_By_Date extends WC_Admin_Report {
/**
* Get the legend for the main chart sidebar
* @return array
*/
public function get_chart_legend() {
$legend = array();
return array();
}
/**
* Output an export link
*/
public function get_export_button() {
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : 'last_month';
?>
<a
href="#"
download="report-<?php echo $current_range; ?>-<?php echo date_i18n( 'Y-m-d', current_time('timestamp') ); ?>.csv"
class="export_csv"
data-export="table"
>
<?php _e( 'Export CSV', 'woocommerce' ); ?>
</a>
<?php
}
/**
* Output the report
*/
public function output_report() {
global $woocommerce, $wpdb, $wp_locale;
$ranges = array(
'year' => __( 'Year', 'woocommerce' ),
'last_month' => __( 'Last Month', 'woocommerce' ),
'month' => __( 'This Month', 'woocommerce' ),
);
$current_range = ! empty( $_GET['range'] ) ? $_GET['range'] : 'last_month';
switch ( $current_range ) {
case 'custom' :
$this->start_date = strtotime( sanitize_text_field( $_GET['start_date'] ) );
$this->end_date = strtotime( '12am + 1 day', strtotime( sanitize_text_field( $_GET['end_date'] ) ) );
if ( ! $this->end_date )
$this->end_date = current_time('timestamp');
$interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$interval ++;
}
// 3 months max for day view
if ( $interval > 3 )
$this->chart_groupby = 'month';
else
$this->chart_groupby = 'day';
break;
case 'year' :
$this->start_date = strtotime( 'first day of january', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'month';
break;
default :
case 'last_month' :
$this->start_date = strtotime( 'first day of last month', current_time('timestamp') );
$this->end_date = strtotime( 'last day of last month', current_time('timestamp') );
$this->chart_groupby = 'day';
break;
case 'month' :
$this->start_date = strtotime( 'first day of this month', current_time('timestamp') );
$this->end_date = strtotime( '12am + 1 day', current_time( 'timestamp' ) );
$this->chart_groupby = 'day';
break;
}
// Group by
switch ( $this->chart_groupby ) {
case 'day' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date), DAY(post_date)';
$this->chart_interval = max( 0, ( $this->end_date - $this->start_date ) / ( 60 * 60 * 24 ) );
$this->barwidth = 60 * 60 * 24 * 1000;
break;
case 'month' :
$this->group_by_query = 'YEAR(post_date), MONTH(post_date)';
$this->chart_interval = 0;
$min_date = $this->start_date;
while ( ( $min_date = strtotime( "+1 MONTH", $min_date ) ) <= $this->end_date ) {
$this->chart_interval ++;
}
$this->barwidth = 60 * 60 * 24 * 7 * 4 * 1000;
break;
}
$hide_sidebar = true;
include( WC()->plugin_path() . '/admin/views/html-report-by-date.php');
}
/**
* Get the main chart
* @return string
*/
public function get_main_chart() {
global $wpdb;
$tax_rows = $this->get_order_report_data( array(
'data' => array(
'_order_tax' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'tax_amount'
),
'_order_shipping_tax' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'shipping_tax_amount'
),
'_order_total' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'total_sales'
),
'_order_shipping' => array(
'type' => 'meta',
'function' => 'SUM',
'name' => 'total_shipping'
),
'ID' => array(
'type' => 'post_data',
'function' => 'COUNT',
'name' => 'total_orders',
'distinct' => true,
),
'post_date' => array(
'type' => 'post_data',
'function' => '',
'name' => 'post_date'
),
),
'group_by' => $this->group_by_query,
'order_by' => 'post_date ASC',
'query_type' => 'get_results',
'filter_range' => true
) );
?>
<table class="widefat">
<thead>
<tr>
<th><?php _e( 'Period', 'woocommerce' ); ?></th>
<th class="total_row"><?php _e( 'Number of orders', 'woocommerce' ); ?></th>
<th class="total_row"><?php _e( 'Total Sales', 'woocommerce' ); ?> <a class="tips" data-tip="<?php _e("This is the sum of the 'Order Total' field within your orders.", 'woocommerce'); ?>" href="#">[?]</a></th>
<th class="total_row"><?php _e( 'Total Shipping', 'woocommerce' ); ?> <a class="tips" data-tip="<?php _e("This is the sum of the 'Shipping Total' field within your orders.", 'woocommerce'); ?>" href="#">[?]</a></th>
<th class="total_row"><?php _e( 'Total Tax', 'woocommerce' ); ?> <a class="tips" data-tip="<?php esc_attr_e( 'This is the total tax for the rate (shipping tax + product tax).', 'woocommerce' ); ?>" href="#">[?]</a></th>
<th class="total_row"><?php _e( 'Net profit', 'woocommerce' ); ?> <a class="tips" data-tip="<?php _e("Total sales minus shipping and tax.", 'woocommerce'); ?>" href="#">[?]</a></th>
</tr>
</thead>
<?php if ( $tax_rows ) :
$gross = array_sum( wp_list_pluck( (array) $tax_rows, 'total_sales' ) ) - array_sum( wp_list_pluck( (array) $tax_rows, 'total_shipping' ) );
$total_tax = array_sum( wp_list_pluck( (array) $tax_rows, 'tax_amount' ) ) - array_sum( wp_list_pluck( (array) $tax_rows, 'shipping_tax_amount' ) );
?>
<tfoot>
<tr>
<th scope="row"><?php _e( 'Totals', 'woocommerce' ); ?></th>
<th class="total_row"><?php echo array_sum( wp_list_pluck( (array) $tax_rows, 'total_orders' ) ); ?></th>
<th class="total_row"><?php echo woocommerce_price( $gross ); ?></th>
<th class="total_row"><?php echo woocommerce_price( array_sum( wp_list_pluck( (array) $tax_rows, 'total_shipping' ) ) ); ?></th>
<th class="total_row"><?php echo woocommerce_price( $total_tax ); ?></th>
<th class="total_row"><?php echo woocommerce_price( $gross - $total_tax ); ?></th>
</tr>
</tfoot>
<tbody>
<?php
foreach ( $tax_rows as $tax_row ) {
$gross = $tax_row->total_sales - $tax_row->total_shipping;
$total_tax = $tax_row->tax_amount + $tax_row->shipping_tax_amount;
?>
<tr>
<th scope="row"><?php
if ( $this->chart_groupby == 'month' )
echo date_i18n( 'F', strtotime( $tax_row->post_date ) );
else
echo date_i18n( get_option( 'date_format' ), strtotime( $tax_row->post_date ) );
?></th>
<td class="total_row"><?php echo $tax_row->total_orders; ?></td>
<td class="total_row"><?php echo woocommerce_price( $gross ); ?></td>
<td class="total_row"><?php echo woocommerce_price( $tax_row->total_shipping ); ?></td>
<td class="total_row"><?php echo woocommerce_price( $total_tax ); ?></td>
<td class="total_row"><?php echo woocommerce_price( $gross - $total_tax ); ?></td>
</tr>
<?php
}
?>
</tbody>
<?php else : ?>
<tbody>
<tr>
<td><?php _e( 'No taxes found in this period', 'woocommerce' ); ?></td>
</tr>
</tbody>
<?php endif; ?>
</table>
<?php
}
}

View File

@ -0,0 +1,54 @@
<div class="wrap woocommerce">
<div class="icon32 icon32-woocommerce-reports" id="icon-woocommerce"><br /></div><h2 class="nav-tab-wrapper woo-nav-tab-wrapper">
<?php
foreach ( $reports as $key => $report_group ) {
echo '<a href="'.admin_url( 'admin.php?page=wc_reports&tab=' . urlencode( $key ) ).'" class="nav-tab ';
if ( $current_tab == $key ) echo 'nav-tab-active';
echo '">' . esc_html( $report_group[ 'title' ] ) . '</a>';
}
?>
<?php do_action('wc_reports_tabs'); ?>
</h2>
<?php if ( sizeof( $reports[ $current_tab ]['reports'] ) > 1 ) {
?>
<ul class="subsubsub">
<li><?php
$links = array();
foreach ( $reports[ $current_tab ]['reports'] as $key => $report ) {
$link = '<a href="admin.php?page=wc_reports&tab=' . urlencode( $current_tab ) . '&amp;report=' . urlencode( $key ) . '" class="';
if ( $key == $current_report ) $link .= 'current';
$link .= '">' . $report['title'] . '</a>';
$links[] = $link;
}
echo implode(' | </li><li>', $links);
?></li>
</ul>
<br class="clear" />
<?php
}
if ( isset( $reports[ $current_tab ][ 'reports' ][ $current_report ] ) ) {
$report = $reports[ $current_tab ][ 'reports' ][ $current_report ];
if ( ! isset( $report['hide_title'] ) || $report['hide_title'] != true )
echo '<h3>' . $report['title'] . '</h3>';
if ( $report['description'] )
echo '<p>' . $report['description'] . '</p>';
if ( $report['callback'] && ( is_callable( $report['callback'] ) ) )
call_user_func( $report['callback'], $current_report );
}
?>
</div>

View File

@ -0,0 +1,68 @@
<?php
/**
* HTML for a report with date filters in admin.
*/
?>
<div id="poststuff" class="woocommerce-reports-wide">
<div class="postbox">
<h3 class="stats_range">
<?php $this->get_export_button(); ?>
<ul>
<?php
foreach ( $ranges as $range => $name )
echo '<li class="' . ( $current_range == $range ? 'active' : '' ) . '"><a href="' . remove_query_arg( array( 'start_date', 'end_date' ), add_query_arg( 'range', $range ) ) . '">' . $name . '</a></li>';
?>
<li class="custom <?php echo $current_range == 'custom' ? 'active' : ''; ?>">
<?php _e( 'Custom:', 'woocommerce' ); ?>
<form method="GET">
<div>
<?php
// Maintain query string
foreach ( $_GET as $key => $value )
if ( is_array( $value ) )
foreach ( $value as $v )
echo '<input type="hidden" name="' . esc_attr( sanitize_text_field( $key ) ) . '[]" value="' . esc_attr( sanitize_text_field( $v ) ) . '" />';
else
echo '<input type="hidden" name="' . esc_attr( sanitize_text_field( $key ) ) . '" value="' . esc_attr( sanitize_text_field( $value ) ) . '" />';
?>
<input type="hidden" name="range" value="custom" />
<input type="text" size="9" placeholder="yyyy-mm-dd" value="<?php if ( ! empty( $_GET['start_date'] ) ) echo esc_attr( $_GET['start_date'] ); ?>" name="start_date" class="range_datepicker from" />
<input type="text" size="9" placeholder="yyyy-mm-dd" value="<?php if ( ! empty( $_GET['end_date'] ) ) echo esc_attr( $_GET['end_date'] ); ?>" name="end_date" class="range_datepicker to" />
<input type="submit" class="button" value="<?php _e( 'Go', 'woocommerce' ); ?>" />
</div>
</form>
</li>
</ul>
</h3>
<?php if ( empty( $hide_sidebar ) ) : ?>
<div class="inside chart-with-sidebar">
<div class="chart-sidebar">
<?php if ( $legends = $this->get_chart_legend() ) : ?>
<ul class="chart-legend">
<?php foreach ( $legends as $legend ) : ?>
<li style="border-color: <?php echo $legend['color']; ?>" <?php if ( isset( $legend['highlight_series'] ) ) echo 'class="highlight_series" data-series="' . $legend['highlight_series'] . '"'; ?>>
<?php echo $legend['title']; ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<ul class="chart-widgets">
<?php foreach ( $this->get_chart_widgets() as $widget ) : ?>
<li class="chart-widget">
<?php if ( $widget['title'] ) : ?><h4><?php echo $widget['title']; ?></h4><?php endif; ?>
<?php call_user_func( $widget['callback'] ); ?>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="main">
<?php $this->get_main_chart(); ?>
</div>
</div>
<?php else : ?>
<div class="inside">
<?php $this->get_main_chart(); ?>
</div>
<?php endif; ?>
</div>
</div>

View File

@ -46,7 +46,7 @@ function woocommerce_init_dashboard_widgets() {
}
if ( current_user_can( 'view_woocommerce_reports' ) || current_user_can( 'publish_shop_orders' ) ) {
wp_add_dashboard_widget( 'woocommerce_dashboard_sales', $sales_heading, 'woocommerce_dashboard_sales' );
//wp_add_dashboard_widget( 'woocommerce_dashboard_sales', $sales_heading, 'woocommerce_dashboard_sales' );
}
}
@ -286,176 +286,4 @@ function woocommerce_dashboard_recent_reviews() {
} else {
echo '<p>' . __( 'There are no product reviews yet.', 'woocommerce' ) . '</p>';
}
}
/**
* Orders this month filter function - filters orders for the month
*
* @access public
* @param string $where (default: '')
* @return void
*/
function orders_this_month( $where = '' ) {
global $the_month_num, $the_year;
$month = $the_month_num;
$year = (int) $the_year;
$first_day = strtotime("{$year}-{$month}-01");
//$last_day = strtotime('-1 second', strtotime('+1 month', $first_day));
$last_day = strtotime('+1 month', $first_day);
$after = date('Y-m-d', $first_day);
$before = date('Y-m-d', $last_day);
$where .= " AND post_date > '$after'";
$where .= " AND post_date < '$before'";
return $where;
}
/**
* Sales widget
*
* @access public
* @return void
*/
function woocommerce_dashboard_sales() {
add_action( 'admin_footer', 'woocommerce_dashboard_sales_js' );
?><div id="placeholder" style="width:100%; height:300px; position:relative;"></div><?php
}
/**
* Sales widget javascript
*
* @access public
* @return void
*/
function woocommerce_dashboard_sales_js() {
global $woocommerce, $wp_locale;
$screen = get_current_screen();
if (!$screen || $screen->id!=='dashboard') return;
global $current_month_offset, $the_month_num, $the_year;
// Get orders to display in widget
add_filter( 'posts_where', 'orders_this_month' );
$args = array(
'numberposts' => -1,
'orderby' => 'post_date',
'order' => 'DESC',
'post_type' => 'shop_order',
'post_status' => 'publish' ,
'suppress_filters' => false,
'tax_query' => array(
array(
'taxonomy' => 'shop_order_status',
'terms' => apply_filters( 'woocommerce_reports_order_statuses', array( 'completed', 'processing', 'on-hold' ) ),
'field' => 'slug',
'operator' => 'IN'
)
)
);
$orders = get_posts( $args );
$order_counts = array();
$order_amounts = array();
// Blank date ranges to begin
$month = $the_month_num;
$year = (int) $the_year;
$first_day = strtotime("{$year}-{$month}-01");
$last_day = strtotime('-1 second', strtotime('+1 month', $first_day));
if ((date('m') - $the_month_num)==0) :
$up_to = date('d', strtotime('NOW'));
else :
$up_to = date('d', $last_day);
endif;
$count = 0;
while ($count < $up_to) :
$time = strtotime(date('Ymd', strtotime('+ '.$count.' DAY', $first_day))).'000';
$order_counts[$time] = 0;
$order_amounts[$time] = 0;
$count++;
endwhile;
if ($orders) :
foreach ($orders as $order) :
$order_data = new WC_Order($order->ID);
if ($order_data->status=='cancelled' || $order_data->status=='refunded') continue;
$time = strtotime(date('Ymd', strtotime($order->post_date))).'000';
if (isset($order_counts[$time])) :
$order_counts[$time]++;
else :
$order_counts[$time] = 1;
endif;
if (isset($order_amounts[$time])) :
$order_amounts[$time] = $order_amounts[$time] + $order_data->order_total;
else :
$order_amounts[$time] = (float) $order_data->order_total;
endif;
endforeach;
endif;
remove_filter( 'posts_where', 'orders_this_month' );
/* Script variables */
$params = array(
'currency_format_num_decimals' => absint( get_option( 'woocommerce_price_num_decimals' ) ),
'currency_format_symbol' => get_woocommerce_currency_symbol(),
'currency_format_decimal_sep' => esc_attr( stripslashes( get_option( 'woocommerce_price_decimal_sep' ) ) ),
'currency_format_thousand_sep' => esc_attr( stripslashes( get_option( 'woocommerce_price_thousand_sep' ) ) ),
'currency_format' => esc_attr( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_price_format() ) ),
'number_of_sales' => absint( array_sum( $order_counts ) ),
'sales_amount' => woocommerce_price( array_sum( $order_amounts ) ),
'i18n_sold' => __( 'Sold', 'woocommerce' ),
'i18n_earned' => __( 'Earned', 'woocommerce' ),
'i18n_month_names' => array_values( $wp_locale->month_abbrev ),
);
$order_counts_array = array();
foreach ($order_counts as $key => $count) :
$order_counts_array[] = array($key, $count);
endforeach;
$order_amounts_array = array();
foreach ($order_amounts as $key => $amount) :
$order_amounts_array[] = array($key, $amount);
endforeach;
$order_data = array( 'order_counts' => $order_counts_array, 'order_amounts' => $order_amounts_array );
$params['order_data'] = json_encode($order_data);
// Queue scripts
$suffix = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min';
wp_register_script( 'woocommerce_dashboard_sales', $woocommerce->plugin_url() . '/assets/js/admin/dashboard_sales' . $suffix . '.js', array( 'jquery', 'flot', 'flot-resize', 'accounting' ), $woocommerce->version );
wp_register_script( 'flot', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot'.$suffix.'.js', 'jquery', '1.0' );
wp_register_script( 'flot-resize', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot.resize'.$suffix.'.js', 'jquery', '1.0' );
wp_localize_script( 'woocommerce_dashboard_sales', 'params', $params );
wp_print_scripts( 'woocommerce_dashboard_sales' );
}

View File

@ -12,6 +12,12 @@
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
include_once( 'class-wc-admin-reports.php' );
/**
* Functions for the product post type
*/
@ -57,26 +63,23 @@ function woocommerce_admin_menu() {
global $menu, $woocommerce;
if ( current_user_can( 'manage_woocommerce' ) )
$menu[] = array( '', 'read', 'separator-woocommerce', '', 'wp-menu-separator woocommerce' );
$menu[] = array( '', 'read', 'separator-woocommerce', '', 'wp-menu-separator woocommerce' );
$main_page = add_menu_page( __( 'WooCommerce', 'woocommerce' ), __( 'WooCommerce', 'woocommerce' ), 'manage_woocommerce', 'woocommerce' , 'woocommerce_settings_page', null, '55.5' );
$reports_page = add_submenu_page( 'woocommerce', __( 'Reports', 'woocommerce' ), __( 'Reports', 'woocommerce' ) , 'view_woocommerce_reports', 'woocommerce_reports', 'woocommerce_reports_page' );
add_submenu_page( 'edit.php?post_type=product', __( 'Attributes', 'woocommerce' ), __( 'Attributes', 'woocommerce' ), 'manage_product_terms', 'woocommerce_attributes', 'woocommerce_attributes_page');
add_action( 'load-' . $main_page, 'woocommerce_admin_help_tab' );
add_action( 'load-' . $reports_page, 'woocommerce_admin_help_tab' );
$wc_screen_id = strtolower( __( 'WooCommerce', 'woocommerce' ) );
$print_css_on = apply_filters( 'woocommerce_screen_ids', array( 'toplevel_page_' . $wc_screen_id, $wc_screen_id . '_page_woocommerce_settings', $wc_screen_id . '_page_woocommerce_reports', $wc_screen_id . '_page_woocommerce_status', $wc_screen_id . '_page_woocommerce_customers', 'toplevel_page_woocommerce', 'woocommerce_page_woocommerce_settings', 'woocommerce_page_woocommerce_reports', 'woocommerce_page_woocommerce_customers', 'woocommerce_page_woocommerce_status', 'product_page_woocommerce_attributes', 'edit-tags.php', 'edit.php', 'index.php', 'post-new.php', 'post.php' ) );
$print_css_on = apply_filters( 'woocommerce_screen_ids', array( 'toplevel_page_' . $wc_screen_id, $wc_screen_id . '_page_woocommerce_settings', $wc_screen_id . '_page_woocommerce_status', $wc_screen_id . '_page_woocommerce_customers', 'toplevel_page_woocommerce', 'woocommerce_page_woocommerce_settings', 'woocommerce_page_woocommerce_customers', 'woocommerce_page_woocommerce_status', 'product_page_woocommerce_attributes', 'edit-tags.php', 'edit.php', 'index.php', 'post-new.php', 'post.php' ) );
foreach ( $print_css_on as $page )
add_action( 'admin_print_styles-'. $page, 'woocommerce_admin_css' );
}
add_action('admin_menu', 'woocommerce_admin_menu', 9);
add_action( 'admin_menu', 'woocommerce_admin_menu', 9 );
/**
* Setup the Admin menu in WordPress - later priority so they appear last
@ -85,7 +88,6 @@ add_action('admin_menu', 'woocommerce_admin_menu', 9);
* @return void
*/
function woocommerce_admin_menu_after() {
$customers_page = add_submenu_page( 'woocommerce', __( 'Customers', 'woocommerce' ), __( 'Customers', 'woocommerce' ) , 'manage_woocommerce', 'woocommerce_customers', 'woocommerce_customers_page' );
$settings_page = add_submenu_page( 'woocommerce', __( 'WooCommerce Settings', 'woocommerce' ), __( 'Settings', 'woocommerce' ) , 'manage_woocommerce', 'woocommerce_settings', 'woocommerce_settings_page');
$status_page = add_submenu_page( 'woocommerce', __( 'WooCommerce Status', 'woocommerce' ), __( 'System Status', 'woocommerce' ) , 'manage_woocommerce', 'woocommerce_status', 'woocommerce_status_page');
@ -132,9 +134,9 @@ function woocommerce_admin_menu_highlight() {
}
}
if ( isset( $submenu['woocommerce'] ) && isset( $submenu['woocommerce'][2] ) ) {
$submenu['woocommerce'][0] = $submenu['woocommerce'][2];
unset( $submenu['woocommerce'][2] );
if ( isset( $submenu['woocommerce'] ) && isset( $submenu['woocommerce'][1] ) ) {
$submenu['woocommerce'][0] = $submenu['woocommerce'][1];
unset( $submenu['woocommerce'][1] );
}
// Sort out Orders menu when on the top level
@ -328,49 +330,6 @@ function woocommerce_settings_page() {
woocommerce_settings();
}
/**
* Include and display the customers page.
*
* @access public
* @return void
*/
function woocommerce_customers_page() {
include_once( 'woocommerce-admin-customers.php' );
$WC_Admin_Customers = new WC_Admin_Customers();
$WC_Admin_Customers->prepare_items();
?>
<div class="wrap">
<div id="icon-woocommerce" class="icon32 icon32-woocommerce-users"><br/></div>
<h2><?php _e( 'Customers', 'wc_software' ); ?></h2>
<?php
if ( ! empty( $_GET['link_orders'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'link_orders' ) ) {
$linked = woocommerce_update_new_customer_past_orders( absint( $_GET['link_orders'] ) );
echo '<div class="updated"><p>' . sprintf( _n( '%s previous order linked', '%s previous orders linked', $linked, 'woocommerce' ), $linked ) . '</p></div>';
}
?>
<form method="post" id="woocommerce_customers">
<?php $WC_Admin_Customers->search_box( __( 'Search customers', 'woocommerce' ), 'customer_search' ); ?>
<?php $WC_Admin_Customers->display() ?>
</form>
</div>
<?php
}
/**
* Include and display the reports page.
*
* @access public
* @return void
*/
function woocommerce_reports_page() {
include_once( 'woocommerce-admin-reports.php' );
woocommerce_reports();
}
/**
* Include and display the attibutes page.
*
@ -439,7 +398,7 @@ function woocommerce_admin_scripts() {
$wc_screen_id = strtolower( __( 'WooCommerce', 'woocommerce' ) );
// WooCommerce admin pages
if ( in_array( $screen->id, apply_filters( 'woocommerce_screen_ids', array( 'toplevel_page_' . $wc_screen_id, $wc_screen_id . '_page_woocommerce_settings', $wc_screen_id . '_page_woocommerce_reports', $wc_screen_id . '_page_woocommerce_status', $wc_screen_id . '_page_woocommerce_customers', 'toplevel_page_woocommerce', 'woocommerce_page_woocommerce_settings', 'woocommerce_page_woocommerce_reports', 'woocommerce_page_woocommerce_status', 'woocommerce_page_woocommerce_customers', 'edit-shop_order', 'edit-shop_coupon', 'shop_coupon', 'shop_order', 'edit-product', 'product' ) ) ) ) {
if ( in_array( $screen->id, apply_filters( 'woocommerce_screen_ids', array( 'toplevel_page_' . $wc_screen_id, $wc_screen_id . '_page_woocommerce_settings', $wc_screen_id . '_page_woocommerce_status', $wc_screen_id . '_page_woocommerce_customers', 'toplevel_page_woocommerce', 'woocommerce_page_woocommerce_settings', 'woocommerce_page_woocommerce_status', 'woocommerce_page_woocommerce_customers', 'edit-shop_order', 'edit-shop_coupon', 'shop_coupon', 'shop_order', 'edit-product', 'product' ) ) ) ) {
wp_enqueue_script( 'woocommerce_admin' );
wp_enqueue_script( 'farbtastic' );
@ -531,15 +490,6 @@ function woocommerce_admin_scripts() {
wp_enqueue_script( 'woocommerce_product_ordering', $woocommerce->plugin_url() . '/assets/js/admin/product-ordering.js', array('jquery-ui-sortable'), '1.0', true );
}
// Reports pages
if ( in_array( $screen->id, apply_filters( 'woocommerce_reports_screen_ids', array( $wc_screen_id . '_page_woocommerce_reports', apply_filters( 'woocommerce_reports_screen_id', 'woocommerce_page_woocommerce_reports' ) ) ) ) ) {
wp_enqueue_script( 'jquery-ui-datepicker' );
wp_enqueue_script( 'flot', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot' . $suffix . '.js', 'jquery', '1.0' );
wp_enqueue_script( 'flot-resize', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot.resize' . $suffix . '.js', array('jquery', 'flot'), '1.0' );
}
}
add_action( 'admin_enqueue_scripts', 'woocommerce_admin_scripts' );

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1078,7 +1078,7 @@ ul.wc_coupon_list_block {
.column-last_order {
width: 11%;
}
.column-order_actions, .column-user_actions {
.column-order_actions, .column-user_actions, .column-wc_actions {
width:110px;
a.button {
float:left;
@ -1462,13 +1462,6 @@ ul.woocommerce_stats {
top: -5px;
}
#woocommerce_dashboard_sales {
h3 a {
float: right;
margin-left: 8px;
}
}
ul.recent-orders, ul.stock_list {
li {
overflow: hidden;
@ -2596,11 +2589,11 @@ img.tips {
#tiptip_holder.tip_left {
padding-right: 5px;
}
#tiptip_content {
#tiptip_content, .chart-tooltip {
font-size: 11px;
color: #fff;
padding: 4px 8px;
background:#a2678c;
padding: .75em 1em;
background:#464646;
border-radius: 3px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
@ -2609,7 +2602,7 @@ img.tips {
-moz-box-shadow: 1px 1px 3px rgba(0,0,0,0.10);
text-align: center;
code {
background: #855c76;
background: #888;
padding: 1px;
}
}
@ -2624,25 +2617,25 @@ img.tips {
#tiptip_holder.tip_top #tiptip_arrow_inner {
margin-top: -7px;
margin-left: -6px;
border-top-color: #a2678c;
border-top-color: #464646;
}
#tiptip_holder.tip_bottom #tiptip_arrow_inner {
margin-top: -5px;
margin-left: -6px;
border-bottom-color: #a2678c;
border-bottom-color: #464646;
}
#tiptip_holder.tip_right #tiptip_arrow_inner {
margin-top: -6px;
margin-left: -5px;
border-right-color: #a2678c;
border-right-color: #464646;
}
#tiptip_holder.tip_left #tiptip_arrow_inner {
margin-top: -6px;
margin-left: -7px;
border-left-color: #a2678c;
border-left-color: #464646;
}
@ -2652,25 +2645,295 @@ img.ui-datepicker-trigger { vertical-align: middle; margin-top: -1px; cursor: po
#ui-datepicker-div { display: none }
/* Reports */
.woocommerce-reports-wrap {
margin-left: 300px;
padding-top: 18px;
.woocommerce-reports-wrap, .woocommerce-reports-wide {
&.woocommerce-reports-wrap {
margin-left: 300px;
padding-top: 18px;
}
&.halved {
margin: 0;
overflow: hidden;
zoom: 1;
}
.widefat {
td {
vertical-align: top;
padding: 7px 7px;
.description {
margin: 4px 0 0 0;
}
}
}
.postbox {
&:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
h3 {
cursor: default !important;
}
.inside {
padding: 10px;
margin: 0 !important;
}
h3.stats_range {
padding: 0 !important;
.export_csv {
float: right;
line-height: 26px;
border-left: 1px solid #dfdfdf;
padding: 9px 10px;
display: block;
text-decoration: none;
}
ul {
list-style: none outside;
margin: 0;
padding: 0;
zoom:1;
&:before, &:after {
content: " ";
display: table;
}
&:after {
clear:both;
}
li {
float: left;
margin: 0;
padding: 0;
line-height: 26px;
a {
border-right: 1px solid #dfdfdf;
padding: 9px 10px;
display: block;
text-decoration: none;
}
&.active {
background: #f9f9f9;
-webkit-box-shadow:0 4px 0 0 #f9f9f9;
box-shadow:0 4px 0 0 #f9f9f9;
a {
color: #777;
}
}
&.custom {
padding: 9px 10px;
vertical-align: middle;
form, div {
display: inline;
margin:0;
input.range_datepicker {
padding: 0;
margin:0 10px 0 0;
background: transparent;
border: 0;
color: #777;
text-align: center;
-webkit-box-shadow:none;
box-shadow:none;
}
}
}
}
}
}
.chart-with-sidebar {
padding: 12px 12px 12px 249px;
margin: 0 !important;
.chart-sidebar {
width: 225px;
margin-left: -237px;
float: left;
}
}
.chart-widgets {
margin:0;
padding: 0;
li.chart-widget {
margin: 0 0 1em;
background: #fafafa;
border:1px solid #dfdfdf;
&:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
h4 {
background: #fff;
border:1px solid #dfdfdf;
border-left-width: 0;
border-right-width: 0;
padding: 10px;
margin:0;
color: #21759b;
border-top-width:0;
background-image: -webkit-gradient(linear,left bottom,left top,from(#ececec),to(#f9f9f9));
background-image: -webkit-linear-gradient(bottom,#ececec,#f9f9f9);
background-image: -moz-linear-gradient(bottom,#ececec,#f9f9f9);
background-image: -o-linear-gradient(bottom,#ececec,#f9f9f9);
background-image: linear-gradient(to top,#ececec,#f9f9f9);
&.section_title:hover {
color:#d54e21;
}
}
.section_title {
cursor: pointer;
span {
display: block;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAFCAYAAACXU8ZrAAAAMElEQVQIW2OcOXPmfwYgSE9PZwTRyAAmB5bAphBZDK4bWRBdE4oVMEl067G6A919AJAYHtDVRR5EAAAAAElFTkSuQmCC) no-repeat right;
}
&.open {
color: #333;
span {
background-image: none;
}
}
}
.section {
border-bottom:1px solid #dfdfdf;
&:last-of-type {
border-radius:0 0 3px 3px;
}
}
table {
width: 100%;
td {
padding: 7px 10px;
vertical-align: top;
border-top: 1px solid #e5e5e5;
line-height: 1.4em;
}
tr:first-child td {
border-top:0;
}
td.count {
background: #f5f5f5;
}
td.name {
padding-right: 0;
}
td.sparkline {
vertical-align: middle;
}
.wc_sparkline {
width: 32px;
height: 1em;
display: block;
float: right;
}
tr.active td {
background: #f5f5f5;
}
}
form, p {
margin: 0;
padding: 10px;
.submit {
margin-top: 10px;
}
}
#product_ids {
width: 100%;
}
.select_all, .select_none {
float: right;
color: #999;
margin-left: 4px;
margin-top: 10px;
}
}
}
.chart-legend {
list-style: none outside;
margin: 0 0 1em;
padding: 0;
border:1px solid #dfdfdf;
border-right-width:0;
border-bottom-width:0;
background: #fff;
li {
border-right: 5px solid #aaa;
color: #aaa;
padding: 1em 1.5em;
display: block;
margin: 0;
-webkit-transition:all ease .5s;
box-shadow:
inset 0 -1px 0 0 #dfdfdf;
strong {
font-size: 2.618em;
line-height: 1.2em;
color: #464646;
font-weight: normal;
display: block;
font-family: "HelveticaNeue-Light","Helvetica Neue Light","Helvetica Neue",sans-serif;
}
&:hover {
box-shadow:
inset 0 -1px 0 0 #dfdfdf,
inset 300px 0 0 fade(#9c5d90,10%);
border-right: 5px solid #9c5d90 !important;
padding-left:2em;
color: #9c5d90;
}
}
}
.pie-chart-legend {
margin: 12px 0 0 0;
overflow: hidden;
li {
float: left;
margin: 0;
padding: 6px 0 0 0;
border-top: 4px solid #999;
text-align: center;
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box; /* Firefox, other Gecko */
box-sizing: border-box; /* Opera/IE 8+ */
width: 50%;
}
}
.stat {
font-size: 1.5em !important;
font-weight: bold;
text-align: center;
}
.chart {
padding: 16px 16px 0;
.chart-placeholder {
width:100%;
height: 650px;
overflow:hidden;
position:relative;
}
.chart-prompt {
line-height: 650px;
margin: 0;
color: #999;
font-size: 1.2em;
font-style: italic;
text-align: center;
}
.chart-container {
background: #fff;
padding: 12px;
position: relative;
border:1px solid #dfdfdf;
border-radius:3px;
}
.main .chart-legend {
margin-top: 12px;
li {
border-right: 0;
margin: 0 8px 0 0;
float: left;
border-top: 4px solid #aaa;
}
}
}
.woocommerce-reports-main {
@ -2722,15 +2985,10 @@ form.report_filters {
}
/* Chart tooltips */
#tooltip {
color: #fff;
font-size: 12px;
-moz-border-radius:4px;
-webkit-border-radius:4px;
-o-border-radius:4px;
-khtml-border-radius:4px;
border-radius:4px;
opacity: 0.80;
.chart-tooltip {
position: absolute;
display: none;
line-height: 1;
}
/* Custom charts */

View File

@ -1 +1,155 @@
.clear{clear:both}.nobr{white-space:nowrap}.woocommerce .col2-set .col-1,.woocommerce-page .col2-set .col-1,.woocommerce .col2-set .col-2,.woocommerce-page .col2-set .col-2{float:none;width:100%}.woocommerce ul.products li.product,.woocommerce-page ul.products li.product{width:48%;float:left;clear:both;margin:0 0 2.992em}.woocommerce ul.products li.product:nth-child(2n),.woocommerce-page ul.products li.product:nth-child(2n){float:right;clear:none!important}.woocommerce div.product div.images,.woocommerce-page div.product div.images,.woocommerce #content div.product div.images,.woocommerce-page #content div.product div.images,.woocommerce div.product div.summary,.woocommerce-page div.product div.summary,.woocommerce #content div.product div.summary,.woocommerce-page #content div.product div.summary{float:none;width:100%}.woocommerce table.cart .product-thumbnail,.woocommerce-page table.cart .product-thumbnail,.woocommerce #content table.cart .product-thumbnail,.woocommerce-page #content table.cart .product-thumbnail{display:none}.woocommerce table.cart td.actions,.woocommerce-page table.cart td.actions,.woocommerce #content table.cart td.actions,.woocommerce-page #content table.cart td.actions{text-align:left}.woocommerce table.cart td.actions .coupon,.woocommerce-page table.cart td.actions .coupon,.woocommerce #content table.cart td.actions .coupon,.woocommerce-page #content table.cart td.actions .coupon{float:none;padding-bottom:.5em}.woocommerce table.cart td.actions .coupon:after,.woocommerce-page table.cart td.actions .coupon:after,.woocommerce #content table.cart td.actions .coupon:after,.woocommerce-page #content table.cart td.actions .coupon:after{content:"";display:block;clear:both}.woocommerce table.cart td.actions input,.woocommerce-page table.cart td.actions input,.woocommerce #content table.cart td.actions input,.woocommerce-page #content table.cart td.actions input,.woocommerce table.cart td.actions .button,.woocommerce-page table.cart td.actions .button,.woocommerce #content table.cart td.actions .button,.woocommerce-page #content table.cart td.actions .button,.woocommerce table.cart td.actions .input-text,.woocommerce-page table.cart td.actions .input-text,.woocommerce #content table.cart td.actions .input-text,.woocommerce-page #content table.cart td.actions .input-text{width:48%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.woocommerce table.cart td.actions .input-text+.button,.woocommerce-page table.cart td.actions .input-text+.button,.woocommerce #content table.cart td.actions .input-text+.button,.woocommerce-page #content table.cart td.actions .input-text+.button,.woocommerce table.cart td.actions .button.alt,.woocommerce-page table.cart td.actions .button.alt,.woocommerce #content table.cart td.actions .button.alt,.woocommerce-page #content table.cart td.actions .button.alt{float:right}.woocommerce .cart-collaterals .cart_totals,.woocommerce-page .cart-collaterals .cart_totals,.woocommerce .cart-collaterals .shipping_calculator,.woocommerce-page .cart-collaterals .shipping_calculator,.woocommerce .cart-collaterals .cross-sells,.woocommerce-page .cart-collaterals .cross-sells{width:100%;float:none;text-align:left}.woocommerce #payment .terms,.woocommerce-page #payment .terms{text-align:left;padding:0}.woocommerce #payment #place_order,.woocommerce-page #payment #place_order{float:none;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-bottom:1em}
/**
* This stylesheet optimises the default WooCommerce layout when viewed on smaller screens.
*/
.clear {
clear: both;
}
.nobr {
white-space: nowrap;
}
/**
* Objects
*/
.woocommerce,
.woocommerce-page {
/**
* General layout
*/
/**
* Products
*/
/**
* Product Details
*/
/**
* Cart
*/
/**
* Checkout
*/
}
.woocommerce .col2-set .col-1,
.woocommerce-page .col2-set .col-1,
.woocommerce .col2-set .col-2,
.woocommerce-page .col2-set .col-2 {
float: none;
width: 100%;
}
.woocommerce ul.products li.product,
.woocommerce-page ul.products li.product {
width: 48%;
float: left;
clear: both;
margin: 0 0 2.992em;
}
.woocommerce ul.products li.product:nth-child(2n),
.woocommerce-page ul.products li.product:nth-child(2n) {
float: right;
clear: none !important;
}
.woocommerce div.product div.images,
.woocommerce-page div.product div.images,
.woocommerce #content div.product div.images,
.woocommerce-page #content div.product div.images,
.woocommerce div.product div.summary,
.woocommerce-page div.product div.summary,
.woocommerce #content div.product div.summary,
.woocommerce-page #content div.product div.summary {
float: none;
width: 100%;
}
.woocommerce table.cart .product-thumbnail,
.woocommerce-page table.cart .product-thumbnail,
.woocommerce #content table.cart .product-thumbnail,
.woocommerce-page #content table.cart .product-thumbnail {
display: none;
}
.woocommerce table.cart td.actions,
.woocommerce-page table.cart td.actions,
.woocommerce #content table.cart td.actions,
.woocommerce-page #content table.cart td.actions {
text-align: left;
}
.woocommerce table.cart td.actions .coupon,
.woocommerce-page table.cart td.actions .coupon,
.woocommerce #content table.cart td.actions .coupon,
.woocommerce-page #content table.cart td.actions .coupon {
float: none;
padding-bottom: .5em;
}
.woocommerce table.cart td.actions .coupon:after,
.woocommerce-page table.cart td.actions .coupon:after,
.woocommerce #content table.cart td.actions .coupon:after,
.woocommerce-page #content table.cart td.actions .coupon:after {
content: "";
display: block;
clear: both;
}
.woocommerce table.cart td.actions input,
.woocommerce-page table.cart td.actions input,
.woocommerce #content table.cart td.actions input,
.woocommerce-page #content table.cart td.actions input,
.woocommerce table.cart td.actions .button,
.woocommerce-page table.cart td.actions .button,
.woocommerce #content table.cart td.actions .button,
.woocommerce-page #content table.cart td.actions .button,
.woocommerce table.cart td.actions .input-text,
.woocommerce-page table.cart td.actions .input-text,
.woocommerce #content table.cart td.actions .input-text,
.woocommerce-page #content table.cart td.actions .input-text {
width: 48%;
-webkit-box-sizing: border-box;
/* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box;
/* Firefox, other Gecko */
box-sizing: border-box;
/* Opera/IE 8+ */
}
.woocommerce table.cart td.actions .input-text + .button,
.woocommerce-page table.cart td.actions .input-text + .button,
.woocommerce #content table.cart td.actions .input-text + .button,
.woocommerce-page #content table.cart td.actions .input-text + .button,
.woocommerce table.cart td.actions .button.alt,
.woocommerce-page table.cart td.actions .button.alt,
.woocommerce #content table.cart td.actions .button.alt,
.woocommerce-page #content table.cart td.actions .button.alt {
float: right;
}
.woocommerce .cart-collaterals .cart_totals,
.woocommerce-page .cart-collaterals .cart_totals,
.woocommerce .cart-collaterals .shipping_calculator,
.woocommerce-page .cart-collaterals .shipping_calculator,
.woocommerce .cart-collaterals .cross-sells,
.woocommerce-page .cart-collaterals .cross-sells {
width: 100%;
float: none;
text-align: left;
}
.woocommerce #payment .terms,
.woocommerce-page #payment .terms {
text-align: left;
padding: 0;
}
.woocommerce #payment #place_order,
.woocommerce-page #payment #place_order {
float: none;
width: 100%;
-webkit-box-sizing: border-box;
/* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box;
/* Firefox, other Gecko */
box-sizing: border-box;
/* Opera/IE 8+ */
margin-bottom: 1em;
}

View File

@ -1,113 +0,0 @@
jQuery(function(){
function weekendAreas(axes) {
var markings = [];
var d = new Date(axes.xaxis.min);
// go to the first Saturday
d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
d.setUTCSeconds(0);
d.setUTCMinutes(0);
d.setUTCHours(0);
var i = d.getTime();
do {
// when we don't set yaxis, the rectangle automatically
// extends to infinity upwards and downwards
markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 } });
i += 7 * 24 * 60 * 60 * 1000;
} while (i < axes.xaxis.max);
return markings;
}
var order_data = jQuery.parseJSON( params.order_data.replace(/&quot;/g, '"') );
var d = order_data.order_counts;
var d2 = order_data.order_amounts;
for (var i = 0; i < d.length; ++i) d[i][0] += 60 * 60 * 1000;
for (var i = 0; i < d2.length; ++i) d2[i][0] += 60 * 60 * 1000;
var placeholder = jQuery("#placeholder");
var plot = jQuery.plot(placeholder, [ { label: params.number_of_sales, data: d }, { label: params.sales_amount, data: d2, yaxis: 2 } ], {
series: {
lines: { show: true, fill: true },
points: { show: true }
},
grid: {
show: true,
aboveData: false,
color: '#ccc',
backgroundColor: '#fff',
borderWidth: 2,
borderColor: '#ccc',
clickable: false,
hoverable: true,
markings: weekendAreas
},
xaxis: {
mode: "time",
timeformat: "%d %b",
monthNames: params.i18n_month_names,
tickLength: 1,
minTickSize: [1, "day"]
},
yaxes: [ { min: 0, tickSize: 10, tickDecimals: 0 }, { position: "right", min: 0, tickDecimals: 2 } ],
colors: ["#8a4b75", "#47a03e"],
legend: {
show: true,
position: "nw"
}
});
placeholder.resize();
function showTooltip(x, y, contents) {
jQuery('<div id="tooltip">' + contents + '</div>').css( {
position: 'absolute',
display: 'none',
top: y + 5,
left: x + 5,
padding: '5px 10px',
border: '3px solid #3da5d5',
background: '#288ab7'
}).appendTo("body").fadeIn(200);
}
var previousPoint = null;
jQuery("#placeholder").bind("plothover", function (event, pos, item) {
if (item) {
if (previousPoint != item.dataIndex) {
previousPoint = item.dataIndex;
jQuery("#tooltip").remove();
if (item.series.label==params.number_of_sales) {
var y = item.datapoint[1];
showTooltip(item.pageX, item.pageY, params.i18n_sold + ": " + y);
} else {
var y = item.datapoint[1].toFixed(2);
var formatted_total = accounting.formatMoney( y, {
symbol : params.currency_format_symbol,
decimal : params.currency_format_decimal_sep,
thousand : params.currency_format_thousand_sep,
precision : params.currency_format_num_decimals,
format : params.currency_format
} );
showTooltip( item.pageX, item.pageY, params.i18n_earned + ": " + formatted_total );
}
}
}
else {
jQuery("#tooltip").remove();
previousPoint = null;
}
});
});

View File

@ -1 +0,0 @@
jQuery(function(){function e(e){var t=[],n=new Date(e.xaxis.min);n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+1)%7);n.setUTCSeconds(0);n.setUTCMinutes(0);n.setUTCHours(0);var r=n.getTime();do{t.push({xaxis:{from:r,to:r+1728e5}});r+=6048e5}while(r<e.xaxis.max);return t}function u(e,t,n){jQuery('<div id="tooltip">'+n+"</div>").css({position:"absolute",display:"none",top:t+5,left:e+5,padding:"5px 10px",border:"3px solid #3da5d5",background:"#288ab7"}).appendTo("body").fadeIn(200)}var t=jQuery.parseJSON(params.order_data.replace(/&quot;/g,'"')),n=t.order_counts,r=t.order_amounts;for(var i=0;i<n.length;++i)n[i][0]+=36e5;for(var i=0;i<r.length;++i)r[i][0]+=36e5;var s=jQuery("#placeholder"),o=jQuery.plot(s,[{label:params.number_of_sales,data:n},{label:params.sales_amount,data:r,yaxis:2}],{series:{lines:{show:!0,fill:!0},points:{show:!0}},grid:{show:!0,aboveData:!1,color:"#ccc",backgroundColor:"#fff",borderWidth:2,borderColor:"#ccc",clickable:!1,hoverable:!0,markings:e},xaxis:{mode:"time",timeformat:"%d %b",monthNames:params.i18n_month_names,tickLength:1,minTickSize:[1,"day"]},yaxes:[{min:0,tickSize:10,tickDecimals:0},{position:"right",min:0,tickDecimals:2}],colors:["#8a4b75","#47a03e"],legend:{show:!0,position:"nw"}});s.resize();var a=null;jQuery("#placeholder").bind("plothover",function(e,t,n){if(n){if(a!=n.dataIndex){a=n.dataIndex;jQuery("#tooltip").remove();if(n.series.label==params.number_of_sales){var r=n.datapoint[1];u(n.pageX,n.pageY,params.i18n_sold+": "+r)}else{var r=n.datapoint[1].toFixed(2),i=accounting.formatMoney(r,{symbol:params.currency_format_symbol,decimal:params.currency_format_decimal_sep,thousand:params.currency_format_thousand_sep,precision:params.currency_format_num_decimals,format:params.currency_format});u(n.pageX,n.pageY,params.i18n_earned+": "+i)}}}else{jQuery("#tooltip").remove();a=null}})});

1986
assets/js/admin/jquery.flot.js Normal file → Executable file

File diff suppressed because it is too large Load Diff

31
assets/js/admin/jquery.flot.min.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,817 @@
/* Flot plugin for rendering pie charts.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin assumes that each series has a single data value, and that each
value is a positive integer or zero. Negative numbers don't make sense for a
pie chart, and have unpredictable results. The values do NOT need to be
passed in as percentages; the plugin will calculate the total and per-slice
percentages internally.
* Created by Brian Medendorp
* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
The plugin supports these options:
series: {
pie: {
show: true/false
radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
offset: {
top: integer value to move the pie up or down
left: integer value to move the pie left or right, or 'auto'
},
stroke: {
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
width: integer pixel width of the stroke
},
label: {
show: true/false, or 'auto'
formatter: a user-defined function that modifies the text/style of the label text
radius: 0-1 for percentage of fullsize, or a specified pixel length
background: {
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
opacity: 0-1
},
threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
},
combine: {
threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
label: any text value of what the combined slice should be labeled
}
highlight: {
opacity: 0-1
}
}
}
More detail and specific examples can be found in the included HTML file.
*/
(function($) {
// Maximum redraw attempts when fitting labels within the plot
var REDRAW_ATTEMPTS = 10;
// Factor by which to shrink the pie when fitting labels within the plot
var REDRAW_SHRINK = 0.95;
function init(plot) {
var canvas = null,
target = null,
maxRadius = null,
centerLeft = null,
centerTop = null,
processed = false,
ctx = null;
// interactive variables
var highlights = [];
// add hook to determine if pie plugin in enabled, and then perform necessary operations
plot.hooks.processOptions.push(function(plot, options) {
if (options.series.pie.show) {
options.grid.show = false;
// set labels.show
if (options.series.pie.label.show == "auto") {
if (options.legend.show) {
options.series.pie.label.show = false;
} else {
options.series.pie.label.show = true;
}
}
// set radius
if (options.series.pie.radius == "auto") {
if (options.series.pie.label.show) {
options.series.pie.radius = 3/4;
} else {
options.series.pie.radius = 1;
}
}
// ensure sane tilt
if (options.series.pie.tilt > 1) {
options.series.pie.tilt = 1;
} else if (options.series.pie.tilt < 0) {
options.series.pie.tilt = 0;
}
}
});
plot.hooks.bindEvents.push(function(plot, eventHolder) {
var options = plot.getOptions();
if (options.series.pie.show) {
if (options.grid.hoverable) {
eventHolder.unbind("mousemove").mousemove(onMouseMove);
}
if (options.grid.clickable) {
eventHolder.unbind("click").click(onClick);
}
}
});
plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
var options = plot.getOptions();
if (options.series.pie.show) {
processDatapoints(plot, series, data, datapoints);
}
});
plot.hooks.drawOverlay.push(function(plot, octx) {
var options = plot.getOptions();
if (options.series.pie.show) {
drawOverlay(plot, octx);
}
});
plot.hooks.draw.push(function(plot, newCtx) {
var options = plot.getOptions();
if (options.series.pie.show) {
draw(plot, newCtx);
}
});
function processDatapoints(plot, series, datapoints) {
if (!processed) {
processed = true;
canvas = plot.getCanvas();
target = $(canvas).parent();
options = plot.getOptions();
plot.setData(combine(plot.getData()));
}
}
function combine(data) {
var total = 0,
combined = 0,
numCombined = 0,
color = options.series.pie.combine.color,
newdata = [];
// Fix up the raw data from Flot, ensuring the data is numeric
for (var i = 0; i < data.length; ++i) {
var value = data[i].data;
// If the data is an array, we'll assume that it's a standard
// Flot x-y pair, and are concerned only with the second value.
// Note how we use the original array, rather than creating a
// new one; this is more efficient and preserves any extra data
// that the user may have stored in higher indexes.
if ($.isArray(value) && value.length == 1) {
value = value[0];
}
if ($.isArray(value)) {
// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
value[1] = +value[1];
} else {
value[1] = 0;
}
} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
value = [1, +value];
} else {
value = [1, 0];
}
data[i].data = [value];
}
// Sum up all the slices, so we can calculate percentages for each
for (var i = 0; i < data.length; ++i) {
total += data[i].data[0][1];
}
// Count the number of slices with percentages below the combine
// threshold; if it turns out to be just one, we won't combine.
for (var i = 0; i < data.length; ++i) {
var value = data[i].data[0][1];
if (value / total <= options.series.pie.combine.threshold) {
combined += value;
numCombined++;
if (!color) {
color = data[i].color;
}
}
}
for (var i = 0; i < data.length; ++i) {
var value = data[i].data[0][1];
if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
newdata.push({
data: [[1, value]],
color: data[i].color,
label: data[i].label,
angle: value * Math.PI * 2 / total,
percent: value / (total / 100)
});
}
}
if (numCombined > 1) {
newdata.push({
data: [[1, combined]],
color: color,
label: options.series.pie.combine.label,
angle: combined * Math.PI * 2 / total,
percent: combined / (total / 100)
});
}
return newdata;
}
function draw(plot, newCtx) {
if (!target) {
return; // if no series were passed
}
var canvasWidth = plot.getPlaceholder().width(),
canvasHeight = plot.getPlaceholder().height(),
legendWidth = target.children().filter(".legend").children().width() || 0;
ctx = newCtx;
// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
// When combining smaller slices into an 'other' slice, we need to
// add a new series. Since Flot gives plugins no way to modify the
// list of series, the pie plugin uses a hack where the first call
// to processDatapoints results in a call to setData with the new
// list of series, then subsequent processDatapoints do nothing.
// The plugin-global 'processed' flag is used to control this hack;
// it starts out false, and is set to true after the first call to
// processDatapoints.
// Unfortunately this turns future setData calls into no-ops; they
// call processDatapoints, the flag is true, and nothing happens.
// To fix this we'll set the flag back to false here in draw, when
// all series have been processed, so the next sequence of calls to
// processDatapoints once again starts out with a slice-combine.
// This is really a hack; in 0.9 we need to give plugins a proper
// way to modify series before any processing begins.
processed = false;
// calculate maximum radius and center point
maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
centerTop = canvasHeight / 2 + options.series.pie.offset.top;
centerLeft = canvasWidth / 2;
if (options.series.pie.offset.left == "auto") {
if (options.legend.position.match("w")) {
centerLeft += legendWidth / 2;
} else {
centerLeft -= legendWidth / 2;
}
} else {
centerLeft += options.series.pie.offset.left;
}
if (centerLeft < maxRadius) {
centerLeft = maxRadius;
} else if (centerLeft > canvasWidth - maxRadius) {
centerLeft = canvasWidth - maxRadius;
}
var slices = plot.getData(),
attempts = 0;
// Keep shrinking the pie's radius until drawPie returns true,
// indicating that all the labels fit, or we try too many times.
do {
if (attempts > 0) {
maxRadius *= REDRAW_SHRINK;
}
attempts += 1;
clear();
if (options.series.pie.tilt <= 0.8) {
drawShadow();
}
} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
if (attempts >= REDRAW_ATTEMPTS) {
clear();
target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
}
if (plot.setSeries && plot.insertLegend) {
plot.setSeries(slices);
plot.insertLegend();
}
// we're actually done at this point, just defining internal functions at this point
function clear() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
target.children().filter(".pieLabel, .pieLabelBackground").remove();
}
function drawShadow() {
var shadowLeft = options.series.pie.shadow.left;
var shadowTop = options.series.pie.shadow.top;
var edge = 10;
var alpha = options.series.pie.shadow.alpha;
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
return; // shadow would be outside canvas, so don't draw it
}
ctx.save();
ctx.translate(shadowLeft,shadowTop);
ctx.globalAlpha = alpha;
ctx.fillStyle = "#000";
// center and rotate to starting position
ctx.translate(centerLeft,centerTop);
ctx.scale(1, options.series.pie.tilt);
//radius -= edge;
for (var i = 1; i <= edge; i++) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
ctx.fill();
radius -= i;
}
ctx.restore();
}
function drawPie() {
var startAngle = Math.PI * options.series.pie.startAngle;
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
// center and rotate to starting position
ctx.save();
ctx.translate(centerLeft,centerTop);
ctx.scale(1, options.series.pie.tilt);
//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
// draw slices
ctx.save();
var currentAngle = startAngle;
for (var i = 0; i < slices.length; ++i) {
slices[i].startAngle = currentAngle;
drawSlice(slices[i].angle, slices[i].color, true);
}
ctx.restore();
// draw slice outlines
if (options.series.pie.stroke.width > 0) {
ctx.save();
ctx.lineWidth = options.series.pie.stroke.width;
currentAngle = startAngle;
for (var i = 0; i < slices.length; ++i) {
drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
}
ctx.restore();
}
// draw donut hole
drawDonutHole(ctx);
ctx.restore();
// Draw the labels, returning true if they fit within the plot
if (options.series.pie.label.show) {
return drawLabels();
} else return true;
function drawSlice(angle, color, fill) {
if (angle <= 0 || isNaN(angle)) {
return;
}
if (fill) {
ctx.fillStyle = color;
} else {
ctx.strokeStyle = color;
ctx.lineJoin = "round";
}
ctx.beginPath();
if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
ctx.moveTo(0, 0); // Center of the pie
}
//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
ctx.closePath();
//ctx.rotate(angle); // This doesn't work properly in Opera
currentAngle += angle;
if (fill) {
ctx.fill();
} else {
ctx.stroke();
}
}
function drawLabels() {
var currentAngle = startAngle;
var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
for (var i = 0; i < slices.length; ++i) {
if (slices[i].percent >= options.series.pie.label.threshold * 100) {
if (!drawLabel(slices[i], currentAngle, i)) {
return false;
}
}
currentAngle += slices[i].angle;
}
return true;
function drawLabel(slice, startAngle, index) {
if (slice.data[0][1] == 0) {
return true;
}
// format label text
var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
if (lf) {
text = lf(slice.label, slice);
} else {
text = slice.label;
}
if (plf) {
text = plf(text, slice);
}
var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
target.append(html);
var label = target.children("#pieLabel" + index);
var labelTop = (y - label.height() / 2);
var labelLeft = (x - label.width() / 2);
label.css("top", labelTop);
label.css("left", labelLeft);
// check to make sure that the label is not outside the canvas
if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
return false;
}
if (options.series.pie.label.background.opacity != 0) {
// put in the transparent background separately to avoid blended labels and label boxes
var c = options.series.pie.label.background.color;
if (c == null) {
c = slice.color;
}
var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
.css("opacity", options.series.pie.label.background.opacity)
.insertBefore(label);
}
return true;
} // end individual label function
} // end drawLabels function
} // end drawPie function
} // end draw function
// Placed here because it needs to be accessed from multiple locations
function drawDonutHole(layer) {
if (options.series.pie.innerRadius > 0) {
// subtract the center
layer.save();
var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
layer.beginPath();
layer.fillStyle = options.series.pie.stroke.color;
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
layer.fill();
layer.closePath();
layer.restore();
// add inner stroke
layer.save();
layer.beginPath();
layer.strokeStyle = options.series.pie.stroke.color;
layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
layer.stroke();
layer.closePath();
layer.restore();
// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
}
}
//-- Additional Interactive related functions --
function isPointInPoly(poly, pt) {
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
&& (c = !c);
return c;
}
function findNearbySlice(mouseX, mouseY) {
var slices = plot.getData(),
options = plot.getOptions(),
radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
x, y;
for (var i = 0; i < slices.length; ++i) {
var s = slices[i];
if (s.pie.show) {
ctx.save();
ctx.beginPath();
ctx.moveTo(0, 0); // Center of the pie
//ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
ctx.closePath();
x = mouseX - centerLeft;
y = mouseY - centerTop;
if (ctx.isPointInPath) {
if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
ctx.restore();
return {
datapoint: [s.percent, s.data],
dataIndex: 0,
series: s,
seriesIndex: i
};
}
} else {
// excanvas for IE doesn;t support isPointInPath, this is a workaround.
var p1X = radius * Math.cos(s.startAngle),
p1Y = radius * Math.sin(s.startAngle),
p2X = radius * Math.cos(s.startAngle + s.angle / 4),
p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
p3X = radius * Math.cos(s.startAngle + s.angle / 2),
p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
p5X = radius * Math.cos(s.startAngle + s.angle),
p5Y = radius * Math.sin(s.startAngle + s.angle),
arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
arrPoint = [x, y];
// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
if (isPointInPoly(arrPoly, arrPoint)) {
ctx.restore();
return {
datapoint: [s.percent, s.data],
dataIndex: 0,
series: s,
seriesIndex: i
};
}
}
ctx.restore();
}
}
return null;
}
function onMouseMove(e) {
triggerClickHoverEvent("plothover", e);
}
function onClick(e) {
triggerClickHoverEvent("plotclick", e);
}
// trigger click or hover event (they send the same parameters so we share their code)
function triggerClickHoverEvent(eventname, e) {
var offset = plot.offset();
var canvasX = parseInt(e.pageX - offset.left);
var canvasY = parseInt(e.pageY - offset.top);
var item = findNearbySlice(canvasX, canvasY);
if (options.grid.autoHighlight) {
// clear auto-highlights
for (var i = 0; i < highlights.length; ++i) {
var h = highlights[i];
if (h.auto == eventname && !(item && h.series == item.series)) {
unhighlight(h.series);
}
}
}
// highlight the slice
if (item) {
highlight(item.series, eventname);
}
// trigger any hover bind events
var pos = { pageX: e.pageX, pageY: e.pageY };
target.trigger(eventname, [pos, item]);
}
function highlight(s, auto) {
//if (typeof s == "number") {
// s = series[s];
//}
var i = indexOfHighlight(s);
if (i == -1) {
highlights.push({ series: s, auto: auto });
plot.triggerRedrawOverlay();
} else if (!auto) {
highlights[i].auto = false;
}
}
function unhighlight(s) {
if (s == null) {
highlights = [];
plot.triggerRedrawOverlay();
}
//if (typeof s == "number") {
// s = series[s];
//}
var i = indexOfHighlight(s);
if (i != -1) {
highlights.splice(i, 1);
plot.triggerRedrawOverlay();
}
}
function indexOfHighlight(s) {
for (var i = 0; i < highlights.length; ++i) {
var h = highlights[i];
if (h.series == s)
return i;
}
return -1;
}
function drawOverlay(plot, octx) {
var options = plot.getOptions();
var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
octx.save();
octx.translate(centerLeft, centerTop);
octx.scale(1, options.series.pie.tilt);
for (var i = 0; i < highlights.length; ++i) {
drawHighlight(highlights[i].series);
}
drawDonutHole(octx);
octx.restore();
function drawHighlight(series) {
if (series.angle <= 0 || isNaN(series.angle)) {
return;
}
//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
octx.beginPath();
if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
octx.moveTo(0, 0); // Center of the pie
}
octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
octx.closePath();
octx.fill();
}
}
} // end init (plugin body)
// define pie specific options and their default values
var options = {
series: {
pie: {
show: false,
radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
innerRadius: 0, /* for donut */
startAngle: 3/2,
tilt: 1,
shadow: {
left: 5, // shadow left offset
top: 15, // shadow top offset
alpha: 0.02 // shadow alpha
},
offset: {
top: 0,
left: "auto"
},
stroke: {
color: "#fff",
width: 1
},
label: {
show: "auto",
formatter: function(label, slice) {
return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
}, // formatter function
radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
background: {
color: null,
opacity: 0
},
threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
},
combine: {
threshold: -1, // percentage at which to combine little slices into one larger slice
color: null, // color to give the new slice (auto-generated if null)
label: "Other" // label to give the new slice
},
highlight: {
//color: "#fff", // will add this functionality once parseColor is available
opacity: 0.5
}
}
}
};
$.plot.plugins.push({
init: init,
options: options,
name: "pie",
version: "1.1"
});
})(jQuery);

56
assets/js/admin/jquery.flot.pie.min.js vendored Normal file

File diff suppressed because one or more lines are too long

24
assets/js/admin/jquery.flot.resize.js Normal file → Executable file
View File

@ -1,26 +1,26 @@
/*
Flot plugin for automatically redrawing plots when the placeholder
size changes, e.g. on window resizes.
/* Flot plugin for automatically redrawing plots as the placeholder resizes.
It works by listening for changes on the placeholder div (through the
jQuery resize event plugin) - if the size changes, it will redraw the
plot.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
It works by listening for changes on the placeholder div (through the jQuery
resize event plugin) - if the size changes, it will redraw the plot.
There are no options. If you need to disable the plugin for some plots, you
can just fix the size of their placeholders.
There are no options. If you need to disable the plugin for some
plots, you can just fix the size of their placeholders.
*/
/* Inline dependency:
/* Inline dependency:
* jQuery resize event - v1.1 - 3/14/2010
* http://benalman.com/projects/jquery-resize-plugin/
*
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
(function($,h,c){var a=$([]),e=$.resize=$.extend($.resize,{}),i,k="setTimeout",j="resize",d=j+"-special-event",b="delay",f="throttleWindow";e[b]=250;e[f]=true;$.event.special[j]={setup:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.add(l);$.data(this,d,{w:l.width(),h:l.height()});if(a.length===1){g()}},teardown:function(){if(!e[f]&&this[k]){return false}var l=$(this);a=a.not(l);l.removeData(d);if(!a.length){clearTimeout(i)}},add:function(l){if(!e[f]&&this[k]){return false}var n;function m(s,o,p){var q=$(this),r=$.data(this,d);r.w=o!==c?o:q.width();r.h=p!==c?p:q.height();n.apply(this,arguments)}if($.isFunction(l)){n=l;return m}else{n=l.handler;l.handler=m}}};function g(){i=h[k](function(){a.each(function(){var n=$(this),m=n.width(),l=n.height(),o=$.data(this,d);if(m!==o.w||l!==o.h){n.trigger(j,[o.w=m,o.h=l])}});g()},e[b])}})(jQuery,this);
(function ($) {
var options = { }; // no options

20
assets/js/admin/jquery.flot.resize.min.js vendored Normal file → Executable file
View File

@ -1 +1,19 @@
(function(n,p,u){var w=n([]),s=n.resize=n.extend(n.resize,{}),o,l="setTimeout",m="resize",t=m+"-special-event",v="delay",r="throttleWindow";s[v]=250;s[r]=true;n.event.special[m]={setup:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.add(a);n.data(this,t,{w:a.width(),h:a.height()});if(w.length===1){q()}},teardown:function(){if(!s[r]&&this[l]){return false}var a=n(this);w=w.not(a);a.removeData(t);if(!w.length){clearTimeout(o)}},add:function(b){if(!s[r]&&this[l]){return false}var c;function a(d,h,g){var f=n(this),e=n.data(this,t);e.w=h!==u?h:f.width();e.h=g!==u?g:f.height();c.apply(this,arguments)}if(n.isFunction(b)){c=b;return a}else{c=b.handler;b.handler=a}}};function q(){o=p[l](function(){w.each(function(){var d=n(this),a=d.width(),b=d.height(),c=n.data(this,t);if(a!==c.w||b!==c.h){d.trigger(m,[c.w=a,c.h=b])}});q()},s[v])}})(jQuery,this);(function(b){var a={};function c(f){function e(){var h=f.getPlaceholder();if(h.width()==0||h.height()==0){return}f.resize();f.setupGrid();f.draw()}function g(i,h){i.getPlaceholder().resize(e)}function d(i,h){i.getPlaceholder().unbind("resize",e)}f.hooks.bindEvents.push(g);f.hooks.shutdown.push(d)}b.plot.plugins.push({init:c,options:a,name:"resize",version:"1.0"})})(jQuery);
/* Flot plugin for automatically redrawing plots as the placeholder resizes.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
It works by listening for changes on the placeholder div (through the jQuery
resize event plugin) - if the size changes, it will redraw the plot.
There are no options. If you need to disable the plugin for some plots, you
can just fix the size of their placeholders.
*//* Inline dependency:
* jQuery resize event - v1.1 - 3/14/2010
* http://benalman.com/projects/jquery-resize-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/(function(e,t,n){function c(){s=t[o](function(){r.each(function(){var t=e(this),n=t.width(),r=t.height(),i=e.data(this,a);(n!==i.w||r!==i.h)&&t.trigger(u,[i.w=n,i.h=r])});c()},i[f])}var r=e([]),i=e.resize=e.extend(e.resize,{}),s,o="setTimeout",u="resize",a=u+"-special-event",f="delay",l="throttleWindow";i[f]=250;i[l]=!0;e.event.special[u]={setup:function(){if(!i[l]&&this[o])return!1;var t=e(this);r=r.add(t);e.data(this,a,{w:t.width(),h:t.height()});r.length===1&&c()},teardown:function(){if(!i[l]&&this[o])return!1;var t=e(this);r=r.not(t);t.removeData(a);r.length||clearTimeout(s)},add:function(t){function s(t,i,s){var o=e(this),u=e.data(this,a);u.w=i!==n?i:o.width();u.h=s!==n?s:o.height();r.apply(this,arguments)}if(!i[l]&&this[o])return!1;var r;if(e.isFunction(t)){r=t;return s}r=t.handler;t.handler=s}}})(jQuery,this);(function(e){function n(e){function t(){var t=e.getPlaceholder();if(t.width()==0||t.height()==0)return;e.resize();e.setupGrid();e.draw()}function n(e,n){e.getPlaceholder().resize(t)}function r(e,n){e.getPlaceholder().unbind("resize",t)}e.hooks.bindEvents.push(n);e.hooks.shutdown.push(r)}var t={};e.plot.plugins.push({init:n,options:t,name:"resize",version:"1.0"})})(jQuery);

View File

@ -0,0 +1,188 @@
/* Flot plugin for stacking data sets rather than overlyaing them.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin assumes the data is sorted on x (or y if stacking horizontally).
For line charts, it is assumed that if a line has an undefined gap (from a
null point), then the line above it should have the same gap - insert zeros
instead of "null" if you want another behaviour. This also holds for the start
and end of the chart. Note that stacking a mix of positive and negative values
in most instances doesn't make sense (so it looks weird).
Two or more series are stacked when their "stack" attribute is set to the same
key (which can be any number or string or just "true"). To specify the default
stack, you can set the stack option like this:
series: {
stack: null/false, true, or a key (number/string)
}
You can also specify it for a single series, like this:
$.plot( $("#placeholder"), [{
data: [ ... ],
stack: true
}])
The stacking order is determined by the order of the data series in the array
(later series end up on top of the previous).
Internally, the plugin modifies the datapoints in each series, adding an
offset to the y value. For line series, extra data points are inserted through
interpolation. If there's a second y value, it's also adjusted (e.g for bar
charts or filled areas).
*/
(function ($) {
var options = {
series: { stack: null } // or number/string
};
function init(plot) {
function findMatchingSeries(s, allseries) {
var res = null;
for (var i = 0; i < allseries.length; ++i) {
if (s == allseries[i])
break;
if (allseries[i].stack == s.stack)
res = allseries[i];
}
return res;
}
function stackData(plot, s, datapoints) {
if (s.stack == null || s.stack === false)
return;
var other = findMatchingSeries(s, plot.getData());
if (!other)
return;
var ps = datapoints.pointsize,
points = datapoints.points,
otherps = other.datapoints.pointsize,
otherpoints = other.datapoints.points,
newpoints = [],
px, py, intery, qx, qy, bottom,
withlines = s.lines.show,
horizontal = s.bars.horizontal,
withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
withsteps = withlines && s.lines.steps,
fromgap = true,
keyOffset = horizontal ? 1 : 0,
accumulateOffset = horizontal ? 0 : 1,
i = 0, j = 0, l, m;
while (true) {
if (i >= points.length)
break;
l = newpoints.length;
if (points[i] == null) {
// copy gaps
for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]);
i += ps;
}
else if (j >= otherpoints.length) {
// for lines, we can't use the rest of the points
if (!withlines) {
for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]);
}
i += ps;
}
else if (otherpoints[j] == null) {
// oops, got a gap
for (m = 0; m < ps; ++m)
newpoints.push(null);
fromgap = true;
j += otherps;
}
else {
// cases where we actually got two points
px = points[i + keyOffset];
py = points[i + accumulateOffset];
qx = otherpoints[j + keyOffset];
qy = otherpoints[j + accumulateOffset];
bottom = 0;
if (px == qx) {
for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]);
newpoints[l + accumulateOffset] += qy;
bottom = qy;
i += ps;
j += otherps;
}
else if (px > qx) {
// we got past point below, might need to
// insert interpolated extra point
if (withlines && i > 0 && points[i - ps] != null) {
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
newpoints.push(qx);
newpoints.push(intery + qy);
for (m = 2; m < ps; ++m)
newpoints.push(points[i + m]);
bottom = qy;
}
j += otherps;
}
else { // px < qx
if (fromgap && withlines) {
// if we come from a gap, we just skip this point
i += ps;
continue;
}
for (m = 0; m < ps; ++m)
newpoints.push(points[i + m]);
// we might be able to interpolate a point below,
// this can give us a better y
if (withlines && j > 0 && otherpoints[j - otherps] != null)
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
newpoints[l + accumulateOffset] += bottom;
i += ps;
}
fromgap = false;
if (l != newpoints.length && withbottom)
newpoints[l + 2] += bottom;
}
// maintain the line steps invariant
if (withsteps && l != newpoints.length && l > 0
&& newpoints[l] != null
&& newpoints[l] != newpoints[l - ps]
&& newpoints[l + 1] != newpoints[l - ps + 1]) {
for (m = 0; m < ps; ++m)
newpoints[l + ps + m] = newpoints[l + m];
newpoints[l + 1] = newpoints[l - ps + 1];
}
}
datapoints.points = newpoints;
}
plot.hooks.processDatapoints.push(stackData);
}
$.plot.plugins.push({
init: init,
options: options,
name: 'stack',
version: '1.2'
});
})(jQuery);

View File

@ -0,0 +1,36 @@
/* Flot plugin for stacking data sets rather than overlyaing them.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
The plugin assumes the data is sorted on x (or y if stacking horizontally).
For line charts, it is assumed that if a line has an undefined gap (from a
null point), then the line above it should have the same gap - insert zeros
instead of "null" if you want another behaviour. This also holds for the start
and end of the chart. Note that stacking a mix of positive and negative values
in most instances doesn't make sense (so it looks weird).
Two or more series are stacked when their "stack" attribute is set to the same
key (which can be any number or string or just "true"). To specify the default
stack, you can set the stack option like this:
series: {
stack: null/false, true, or a key (number/string)
}
You can also specify it for a single series, like this:
$.plot( $("#placeholder"), [{
data: [ ... ],
stack: true
}])
The stacking order is determined by the order of the data series in the array
(later series end up on top of the previous).
Internally, the plugin modifies the datapoints in each series, adding an
offset to the y value. For line series, extra data points are inserted through
interpolation. If there's a second y value, it's also adjusted (e.g for bar
charts or filled areas).
*/(function(e){function n(e){function t(e,t){var n=null;for(var r=0;r<t.length;++r){if(e==t[r])break;t[r].stack==e.stack&&(n=t[r])}return n}function n(e,n,r){if(n.stack==null||n.stack===!1)return;var i=t(n,e.getData());if(!i)return;var s=r.pointsize,o=r.points,u=i.datapoints.pointsize,a=i.datapoints.points,f=[],l,c,h,p,d,v,m=n.lines.show,g=n.bars.horizontal,y=s>2&&(g?r.format[2].x:r.format[2].y),b=m&&n.lines.steps,w=!0,E=g?1:0,S=g?0:1,x=0,T=0,N,C;for(;;){if(x>=o.length)break;N=f.length;if(o[x]==null){for(C=0;C<s;++C)f.push(o[x+C]);x+=s}else if(T>=a.length){if(!m)for(C=0;C<s;++C)f.push(o[x+C]);x+=s}else if(a[T]==null){for(C=0;C<s;++C)f.push(null);w=!0;T+=u}else{l=o[x+E];c=o[x+S];p=a[T+E];d=a[T+S];v=0;if(l==p){for(C=0;C<s;++C)f.push(o[x+C]);f[N+S]+=d;v=d;x+=s;T+=u}else if(l>p){if(m&&x>0&&o[x-s]!=null){h=c+(o[x-s+S]-c)*(p-l)/(o[x-s+E]-l);f.push(p);f.push(h+d);for(C=2;C<s;++C)f.push(o[x+C]);v=d}T+=u}else{if(w&&m){x+=s;continue}for(C=0;C<s;++C)f.push(o[x+C]);m&&T>0&&a[T-u]!=null&&(v=d+(a[T-u+S]-d)*(l-p)/(a[T-u+E]-p));f[N+S]+=v;x+=s}w=!1;N!=f.length&&y&&(f[N+2]+=v)}if(b&&N!=f.length&&N>0&&f[N]!=null&&f[N]!=f[N-s]&&f[N+1]!=f[N-s+1]){for(C=0;C<s;++C)f[N+s+C]=f[N+C];f[N+1]=f[N-s+1]}}r.points=f}e.hooks.processDatapoints.push(n)}var t={series:{stack:null}};e.plot.plugins.push({init:n,options:t,name:"stack",version:"1.2"})})(jQuery);

View File

@ -0,0 +1,431 @@
/* Pretty handling of time axes.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
Set axis.mode to "time" to enable. See the section "Time series data" in
API.txt for details.
*/
(function($) {
var options = {
xaxis: {
timezone: null, // "browser" for local to the client or timezone for timezone-js
timeformat: null, // format string to use
twelveHourClock: false, // 12 or 24 time in time mode
monthNames: null // list of names of months
}
};
// round to nearby lower multiple of base
function floorInBase(n, base) {
return base * Math.floor(n / base);
}
// Returns a string with the date d formatted according to fmt.
// A subset of the Open Group's strftime format is supported.
function formatDate(d, fmt, monthNames, dayNames) {
if (typeof d.strftime == "function") {
return d.strftime(fmt);
}
var leftPad = function(n, pad) {
n = "" + n;
pad = "" + (pad == null ? "0" : pad);
return n.length == 1 ? pad + n : n;
};
var r = [];
var escape = false;
var hours = d.getHours();
var isAM = hours < 12;
if (monthNames == null) {
monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
}
if (dayNames == null) {
dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
}
var hours12;
if (hours > 12) {
hours12 = hours - 12;
} else if (hours == 0) {
hours12 = 12;
} else {
hours12 = hours;
}
for (var i = 0; i < fmt.length; ++i) {
var c = fmt.charAt(i);
if (escape) {
switch (c) {
case 'a': c = "" + dayNames[d.getDay()]; break;
case 'b': c = "" + monthNames[d.getMonth()]; break;
case 'd': c = leftPad(d.getDate()); break;
case 'e': c = leftPad(d.getDate(), " "); break;
case 'h': // For back-compat with 0.7; remove in 1.0
case 'H': c = leftPad(hours); break;
case 'I': c = leftPad(hours12); break;
case 'l': c = leftPad(hours12, " "); break;
case 'm': c = leftPad(d.getMonth() + 1); break;
case 'M': c = leftPad(d.getMinutes()); break;
// quarters not in Open Group's strftime specification
case 'q':
c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
case 'S': c = leftPad(d.getSeconds()); break;
case 'y': c = leftPad(d.getFullYear() % 100); break;
case 'Y': c = "" + d.getFullYear(); break;
case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
case 'w': c = "" + d.getDay(); break;
}
r.push(c);
escape = false;
} else {
if (c == "%") {
escape = true;
} else {
r.push(c);
}
}
}
return r.join("");
}
// To have a consistent view of time-based data independent of which time
// zone the client happens to be in we need a date-like object independent
// of time zones. This is done through a wrapper that only calls the UTC
// versions of the accessor methods.
function makeUtcWrapper(d) {
function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
sourceObj[sourceMethod] = function() {
return targetObj[targetMethod].apply(targetObj, arguments);
};
};
var utc = {
date: d
};
// support strftime, if found
if (d.strftime != undefined) {
addProxyMethod(utc, "strftime", d, "strftime");
}
addProxyMethod(utc, "getTime", d, "getTime");
addProxyMethod(utc, "setTime", d, "setTime");
var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
for (var p = 0; p < props.length; p++) {
addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
}
return utc;
};
// select time zone strategy. This returns a date-like object tied to the
// desired timezone
function dateGenerator(ts, opts) {
if (opts.timezone == "browser") {
return new Date(ts);
} else if (!opts.timezone || opts.timezone == "utc") {
return makeUtcWrapper(new Date(ts));
} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
var d = new timezoneJS.Date();
// timezone-js is fickle, so be sure to set the time zone before
// setting the time.
d.setTimezone(opts.timezone);
d.setTime(ts);
return d;
} else {
return makeUtcWrapper(new Date(ts));
}
}
// map of app. size of time units in milliseconds
var timeUnitSize = {
"second": 1000,
"minute": 60 * 1000,
"hour": 60 * 60 * 1000,
"day": 24 * 60 * 60 * 1000,
"month": 30 * 24 * 60 * 60 * 1000,
"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
"year": 365.2425 * 24 * 60 * 60 * 1000
};
// the allowed tick sizes, after 1 year we use
// an integer algorithm
var baseSpec = [
[1, "second"], [2, "second"], [5, "second"], [10, "second"],
[30, "second"],
[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
[30, "minute"],
[1, "hour"], [2, "hour"], [4, "hour"],
[8, "hour"], [12, "hour"],
[1, "day"], [2, "day"], [3, "day"],
[0.25, "month"], [0.5, "month"], [1, "month"],
[2, "month"]
];
// we don't know which variant(s) we'll need yet, but generating both is
// cheap
var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
[1, "year"]]);
var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
[1, "year"]]);
function init(plot) {
plot.hooks.processOptions.push(function (plot, options) {
$.each(plot.getAxes(), function(axisName, axis) {
var opts = axis.options;
if (opts.mode == "time") {
axis.tickGenerator = function(axis) {
var ticks = [];
var d = dateGenerator(axis.min, opts);
var minSize = 0;
// make quarter use a possibility if quarters are
// mentioned in either of these options
var spec = (opts.tickSize && opts.tickSize[1] ===
"quarter") ||
(opts.minTickSize && opts.minTickSize[1] ===
"quarter") ? specQuarters : specMonths;
if (opts.minTickSize != null) {
if (typeof opts.tickSize == "number") {
minSize = opts.tickSize;
} else {
minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
}
}
for (var i = 0; i < spec.length - 1; ++i) {
if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+ spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
break;
}
}
var size = spec[i][0];
var unit = spec[i][1];
// special-case the possibility of several years
if (unit == "year") {
// if given a minTickSize in years, just use it,
// ensuring that it's an integer
if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
size = Math.floor(opts.minTickSize[0]);
} else {
var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
var norm = (axis.delta / timeUnitSize.year) / magn;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
size *= magn;
}
// minimum size for years is 1
if (size < 1) {
size = 1;
}
}
axis.tickSize = opts.tickSize || [size, unit];
var tickSize = axis.tickSize[0];
unit = axis.tickSize[1];
var step = tickSize * timeUnitSize[unit];
if (unit == "second") {
d.setSeconds(floorInBase(d.getSeconds(), tickSize));
} else if (unit == "minute") {
d.setMinutes(floorInBase(d.getMinutes(), tickSize));
} else if (unit == "hour") {
d.setHours(floorInBase(d.getHours(), tickSize));
} else if (unit == "month") {
d.setMonth(floorInBase(d.getMonth(), tickSize));
} else if (unit == "quarter") {
d.setMonth(3 * floorInBase(d.getMonth() / 3,
tickSize));
} else if (unit == "year") {
d.setFullYear(floorInBase(d.getFullYear(), tickSize));
}
// reset smaller components
d.setMilliseconds(0);
if (step >= timeUnitSize.minute) {
d.setSeconds(0);
}
if (step >= timeUnitSize.hour) {
d.setMinutes(0);
}
if (step >= timeUnitSize.day) {
d.setHours(0);
}
if (step >= timeUnitSize.day * 4) {
d.setDate(1);
}
if (step >= timeUnitSize.month * 2) {
d.setMonth(floorInBase(d.getMonth(), 3));
}
if (step >= timeUnitSize.quarter * 2) {
d.setMonth(floorInBase(d.getMonth(), 6));
}
if (step >= timeUnitSize.year) {
d.setMonth(0);
}
var carry = 0;
var v = Number.NaN;
var prev;
do {
prev = v;
v = d.getTime();
ticks.push(v);
if (unit == "month" || unit == "quarter") {
if (tickSize < 1) {
// a bit complicated - we'll divide the
// month/quarter up but we need to take
// care of fractions so we don't end up in
// the middle of a day
d.setDate(1);
var start = d.getTime();
d.setMonth(d.getMonth() +
(unit == "quarter" ? 3 : 1));
var end = d.getTime();
d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
carry = d.getHours();
d.setHours(0);
} else {
d.setMonth(d.getMonth() +
tickSize * (unit == "quarter" ? 3 : 1));
}
} else if (unit == "year") {
d.setFullYear(d.getFullYear() + tickSize);
} else {
d.setTime(v + step);
}
} while (v < axis.max && v != prev);
return ticks;
};
axis.tickFormatter = function (v, axis) {
var d = dateGenerator(v, axis.options);
// first check global format
if (opts.timeformat != null) {
return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
}
// possibly use quarters if quarters are mentioned in
// any of these places
var useQuarters = (axis.options.tickSize &&
axis.options.tickSize[1] == "quarter") ||
(axis.options.minTickSize &&
axis.options.minTickSize[1] == "quarter");
var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
var span = axis.max - axis.min;
var suffix = (opts.twelveHourClock) ? " %p" : "";
var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
var fmt;
if (t < timeUnitSize.minute) {
fmt = hourCode + ":%M:%S" + suffix;
} else if (t < timeUnitSize.day) {
if (span < 2 * timeUnitSize.day) {
fmt = hourCode + ":%M" + suffix;
} else {
fmt = "%b %d " + hourCode + ":%M" + suffix;
}
} else if (t < timeUnitSize.month) {
fmt = "%b %d";
} else if ((useQuarters && t < timeUnitSize.quarter) ||
(!useQuarters && t < timeUnitSize.year)) {
if (span < timeUnitSize.year) {
fmt = "%b";
} else {
fmt = "%b %Y";
}
} else if (useQuarters && t < timeUnitSize.year) {
if (span < timeUnitSize.year) {
fmt = "Q%q";
} else {
fmt = "Q%q %Y";
}
} else {
fmt = "%Y";
}
var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
return rt;
};
}
});
});
}
$.plot.plugins.push({
init: init,
options: options,
name: 'time',
version: '1.0'
});
// Time-axis support used to be in Flot core, which exposed the
// formatDate function on the plot object. Various plugins depend
// on the function, so we need to re-expose it here.
$.plot.formatDate = formatDate;
})(jQuery);

9
assets/js/admin/jquery.flot.time.min.js vendored Executable file
View File

@ -0,0 +1,9 @@
/* Pretty handling of time axes.
Copyright (c) 2007-2013 IOLA and Ole Laursen.
Licensed under the MIT license.
Set axis.mode to "time" to enable. See the section "Time series data" in
API.txt for details.
*/(function(e){function n(e,t){return t*Math.floor(e/t)}function r(e,t,n,r){if(typeof e.strftime=="function")return e.strftime(t);var i=function(e,t){e=""+e;t=""+(t==null?"0":t);return e.length==1?t+e:e},s=[],o=!1,u=e.getHours(),a=u<12;n==null&&(n=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]);r==null&&(r=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]);var f;u>12?f=u-12:u==0?f=12:f=u;for(var l=0;l<t.length;++l){var c=t.charAt(l);if(o){switch(c){case"a":c=""+r[e.getDay()];break;case"b":c=""+n[e.getMonth()];break;case"d":c=i(e.getDate());break;case"e":c=i(e.getDate()," ");break;case"h":case"H":c=i(u);break;case"I":c=i(f);break;case"l":c=i(f," ");break;case"m":c=i(e.getMonth()+1);break;case"M":c=i(e.getMinutes());break;case"q":c=""+(Math.floor(e.getMonth()/3)+1);break;case"S":c=i(e.getSeconds());break;case"y":c=i(e.getFullYear()%100);break;case"Y":c=""+e.getFullYear();break;case"p":c=a?"am":"pm";break;case"P":c=a?"AM":"PM";break;case"w":c=""+e.getDay()}s.push(c);o=!1}else c=="%"?o=!0:s.push(c)}return s.join("")}function i(e){function t(e,t,n,r){e[t]=function(){return n[r].apply(n,arguments)}}var n={date:e};e.strftime!=undefined&&t(n,"strftime",e,"strftime");t(n,"getTime",e,"getTime");t(n,"setTime",e,"setTime");var r=["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds"];for(var i=0;i<r.length;i++){t(n,"get"+r[i],e,"getUTC"+r[i]);t(n,"set"+r[i],e,"setUTC"+r[i])}return n}function s(e,t){if(t.timezone=="browser")return new Date(e);if(!t.timezone||t.timezone=="utc")return i(new Date(e));if(typeof timezoneJS!="undefined"&&typeof timezoneJS.Date!="undefined"){var n=new timezoneJS.Date;n.setTimezone(t.timezone);n.setTime(e);return n}return i(new Date(e))}function l(t){t.hooks.processOptions.push(function(t,i){e.each(t.getAxes(),function(e,t){var i=t.options;if(i.mode=="time"){t.tickGenerator=function(e){var t=[],r=s(e.min,i),u=0,l=i.tickSize&&i.tickSize[1]==="quarter"||i.minTickSize&&i.minTickSize[1]==="quarter"?f:a;i.minTickSize!=null&&(typeof i.tickSize=="number"?u=i.tickSize:u=i.minTickSize[0]*o[i.minTickSize[1]]);for(var c=0;c<l.length-1;++c)if(e.delta<(l[c][0]*o[l[c][1]]+l[c+1][0]*o[l[c+1][1]])/2&&l[c][0]*o[l[c][1]]>=u)break;var h=l[c][0],p=l[c][1];if(p=="year"){if(i.minTickSize!=null&&i.minTickSize[1]=="year")h=Math.floor(i.minTickSize[0]);else{var d=Math.pow(10,Math.floor(Math.log(e.delta/o.year)/Math.LN10)),v=e.delta/o.year/d;v<1.5?h=1:v<3?h=2:v<7.5?h=5:h=10;h*=d}h<1&&(h=1)}e.tickSize=i.tickSize||[h,p];var m=e.tickSize[0];p=e.tickSize[1];var g=m*o[p];p=="second"?r.setSeconds(n(r.getSeconds(),m)):p=="minute"?r.setMinutes(n(r.getMinutes(),m)):p=="hour"?r.setHours(n(r.getHours(),m)):p=="month"?r.setMonth(n(r.getMonth(),m)):p=="quarter"?r.setMonth(3*n(r.getMonth()/3,m)):p=="year"&&r.setFullYear(n(r.getFullYear(),m));r.setMilliseconds(0);g>=o.minute&&r.setSeconds(0);g>=o.hour&&r.setMinutes(0);g>=o.day&&r.setHours(0);g>=o.day*4&&r.setDate(1);g>=o.month*2&&r.setMonth(n(r.getMonth(),3));g>=o.quarter*2&&r.setMonth(n(r.getMonth(),6));g>=o.year&&r.setMonth(0);var y=0,b=Number.NaN,w;do{w=b;b=r.getTime();t.push(b);if(p=="month"||p=="quarter")if(m<1){r.setDate(1);var E=r.getTime();r.setMonth(r.getMonth()+(p=="quarter"?3:1));var S=r.getTime();r.setTime(b+y*o.hour+(S-E)*m);y=r.getHours();r.setHours(0)}else r.setMonth(r.getMonth()+m*(p=="quarter"?3:1));else p=="year"?r.setFullYear(r.getFullYear()+m):r.setTime(b+g)}while(b<e.max&&b!=w);return t};t.tickFormatter=function(e,t){var n=s(e,t.options);if(i.timeformat!=null)return r(n,i.timeformat,i.monthNames,i.dayNames);var u=t.options.tickSize&&t.options.tickSize[1]=="quarter"||t.options.minTickSize&&t.options.minTickSize[1]=="quarter",a=t.tickSize[0]*o[t.tickSize[1]],f=t.max-t.min,l=i.twelveHourClock?" %p":"",c=i.twelveHourClock?"%I":"%H",h;a<o.minute?h=c+":%M:%S"+l:a<o.day?f<2*o.day?h=c+":%M"+l:h="%b %d "+c+":%M"+l:a<o.month?h="%b %d":u&&a<o.quarter||!u&&a<o.year?f<o.year?h="%b":h="%b %Y":u&&a<o.year?f<o.year?h="Q%q":h="Q%q %Y":h="%Y";var p=r(n,h,i.monthNames,i.dayNames);return p}}})})}var t={xaxis:{timezone:null,timeformat:null,twelveHourClock:!1,monthNames:null}},o={second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,quarter:7776e6,year:525949.2*60*1e3},u=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]],a=u.concat([[3,"month"],[6,"month"],[1,"year"]]),f=u.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);e.plot.plugins.push({init:l,options:t,name:"time",version:"1.0"});e.plot.formatDate=r})(jQuery);

224
assets/js/admin/reports.js Normal file
View File

@ -0,0 +1,224 @@
jQuery(document).ready(function($) {
function showTooltip(x, y, contents) {
jQuery('<div class="chart-tooltip">' + contents + '</div>').css( {
top: y - 16,
left: x + 20
}).appendTo("body").fadeIn(200);
}
var prev_data_index = null;
var prev_series_index = null;
jQuery(".chart-placeholder").bind( "plothover", function (event, pos, item) {
if (item) {
if ( prev_data_index != item.dataIndex || prev_series_index != item.seriesIndex ) {
prev_data_index = item.dataIndex;
prev_series_index = item.seriesIndex;
jQuery( ".chart-tooltip" ).remove();
if ( item.series.points.show || item.series.enable_tooltip ) {
var y = item.series.data[item.dataIndex][1];
tooltip_content = '';
if ( item.series.prepend_label )
tooltip_content = tooltip_content + item.series.label + ": ";
if ( item.series.prepend_tooltip )
tooltip_content = tooltip_content + item.series.prepend_tooltip;
tooltip_content = tooltip_content + y;
if ( item.series.append_tooltip )
tooltip_content = tooltip_content + item.series.append_tooltip;
if ( item.series.pie.show ) {
showTooltip( pos.pageX, pos.pageY, tooltip_content );
} else {
showTooltip( item.pageX, item.pageY, tooltip_content );
}
}
}
}
else {
jQuery(".chart-tooltip").remove();
prev_data_index = null;
}
});
$('.wc_sparkline.bars').each(function() {
var chart_data = $(this).data('sparkline');
var options = {
grid: {
show: false
}
};
// main series
var series = [{
data: chart_data,
color: $(this).data('color'),
bars: { fillColor: $(this).data('color'), fill: true, show: true, lineWidth: 1, barWidth: $(this).data('barwidth'), align: 'center' },
shadowSize: 0
}];
// draw the sparkline
var plot = $.plot( $(this), series, options );
});
$('.wc_sparkline.lines').each(function() {
var chart_data = $(this).data('sparkline');
var options = {
grid: {
show: false
}
};
// main series
var series = [{
data: chart_data,
color: $(this).data('color'),
lines: { fill: false, show: true, lineWidth: 1, align: 'center' },
shadowSize: 0
}];
// draw the sparkline
var plot = $.plot( $(this), series, options );
});
var dates = jQuery( ".range_datepicker" ).datepicker({
defaultDate: "",
dateFormat: "yy-mm-dd",
numberOfMonths: 1,
maxDate: "+0D",
showButtonPanel: true,
showOn: "focus",
buttonImageOnly: true,
onSelect: function( selectedDate ) {
var option = jQuery(this).is('.from') ? "minDate" : "maxDate",
instance = jQuery( this ).data( "datepicker" ),
date = jQuery.datepicker.parseDate(
instance.settings.dateFormat ||
jQuery.datepicker._defaults.dateFormat,
selectedDate, instance.settings );
dates.not( this ).datepicker( "option", option, date );
}
});
// Export
$('.export_csv').click(function(){
var exclude_series = $(this).data( 'exclude_series' ) || '';
exclude_series = exclude_series.toString();
exclude_series = exclude_series.split(',');
var xaxes_label = $(this).data('xaxes');
var groupby = $(this).data('groupby');
var export = $(this).data('export');
var csv_data = "data:application/csv;charset=utf-8,"
if ( export == 'table' ) {
$(this).closest('div').find('thead tr,tbody tr').each(function() {
$(this).find('th,td').each(function() {
value = $(this).text();
value = value.replace( '[?]', '' );
csv_data += '"' + value + '"' + ",";
});
csv_data = csv_data.substring( 0, csv_data.length - 1 );
csv_data += "\n";
});
$(this).closest('div').find('tfoot tr').each(function() {
$(this).find('th,td').each(function() {
value = $(this).text();
value = value.replace( '[?]', '' );
csv_data += '"' + value + '"' + ",";
if ( $(this).attr('colspan') > 0 )
for ( i = 1; i < $(this).attr('colspan'); i++ )
csv_data += '"",';
});
csv_data = csv_data.substring( 0, csv_data.length - 1 );
csv_data += "\n";
});
} else {
if ( ! window.main_chart )
return false;
var the_series = window.main_chart.getData();
var series = [];
csv_data += xaxes_label + ",";
$.each(the_series, function( index, value ) {
if ( ! exclude_series || $.inArray( index.toString(), exclude_series ) == -1 )
series.push( value );
});
// CSV Headers
for ( var s = 0; s < series.length; ++s ) {
csv_data += series[s].label + ',';
}
csv_data = csv_data.substring( 0, csv_data.length - 1 );
csv_data += "\n";
// Get x axis values
var xaxis = {}
for ( var s = 0; s < series.length; ++s ) {
var series_data = series[s].data;
for ( var d = 0; d < series_data.length; ++d ) {
xaxis[series_data[d][0]] = new Array();
// Zero values to start
for ( var i = 0; i < series.length; ++i ) {
xaxis[series_data[d][0]].push(0);
}
}
}
// Add chart data
for ( var s = 0; s < series.length; ++s ) {
var series_data = series[s].data;
for ( var d = 0; d < series_data.length; ++d ) {
xaxis[series_data[d][0]][s] = series_data[d][1];
}
}
// Loop data and output to csv string
$.each( xaxis, function( index, value ) {
var date = new Date( parseInt( index ) );
if ( groupby == 'day' )
csv_data += date.getFullYear() + "-" + parseInt( date.getMonth() + 1 ) + "-" + date.getDate() + ',';
else
csv_data += date.getFullYear() + "-" + parseInt( date.getMonth() + 1 ) + ',';
for ( var d = 0; d < value.length; ++d ) {
val = value[d];
if( Math.round( val ) != val )
val = val.toFixed(2);
csv_data += val + ',';
}
csv_data = csv_data.substring( 0, csv_data.length - 1 );
csv_data += "\n";
} );
}
// Set data as href and return
$(this).attr( 'href', encodeURI( csv_data ) );
return true;
});
});

1
assets/js/admin/reports.min.js vendored Normal file
View File

@ -0,0 +1 @@
jQuery(document).ready(function(e){function t(e,t,n){jQuery('<div class="chart-tooltip">'+n+"</div>").css({top:t-16,left:e+20}).appendTo("body").fadeIn(200)}var n=null,r=null;jQuery(".chart-placeholder").bind("plothover",function(e,i,s){if(s){if(n!=s.dataIndex||r!=s.seriesIndex){n=s.dataIndex;r=s.seriesIndex;jQuery(".chart-tooltip").remove();if(s.series.points.show||s.series.enable_tooltip){var o=s.series.data[s.dataIndex][1];tooltip_content="";s.series.prepend_label&&(tooltip_content=tooltip_content+s.series.label+": ");s.series.prepend_tooltip&&(tooltip_content+=s.series.prepend_tooltip);tooltip_content+=o;s.series.append_tooltip&&(tooltip_content+=s.series.append_tooltip);s.series.pie.show?t(i.pageX,i.pageY,tooltip_content):t(s.pageX,s.pageY,tooltip_content)}}}else{jQuery(".chart-tooltip").remove();n=null}});e(".wc_sparkline.bars").each(function(){var t=e(this).data("sparkline"),n={grid:{show:!1}},r=[{data:t,color:e(this).data("color"),bars:{fillColor:e(this).data("color"),fill:!0,show:!0,lineWidth:1,barWidth:e(this).data("barwidth"),align:"center"},shadowSize:0}],i=e.plot(e(this),r,n)});e(".wc_sparkline.lines").each(function(){var t=e(this).data("sparkline"),n={grid:{show:!1}},r=[{data:t,color:e(this).data("color"),lines:{fill:!1,show:!0,lineWidth:1,align:"center"},shadowSize:0}],i=e.plot(e(this),r,n)});var i=jQuery(".range_datepicker").datepicker({defaultDate:"",dateFormat:"yy-mm-dd",numberOfMonths:1,maxDate:"+0D",showButtonPanel:!0,showOn:"focus",buttonImageOnly:!0,onSelect:function(e){var t=jQuery(this).is(".from")?"minDate":"maxDate",n=jQuery(this).data("datepicker"),r=jQuery.datepicker.parseDate(n.settings.dateFormat||jQuery.datepicker._defaults.dateFormat,e,n.settings);i.not(this).datepicker("option",t,r)}});e(".export_csv").click(function(){var t=e(this).data("exclude_series")||"";t=t.toString();t=t.split(",");var n=e(this).data("xaxes"),r=e(this).data("groupby"),i=e(this).data("export"),s="data:application/csv;charset=utf-8,";if(i=="table"){e(this).closest("div").find("thead tr,tbody tr").each(function(){e(this).find("th,td").each(function(){value=e(this).text();value=value.replace("[?]","");s+='"'+value+'"'+","});s=s.substring(0,s.length-1);s+="\n"});e(this).closest("div").find("tfoot tr").each(function(){e(this).find("th,td").each(function(){value=e(this).text();value=value.replace("[?]","");s+='"'+value+'"'+",";if(e(this).attr("colspan")>0)for(h=1;h<e(this).attr("colspan");h++)s+='"",'});s=s.substring(0,s.length-1);s+="\n"})}else{if(!window.main_chart)return!1;var o=window.main_chart.getData(),u=[];s+=n+",";e.each(o,function(n,r){(!t||e.inArray(n.toString(),t)==-1)&&u.push(r)});for(var a=0;a<u.length;++a)s+=u[a].label+",";s=s.substring(0,s.length-1);s+="\n";var f={};for(var a=0;a<u.length;++a){var l=u[a].data;for(var c=0;c<l.length;++c){f[l[c][0]]=new Array;for(var h=0;h<u.length;++h)f[l[c][0]].push(0)}}for(var a=0;a<u.length;++a){var l=u[a].data;for(var c=0;c<l.length;++c)f[l[c][0]][a]=l[c][1]}e.each(f,function(e,t){var n=new Date(parseInt(e));r=="day"?s+=n.getFullYear()+"-"+parseInt(n.getMonth()+1)+"-"+n.getDate()+",":s+=n.getFullYear()+"-"+parseInt(n.getMonth()+1)+",";for(var i=0;i<t.length;++i){val=t[i];Math.round(val)!=val&&(val=val.toFixed(2));s+=val+","}s=s.substring(0,s.length-1);s+="\n"})}e(this).attr("href",encodeURI(s));return!0})});

View File

@ -1,17 +1,16 @@
// Chosen, a Select Box Enhancer for jQuery and Prototype
// by Patrick Filler for Harvest, http://getharvest.com
//
// Version 0.9.14
// Version 0.10.0
// Full source at https://github.com/harvesthq/chosen
// Copyright (c) 2011 Harvest http://getharvest.com
// MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
// This file is generated by `cake build`, do not edit it by hand.
// This file is generated by `grunt build`, do not edit it by hand.
(function() {
var SelectParser;
SelectParser = (function() {
function SelectParser() {
this.options_index = 0;
this.parsed = [];
@ -92,19 +91,12 @@
}).call(this);
/*
Chosen source: generate output using 'cake build'
Copyright (c) 2011 by Harvest
*/
(function() {
var AbstractChosen, root;
root = this;
AbstractChosen = (function() {
function AbstractChosen(form_field, options) {
this.form_field = form_field;
this.options = options != null ? options : {};
@ -189,23 +181,25 @@ Copyright (c) 2011 by Harvest
AbstractChosen.prototype.result_add_option = function(option) {
var classes, style;
if (!option.disabled) {
option.dom_id = this.container_id + "_o_" + option.array_index;
classes = option.selected && this.is_multiple ? [] : ["active-result"];
if (option.selected) {
classes.push("result-selected");
}
if (option.group_array_index != null) {
classes.push("group-option");
}
if (option.classes !== "") {
classes.push(option.classes);
}
style = option.style.cssText !== "" ? " style=\"" + option.style + "\"" : "";
return '<li id="' + option.dom_id + '" class="' + classes.join(' ') + '"' + style + '>' + option.html + '</li>';
} else {
return "";
option.dom_id = this.container_id + "_o_" + option.array_index;
classes = [];
if (!option.disabled && !(option.selected && this.is_multiple)) {
classes.push("active-result");
}
if (option.disabled && !(option.selected && this.is_multiple)) {
classes.push("disabled-result");
}
if (option.selected) {
classes.push("result-selected");
}
if (option.group_array_index != null) {
classes.push("group-option");
}
if (option.classes !== "") {
classes.push(option.classes);
}
style = option.style.cssText !== "" ? " style=\"" + option.style + "\"" : "";
return '<li id="' + option.dom_id + '" class="' + classes.join(' ') + '"' + style + '>' + option.html + '</li>';
};
AbstractChosen.prototype.results_update_field = function() {
@ -252,7 +246,7 @@ Copyright (c) 2011 by Harvest
AbstractChosen.prototype.choices_click = function(evt) {
evt.preventDefault();
if (!this.results_showing) {
if (!(this.results_showing || this.is_disabled)) {
return this.results_show();
}
};
@ -337,14 +331,8 @@ Copyright (c) 2011 by Harvest
}).call(this);
/*
Chosen source: generate output using 'cake build'
Copyright (c) 2011 by Harvest
*/
(function() {
var $, Chosen, root,
var $, Chosen, root, _ref,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
@ -368,11 +356,11 @@ Copyright (c) 2011 by Harvest
});
Chosen = (function(_super) {
__extends(Chosen, _super);
function Chosen() {
return Chosen.__super__.constructor.apply(this, arguments);
_ref = Chosen.__super__.constructor.apply(this, arguments);
return _ref;
}
Chosen.prototype.setup = function() {
@ -534,8 +522,8 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.search_results_mousewheel = function(evt) {
var delta, _ref, _ref1;
delta = -((_ref = evt.originalEvent) != null ? _ref.wheelDelta : void 0) || ((_ref1 = evt.originialEvent) != null ? _ref1.detail : void 0);
var delta, _ref1, _ref2;
delta = -((_ref1 = evt.originalEvent) != null ? _ref1.wheelDelta : void 0) || ((_ref2 = evt.originialEvent) != null ? _ref2.detail : void 0);
if (delta != null) {
evt.preventDefault();
if (evt.type === 'DOMMouseScroll') {
@ -556,7 +544,6 @@ Copyright (c) 2011 by Harvest
this.active_field = false;
this.results_hide();
this.container.removeClass("chzn-container-active");
this.winnow_results_clear();
this.clear_backstroke();
this.show_search_field_default();
return this.search_field_scale();
@ -578,24 +565,26 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.results_build = function() {
var content, data, _i, _len, _ref;
var content, data, _i, _len, _ref1;
this.parsing = true;
this.selected_option_count = null;
this.results_data = root.SelectParser.select_to_array(this.form_field);
if (this.is_multiple && this.choices_count() > 0) {
if (this.is_multiple) {
this.search_choices.find("li.search-choice").remove();
} else if (!this.is_multiple) {
this.selected_item.addClass("chzn-default").find("span").text(this.default_text);
if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold) {
this.search_field.prop('readonly', true);
this.container.addClass("chzn-container-single-nosearch");
} else {
this.search_field.prop('readonly', false);
this.container.removeClass("chzn-container-single-nosearch");
}
}
content = '';
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
data = _ref[_i];
_ref1 = this.results_data;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
data = _ref1[_i];
if (data.group) {
content += this.result_add_group(data);
} else if (!data.empty) {
@ -618,12 +607,8 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.result_add_group = function(group) {
if (!group.disabled) {
group.dom_id = this.container_id + "_g_" + group.array_index;
return '<li id="' + group.dom_id + '" class="group-result">' + $("<div />").text(group.label).html() + '</li>';
} else {
return "";
}
group.dom_id = this.container_id + "_g_" + group.array_index;
return '<li id="' + group.dom_id + '" class="group-result">' + $("<div />").text(group.label).html() + '</li>';
};
Chosen.prototype.result_do_highlight = function(el) {
@ -653,9 +638,7 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.results_show = function() {
if (this.result_single_selected != null) {
this.result_do_highlight(this.result_single_selected);
} else if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
if (this.is_multiple && this.max_selected_options <= this.choices_count()) {
this.form_field_jq.trigger("liszt:maxselected", {
chosen: this
});
@ -672,11 +655,13 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.results_hide = function() {
this.result_clear_highlight();
this.container.removeClass("chzn-with-drop");
this.form_field_jq.trigger("liszt:hiding_dropdown", {
chosen: this
});
if (this.results_showing) {
this.result_clear_highlight();
this.container.removeClass("chzn-with-drop");
this.form_field_jq.trigger("liszt:hiding_dropdown", {
chosen: this
});
}
return this.results_showing = false;
};
@ -693,7 +678,7 @@ Copyright (c) 2011 by Harvest
var _this = this;
this.form_field_label = this.form_field_jq.parents("label");
if (!this.form_field_label.length && this.form_field.id.length) {
this.form_field_label = $("label[for=" + this.form_field.id + "]");
this.form_field_label = $("label[for='" + this.form_field.id + "']");
}
if (this.form_field_label.length > 0) {
return this.form_field_label.click(function(evt) {
@ -814,7 +799,7 @@ Copyright (c) 2011 by Harvest
return false;
}
if (this.is_multiple) {
this.result_deactivate(high);
high.removeClass("active-result");
} else {
this.search_results.find(".result-selected").removeClass("result-selected");
this.result_single_selected = high;
@ -848,12 +833,18 @@ Copyright (c) 2011 by Harvest
}
};
Chosen.prototype.result_activate = function(el) {
return el.addClass("active-result");
Chosen.prototype.result_activate = function(el, option) {
if (option.disabled) {
return el.addClass("disabled-result");
} else if (this.is_multiple && option.selected) {
return el.addClass("result-selected");
} else {
return el.addClass("active-result");
}
};
Chosen.prototype.result_deactivate = function(el) {
return el.removeClass("active-result");
return el.removeClass("active-result result-selected disabled-result");
};
Chosen.prototype.result_deselect = function(pos) {
@ -878,26 +869,30 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.single_deselect_control_build = function() {
if (this.allow_single_deselect && this.selected_item.find("abbr").length < 1) {
return this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
if (!this.allow_single_deselect) {
return;
}
if (!this.selected_item.find("abbr").length) {
this.selected_item.find("span").first().after("<abbr class=\"search-choice-close\"></abbr>");
}
return this.selected_item.addClass("chzn-single-with-deselect");
};
Chosen.prototype.winnow_results = function() {
var found, option, part, parts, regex, regexAnchor, result, result_id, results, searchText, startpos, text, zregex, _i, _j, _len, _len1, _ref;
var found, option, part, parts, regex, regexAnchor, result, result_id, results, searchText, startpos, text, zregex, _i, _j, _len, _len1, _ref1;
this.no_results_clear();
results = 0;
searchText = this.search_field.val() === this.default_text ? "" : $('<div/>').text($.trim(this.search_field.val())).html();
regexAnchor = this.search_contains ? "" : "^";
regex = new RegExp(regexAnchor + searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');
zregex = new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
if (!option.disabled && !option.empty) {
_ref1 = this.results_data;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
option = _ref1[_i];
if (!option.empty) {
if (option.group) {
$('#' + option.dom_id).css('display', 'none');
} else if (!(this.is_multiple && option.selected)) {
} else {
found = false;
result_id = option.dom_id;
result = $("#" + result_id);
@ -925,7 +920,7 @@ Copyright (c) 2011 by Harvest
text = option.html;
}
result.html(text);
this.result_activate(result);
this.result_activate(result, option);
if (option.group_array_index != null) {
$("#" + this.results_data[option.group_array_index].dom_id).css('display', 'list-item');
}
@ -945,25 +940,6 @@ Copyright (c) 2011 by Harvest
}
};
Chosen.prototype.winnow_results_clear = function() {
var li, lis, _i, _len, _results;
this.search_field.val("");
lis = this.search_results.find("li");
_results = [];
for (_i = 0, _len = lis.length; _i < _len; _i++) {
li = lis[_i];
li = $(li);
if (li.hasClass("group-result")) {
_results.push(li.css('display', 'auto'));
} else if (!this.is_multiple || !li.hasClass("result-selected")) {
_results.push(this.result_activate(li));
} else {
_results.push(void 0);
}
}
return _results;
};
Chosen.prototype.winnow_results_set_highlight = function() {
var do_high, selected_results;
if (!this.result_highlight) {
@ -987,19 +963,13 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.keydown_arrow = function() {
var first_active, next_sib;
if (!this.result_highlight) {
first_active = this.search_results.find("li.active-result").first();
if (first_active) {
this.result_do_highlight($(first_active));
}
} else if (this.results_showing) {
var next_sib;
if (this.results_showing && this.result_highlight) {
next_sib = this.result_highlight.nextAll("li.active-result").first();
if (next_sib) {
this.result_do_highlight(next_sib);
return this.result_do_highlight(next_sib);
}
}
if (!this.results_showing) {
} else {
return this.results_show();
}
};
@ -1047,8 +1017,8 @@ Copyright (c) 2011 by Harvest
};
Chosen.prototype.keydown_checker = function(evt) {
var stroke, _ref;
stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
var stroke, _ref1;
stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
this.search_field_scale();
if (stroke !== 8 && this.pending_backstroke) {
this.clear_backstroke();
@ -1071,6 +1041,7 @@ Copyright (c) 2011 by Harvest
this.keyup_arrow();
break;
case 40:
evt.preventDefault();
this.keydown_arrow();
break;
}

File diff suppressed because one or more lines are too long

View File

@ -17,6 +17,16 @@ function woocommerce_show_messages() {
wc_show_messages();
}
function woocommerce_weekend_area_js() {
_deprecated_function( 'woocommerce_weekend_area_js', '2.1', '' );
}
function woocommerce_tooltip_js() {
_deprecated_function( 'woocommerce_tooltip_js', '2.1', '' );
}
function woocommerce_datepicker_js() {
_deprecated_function( 'woocommerce_datepicker_js', '2.1', '' );
}
/**
* Handle renamed filters
*/

View File

@ -0,0 +1,14 @@
<?php
/**
* WooCommerce Reporting Functions
*
* Functions for getting sales reports.
*
* @author WooThemes
* @category Core
* @package WooCommerce/Functions
* @version 2.1.0
*/
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly