HEX
Server: Apache/2
System: Linux server-80-13-140-150.da.direct 5.14.0-362.24.1.el9_3.0.1.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Apr 4 22:31:43 UTC 2024 x86_64
User: cpt (1004)
PHP: 8.1.24
Disabled: exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: /home/cpt/public_html/wp-content/plugins/events-manager/admin/dashboard.php
<?php
namespace EM\Admin;
use EM_DateTime;

class Dashboard {
	
	protected static $footer_js;
	
	public static function init() {
		if( get_option('dbem_booking_charts_wpdashboard') ){
			add_action( 'wp_dashboard_setup', array( static::class, 'wp_dashboard_setup') );
			add_action( 'admin_print_scripts', array( static::class, 'enqueue_scripts'), 10, 1 );
		}
		if( get_option('dbem_booking_charts_frontend') && !is_admin() ){
			add_action( 'em_enqueue_scripts', array( static::class, 'enqueue_scripts'), 10, 1 );
		}
		add_action( 'wp_ajax_em_chart_bookings', array( static::class, 'ajax'), 10, 1 );
	}

	public static function wp_dashboard_setup() {
		wp_add_dashboard_widget('em_booking_stats', __('Events Manager Bookings', 'events-manager'), array( static::class, 'stats_widget'));
	}
	
	public static function enqueue_scripts( $hook_suffix = false ){
		if( is_admin() ) {
			$screen = get_current_screen();
			if ( $screen->id == 'dashboard' ) {
				$min = \EM_Scripts_and_Styles::min_suffix();
				wp_enqueue_script( 'chart-js', EM_DIR_URI . '/includes/external/chartjs/chart.umd' . $min . '.js', array(), EM_VERSION );
				\EM_Scripts_and_Styles::admin_enqueue( true );
				//wp_enqueue_script( 'chart-js', 'https://cdn.jsdelivr.net/npm/chart.js@4.0.1/dist/chart.umd.min.js', array(), EM_VERSION);
				//wp_enqueue_script( 'chart-js-utils', 'https://www.chartjs.org/samples/2.9.4/utils.js', array());
			} elseif ( !empty( $_REQUEST['page'] ) && $_REQUEST['page'] == 'events-manager-bookings' ) {
				// we need to call this before enqueue, otherwise it'll get enqueued at footer and possibly dependent EM stuff isn't loaded
				$min = \EM_Scripts_and_Styles::min_suffix();
				wp_enqueue_script( 'chart-js', EM_DIR_URI . '/includes/external/chartjs/chart.umd' . $min . '.js', array( 'moment' ), EM_VERSION );
			} elseif ( $hook_suffix === true ) {
				// we need to call this before enqueue, otherwise it'll get enqueued at footer and possibly dependent EM stuff isn't loaded
				$min = \EM_Scripts_and_Styles::min_suffix();
				wp_enqueue_script( 'chart-js', EM_DIR_URI . '/includes/external/chartjs/chart.umd' . $min . '.js', array(), EM_VERSION, true );
				\EM_Scripts_and_Styles::admin_enqueue( true );
			}
		} elseif ( get_option( 'dbem_booking_charts_frontend' ) ) {
			// we assume it's the bookings admin page if this class was loaded
			$min = \EM_Scripts_and_Styles::min_suffix();
			wp_enqueue_script( 'chart-js', EM_DIR_URI . '/includes/external/chartjs/chart.umd' . $min . '.js', array( 'moment' ), EM_VERSION );
		}
	}
	
	public static function stats_widget(){
		static::output( 'wp-dashboard' );
	}
	
	public static function ajax(){
		if( !empty($_REQUEST['_nonce']) && !empty($_REQUEST['view']) ) {
			if( get_option('dbem_booking_charts_wpdashboard') || get_option('dbem_booking_charts_dashboard') || get_option('dbem_booking_charts_event') ){
				$args = static::get_post_args();
				$nonce_action = 'em-chart-'.$args['view'];
				if( $args['view'] === 'event' || $args['view'] === 'ticket' ){
					$nonce_action = 'em-chart-'.$args['view'].'-'.$args[$args['view']];
				}
				if( wp_verify_nonce($_REQUEST['_nonce'], $nonce_action) ) {
					static::save_view( $args );
					static::output_chart( $args );
					die();
				}
			}
		}
	}
	
	public static function save_view($args){
		$views = get_user_meta( get_current_user_id(), 'em_charts_views', true );
		if( !is_array($views) ) $views = array('bookings' => array());
		$views['bookings'][$args['view']] = $args;
		update_user_meta( get_current_user_id(), 'em_charts_views', $views);
	}
	
	public static function get_post_args(){
		$args = static::get_default_args();
		if( !empty($_REQUEST['mode']) && in_array($_REQUEST['mode'], array('day','week','month','year')) ){
			$args['mode'] = $_REQUEST['mode'];
		}
		if( !empty($_REQUEST['range_dates']) && is_array($_REQUEST['range_dates']) ){
			$date_regex = '/^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$/';
			if( !empty($_REQUEST['range_dates'][0][0]) && preg_match($date_regex, $_REQUEST['range_dates'][0][0]) ){
				$args['range_dates'][0][0] = $_REQUEST['range_dates'][0][0];
				if( !empty($_REQUEST['range_dates'][0][1]) && preg_match($date_regex, $_REQUEST['range_dates'][0][1]) ){
					$args['range_dates'][0][1] = $_REQUEST['range_dates'][0][1];
				}
			}
			if( !empty($_REQUEST['range_dates'][1]) && preg_match($date_regex, $_REQUEST['range_dates'][1]) ){
				$args['range_dates'][1] = $_REQUEST['range_dates'][1];
			}
		}
		$args['range_type'] = !empty($_REQUEST['range_type']) && preg_match('/^[a-zA-Z0-9 ]+$/', $_REQUEST['range_type']) ? $_REQUEST['range_type'] : '3 months';
		if( isset($_REQUEST['compare']) ) {
			$args['compare'] = sanitize_key($_REQUEST['compare']);
		}
		if( !empty($_REQUEST['unit']) && in_array($_REQUEST['unit'], array('price', 'spaces', 'bookings')) ){
			$args['unit'] = $_REQUEST['unit'];
		}
		if( !empty($_REQUEST['type']) && in_array($_REQUEST['type'], array('pie', 'line', 'bar')) ){
			$args['type'] = $_REQUEST['type'];
		}
		$args['subgroup'] = !empty($_REQUEST['stacked']) ? $_REQUEST['stacked']:false;
		$args['status'] = 'all';
		if( !empty($_REQUEST['status']) ){
			$status = explode(',', $_REQUEST['status']);
			if( \EM_Object::array_is_numeric($status) ){
				$args['status'] = $status;
			}
		}
		if( !empty($_REQUEST['summaries']) && is_array($_REQUEST['summaries']) ) {
			$args['summaries'] = array_intersect( $_REQUEST['summaries'], $args['summaries'] );
			foreach( $args['summaries'] as $k => $v ){
				$args['summaries'][$k] = $v == true;
			}
		}
		if( !empty($_REQUEST['view']) ){
			$args['view'] = sanitize_key($_REQUEST['view']);
		}
		if( $args['view'] === 'event' && !empty($_REQUEST['event']) ){
			$args['event'] = absint($_REQUEST['event']);
		}elseif( $args['view'] === 'ticket' && !empty($_REQUESt['ticket']) ){
			$args['ticket'] = absint($_REQUEST['ticket']);
		}
		$args['show_filters'] = !empty($_REQUEST['show_filters']);
		return $args;
	}
	
	public static function get_default_args( $view = 'dashboard' ){
		$views = get_user_meta( get_current_user_id(), 'em_charts_views', true );
		$args = array('view' => $view);
		if( is_array($views) && !empty($views['bookings'][$view]) ){
			$args = $views['bookings'][$view];
		}
		$default_scope = $view === 'event' || $view === 'ticket' ? 'all':'3 months';
		$default_compare = $view === 'event' || $view === 'ticket' ? false:'previous';
		return array_merge( array(
			'mode' => 'month',
			'unit' => 'price',
			'compare' => $default_compare,
			'range_type' => $default_scope,
			'range_dates' => array( array(), '' ),
			'summaries' => array(
				'price' => true,
				'spaces' => true,
				'bookings' => $view !== 'wp-dashboard',
				'price_avg' => $view !== 'wp-dashboard',
				'spaces_avg' => $view !== 'wp-dashboard',
				'tickets_avg' => true,
			),
			'show_filters' => false,
			'status' => array('1'),
			'type' => 'line'
		), $args);
	}
	
