laipower/wp-content/plugins/easy-digital-downloads/includes/reports/data/charts/v2/class-manifest.php

558 lines
12 KiB
PHP

<?php
/**
* Reports API - Chart Manifest class
*
* @package EDD
* @subpackage Reports\Data\Charts
* @copyright Copyright (c) 2018, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/namespace EDD\Reports\Data\Charts\v2;
use EDD\Reports;
use EDD\Reports\Data\Chart_Endpoint;
use EDD\Utils\Error_Logger_Interface as Error_Logger;
/**
* Represents a manifestation of a ChartJS v2 object's attributes in PHP form.
*
* Primarily used to simplify translating server-side arguments into client-side ones.
*
* @since 3.0
*/
class Manifest implements Error_Logger {
/**
* Represents the chart type to be manifested.
*
* @since 3.0
* @var string
*/
private $type;
/**
* Represents the unfiltered chart options for the manifest.
*
* @since 3.0
* @var array
*/
private $options = array();
/**
* Datasets associated with the current chart.
*
* @since 3.0
* @var Dataset[]
*/
private $datasets = array();
/**
* Labels associated with the current pie or doughnut chart.
*
* @since 3.0
* @var array
*/
private $labels = array();
/**
* Represents the current Chart_Endpoint instance.
*
* @since 3.0
* @var Chart_Endpoint
*/
private $endpoint;
/**
* Holds errors related to instantiating the manifest.
*
* @since 3.0
* @var \WP_Error
*/
protected $errors;
/**
* Sets up the manifest.
*
* @since 3.0
*
* @param Chart_Endpoint $endpoint Chart endpoint.
*/
public function __construct( $endpoint ) {
$this->setup_error_logger();
$this->set_type( $endpoint->get_type() );
$this->set_endpoint( $endpoint );
$options = $endpoint->get_options();
if ( $this->is_pie_manifest() && ! empty( $options['labels'] ) ) {
$this->set_labels( $options['labels'] );
unset( $options['labels'] );
}
$this->set_options( $options );
}
/**
* Retrieves the chart type.
*
* @since 3.0
*
* @return string Chart type.
*/
public function get_type() {
return $this->type;
}
/**
* Sets the chart type for the manifest.
*
* @since 3.0
*
* @param string $type Chart type to be manifested.
*/
private function set_type( $type ) {
$this->type = sanitize_key( $type );
}
/**
* Retrieves the chart endpoint object for this manifest.
*
* @since 3.0
*
* @return Chart_Endpoint Chart endpoint.
*/
public function get_endpoint() {
return $this->endpoint;
}
/**
* Sets the chart endpoint object.
*
* @since 3.0
*
* @param EDD\Reports\Data\Chart_Endpoint $endpoint Chart_Endpoint object.
*/
private function set_endpoint( $endpoint ) {
$this->endpoint = $endpoint;
}
/**
* Stores the unfiltered chart options for later access.
*
* @since 3.0
*
* @param array $options Chart options and datasets.
*/
private function set_options( $options ) {
if ( ! empty( $options['datasets'] ) && is_array( $options['datasets'] ) ) {
foreach ( $options['datasets'] as $id => $data ) {
$this->add_dataset( $id, $data );
}
} else {
$message = sprintf( 'The %s endpoint has no datasets.', $this->get_endpoint()->get_id() );
$this->errors->add( 'missing_chart_datasets', $message, $this->get_endpoint() );
}
unset( $options['datasets'] );
$this->options = $options;
}
/**
* Retrieves parsed options for the chart manifest.
*
* @since 3.0
*
* @return array Chart options.
*/
public function get_options() {
return $this->options;
}
/**
* Retrieves the manifest datasets.
*
* @since 3.0
*
* @return Dataset[] Datasets for this chart if any are defined, otherwise an empty array.
*/
public function get_datasets() {
return $this->datasets;
}
/**
* Determines whether the current chart manifest contains any datasets.
*
* @since 3.0
*
* @return bool True if there are datasets, otherwise false.
*/
public function has_datasets() {
$datasets = $this->get_datasets();
return ! empty( $datasets );
}
/**
* Sets the labels property (for pie and doughnut charts).
*
* @since 3.0
*
* @param array $labels Array of pie or doughnut chart labels.
*/
private function set_labels( $labels ) {
$this->labels = $labels;
}
/**
* Retrieves the manifest labels (for pie and doughnut charts).
*
* @since 3.0
*/
public function get_labels() {
return $this->labels;
}
/**
* Determines whether the current chart manifest contains any labels (for pie and doughnut charts).
*
* @since 3.0
*
* @return bool True if there are labels, otherwise false.
*/
public function has_labels() {
$labels = $this->get_labels();
return ! empty( $labels );
}
/**
* Adds a dataset.
*
* @since 3.0
*
* @param string $dataset_id ID to associate the dataset with.
* @param array $options Dataset options.
* @return bool True if the dataset was added, otherwise false.
*/
public function add_dataset( $dataset_id, $options ) {
$handler = $this->get_dataset_handler();
if ( ! empty( $handler ) && class_exists( $handler ) ) {
/** @var Dataset $dataset */
$dataset = new $handler( $dataset_id, $this->get_endpoint(), $options );
if ( ! $dataset->has_errors() ) {
$this->datasets[ $dataset_id ] = $dataset;
return true;
} else {
$this->errors->add( 'dataset_errors_passthrough', 'Errors have been passed through from dataset parsing.', $dataset->get_errors() );
}
}
return false;
}
/**
* Retrieves the handler class for the current dataset type.
*
* @since 3.0
*
* @return string Dataset handler class.
*/
public function get_dataset_handler() {
$handler = '';
switch( $this->get_type() ) {
case 'doughnut':
case 'pie':
$handler = 'EDD\Reports\Data\Charts\v2\Pie_Dataset';
break;
case 'bar':
$handler = 'EDD\Reports\Data\Charts\v2\Bar_Dataset';
break;
case 'line':
$handler = 'EDD\Reports\Data\Charts\v2\Line_Dataset';
break;
}
return $handler;
}
/**
* Generate the name of an element used to reference a rendered chart.
*
* @since 3.0
*
* @return string
*/
public function get_target_el() {
$endpoint = $this->get_endpoint();
$default = "edd_reports_graph_{$endpoint->get_id()}";
return $endpoint->get_display_arg( 'target', $default );
}
/**
* Renders the manifest in JS form.
*
* @since 3.0
*/
public function render() {
// Render a <canvas> element to inject the chart in to.
printf( '<canvas id="%s"></canvas>', esc_attr( $this->get_target_el() ) );
// Enqueue script and configuration to render the chart.
wp_enqueue_script( 'edd-admin-reports' );
wp_add_inline_script(
'edd-admin-reports',
sprintf( 'window.edd.renderChart(%s)', wp_json_encode( $this->build_config() ) )
);
}
/**
* Builds the chart config.
*
* @since 3.0
*
* @return object Config object.
*/
public function build_config() {
$config = new \stdClass();
// Dates.
$dates = Reports\get_dates_filter( 'objects' );
$day_by_day = Reports\get_dates_filter_day_by_day();
$hour_by_hour = Reports\get_dates_filter_hour_by_hour();
// Adjust end date forward by 1 second to push into the next day (for ChartJS display purposes).
$dates['end']->addSeconds( 1 );
// Get the timezone ID for parsing.
$timezone = edd_get_timezone_id();
// Apply UTC offset.
$dates['start']->setTimezone( $timezone );
$dates['end']->setTimezone( $timezone );
$time_format = 'MMM YYYY';
if ( $hour_by_hour ) {
$time_format = 'hA';
} else if ( $day_by_day ) {
$time_format = 'MMM D';
}
$config->type = $this->get_type();
$config->data = $this->get_chart_data();
$config->options = $this->get_chart_options();
$config->target = $this->get_target_el();
$config->dates = array_merge(
$dates,
array(
'hour_by_hour' => $hour_by_hour,
'day_by_day' => $day_by_day,
'utc_offset' => esc_js( EDD()->utils->get_gmt_offset() / HOUR_IN_SECONDS ),
'timezone' => $timezone,
'time_format' => $time_format,
)
);
return $config;
}
/**
* Retrieves the parsed chart datasets as an object.
*
* @since 3.0
*
* @return array Parsed chart data.
*/
public function get_chart_data() {
$data = array();
if ( $this->has_datasets() ) {
$datasets = $this->get_datasets();
$data['datasets'] = array();
foreach ( $datasets as $id => $set ) {
if ( $set->has_errors() ) {
continue;
}
$data['datasets'][] = $set->get_options();
}
}
if ( $this->is_pie_manifest() ) {
$data['labels'] = $this->get_labels();
}
return $data;
}
/**
* Retrieves the parsed chart options as an object.
*
* @since 3.0
*
* @return array Parsed chart options.
*/
public function get_chart_options() {
$endpoint_options = $this->get_endpoint()->get_options();
if ( $this->is_pie_manifest() ) {
$defaults = array(
'animation' => array(
'duration' => 0,
),
'responsive' => true,
'legend' => array(
'position' => 'left',
),
);
} else {
$day_by_day = Reports\get_dates_filter_day_by_day();
$hour_by_hour = Reports\get_dates_filter_hour_by_hour();
$time_unit = 'month';
$time_format = 'MMM YYYY';
if ( $hour_by_hour ) {
$time_unit = 'hour';
$time_format = 'hA';
} else if ( $day_by_day ) {
$time_unit = 'day';
$time_format = 'MMM D';
}
$defaults = array(
'animation' => array(
'duration' => 0,
),
'responsive' => true,
'hoverMode' => 'index',
'stacked' => false,
'title' => array(
'display' => $this->get_endpoint()->get_label() && $this->get_endpoint()->get( 'show_chart_title' ),
'text' => $this->get_endpoint()->get_label(),
),
'scales' => array(
'xAxes' => array(),
'yAxes' => array(),
),
);
$default_xAxes = array(
array(
'type' => 'time',
'display' => true,
'ticks' => array(
'source' => 'auto',
'maxRotation' => 0,
),
'position' => 'bottom',
'time' => array(
'unit' => $time_unit,
'tooltipFormat' => $time_format,
),
),
);
$default_yAxes = array(
array(
'type' => 'linear',
'display' => true,
'position' => 'left',
'ticks' => array(
'formattingType' => 'format',
'beginAtZero' => true,
'suggestedMin' => 0,
),
),
);
// Check if specific axes are missing from the endpoint options and load them from defaults.
foreach ( array( 'xAxes', 'yAxes' ) as $axes_name) {
if ( empty( $endpoint_options['scales'][ $axes_name ] ) ) {
$endpoint_options['scales'][ $axes_name ] = ${ "default_{$axes_name}" };
}
}
}
return array_merge( $defaults, $endpoint_options );
}
/**
* Determines whether the chart manifest is for a pie or doughnut chart.
*
* @since 3.0
*
* @return bool True if the manifest is for a pie or doughnut chart, otherwise false.
*/
public function is_pie_manifest() {
return in_array( $this->get_type(), array( 'pie', 'doughnut' ), true );
}
/**
* Determines whether the dataset has generated errors during instantiation.
*
* @since 3.0
*
* @return bool True if errors have been logged, otherwise false.
*/
public function has_errors() {
if ( method_exists( $this->errors, 'has_errors' ) ) {
return $this->errors->has_errors();
} else {
$errors = $this->errors->get_error_codes();
return ! empty( $errors );
}
}
/**
* Retrieves any logged errors for the dataset.
*
* @since 3.0
*
* @return \WP_Error WP_Error object for the current dataset.
*/
public function get_errors() {
return $this->errors;
}
/**
* Sets up the WP_Error instance.
*
* @since 3.0
*/
public function setup_error_logger() {
if ( ! isset( $this->errors ) ) {
$this->errors = new \WP_Error();
}
}
}