	public static function get_compare_options(){
		$options = array(
			'time' => array(
				'label' => __('Earlier Dates', 'events-manager'),
				'options' => array(
					'previous' => __('Previous Period', 'events-manager'),
					'year' => __('Previous Year', 'events-manager'),
					'custom' => __('Custom Dates', 'events-manager'),
				),
			),
			'units' => array(
				'label' => __('Metrics', 'events-manager'),
				'options' => array(
					'spaces' => __('Spaces', 'events-manager'),
					'price' => __('Sales', 'events-manager'),
					'bookings' => __('Bookings', 'events-manager'),
				)
			),
		);
		return apply_filters('em_chart_compare_options', $options);
	}
	
	public static function get_range_types(){
		$range_types = array(
			'all' => __('All Time', 'events-manager'),
			'today'=> __('Today', 'events-manager'),
			'yesterday' => __('Yesterday', 'events-manager'),
			'this month' => __('This Month', 'events-manager'),
			'last month' => __('Last Month', 'events-manager'),
			'3 months' => sprintf(__('Last %d Months', 'events-manager'),3),
			'6 months' => sprintf(__('Last %d Months', 'events-manager'),6),
			'12 months' => sprintf(__('Last %d Months', 'events-manager'),12),
			'this year' => __('This Year', 'events-manager'),
			'last year' => __('Last Year', 'events-manager'),
		);
		return apply_filters('em_chart_range_types', $range_types);
	}

	public static function get_range_dates(){
		$today = date('Y-m-d');
		$day = 0;
		$ranges = array(
			'all' => array('1970-01-01', $today),
			'today'=> $today,
			'yesterday'=> date('Y-m-d', strtotime('yesterday')),
			'this month'=> array( date('Y-m-01'), $today ),
			'last month'=> array( date('Y-m-01', strtotime('last month')), date('Y-m-t', strtotime('last month')) ),
			'3 months'=> array( date('Y-m-d', strtotime('-3 months')+$day), $today ),
			'6 months'=> array( date('Y-m-d', strtotime('-6 months')+$day), $today ),
			'12 months'=> array( date('Y-m-d', strtotime('-12 months')+$day), $today ),
			'this year'=> array( date('Y-01-01'), $today ),
			'last year'=> array( date('Y-01-01', strtotime('last year')), date('Y-12-31', strtotime('last year')) ),
		);
		return apply_filters('em_chart_range_dates', $ranges);
	}
	
	public static function get_stats( $args ){
		global $wpdb;
		$conditions = array();
		if( !current_user_can('manage_bookings') ){
			return array(); // no permissions for bookings, should have not even got here!
		}elseif( !current_user_can('manage_others_bookings') ) {
			// get current user bookings
			$conditions['owner'] = $wpdb->prepare( ' event_id IN (SELECT event_id FROM ' . EM_EVENTS_TABLE . ' WHERE event_owner=%d )', get_current_user_id() );
		}
		if( !empty($args['event']) ){
			$EM_Event = em_get_event($args['event']);
			if( $EM_Event->can_manage('manage_bookings', 'manage_others_bookings') ) {
				if ( ! empty( $conditions['owner'] ) ) {
					unset( $conditions['owner'] );
				}
				$conditions['event'] = $wpdb->prepare( ' event_id = %d ', $EM_Event->event_id );
			}
		}
		if( !empty($args['status']) && \EM_Object::array_is_numeric($args['status']) ){
			$conditions['status'] = $wpdb->prepare( ' booking_status IN ('. implode(',', array_fill(0,count($args['status']), '%d')) .')', $args['status'] );
		}
		// determine unit of measurement
		$selectors = array();
		$averages = array(
			'spaces_avg' => 'AVG(booking_spaces) AS spaces_avg',
			'price_avg' => 'AVG(booking_price) AS price_avg',
		);
		$summaries = array(
			'spaces' => 'SUM(booking_spaces) AS spaces',
			'bookings' => 'COUNT(*) AS bookings',
			'price' => 'SUM(booking_price) AS price',
		);
		$selector_units = array('price', 'bookings', 'spaces');
		if( !empty($args['unit']) && in_array( $args['unit'], $selector_units) ){
			$units = $args['unit']; // array
			if( !is_array($args['unit']) ){
				$units = array($args['unit']);
			}
			if( !empty($args['compare']) && $args['unit'] !== $args['compare'] && in_array( $args['compare'], $selector_units) ){
				$units[] = $args['compare'];
			}
			foreach( $units as $unit ){
				if( !empty($summaries[$unit]) ){
					$selectors[$unit] = $summaries[$unit];
				}
			}
		}else{
			$selectors['price'] = $summaries['price'];
		}
		$unit_labels = array(
			'spaces' => __('Spaces', 'events-manager'),
			'bookings' => __('Bookings', 'events-manager'),
			'price' => __('Sales', 'events-manager'),
		);
		// get scopes
		$scopes = array(); // we'll get a range of dates here regardeless
		$today = date('Y-m-d');
		if( $args['range_type'] === 'custom' && !empty($args['range_dates'][0]) ){
			$scope_dates[0] = $args['range_dates'][0];
		}elseif( $args['range_type'] === 'all' ){
			// we need to get the first date of a booking according to conditions set further up without scope
			$where = count($conditions) > 0 ? ' WHERE '. implode(' AND ', $conditions):'';
			$sql = 'SELECT booking_date FROM '. EM_BOOKINGS_TABLE . $where ." ORDER BY booking_date ASC LIMIT 1";
			$first_booking_date = $wpdb->get_var( $sql );
			if( $first_booking_date ){
				$scope_dates[0][] = substr($first_booking_date, 0, 10);
				$sql = 'SELECT booking_date FROM '. EM_BOOKINGS_TABLE . $where ." ORDER BY booking_date DESC LIMIT 1";
				$last_booking_date = $wpdb->get_var( $sql );
				if( $last_booking_date ) {
					$scope_dates[0][] = substr($last_booking_date, 0, 10);
				}else{
					$scope_dates[0][] = $scope_dates[0][0];
				}
			}else{
				$scope_dates[0] = $today; // no results to find anyway
			}
		}else{
			// get the range of dates
			$range_dates = static::get_range_dates();
			if( empty( $range_dates[$args['range_type']] ) ){
				$args['range_type'] = '3 months'; // default
			}
			$scope_dates[0] = $range_dates[$args['range_type']];
		}
		
		// get compare data scopes
		if( $args['range_type'] !== 'all' && in_array( $args['compare'], array('custom', 'previous', 'year')) ) {
			// get difference of dates for first scope and add it to the base date via DateTime
			$start_scope_date = is_array($scope_dates[0]) ? $scope_dates[0][0] : $scope_dates[0];
			$end_scope_date   = is_array($scope_dates[0]) && !empty($scope_dates[0][1]) ? $scope_dates[0][1] : 'now';
			$base_start_scope = new EM_DateTime( $start_scope_date, 'UTC' );
			$base_end_scope   = new EM_DateTime( $end_scope_date, 'UTC' );
			$interval         = $base_end_scope->diff( $base_start_scope, true );
			if ( $args['compare'] === 'custom' && ! empty( $args['range_dates'][1] ) ) {
				// get custom dates, working off the given date and calculating the difference of first scope to determine same range of days
				$scope_dates[1] = array( $args['range_dates'][1] );
				$scope_date     = new EM_DateTime( $args['range_dates'][1] . ' 00:00:00', 'UTC' );
				$scope_date->add( $interval );
				$scope_dates[1][] = $scope_date->format( 'Y-m-d' );
			} elseif ( $args['compare'] === 'previous' ) {
				// get 1 day less than first scope day and then work backwards to difference
				$scope_date_end = $base_start_scope->sub( 'P1D' )->format( 'Y-m-d'); // extra day so we end day before first scope
				$base_start_scope->sub( 'P'. $interval->days .'D' ); // we want pure days, not months hours etc.
				$scope_dates[1] = array( $base_start_scope->format( 'Y-m-d' ), $scope_date_end );
			} elseif ( $args['compare'] === 'year' ) {
				// get end date and go back until we're earlier than start period, so we have exact same year periods. We subtract days difference to account for leap years
				while( $base_end_scope >= $base_start_scope ) {
					$base_end_scope->sub( 'P1Y' );
				}
				$scope_date_end = $base_end_scope->format( 'Y-m-d' );
				$base_end_scope->sub( 'P' . $interval->days . 'D' ); // we want pure days, not months hours etc.
				$scope_dates[1] = array( $base_end_scope->format( 'Y-m-d' ), $scope_date_end );
			}
		}
		
		// convert dates to SQL-able array
		foreach( $scope_dates as $scope_date ) {
			if ( is_array( $scope_date ) ) {
				$scope = array(
					'start' => new EM_DateTime($scope_date[0] . ' 00:00:00', 'UTC'),
					'end' => new EM_DateTime($scope_date[1] . ' 23:59:59', 'UTC')
				);
			} else {
				$scope = array(
					'start' => new EM_DateTime($scope_date . ' 00:00:00', 'UTC'),
					'end' => new EM_DateTime('now', 'UTC')
				);
				$scope['end']->setTime(23,59,59);
			}
			$scopes[] = $scope;
		}
		
		// group/split results by time frame
		$args['mode'] = in_array( $args['mode'], array('year', 'month', 'week', 'day') ) ? $args['mode'] : 'month';
		$groupby = static::get_sql_groupby($args['mode']);
		if( $args['mode'] !== 'year' ){
			$groupby .= ', YEAR(booking_date)';
		}
		
		// prep stats object to return all data
		if( empty($args['type']) ) $args['type'] = 'line';
		$stats = (object) array(
			'type' => $args['type'],
			'data' => (object) array(
				'labels' => array(),
				'datasets' => array(),
			),
			'currency' => get_option('dbem_bookings_currency'),
			'locale' => str_replace('_', '-', \EM_ML::$current_language),
			'compare' => count($scopes) > 1 || count($selectors) > 1,
			'compareType' => false,
			'scales' => (object) array(),
			'subgroups' => false,
			'nulls' => array(), // null datasets with no info (stacks) so we warn user
			'stackable' => true,
			'views' => array('pie' => true, 'line' => true, 'bar' => true),
		);
		
		// get chart title
		if( count($scopes) > 1 ){
			// same unit over a period of time, get difference from first scope
			$title = esc_html__('%s  |  %s  vs  %s');
			$sprintf = array($unit_labels[ $unit ]);
			foreach( $scopes as $scope ){
				$start_scope = $scope['start'];
				$end_scope = $scope['end'];
				$sprintf[] = static::get_date_range_i18n($start_scope, $end_scope);
			}
			$stats->chartTitle = vsprintf( $title, $sprintf );
			$stats->compareType = 'scope';
		}elseif( $args['range_type'] === 'all' ){
			$all_time = esc_html__('All Time', 'events-manager');
			if( count($selectors) > 1 ){
				// comparing units
				$title = esc_html__('%s  vs  %s  |  %s');
				$units = array_keys($selectors);
				$stats->chartTitle = sprintf( $title, $unit_labels[array_shift($units)], $unit_labels[array_shift($units)], $all_time);
				$stats->compareType = 'unit';
			}else{
				$title = esc_html__('%s  |  %s');
				$stats->chartTitle = sprintf( $title, $unit_labels[key($selectors)], $all_time);
			}
		}elseif( count($selectors) > 1 ){
			// comparing units
			$title = esc_html__('%s  vs  %s  |  %s');
			$dates = static::get_date_range_i18n( $scopes[0]['start'], $scopes[0]['end'] );
			$units = array_keys($selectors);
			$stats->chartTitle = sprintf( $title, $unit_labels[array_shift($units)], $unit_labels[array_shift($units)], $dates);
			$stats->compareType = 'unit';
		}else{
			$title = esc_html__('%s  |  %s');
			$dates = static::get_date_range_i18n( $scopes[0]['start'], $scopes[0]['end'] );
			$stats->chartTitle = sprintf( $title, $unit_labels[key($selectors)], $dates);
		}
		
		// set up subgroup values
		$subgroups = array(
			'day' => array('ticket'),
			'week' => array('day','ticket'),
			'month' => array('week','day','ticket'),
			'year' => array('month','week','day','ticket'),
		);
		if( !empty($args['subgroup']) && !empty($subgroups[$args['mode']]) && in_array($args['subgroup'], $subgroups[$args['mode']]) ){
			$subgroup = $args['subgroup'];
		}else{
			$subgroup = $args['subgroup'] = false;
		}
		$stats->subgroups = $subgroup !== false;
		
		// determine if we can show a line graph (if requested) or some other type, otherwise revert to bars which can accommodate all types
		if( $subgroup && $stats->compare ) {
			$stats->type = 'bar';
			$stats->views = array('pie' => false, 'line' => false, 'bar' => true);
		}elseif( $subgroup ){
			if( $stats->type == 'pie' ){
				$stats->type = 'bar';
			}
			$stats->views['pie'] = false;
		}
		$stats->stackable = !empty($subgroups[$subgroup]) && $stats->type !== 'pie';
		
		$datasets = static::build_dataset_template($scopes, $args, $subgroup);
		// duplicate stack if we're comparing units rather than scopes
		if( count($selectors) > 1 ){
			$datasets[1] = $datasets[0];
		}
		// fix siatuations where there's an extra label in comparison (since we're comparing fixed days some ranges will spill into an extra year or month)
		if( $stats->compare && count($datasets[0]['labels']) != count($datasets[1]['labels']) ){
			// we're altering the smallest dataset so they match up
			$i_smaller = count($datasets[0]['labels']) > count($datasets[1]['labels']) ? 1:0;
			// add blank key with a 0 index, so it always gets prepended to front of array, it doesn't matter since the value is null for sure so no index reference needed
			$datasets[$i_smaller]['labels'][0] = ' ';
			$datasets[$i_smaller]['compareLabels'][0] = '';
			$datasets[$i_smaller]['data'][0] = null;
			// resort labels since the loop above was chronologically ordered already
			ksort($datasets[$i_smaller]['labels']);
			ksort($datasets[$i_smaller]['compareLabels']);
			ksort($datasets[$i_smaller]['data']);
		}
		// now go through scopes, get results and fill in data
		foreach( $scopes as $stack => $scope ){
			// prepare data sql
			if( $args['range_type'] !== 'all' ){
				$conditions['scope'] = $wpdb->prepare(' booking_date BETWEEN %s AND %s ', $scope['start']->getDateTime(), $scope['end']->getDateTime());
			}
			// prepare WHERE condition
			$where = '';
			if( count($conditions) >  0 ){
				$where = ' WHERE ' . implode(' AND ', $conditions);
			}
			if( !empty($subgroup) ) {
				// determine how many sets of data we need here for this stack, e.g. for a year we'll need 12 months i.e. a stack will have 12 data arrays
				$subgroupby = static::get_sql_groupby($subgroup);
				$sql = 'SELECT '. implode(', ', $selectors) .', MONTH(booking_date) AS month, WEEK(booking_date) as week, YEAR(booking_date) as year, DAYOFYEAR(booking_date) as day, booking_date FROM '. EM_BOOKINGS_TABLE . $where ." GROUP BY $subgroupby , $groupby";
				$booking_data = $wpdb->get_results( $sql, ARRAY_A );
			}else{
				$sql = 'SELECT '. implode(', ', $selectors) .', MONTH(booking_date) AS month, WEEK(booking_date) as week, YEAR(booking_date) as year, DAYOFYEAR(booking_date) as day, booking_date FROM '. EM_BOOKINGS_TABLE . $where .' GROUP BY '.$groupby;
				$booking_data = $wpdb->get_results( $sql, ARRAY_A );
			}
			
			foreach( array_keys($selectors) as $unit ) {
				// we go through each found date and add it to an array, then we fill in potential blanks
				$dataset = (object) array(
					'label'         => $unit_labels[ $unit ],
					'data'          => array(),
					'format'        => $unit,
					'compareLabels' => $datasets[$stack]['compareLabels'],
					'stack'         => $stack,
					'skipNull'      => true,
					'fill'          => !empty($subgroup),
				);
				// add label for dataset
				if( $stats->compare ){
					$dataset->label = static::get_date_range_i18n($scope['start'], $scope['end']);
				}
				
				$dataset_data = $datasets[$stack]['data'];
				if( !empty($subgroup) ){
					// fill up each data array item with subgroup data
					foreach( $dataset_data as $k => $v ){
						$dataset_data[$k] = $datasets['subgroups']['data'];
					}
					// get a subgroup query and group up
					foreach ( $booking_data as $result ) {
						// change date into string meaningful info
						if( $result[$unit] > 0 ){
							$date = new EM_DateTime($result['booking_date'], 'UTC');
							$keys = static::get_date_indexes( $date, $args['mode'] );
							$subgroup_keys = static::get_date_indexes( $date, $subgroup, true );
							if( empty($dataset_data[ $keys[0] ][ $subgroup_keys[0] ]) ) {
								$dataset_data[ $keys[0] ][ $subgroup_keys[0] ] = $result[ $unit ];
							}else{
								$dataset_data[ $keys[0] ][ $subgroup_keys[0] ] += $result[ $unit ];
							}
						}
					}
					// add dataset
					$dataset->data = $dataset_data;
					$stats->data->datasets[$stack] = $dataset;
				}else{
					foreach ( $booking_data as $result ) {
						// change date into string meaningful info
						if( $result[$unit] > 0 ){
							$date = new EM_DateTime($result['booking_date'], 'UTC');
							$keys = static::get_date_indexes( $date, $args['mode'] );
							$dataset_data[$keys[0]] = $result[$unit];
						}
					}
					// add dataset
					$dataset->data = $dataset_data;
					$stats->data->datasets[$stack] = $dataset;
				}
				
				// if we're dealing with selectors, we can't compare scopes so we increase the stack count here, since they are essentially the stacks
				if( count($selectors) > 1 ){
					$stack++;
				}
			}
			// if we're dealing with selectors, we're done regardless of scope comparison
			if( count($selectors) > 1 ){
				break;
			}
		}
		// if there's only one point of data, then we default to bars since there isn't any lines just a dot
		if( count($stats->data->datasets[0]->data) === 1 && $stats->type !== 'pie' ){
			$stats->type = 'bar';
			// convert 0s to null so skipNull takes effect
			foreach( $stats->data->datasets as $k => $dataset ){
				$dataset->skipNull = true;
				// if subgrouped, $data will be objects and the null values are already applied, this will be skipped
				foreach( $dataset->data as $dk => $data ){
					if( $data === 0 ){
						$dataset->data[$dk] = null;
					}
				}
			}
			if( $stats->compare && count($stats->data->datasets[0]->data) === 1 ){ // both are objects with unique indexes, shared or unique
				// rebuild labels of previous period to front of labels
				$datasets[0]['labels'] = $datasets[1]['labels'] + $datasets[0]['labels'];
				// padd data so it matches labels
				foreach( $datasets[0]['labels'] as $k => $v ){
					foreach( $stats->data->datasets as $stack => $dataset ){
						$dataset->compareLabels = $datasets[1]['compareLabels'] + $datasets[0]['compareLabels'];
						if( !isset($dataset->data[$k]) ){
							if( $subgroup ){
								// force null values since it may have started as a line graph
								$dataset_data = $datasets['subgroups']['data'];
								foreach( $dataset_data as $dk => $dv ){
									$dataset_data[$dk] = null;
								}
								// save dataset array so it's sorted further down
								$dataset->data[$k] = $dataset_data;
							}else{
								// null i.e. non-existent so it's skipped
								$dataset->data[$k] = null;
							}
						}
					}
				}
				/* not needed anymore since it's sorted at the end
				// resort out data so they lign up with labels
				foreach( $datasets as $stack => $dataset ){
					ksort($dataset['data']);
					$stats->data->datasets[$stack]->data = array_values($dataset['data']);
				}
				// add labels to stats
				ksort($labels);
				$stats->data->labels = array_values($labels);
				 **/
			}
			// we unify the data for bars and pies, otherwise might as well just show a line
			
		}
		
		if ( $stats->type === 'pie' ) {
			// pies need all labels and all data, in corresponding order without 0s, that's about it!
			$pie_data = array( 'labels' => array(), 'data' => array() );
			foreach ( $stats->data->datasets as $stack => $dataset ) {
				foreach ( $dataset->data as $k => $v ) {
					$value = is_object($v) ? $v->y:$v;
					if( $value > 0 ) {
						if ( empty( $pie_data['data'][ $k ] ) ) {
							$pie_data['data'][ $k ] = $value;
						} else {
							$pie_data['data'][ $k ] += $value;
						}
						$pie_data['labels'][ $k ] = $datasets[$stack]['labels'][ $k ];
					}
				}
			}
			// now we have matching data and labels, reorder and pass ordered arrays
			ksort($pie_data['labels']);
			ksort($pie_data['data']);
			$stats->data->labels = array_values($pie_data['labels']);
			$stats->data->datasets = array( (object) array(
				'data' => array_values($pie_data['data']),
				'format' => $unit, // assumed same for a pie chart
			));
			$stats->scales = false;
		} else {
			// Add y axes, with a second one if units are different (i.e. price vs number)
			$multiscale = count($selectors) > 1 && !empty($selectors['price']);
			foreach( $stats->data->datasets as $stack => $dataset ){
				// add second axis to chart.js
				$scale_index = $multiscale ? 'y' . $stack : 'y0'; // we know we're comparing different scale types so we'll need two y-axes
				$dataset->yAxisID = $scale_index;
				if( empty($stats->scales->{$scale_index}) ) {
					$stats->scales->{$scale_index} = (object) array(
						'type' => $dataset->format,
						'stacked' => true, //empty($subgroup) && $stats->type === 'line'
					);
				}
				if( $subgroup ) {
					$stats->scales->x = (object) array( 'stacked' => true );
				}
			}
			
			// sort and match numerical indexes for compareLabel array
			foreach ( $stats->data->datasets as $dataset ) {
				ksort( $dataset->compareLabels );
				$dataset->compareLabels = array_values( $dataset->compareLabels );
			}
			// reorder, flatten dataset data structures and/or remove associative keys
			if( $subgroup ) {
				$datasets_data = array();
				foreach ( $stats->data->datasets as $stack => $dataset_stack ) {
					// subgroups will have an array for data, so we need to split this up into a group of datasets for each dataset
					$dataset_subgroups = array();
					$non_null_datasets = array();
					foreach ( $dataset_stack->data as $group_key => $dataset_group ) {
						// clone stack dataset, clear data prop and make it a single array
						foreach ( $dataset_group as $dataset_key => $subgroup_value ) {
							if ( empty( $dataset_subgroups[ $dataset_key ] ) ) {
								// set up dataset
								$dataset_subgroup = clone( $dataset_stack );
								$dataset_subgroup->data = array();
								$dataset_subgroup->label = $datasets['subgroups']['labels'][$dataset_key];
								/*
								foreach( $dataset_subgroup->compareLabels as $k => $v ){
									$dataset_subgroup->compareLabels[$k] = $dataset_subgroup->label;
								}
								*/
								$dataset_subgroups[ $dataset_key ] = $dataset_subgroup;
							}
							$null = $stats->type === 'line' ? 0 : null;
							$dataset_subgroups[ $dataset_key ]->data[$group_key] = $subgroup_value;
							if( $subgroup_value ){
								$non_null_datasets[$dataset_key] = true;
							}
						}
					}
					// sort out subgroups and remove indexes
					ksort( $dataset_subgroups );
					foreach( $dataset_subgroups as $dataset_key => $dataset_subgroup ){
						if( empty($non_null_datasets[$dataset_key]) ){
							unset($dataset_subgroups[$dataset_key]);
						}else{
							ksort($dataset_subgroup->data);
							$dataset_subgroup->data = array_values( $dataset_subgroup->data );
							$datasets_data[] = $dataset_subgroup;
						}
					}
					if( empty($dataset_subgroups) ){
						// empty data, add a warning
						$stats->nulls[$stack] = true;
					}
				}
				$stats->data->datasets = $datasets_data;
			}else{
				foreach ( $stats->data->datasets as $dataset_stack ) {
					ksort( $dataset_stack->compareLabels );
					$dataset_stack->compareLabels = array_values( $dataset_stack->compareLabels );
					// now we sort and remove keys from data array
					ksort( $dataset_stack->data );
					$dataset_stack->data = array_values( $dataset_stack->data );
				}
			}
			
			// add main labels
			ksort($datasets[0]['labels']);
			$stats->data->labels = array_values($datasets[0]['labels']);
		}
		// finally take some overview stats for below the graph, adding a previous range scope if not comparing
		/*
		if( count($scopes) === 1 ){
			// get 1 day less than first scope day and then work backwards to difference
			$interval = $scopes[0]['end']->diff( $scopes[0]['start'], true );
			$scopes[1]['end'] = $scopes[0]['start']->copy()->sub( 'P1D' ); // extra day so we end day before first scope
			$scopes[1]['start'] = $scopes[0]['start']->copy()->sub( 'P'. $interval->days .'D' )->setTime(23,59,59); // we want pure days, not months hours etc.
			$scopes[1]['previous'] = true;
		}
		*/
		foreach( $scopes as $stack => $scope ) {
			// prepare data sql
			if( $args['range_type'] !== 'all' ) {
				$conditions['scope'] = $wpdb->prepare( ' booking_date BETWEEN %s AND %s ', $scope['start']->getDateTime(), $scope['end']->getDateTime() );
			}
			// prepare WHERE
			$where = '';
			if( count($conditions) >  0 ){
				$where = ' WHERE ' . implode(' AND ', $conditions);
			}
			// get data
			$sql = 'SELECT ' . implode( ', ', $summaries) .' , '. implode( ', ', $averages ) . ' FROM ' . EM_BOOKINGS_TABLE . $where;
			$booking_data = $wpdb->get_row( $sql, ARRAY_A );
			// clean null data to 0 and add to stats
			if( $booking_data ) {
				foreach ( $booking_data as $k => $v ) {
					if ( $v === null ) {
						$booking_data[ $k ] = 0;
					}
				}
			} else {
				$keys = array_merge( array_keys($summaries), array_keys($averages) );
				$booking_data = array_combine( $keys, array_fill(0, count($keys), 0)  );
			}
			$stats->stats[$stack] = $booking_data;
		}
		return $stats;
	}
	
	public static function build_dataset_template($scopes, $args, $subgroup = false){
		
		// build datasets array with re-usable data for each x-Axis (stack) set
		$datasets = array();
		foreach( $scopes as $stack => $scope ){
			// before returning data, label and dataset will be reordered by index and converted to numeric array, so that all data lines up
			$start_scope = $scope['start']->copy();
			$end_scope = $scope['end']->copy();
			switch ( $args['mode'] ) {
				case 'year' :
					// get end of year
					$start_scope->modify('first day of this month');
					$end_scope->setDate($end_scope->format('Y'), 12, 31);
					break;
				case 'day' :
					// today (tomorrow further down)
					$end_scope->add('P1D');
					break;
				case 'week' :
					// get last day of week
					$end_scope->setStartOfWeek()->add('P6D');
					break;
				case 'month' :
				default :
					// last day of month
					$start_scope->modify('first day of this month');
					$end_scope = $end_scope->modify('last day of this month');
					break;
			}
			$end_scope->add('P1D')->setTime(0,0,0); // so any sec less than this is day before
			// loop through scope in units and set datasets
			$datasets[$stack] = array('labels' => array(), 'data' => array(), 'compareLabels' => array());
			while( $start_scope < $end_scope ){
				$keys = static::get_date_indexes( $start_scope, $args['mode'] );
				$datasets[$stack]['labels'][$keys[0]] = $keys[1];
				$datasets[$stack]['compareLabels'][$keys[0]] = $keys[2];
				$datasets[$stack]['data'][$keys[0]] = 0; // pies and bars don't look as nice with empty 0 vals
				// add time and loop
				$start_scope->modify('+1 '.$args['mode']);
			}
		}
		// now deal with subgroup data labels, for each group e.g. a year, we add the max number of subgroup items i.e. months in each stack key
		// this is fixed per grouping because we need symetrical arrays for the data prop, nulls are just ignored anyway
		if( $subgroup ){
			// we decide how to loop current bracket in parent scope, since we're not doing ranges here but subsets (e.g. months in a year, not months in 12 months - subtle profound difference)
			$datasets['subgroups'] = array('data' => array(), 'labels' => array());
			$default = $args['type'] === 'line' ? 0 : null;
			switch ( $subgroup ) {
				case 'day' :
					// 31 days, so all months are same length
					$translation = esc_html__('Day %d', 'events-manager');
					for( $i = 1; $i <= 31; $i++ ){
						$index = str_pad($i, 2, '0', STR_PAD_LEFT);
						$datasets['subgroups']['data'][$index] = $default;
						$datasets['subgroups']['labels'][$index] = sprintf($translation, $i);
					}
					break;
				case 'week' :
					// 5 weeks (starting from 1st of month, not weekdays, therefore we can compare groups evenly), so we have max week count for every month
					$translation = esc_html__('Week %d', 'events-manager');
					for( $i = 1; $i <= 5; $i++ ){
						$datasets['subgroups']['data']['0'.$i] = $default;
						$datasets['subgroups']['labels']['0'.$i] = sprintf($translation, $i);
					}
					break;
				case 'month' :
					// we know this now
					$datasets['subgroups'] = array(
						'data' =>  array('01' => $default,'02' => $default,'03' => $default,'04' => $default,'05' => $default,'06' => $default,'07' => $default,'08' => $default,'09' => $default,'10' => $default,'11' => $default,'12' => $default, ),
						'labels' => array('01' => __('January'),'02' => __('February'),'03' => __('March'),'04' => __('April'),'05' => __('May'),'06' => __('June'),'07' => __('July'),'08' => __('August'),'09' => __('September'),'10' => __('October'),'11' => __('November'),'12' => __('December'), ),
					);
					break;
			}
		}
		return $datasets;
	}
	
	/**
	 * @param EM_DateTime $start
	 * @param EM_DateTime $end
	 *
	 * @return string
	 */
	public static function get_date_range_i18n( $start, $end ){
		if( $start->format('Y-m-d') === $end->format('Y-m-d') ){
			// same day, one date
			$format = $start->i18n('M d, Y');
		}elseif( $start->format('Y') === $end->format('Y') ){
			if( $start->format('m') === $end->format('m') ) {
				$format = $start->i18n('d') . ' - ' . $end->i18n('d M Y');
			}else{
				$format = $start->i18n('M d') . ' - ' . $end->i18n('M d Y');
			}
		}else{
			$format = $start->i18n('M d, Y') . ' - ' . $end->i18n('M d, Y');
		}
		return $format;
	}
	
	public static function get_date_indexes( $date, $mode = 'month', $subgroup = false ){
		switch ( $mode ) {
			case 'year' :
				$label = $date->format('Y');
				$index = $label;
				$title = $date->format('Y');
				break;
			case 'day' :
				if( $subgroup ){
					$index = $date->format('d');
					$label = sprintf( esc_html__('Day %d'), $index );
					$title = $label;
				}else{
					$title = $date->format('Y-m-d');
					$label = $date->format('Y-m-d');
					$index = $date->format('z');
				}
				break;
			case 'week' :
				// get first/last day of weeks
				if( $subgroup ){
					// calculate week number based on days of month, not weekday etc. so we have comparative 7-day chunks, otherwise some months may have 1 day and others 7
					$day = $date->format('d');
					$index = '0'.ceil($day / 7);
					$label = sprintf( esc_html__('Week %d'), $index );
					$title = $label;
				}else{
					$date->setStartOfWeek();
					$week_start_date = $date->i18n('d');
					$week_start_month = $date->i18n('M');
					$week_end_date = $date->add('P6D')->i18n('d');
					$week_end_month = $date->i18n('M');
					if( $week_start_month === $week_end_month ){
						$label = "$week_start_date-$week_end_date $week_end_month";
					}else{
						$label = "$week_start_date $week_start_month - $week_end_date $week_end_month";
					}
					$index = $date->format('Y-W');
					$title = $label . $date->format(' Y');
				}
				break;
			case 'month' :
				if( $subgroup ){
					// we need only munth number and name to subgroup it
					$index = $date->format('m');
					$label = $date->i18n('M');
					$title = $label;
				}else {
					$index = $date->format( 'Y-m' );
					$label = $date->i18n( 'M' );
					if ( $date->format( 'Y' ) != date( 'Y' ) ) {
						$label .= ' \'' . $date->format( 'y' );
					}
					$title = $date->i18n( 'M Y' );
				}
				break;
		}
		return array($index, $label, $title);
	}
	
	public static function get_sql_groupby( $mode ){
		switch ( $mode ) {
			case 'year' :
				$groupby = 'YEAR(booking_date)';
				break;
			case 'day' :
				$groupby = 'DAYOFYEAR(booking_date)';
				break;
			case 'week' :
				// here we need to do a litle post-processing so that we can avoid problems with weekdays, starting days etc. missing out entries within a month
				$groupby = 'DAYOFMONTH(booking_date)';
				break;
			case 'month' :
			default :
				$groupby = 'MONTH(booking_date)';
				break;
		}
		return $groupby;
	}
	
	/**
	 * Outputs a chart with the provided view settings, or a set of args to use.
	 * @param array|string $view
	 *
	 * @return void
	 */
	public static function output( $view = 'dashboard', $item_id = null ) {
		$args = is_array($view) ? $view : static::get_default_args( $view );
		$id = rand(10,99999);
		$today = date('Y-m-d');
		$range_dates = static::get_range_dates();
		if( $view === 'event' ){
			$args['event'] = $item_id;
		}elseif( $view === 'ticket' ){
			$args['ticket'] = $item_id;
		}
		$stats = static::get_stats( $args );
		?>
		<div class="<?php em_template_classes('chart', 'bookings-chart'); if( empty($args['show_filters']) ) echo ' hidden-filters'; ?>">
			<div class="em-chart-header">
				<div class="em-chart-title">
					<?php echo esc_html($stats->chartTitle); ?>
				</div>
				<section class="option-triggers">
					<span class="em-chart-filters-trigger em-icon em-icon-filter em-tooltip" aria-label="<?php esc_html_e('Show/Hide Filters', 'events-manager'); ?>" data-tippy-placement="bottom" data-tippy-theme="dark"></span>
					<span class="em-chart-settings-trigger em-icon em-icon-settings em-clickable em-tooltip" rel="#em-chart-settings-<?php echo $id; ?>" aria-label="<?php esc_html_e('Display Settings', 'events-manager'); ?>" data-tippy-placement="bottom" data-tippy-theme="dark"></span>
				</section>
			</div>
			<form action="" method="post" id="em-chart-form-<?php echo $id; ?>">
				<div class="em-chart-filters">
					<section class="main-filters">
						<div class="em-chart-filter-set dataset-1">
							<div class="dataset-1-metric">
								<label>
									<span><?php esc_html_e('Metric', 'events-manager'); ?></span>
									<select name="unit" class="em-chart-filter em-chart-unit">
										<option value="spaces" <?php self::selected('unit', 'spaces', $args); ?>><?php esc_html_e('Spaces', 'events-manager'); ?></option>
										<option value="price" <?php self::selected('unit', 'price', $args); ?>><?php esc_html_e('Sales', 'events-manager'); ?></option>
										<option value="bookings" <?php self::selected('unit', 'bookings', $args); ?>><?php esc_html_e('Bookings', 'events-manager'); ?></option>
									</select>
								</label>
								<label>
									<span><?php esc_html_e('During', 'events-manager'); ?></span>
									<select name="range_type" aria-label="<?php esc_html_e('View Range', 'events-manager'); ?>">
										<option value="custom" <?php self::selected('range_type', 'custom', $args); ?>>
											<?php esc_html_e('Custom Dates', 'events-manager'); ?>
										</option>
										<?php foreach( static::get_range_types() as $name => $label ): ?>
											<?php
											$date_data = is_array($range_dates[$name]) ? implode(',', $range_dates[$name]) : $range_dates[$name];
											?>
											<option value="<?php echo esc_attr($name); ?>" <?php self::selected('range_type', $name, $args); ?> data-date="<?php echo esc_attr($date_data); ?>"><?php echo esc_html($label); ?></option>
										<?php endforeach; ?>
									</select>
								</label>
							</div>
							<div class="em-datepicker em-datepicker-range em-chart-dates-custom" data-until-id="em-chart-dates-compare-<?php echo $id ?>" data-datepicker="<?php echo esc_js(json_encode( (object) array('allowInput', false) )); ?>">
								<label for="em-date-start-end-<?php echo $id ?>" class="screen-reader-text"><?php esc_html_e ( 'Dates', 'events-manager'); ?></label>
								<input id="em-date-start-end-<?php echo $id ?>" type="hidden" class="em-date-input em-date-start-end" aria-hidden="true" placeholder="<?php esc_html_e ( 'Select date range', 'events-manager'); ?>">
								<div class="em-datepicker-data">
									<input type="date" name="range_dates[0][0]" value="<?php if( !empty($args['range_dates'][0][0]) ) echo esc_attr($args['range_dates'][0][0]); ?>" aria-label="<?php esc_html_e ( 'From ', 'events-manager'); ?>">
									<span class="separator"><?php esc_html_e('to','events-manager'); ?></span>
									<input type="date" name="range_dates[0][1]" value="<?php if( !empty($args['range_dates'][0][1]) ) echo esc_attr($args['range_dates'][0][1]); ?>" aria-label="<?php esc_html_e('to','events-manager'); ?>">
								</div>
							</div>
							<label>
								<span><?php esc_html_e('Booking Status', 'events-manager'); ?></span>
								<select name="status" class="em-chart-filter em-booking-status" aria-label="<?php esc_html_e('Booking Statuses', 'events-manager'); ?>">
									<?php
									$status_arg = is_array($args['status']) ? implode(',', $args['status']) : $args['status'];
									?>
									<option value="all" <?php self::selected($status_arg, 'all'); ?>><?php esc_html_e('All', 'events-manager'); ?></option>
									<option value="0,1" <?php self::selected($status_arg, '0,1'); ?>><?php esc_html_e('Pending and Approved', 'events-manager'); ?></option>
									<?php
									$EM_Booking = em_get_booking();
									foreach( $EM_Booking->status_array as $status => $label){
										?>
										<option value="<?php echo esc_attr($status); ?>" <?php self::selected($status_arg, (string) $status); ?>><?php echo esc_html($label); ?></option>
										<?php
									}
									?>
								</select>
							</label>
						</div>
						<div class="em-chart-filter-set dataset-2">
							<label>
								<span><?php esc_html_e('Compare With', 'events-manager'); ?></span>
								<select name="compare" class="em-chart-filter em-chart-compare" aria-label="<?php esc_html_e('Compare With', 'events-manager'); ?>">
									<option value="0"><?php esc_html_e('None', 'events-manager'); ?></option>
									<?php foreach( static::get_compare_options() as $key => $optgroup ): ?>
										<optgroup label="<?php echo esc_attr($optgroup['label']); ?>" data-label-key="<?php echo esc_attr($key); ?>">
											<?php foreach( $optgroup['options'] as $option_name => $option_label ): ?>
											<option value="<?php echo esc_attr($option_name); ?>" <?php self::selected('compare', $option_name, $args); ?>><?php echo esc_html($option_label); ?></option>
											<?php endforeach; ?>
										</optgroup>
									<?php endforeach; ?>
								</select>
							</label>
							<div>
								<div class="em-datepicker em-chart-dates-compare em-tooltip" id="em-chart-dates-compare-<?php echo $id ?>" data-datepicker="<?php echo esc_js(json_encode( (object) array('allowInput', false) )); ?>" aria-label="<?php esc_attr_e('Choose the start date for comparing equivalent date ranges. The latest comparison end date is day before main duration start date.', 'events-manager'); ?>" data-tippy-placement="bottom" data-tippy-theme="dark">
									<label for="em-date-start-end-<?php echo $id ?>" class="screen-reader-text"><?php esc_html_e ( 'Dates', 'events-manager'); ?></label>
									<input id="em-date-start-end-<?php echo $id ?>" type="hidden" class="em-date-input em-date-start-end" aria-hidden="true" placeholder="<?php esc_html_e ( 'Start comparison date', 'events-manager'); ?>">
									<div class="em-datepicker-data">
										<input type="date" name="range_dates[1]" value="<?php if( !empty($args['range_dates'][1]) ) echo esc_attr($args['range_dates'][1]); ?>" aria-label="<?php esc_html_e ( 'From ', 'events-manager'); ?>">
									</div>
								</div>
							</div>
							<label>
								<span><?php esc_html_e('Chart Type', 'events-manager'); ?></span>
								<select name="type" class="em-chart-filter em-chart-mode" aria-label="<?php esc_html_e('View As', 'events-manager'); ?>">
									<option value="line" <?php self::selected('type', 'line', $args); ?>><?php esc_html_e('Line Graph', 'events-manager'); ?></option>
									<option value="bar" <?php self::selected('type', 'bar', $args); ?>><?php esc_html_e('Bar Graph', 'events-manager'); ?></option>
									<option value="pie" <?php self::selected('type', 'pie', $args); ?>><?php esc_html_e('Pie Chart', 'events-manager'); ?></option>
								</select>
							</label>
						</div>
						<div class="mode-filter">
							<label>
								<span><?php esc_html_e('Group By', 'events-manager'); ?></span>
								<select name="mode" class="em-chart-filter em-chart-mode" aria-label="<?php esc_html_e('Group By', 'events-manager'); ?>">
									<option value="day" <?php self::selected('mode', 'day', $args); ?>><?php esc_html_e('Day'); ?></option>
									<option value="week" <?php self::selected('mode', 'week', $args); ?>><?php esc_html_e('Week'); ?></option>
									<option value="month" <?php self::selected('mode', 'month', $args); ?>><?php esc_html_e('Month'); ?></option>
									<option value="year" <?php self::selected('mode', 'year', $args); ?>><?php esc_html_e('Year'); ?></option>
								</select>
							</label>
							<div class="mode-filter em-tooltip" aria-label="<?php esc_attr_e('Choose graph type and stacking view options. Some views and stacking may not be available for all setting combinations.', 'events-manager'); ?>" data-tippy-theme="dark">
								<label>
									<span><?php esc_html_e('Stack By', 'events-manager'); ?></span>
								</label>
								<select name="stacked" class="em-chart-filter em-chart-mode" aria-label="<?php esc_html_e('Group By', 'events-manager'); ?>">
									<option value="0"><?php esc_html_e('Not Stacked'); ?></option>
									<option value="day" <?php self::selected('subgroup', 'day', $args); ?>><?php esc_html_e('Day'); ?></option>
									<option value="week" <?php self::selected('subgroup', 'week', $args); ?>><?php esc_html_e('Week'); ?></option>
									<option value="month" <?php self::selected('subgroup', 'month', $args); ?>><?php esc_html_e('Month'); ?></option>
								</select>
							</div>
						</div>
					</section>
				</div>
				<input type="hidden" name="view" value="<?php echo esc_attr($args['view']); ?>">
				<?php if( $args['view'] === 'event' || $args['view'] === 'ticket' ): ?>
					<input type="hidden" name="_nonce" value="<?php echo esc_attr(wp_create_nonce('em-chart-'.$args['view'].'-'. $args[$args['view']])); ?>">
					<input type="hidden" name="<?php echo esc_attr($args['view']); ?>" value="<?php echo esc_attr($args[$args['view']]); ?>">
				<?php else: ?>
					<input type="hidden" name="_nonce" value="<?php echo esc_attr(wp_create_nonce('em-chart-'.$args['view'])); ?>">
				<?php endif; ?>
				
				<div class="em-modal <?php em_template_classes('chart-settings-modal'); ?>" id="em-chart-settings-<?php echo $id; ?>" data-parent="em-chart-form-<?php echo $id; ?>" style="opacity:0;">
					<div class="em-modal-popup">
						<header>
							<a class="em-close-modal" href="#"></a><!-- close modal -->
							<div class="em-modal-title">
								<?php esc_html_e('Chart Options', 'events-manager'); ?>
							</div>
						</header>
						<div class="em-modal-content input">
							<p>
								<label><input type="checkbox" name="show_filters" value="1" <?php if( !empty($args['show_filters'])) echo 'checked'; ?>> <?php esc_html_e('Show search filters on page load', 'events-manager'); ?></label>
							</p>
							<section>
								<header><?php esc_html_e('Statistic Averages/Totals', 'events-manager'); ?></header>
								<div>
									<p><em><?php esc_html_e('Choose what statistic summaries to show below the graph, saved per-view type, such as on the bookings dashboard, single event views, single-ticket views etc.'); ?></em></p>
									<p>
										<label><input type="checkbox" name="summaries[price]" value="1" <?php if( !empty($args['summaries']['price'])) echo 'checked'; ?>> <?php esc_html_e('Total Sales', 'events-manager'); ?></label>
										<label><input type="checkbox" name="summaries[spaces]" value="1" <?php if( !empty($args['summaries']['spaces'])) echo 'checked'; ?>> <?php esc_html_e('Total Tickets', 'events-manager'); ?></label>
										<label><input type="checkbox" name="summaries[bookings]" value="1" <?php if( !empty($args['summaries']['bookings'])) echo 'checked'; ?>> <?php esc_html_e('Total Bookings', 'events-manager'); ?></label>
										<label><input type="checkbox" name="summaries[price_avg]" value="1" <?php if( !empty($args['summaries']['price_avg'])) echo 'checked'; ?>> <?php esc_html_e('Average Sale', 'events-manager'); ?></label>
										<label><input type="checkbox" name="summaries[spaces_avg]" value="1" <?php if( !empty($args['summaries']['spaces_avg'])) echo 'checked'; ?>> <?php esc_html_e('Average Ticket', 'events-manager'); ?></label>
										<label><input type="checkbox" name="summaries[tickets_avg]" value="1" <?php if( !empty($args['summaries']['tickets_avg'])) echo 'checked'; ?>> <?php esc_html_e('Tickets / Booking', 'events-manager'); ?></label>
									</p>
								</div>
							</section>
						</div><!-- content -->
						<footer class="em-submit-section input">
							<div>
								<button type="submit" class="button button-primary"><?php esc_html_e('Save Settings', 'events-manager'); ?></button>
							</div>
						</footer>
					</div><!-- modal -->
				</div>
			</form>
			<?php static::output_chart( $args, $stats ); ?>
		</div>
		<?php
		add_action('wp_footer', array(static::class, 'js_footer'));
		add_action('admin_footer', array(static::class, 'js_footer'));
	}
	
	public static function output_chart( $args, $stats = null ){
		if( $stats === null ) {
			$stats = static::get_stats( $args );
		}
		$s = $stats->stats;
		unset($stats->stats); // not needed in json response
		?>
		<div data-chart="<?php echo esc_attr(json_encode($stats)); ?>" class="em-chart-wrapper">
			<canvas></canvas>
			<div class="em-chart-stats">
				<?php if( !empty($args['summaries']['price']) ): ?>
				<div>
					<div class="title"><?php esc_html_e('Total Sales','events-manager'); ?></div>
					<div class="total">
						<?php echo em_get_currency_formatted($s[0]['price']); ?>
					</div>
					<?php if( !empty($s[1]) ): ?>
						<?php
							$diff = static::get_differences($s[0]['price'], $s[1]['price']);
						?>
						<div class="change <?php echo $diff['class']; ?>">
							<span class="<?php echo $diff['class']; ?>"><?php echo $diff['op'] . number_format($diff['%'], 0) .'%'; ?></span>
							<span class="sub"> (<?php echo em_get_currency_formatted($diff['amt']); ?>)</span>
						</div>
						<div class="comparison"><?php esc_html_e('Compared to', 'events-manager'); ?> <?php echo  em_get_currency_formatted($s[1]['price']); ?></div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
				<?php if( !empty($args['summaries']['bookings']) ): ?>
				<div>
					<div class="title"><?php esc_html_e('Total Bookings','events-manager'); ?></div>
					<div class="total">
						<?php echo $s[0]['bookings']; ?>
					</div>
					<?php if( !empty($s[1]) ): ?>
						<?php
						$diff = static::get_differences($s[0]['bookings'], $s[1]['bookings']);
						?>
						<div class="change">
							<span class="<?php echo $diff['class']; ?>"><?php echo $diff['op'] . number_format($diff['%'], 0) .'%'; ?></span>
							<span class="sub"> (<?php echo $diff['amt']; ?>)</span>
						</div>
						<div class="comparison"><?php esc_html_e('Compared to', 'events-manager'); ?> <?php echo $s[1]['bookings']; ?></div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
				<?php if( !empty($args['summaries']['spaces']) ): ?>
				<div>
					<div class="title"><?php esc_html_e('Total Spaces','events-manager'); ?></div>
					<div class="total">
						<?php echo $s[0]['spaces']; ?>
					</div>
					<?php if( !empty($s[1]) ): ?>
						<?php
						$diff = static::get_differences($s[0]['spaces'], $s[1]['spaces']);
						?>
						<div class="change">
							<span class="<?php echo $diff['class']; ?>"><?php echo $diff['op'] . number_format($diff['%'], 0) .'%'; ?></span>
							<span class="sub"> (<?php echo $diff['amt']; ?>)</span>
						</div>
						<div class="comparison"><?php esc_html_e('Compared to', 'events-manager'); ?> <?php echo $s[1]['spaces']; ?></div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
				<?php if( !empty($args['summaries']['price_avg']) ): ?>
				<div>
					<div class="title"><?php esc_html_e('Sale Averages','events-manager'); ?></div>
					<div class="total">
						<?php echo em_get_currency_formatted($s[0]['price_avg']); ?>
						<span class="sub">/ <?php esc_html_e('booking', 'events-manager'); ?></span>
					</div>
					<?php if( !empty($s[1]) ): ?>
						<?php
						$diff = static::get_differences($s[0]['price_avg'], $s[1]['price_avg']);
						?>
						<div class="change">
							<span class="<?php echo $diff['class']; ?>"><?php echo $diff['op'] . number_format($diff['%'], 0) .'%'; ?></span>
							<span class="sub"> (<?php echo em_get_currency_formatted($diff['amt']); ?>)</span>
						</div>
						<div class="comparison"><?php esc_html_e('Compared to', 'events-manager'); ?> <?php echo  em_get_currency_formatted($s[1]['price_avg']); ?></div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
				<?php if( !empty($args['summaries']['tickets_avg']) ): ?>
				<div>
					<div class="title"><?php esc_html_e('Average Ticket','events-manager'); ?></div>
					<div class="total">
						<?php
						$newer = $s[0]['price'] > 0 && $s[0]['spaces'] > 0 ? $s[0]['price'] / $s[0]['spaces'] : 0;
						echo em_get_currency_formatted($newer);
						?>
						<span class="sub">/ <?php esc_html_e('ticket', 'events-manager'); ?></span>
					</div>
					<?php if( !empty($s[1]) ): ?>
						<?php
						$older = $s[1]['price'] > 0 && $s[1]['spaces'] > 0 ? $s[1]['price'] / $s[1]['spaces'] : 0;
						$diff = static::get_differences($newer, $older);
						?>
						<div class="change">
							<span class="<?php echo $diff['class']; ?>"><?php echo $diff['op'] . number_format($diff['%'], 0) .'%'; ?></span>
							<span class="sub"> (<?php echo em_get_currency_formatted($diff['amt']); ?>)</span>
						</div>
					<div class="comparison"><?php esc_html_e('Compared to', 'events-manager'); ?> <?php echo  em_get_currency_formatted($older); ?></div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
				<?php if( !empty($args['summaries']['spaces_avg']) ): ?>
				<div>
					<div class="title"><?php esc_html_e('Tickets / Booking','events-manager'); ?></div>
					<div class="total">
						<?php echo number_format($s[0]['spaces_avg'], 2); ?>
						<span class="sub">/ <?php esc_html_e('booking', 'events-manager'); ?></span>
					</div>
					<?php if( !empty($s[1]) ): ?>
						<?php
						$diff = static::get_differences($s[0]['spaces_avg'], $s[1]['spaces_avg']);
						?>
						<div class="change">
							<span class="<?php echo $diff['class']; ?>"><?php echo $diff['op'] . number_format($diff['%'], 0) .'%'; ?></span>
							<span class="sub"> (<?php echo number_format($diff['amt'], 2); ?>)</span>
						</div>
						<div class="comparison"><?php esc_html_e('Compared to', 'events-manager'); ?> <?php echo number_format($s[1]['spaces_avg'], 2); ?></div>
					<?php endif; ?>
				</div>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}
	
	public static function get_differences( $newer, $older ) {
		$diff = $newer - $older;
		$class  = $op = '';
		if( $diff == 0 ){
			$change = 0;
		}elseif( $newer == 0 || $older == 0 ){
			$change = 100;
			$op = $newer > $older ? '+':'-';
			$class = $newer > $older ? 'plus':'minus';
		}else {
			$change = $diff / $older * 100;
			if ( $diff > 0 ) {
				$class = 'plus';
				$op    = '+';
			} elseif ( $diff < 0 ) {
				$class = 'minus';
				$op    = '-';
			}
		}
		
		return array(
			'amt'   => abs($diff),
			'%'     => abs($change),
			'class' => $class,
			'op'    => $op
		);
	}
	
	public static function selected( $index, $value, $args = array() ){
		if( empty($args) && $index === $value ){
			echo 'selected';
		}elseif( !empty($args[$index]) && $args[$index] === $value ){
			echo 'selected';
		}
	}
	
	public static function implode_js( $separator, $values, $encloser = null ){
		if( $encloser === null ){
			$encloser = "'";
		}elseif( !$encloser ){
			$encloser = '';
		}
		$outputs = array();
		foreach( $values as $value ){
			$outputs[] = $encloser . esc_js($value) . $encloser;
		}
		return implode( $separator, $outputs );
	}
	
	public static function js_footer(){
		if( static::$footer_js ) return;
		static::$footer_js = true;
		?>
		<script>
			<?php include(EM_DIR.'/includes/js/em-charts.js'); ?>
		</script>
		<?php
	}
}
Dashboard::init();
/*
$colors = array(
	'rgba(54, 162, 235, 0.75)',
	'rgba(255, 99, 132, 0.75)',
	'rgba(255, 159, 64, 0.75)',
	'rgba(255, 205, 86, 0.75)',
	'rgba(75, 192, 192, 0.75)',
	'rgba(153, 102, 255, 0.75)',
	'rgba(146, 203, 207, 0.75)',
	'rgba(201, 231, 127, 0.75)',
	'rgba(203, 67, 53, 0.75)',
	'rgba(31, 97, 141, 0.75)',
	'rgba(241, 196, 15, 0.75)',
	'rgba(39, 174, 96, 0.75)',
	'rgba(136, 78, 160, 0.75)',
	'rgba(211, 84, 0, 0.75)',
	'rgba(213, 90, 200, 0.75)',
	'rgba(200, 90, 100, 0.75)',
	'rgba(34, 207, 207, 0.75)',
	'rgba(5, 155, 255, 0.75)',
	'rgba(201, 203, 207, 0.75)',
	'rgba(129, 129, 129, 0.75)',
	);
$colors = array_unique($colors);
foreach( $colors as $col ){
	echo '<div style="width:40px; height: 40px; padding:10px; background:'.$col.'"></div>';
}
die();
*/