installed plugin Easy Digital Downloads version 3.1.0.3

This commit is contained in:
2022-11-27 15:03:07 +00:00
committed by Gitium
parent 555673545b
commit c5dce2cec6
1200 changed files with 238970 additions and 0 deletions

View File

@ -0,0 +1,34 @@
<?php
/**
* Reports API - Bar Dataset 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;
/**
* Represents a manifestation of a ChartJS v2 bar chart dataset in PHP form.
*
* @since 3.0
*
* @see Dataset
* @see Manifest
*/
class Bar_Dataset extends Dataset {
/**
* Represents the list of fields for a given dataset.
*
* @since 3.0
* @var array
*/
protected $fields = array(
'borderSkipped', 'hoverBackgroundColor',
'hoverBorderColor', 'hoverBorderWidth'
);
}

View File

@ -0,0 +1,340 @@
<?php
/**
* Reports API - Dataset 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\Data\Chart_Endpoint;
use EDD\Utils\Error_Logger_Interface as Error_Logger;
/**
* Represents the manifestation of a ChartJS v2 dataset in PHP form.
*
* @since 3.0
*
* @see Error_Logger_Interface
*/
abstract class Dataset implements Error_Logger {
/**
* The ID associated with the dataset.
*
* Primarily used for locating associated data via the endpoint's data callback.
*
* @since 3.0
* @var string
*/
private $dataset_id;
/**
* Represents the list of fields for a given dataset.
*
* Should be defined by all sub-classes.
*
* @since 3.0
* @var array
*/
protected $fields = array();
/**
* Holds errors related to instantiating the object.
*
* @since 3.0
* @var \WP_Error
*/
protected $errors;
/**
* Raw dataset options and data.
*
* @since 3.0
* @var array
*/
private $options = array();
/**
* Represents the chart endpoint the dataset is associated with.
*
* @since 3.0
* @var Chart_Endpoint
*/
private $endpoint;
/**
* Represents the list of global fields for all datasets.
*
* @since 3.0
* @var array
*/
protected $global_fields = array(
'label', 'xAxisID', 'yAxisID', 'data',
'backgroundColor', 'borderColor', 'borderWidth',
);
/**
* Sets up the dataset for population.
*
* @since 3.0
*
* @param string $dataset_id Dataset ID.
* @param Chart_Endpoint $endpoint Chart endpoint object.
* @param array $options Dataset options.
*/
public function __construct( $dataset_id, $endpoint, $options ) {
$this->setup_error_logger();
$this->set_id( $dataset_id );
$this->set_endpoint( $endpoint );
$this->validate( $options );
}
/**
* Retrieves the dataset ID.
*
* @since 3.0
*
* @return string Dataset ID.
*/
public function get_id() {
return $this->dataset_id;
}
/**
* Sets the dataset ID.
*
* @since 3.0
*
* @param string $dataset_id Dataset ID
*/
private function set_id( $dataset_id ) {
$this->dataset_id = sanitize_key( $dataset_id );
}
/**
* 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;
}
/**
* Retrieves the raw dataset options.
*
* @since 3.0
*
* @return array Dataset options (raw).
*/
public function get_options() {
return $this->options;
}
/**
* Retrieves the chart endpoint object for this dataset.
*
* @since 3.0
*
* @return Chart_Endpoint Chart endpoint.
*/
public function get_endpoint() {
return $this->endpoint;
}
/**
* Retrieves the list of local fields.
*
* @since 3.0
*
* @return array List of local fields.
*/
public function get_fields() {
return $this->fields;
}
/**
* Retrieves the list of global fields.
*
* @since 3.0
*
* @return array List of global fields.
*/
public function get_global_fields() {
return $this->global_fields;
}
/**
* Retrieves the list of fields for the current dataset.
*
* Includes the global fields.
*
* @since 3.0
*
* @return array List of fields available to the dataset.
*/
public function get_all_fields() {
$fields = array_merge( $this->get_global_fields(), $this->get_fields() );
/**
* Filters the fields available to a ChartJS graph.
*
* @since 3.0
*
* @param array $fields ChartJS fields (global and local).
* @param Dataset $this Dataset instance.
*/
return apply_filters( 'edd_reports_chart_fields', $fields, $this );
}
/**
* Attempts to retrieve data associated with the current dataset.
*
* @since 3.0
*
* @return mixed Data associated with the current dataset.
*/
public function get_data() {
return $this->get_endpoint()->get_data_by_set( $this->get_id() );
}
/**
* Performs validation on incoming dataset options.
*
* @since 3.0
*
* @param array $options Dataset options.
*/
public function validate( $options ) {
$fields = $this->get_all_fields();
// Strip invalid options.
foreach ( $options as $key => $value ) {
if ( ! in_array( $key, $fields, true ) ) {
unset( $options[ $key ] );
}
}
$data = $this->get_data();
$processed = array();
if ( ! empty( $data ) ) {
$options['data'] = $this->parse_data_for_output( $data );
$this->options = $options;
} else {
$message = sprintf( 'The data for the \'%1$s\' dataset for the \'%2$s\' endpoint in the \'%3$s\' report is missing or invalid.',
$this->get_id(),
$this->get_endpoint()->get_id(),
$this->get_endpoint()->get_report_id()
);
$this->errors->add( 'missing_chart_data', $message, $data );
}
}
/**
* Parses the dataset data for output via JS.
*
* @since 3.0
*
* @param array $data Dataset data.
* @return array Processed data.
*/
public function parse_data_for_output( $data ) {
if ( $this instanceof Pie_Dataset ) {
$processed = $data;
} else {
foreach ( $data as $key => $values ) {
if ( is_array( $values ) && isset( $values[1] ) ) {
$processed[ $key ] = array(
'x' => $this->adjust_time_string( $values[0] ),
'y' => $values[1],
);
} else {
$processed[ $key ] = array(
'x' => $this->adjust_time_string( $values ),
);
}
}
}
return $processed;
}
/**
* Given a date as a string or numeric timestamp, adjust it for a specific timezone.
*
* This allows the points on the graph to line up with the ticks, which are already adjusted.
*
* @since 3.1
*
* @param string|int $time_string The time string to possibly adjust.
*
* @return string If a timestamp, it's adjusted for the timezone of the store.
*/
private function adjust_time_string( $time_string ) {
if ( is_numeric( $time_string ) ) {
$timezone = new \DateTimeZone( edd_get_timezone_id() );
$date_on_chart = new \DateTime( '@' . $time_string );
$time_string = $date_on_chart->setTimeZone( $timezone )->format( 'Y-m-d H:i:s' );
}
return $time_string;
}
/**
* 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();
}
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Reports API - LINE Dataset 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;
/**
* Represents a manifestation of a ChartJS v2 line chart dataset in PHP form.
*
* @since 3.0
*
* @see Dataset
* @see Manifest
*/
class Line_Dataset extends Dataset {
/**
* Represents the list of fields for a given dataset.
*
* @since 3.0
* @var array
*/
protected $fields = array(
'borderDash', 'borderDashOffset', 'borderCapStyle', 'borderJoinStyle',
'cubicInterpolationMode', 'fill', 'lineTension', 'pointBackgroundColor',
'pointBorderColor', 'pointBorderWidth', 'pointRadius', 'pointStyle',
'pointHitRadius', 'pointHoverBackgroundColor', 'pointHoverBorderColor',
'pointHoverBorderWidth', 'pointHoverRadius', 'showLine', 'spanGaps',
'steppedLine',
);
}

View File

@ -0,0 +1,557 @@
<?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();
}
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Reports API - Pie Dataset
*
* @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;
/**
* Represents a manifestation of a ChartJS v2 pie chart dataset in PHP form.
*
* @since 3.0
*
* @see Dataset
* @see Manifest
*/
class Pie_Dataset extends Dataset {
/**
* Represents the list of fields for a given dataset.
*
* @since 3.0
* @var array
*/
protected $fields = array(
'hoverBackgroundColor', 'hoverBorderColor',
'hoverBorderWidth'
);
}

View File

@ -0,0 +1,181 @@
<?php
/**
* Reports API - Base object
*
* @package EDD
* @subpackage Reports
* @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;
use EDD\Utils\Error_Logger_Interface as Error_Logger;
/**
* Represents an abstract base reports object.
*
* @since 3.0
*/
abstract class Base_Object implements Error_Logger {
/**
* Object ID.
*
* @since 3.0
* @var string
*/
public $object_id;
/**
* Object label.
*
* @since 3.0
* @var string
*/
public $label;
/**
* Holds errors related to instantiating the object.
*
* @since 3.0
* @var \WP_Error
*/
protected $errors;
/**
* Constructs the object.
*
* @since 3.0
*
* @param array $args Arguments for instantiating the object.
*/
public function __construct( $args ) {
$this->setup_error_logger();
$this->set_props( $args );
}
/**
* Sets props for the object.
*
* @since 3.0
*
* @param array $attributes Object attributes.
*/
public function set_props( $attributes ) {
if ( ! empty( $attributes['id'] ) ) {
$this->set_id( $attributes['id'] );
} else {
$this->errors->add( 'missing_object_id', 'The object ID is missing.', $attributes );
}
if ( ! empty( $attributes['label'] ) ) {
$this->set_label( $attributes['label'] );
} else {
$this->errors->add( 'missing_object_label', 'The object label is missing.', $attributes );
}
}
/**
* Retrieves the object ID.
*
* @since 3.0
*
* @return string Object ID.
*/
public function get_id() {
return $this->object_id;
}
/**
* Sets the object ID.
*
* @since 3.0
*
* @param string $object_id Object ID
* @return void
*/
private function set_id( $object_id ) {
$this->object_id = sanitize_key( $object_id );
}
/**
* Retrieves the global label for the current object.
*
* @since 3.0
*
* @return string Object label string.
*/
public function get_label() {
return $this->label;
}
/**
* Sets the object label.
*
* @since 3.0
*
* @param string $label Object label.
* @return void
*/
private function set_label( $label ) {
$this->label = $label;
}
/**
* Renders the object via its display callback.
*
* Each sub-class must define its own display() method.
*
* @since 3.0
*/
abstract public function display();
/**
* Determines whether the object 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 object.
*
* @since 3.0
*
* @return \WP_Error WP_Error object for the current object.
*/
public function get_errors() {
return $this->errors;
}
/**
* Sets up the WP_Error instance for logging errors.
*
* @since 3.0
*/
public function setup_error_logger() {
if ( ! isset( $this->errors ) ) {
$this->errors = new \WP_Error();
}
}
}

View File

@ -0,0 +1,275 @@
<?php
/**
* Reports API - Chart Endpoint Handler
*
* @package EDD
* @subpackage Reports
* @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;
use EDD\Reports\Data\Charts\v2 as Chart;
/**
* Handler for building a chart endpoint in the Reports API.
*
* @since 3.0
*/
class Chart_Endpoint extends Endpoint {
/**
* Endpoint view (type).
*
* @since 3.0
* @var string
*/
protected $view = 'chart';
/**
* Represents the chart type.
*
* @since 3.0
* @var string
*/
private $type;
/**
* Represents the PHP manifestation of the chart data and options.
*
* @since 3.0
* @var Chart\Manifest
*/
private $manifest;
/**
* Represents the ChartJS options passed to the chart endpoint.
*
* @since 3.0
* @var array
*/
private $options = array();
/**
* Call to override JS output for the chart.
*
* Completely overrides the manifest process for the current chart..
*
* @since 3.0
* @var callable
*/
private $js_callback;
/**
* Sets up the chart endpoint.
*
* @since 3.0
*
* @param array $args Chart endpoint attributes.
*/
public function __construct( $args ) {
$this->errors = new \WP_Error();
// ID and Label.
$this->set_props( $args );
$args = $this->parse_display_props( $args );
// Common values set last to account for overrides.
parent::__construct( $args );
// Chart props.
$this->setup_chart( $args );
}
/**
* Sets up the chart props needed for rendering.
*
* @since 3.0
*
* @param array $atts Endpoint attributes.
*/
private function setup_chart( $atts ) {
$view_type = $this->get_view();
if ( ! empty( $atts['views'][ $view_type ] ) ) {
$view_atts = $atts['views'][ $view_type ];
if ( ! empty( $view_atts['type'] ) ) {
$this->set_type( $view_atts['type'] );
} else {
$this->errors->add(
'missing_chart_type',
sprintf( 'The chart type for \'%1$s\' endpoint is missing.', $this->get_id() )
);
}
if ( ! empty( $view_atts['options'] ) ) {
$this->set_options( $view_atts['options'] );
} else {
$this->errors->add(
'missing_chart_options',
sprintf( 'The chart options for the \'%1$s\' endpoint is missing.', $this->get_id() )
);
}
if ( isset( $view_atts['render_js'] ) && is_callable( $view_atts['render_js'] ) ) {
$this->js_callback = $atts['render_js'];
}
}
if ( null === $this->js_callback ) {
// Due to the parent constructor firing last, make sure the report gets set for the benefit of the manifest.
if ( ! empty( $atts['report'] ) ) {
parent::set_report_id( $atts['report'] );
}
$this->build_manifest();
}
}
/**
* Sets display-related properties for the Endpoint.
*
* @since 3.0
*
* @param array $atts Endpoint attributes.
*/
private function parse_display_props( $atts ) {
$view_type = $this->get_view();
if ( ! empty( $atts['views'][ $view_type ] ) ) {
$atts['views'][ $view_type ] = $this->maybe_convert_callbacks_to_methods( $atts['views'][ $view_type ] );
}
return $atts;
}
/**
* Retrieves the graphing library options set for the current endpoint.
*
* @since 3.0
*
* @return array Options set for the current graph endpoint.
*/
public function get_options() {
return $this->options;
}
/**
* Sets options for displaying the graph.
*
* @since 3.0
*
* @param array $options Options for displaying the graph via the graphing library.
*/
protected function set_options( $options ) {
$this->options = $options;
}
/**
* Retrieves the value of a graph option if set.
*
* @since 3.0
*
* @param string $key Option key to retrieve a value for.
* @return mixed Value of the option key if set, otherwise an empty string.
*/
public function get( $key ) {
if ( isset( $this->options[ $key ] ) ) {
$value = $this->options[ $key ];
} else {
$value = '';
}
return $value;
}
/**
* Retrieves the chart type.
*
* @since 3.0
*
* @return string Chart type.
*/
public function get_type() {
return $this->type;
}
/**
* Sets the chart type.
*
* @since 3.0
*
* @param string $type Chart type to set.
*/
private function set_type( $type ) {
$this->type = sanitize_key( $type );
}
/**
* Retrieves the manifest instance.
*
* @since 3.0
*
* @return Chart\Manifest Chart manifest.
*/
public function get_manifest() {
return $this->manifest;
}
/**
* Instantiates the manifest based on chart type and options.
*
* @since 3.0
*/
private function build_manifest() {
$this->manifest = new Chart\Manifest( $this );
}
/**
* Retrieves a specific axis' data if set.
*
* @since 3.0
*
* @param string $set Dataset to retrieve corresponding data for.
* @return array Data corresponding to `$set` if it's set, otherwise an empty array.
*/
public function get_data_by_set( $set ) {
$data = $this->get_data();
if ( isset( $data[ $set ] ) ) {
return $data[ $set ];
} else {
return array();
}
}
/**
* Builds and outputs the graph JS to the page.
*
* @since 3.0
*/
public function display() {
// JS callback override.
if ( is_callable( $this->js_callback ) ) {
call_user_func( $this->js_callback, $this->get_display_args() );
return;
}
// Start parsing the manifest for output as JS.
$manifest = $this->get_manifest();
$manifest->render();
}
}

View File

@ -0,0 +1,314 @@
<?php
/**
* Reports API - Data Endpoints Registry
*
* @package EDD
* @subpackage Reports/Data
* @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;
use EDD\Utils;
use EDD\Reports;
use EDD\Reports\Exceptions as Reports_Exceptions;
/**
* Implements a singleton registry for registering reports data endpoints.
*
* @since 3.0
*
* @see \EDD\Reports\Registry
* @see \EDD\Utils\Static_Registry
*
* @method array get_endpoint( string $endpoint_id )
* @method bool endpoint_exists( string $endpoing_id )
* @method void unregister_endpoint( string $endpoint_id )
* @method array get_endpoints( string $sort )
*/
class Endpoint_Registry extends Reports\Registry implements Utils\Static_Registry {
/**
* Registry item error label.
*
* @since 3.0
* @var string
*/
public static $item_error_label = 'reports endpoint';
/**
* The one true Endpoint_Registry instance.
*
* @since 3.0
* @var Endpoint_Registry
*/
private static $instance;
/**
* Retrieves the one true Endpoint_Registry instance.
*
* @since 3.0
*
* @return Endpoint_Registry Registry instance.
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new Endpoint_Registry();
}
return self::$instance;
}
/**
* Handles magic method calls for endpoint manipulation.
*
* @since 3.0
*
* @throws \EDD_Exception If the endpoint doesn't exist for get_endpoint().
*
* @param string $name Method name.
* @param array $arguments Method arguments (if any)
* @return mixed Results of the method call (if any).
*/
public function __call( $name, $arguments ) {
$endpoint_id_or_sort = isset( $arguments[0] )
? $arguments[0]
: '';
switch( $name ) {
case 'get_endpoint':
return parent::get_item( $endpoint_id_or_sort );
case 'endpoint_exists':
return parent::offsetExists( $endpoint_id_or_sort );
case 'unregister_endpoint':
parent::remove_item( $endpoint_id_or_sort );
break;
case 'get_endpoints':
return $this->get_items_sorted( $endpoint_id_or_sort );
}
}
/**
* Registers a new data endpoint to the master registry.
*
* @since 3.0
*
* @throws \EDD_Exception if the endpoint could not be validated.
*
* @param string $endpoint_id Reports data endpoint ID.
* @param array $attributes {
* Endpoint attributes. All arguments are required unless otherwise noted.
*
* @type string $label Endpoint label.
* @type int $priority Optional. Priority by which to retrieve the endpoint. Default 10.
* @type array $views {
* Array of view handlers by type.
*
* @type array $view_type {
* View type slug, with array beneath it.
*
* @type callable $data_callback Callback used to retrieve data for the view.
* @type callable $display_callback Callback used to render the view.
* @type array $display_args Optional. Array of arguments to pass to the
* display_callback (if any). Default empty array.
* }
* }
* }
* @return bool True if the endpoint was successfully registered, otherwise false.
*/
public function register_endpoint( $endpoint_id, $attributes ) {
$defaults = array(
'label' => '',
'priority' => 10,
'views' => array(),
);
$attributes = array_merge( $defaults, $attributes );
$attributes['id'] = $endpoint_id;
$attributes['views'] = Reports\parse_endpoint_views( $attributes['views'] );
// Bail if this endpoint ID is already registered.
if ( $this->offsetExists( $endpoint_id ) ) {
$message = sprintf( 'The \'%1$s\' endpoint already exists and cannot be registered.', $endpoint_id );
throw new Utils\Exception( $message );
}
try {
$valid = $this->validate_endpoint( $endpoint_id, $attributes );
} catch ( \EDD_Exception $exception ) {
throw $exception;
}
if ( false === $valid ) {
return false;
} else {
try {
$return_value = parent::add_item( $endpoint_id, $attributes );
} catch ( \EDD_Exception $exception ) {
throw $exception;
}
return $return_value;
}
}
/**
* Validates the endpoint attributes.
*
* @since 3.0
*
* @throws \EDD_Exception if the `$label` or `$views` attributes are empty.
* @throws \EDD_Exception if any of the `$views` sub-attributes are empty, except `$filters`.
*
* @param string $endpoint_id Reports data endpoint ID.
* @param array $attributes Endpoint attributes. See register_endpoint() for full accepted attributes.
* @return bool True if the endpoint is considered 'valid', otherwise false.
*/
public function validate_endpoint( $endpoint_id, $attributes ) {
$is_valid = true;
try {
$this->validate_attributes( $attributes, $endpoint_id );
try {
$this->validate_views( $attributes['views'], $endpoint_id );
} catch( \EDD_Exception $exception ) {
edd_debug_log_exception( $exception );
$is_valid = false;
throw $exception;
}
} catch( \EDD_Exception $exception ) {
edd_debug_log_exception( $exception );
$is_valid = false;
throw $exception;
}
return $is_valid;
}
/**
* Builds an endpoint object from a registry entry.
*
* @since 3.0
*
* @param string|Endpoint $endpoint Endpoint ID or object.
* @param string $view_type View type to use when building the object.
* @param string $report Optional. Report ID. Default null.
* @return Endpoint|\WP_Error Endpoint object on success, otherwise a WP_Error object.
*/
public function build_endpoint( $endpoint, $view_type, $report = null ) {
// If an endpoint object was passed, just return it.
if ( $endpoint instanceof Endpoint ) {
return $endpoint;
}
try {
$_endpoint = $this->get_endpoint( $endpoint );
} catch( \EDD_Exception $exception ) {
edd_debug_log_exception( $exception );
return new \WP_Error( 'invalid_endpoint', $exception->getMessage(), $endpoint );
}
if ( ! empty( $_endpoint ) ) {
if ( Reports\validate_endpoint_view( $view_type ) ) {
$_endpoint['report'] = $report;
$handler = Reports\get_endpoint_handler( $view_type );
if ( ! empty( $handler ) && class_exists( $handler ) ) {
$_endpoint = new $handler( $_endpoint );
} else {
$_endpoint = new \WP_Error(
'invalid_handler',
sprintf( 'The handler for the \'%1$s\' view is invalid.', $view_type ),
$handler
);
}
} else {
$_endpoint = new \WP_Error(
'invalid_view',
sprintf( 'The \'%1$s\' view is invalid.', $view_type )
);
}
}
return $_endpoint;
}
/**
* Validates view properties for an incoming endpoint.
*
* @since 3.0
*
* @throws \EDD_Exception if the view attributes is empty or it's not a valid view.
*
* @param array $views List of attributes to check.
* @param string $endpoint_id Endpoint ID.
* @return void
*/
public function validate_views( $views, $endpoint_id ) {
$valid_views = Reports\get_endpoint_views();
$this->validate_attributes( $views, $endpoint_id );
foreach ( $views as $view => $attributes ) {
if ( array_key_exists( $view, $valid_views ) ) {
if ( ! empty( $valid_views[ $view ]['allow_empty'] ) ) {
$skip = $valid_views[ $view ]['allow_empty'];
} else {
$skip = array();
}
// View atts have already been parsed at this point, just validate them.
$this->validate_view_attributes( $attributes, $view, $skip );
} else {
throw Reports_Exceptions\Invalid_View::from( $view, __METHOD__, $endpoint_id );
}
}
}
/**
* Validates a list of endpoint view attributes.
*
* @since 3.0
*
* @throws \EDD_Exception if a required view attribute is empty.
*
* @param array $attributes List of view attributes to check for emptiness.
* @param string $view View slug.
* @param array $skip Optional. List of view attributes to skip validating.
* Default empty array.
* @return void
*/
public function validate_view_attributes( $attributes, $view, $skip = array() ) {
foreach ( $attributes as $attribute => $value ) {
if ( in_array( $attribute, $skip, true ) ) {
continue;
}
if ( empty( $value ) ) {
throw Reports_Exceptions\Invalid_View_Parameter::from( $attribute, __METHOD__, $view );
}
}
}
}

View File

@ -0,0 +1,244 @@
<?php
/**
* Reports API - Endpoint View Registry
*
* @package EDD
* @subpackage Reports
* @copyright Copyright (c) 2019, Easy Digital Downloads, LLC
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
* @since 3.0
*/
namespace EDD\Reports\Data;
use EDD\Utils;
use EDD\Reports;
/**
* Implements a singleton registry for registering endpoint views.
*
* @since 3.0
*
* @see \EDD\Reports\Registry
* @see \EDD\Utils\Static_Registry
*
* @method array get_endpoint_view( string $view_id )
*/
final class Endpoint_View_Registry extends Reports\Registry implements Utils\Static_Registry {
/**
* Item error label.
*
* @since 3.0
* @var string
*/
public static $item_error_label = 'endpoint view';
/**
* The one true registry instance.
*
* @since 3.0
* @var Endpoint_View_Registry
*/
private static $instance;
/**
* Retrieves the one true Endpoint View registry instance.
*
* @since 3.0
*
* @return Endpoint_View_Registry Registry instance.
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new Endpoint_View_Registry();
}
return self::$instance;
}
/**
* Handles magic method calls for endpoint view manipulation.
*
* @since 3.0
*
* @throws \EDD_Exception in get_item() if the item does not exist.
*
* @param string $name Method name.
* @param array $arguments Method arguments (if any)
* @return mixed Results of the method call (if any).
*/
public function __call( $name, $arguments ) {
$view_id_or_sort = isset( $arguments[0] )
? $arguments[0]
: '';
switch ( $name ) {
case 'get_endpoint_view':
return parent::get_item( $view_id_or_sort );
}
}
/**
* Registers a new endpoint view to the master registry.
*
* @since 3.0
*
* @throws \EDD_Exception if the `$view_id` doesn't match a core view.
* @throws \EDD_Exception if attributes other than 'group_callback', 'handler', or 'display_callback'
* are defined.
* @throws \EDD_Exception if one or more attributes are not of a valid specification.
*
* @param string $view_id Endpoint view ID.
* @param array $attributes {
* Endpoint view attributes. All arguments are required.
*
* @type string $label Report label.
* @type int $priority Optional. Priority by which to register the report. Default 10.
* @type array $filters Filters available to the report.
* @type array $endpoints Optional. Endpoints to associate with the report.
* }
* @return bool True if the report was successfully registered, otherwise false.
*/
public function register_endpoint_view( $view_id, $attributes ) {
$error = false;
$view_atts = $this->get_core_view( $view_id );
if ( empty( $view_atts ) ) {
throw new Utils\Exception( sprintf( 'The \'%1$s\' endpoint view is invalid.', $view_id ) );
}
if ( ! empty( $attributes['group_callback'] ) ) {
$view_atts['group_callback'] = $attributes['group_callback'];
}
if ( ! empty( $attributes['handler'] ) ) {
$view_atts['handler'] = $attributes['handler'];
}
if ( ! empty( $attributes['fields']['display_callback'] ) ) {
$view_atts['fields']['display_callback'] = $attributes['fields']['display_callback'];
}
try {
$this->validate_attributes( $view_atts, $view_id );
} catch ( \EDD_Exception $exception ) {
$error = true;
throw $exception;
}
if ( true === $error ) {
return false;
} else {
return parent::add_item( $view_id, $view_atts );
}
}
/**
* Retrieves registered endpoint views.
*
* @since 3.0
*
* @return array Endpoint view records.
*/
public function get_endpoint_views() {
return $this->get_items_sorted( 'ID' );
}
/**
* Prevents removing items from the registry.
*
* @since 3.0
*
* @param string $item_id Item ID.
*/
public function remove_item( $item_id ) {
return;
}
/**
* Prevents removing items from the registry.
*
* @since 3.0
*
* @param mixed $index Item index to check.
*/
public function offsetUnset( $index ) {
return;
}
/**
* Retrieves the core-defined views and their (mostly) immutable defaults.
*
* @since 3.0
*
* @param string $view_id View ID.
* @return array List of attributes for the given view ID if it exists, otherwise an empty array.
*/
public function get_core_view( $view_id ) {
$views = $this->get_core_views();
$attributes = array();
if ( array_key_exists( $view_id, $views ) ) {
$attributes = $views[ $view_id ];
}
return $attributes;
}
/**
* Retrieves the core-defined views and their (mostly) immutable defaults.
*
* @since 3.0
*
* @return array List of supported endpoint types and their attributes.
*/
public function get_core_views() {
return array(
'tile' => array(
'group' => 'tiles',
'group_callback' => 'EDD\Reports\default_display_tiles_group',
'handler' => 'EDD\Reports\Data\Tile_Endpoint',
'fields' => array(
'data_callback' => '::get_data',
'display_callback' => 'EDD\Reports\default_display_tile',
'display_args' => array(
'type' => '',
'context' => 'primary',
'comparison_label' => __( 'All time', 'easy-digital-downloads' ),
),
),
),
'chart' => array(
'group' => 'charts',
'group_callback' => 'EDD\Reports\default_display_charts_group',
'handler' => 'EDD\Reports\Data\Chart_Endpoint',
'fields' => array(
'type' => 'line',
'options' => array(),
'data_callback' => '::get_data',
'display_callback' => '::display',
'display_args' => array(
'colors' => 'core',
),
),
),
'table' => array(
'group' => 'tables',
'group_callback' => 'EDD\Reports\default_display_tables_group',
'handler' => 'EDD\Reports\Data\Table_Endpoint',
'fields' => array(
'data_callback' => '::prepare_items',
'display_callback' => '::display',
'display_args' => array(
'class_name' => '',
'class_file' => '',
),
),
),
);
}
}

View File

@ -0,0 +1,460 @@
<?php
/**
* Reports API - Endpoint View object
*
* @package EDD
* @subpackage Reports
* @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;
use EDD\Reports;
/**
* Represents a data endpoint for the Reports API.
*
* @since 3.0
*/
abstract class Endpoint extends Base_Object {
/**
* Endpoint view (type).
*
* @since 3.0
* @var string
*/
protected $view;
/**
* ID of the report the endpoint is being built against (if provided).
*
* @since 3.0
* @var string
*/
protected $report_id;
/**
* Represents the callback used to retrieve data based on the set view type.
*
* @since 3.0
* @var callable
*/
private $data_callback;
/**
* Represents the display callback based on the set view type.
*
* @since 3.0
* @var callable
*/
private $display_callback;
/**
* Represents the display arguments (passed to the display callback) for the view (type).
*
* @since 3.0
* @var array
*/
private $display_args = array();
/**
* Constructs the tile endpoint object.
*
* @since 3.0
*
* @param array $args Arguments for instantiating the endpoint as retrieved from the endpoint registry.
*/
public function __construct( $args ) {
parent::__construct( $args );
$this->check_view();
if ( ! empty( $args['report'] ) ) {
$this->set_report_id( $args['report'] );
}
$this->set_display_props( $args );
}
/**
* Displays the endpoint based on the view (type).
*
* @since 3.0
*
* @return void
*/
public function display() {
$callback = $this->get_display_callback();
if ( is_callable( $callback ) ) {
call_user_func_array( $callback, array(
$this, // Endpoint
$this->get_data(), // Data
$this->get_display_args(), // Args
) );
}
}
/**
* Retrieves the data for the endpoint view (type).
*
* @since 3.0
*
* @return mixed Endpoint data.
*/
public function get_data() {
$data_callback = $this->get_data_callback();
if ( is_callable( $data_callback ) ) {
$data = call_user_func( $data_callback );
} else {
$data = '';
}
/**
* Filters data for the current endpoint.
*
* @since 3.0
*
* @param mixed|string $data Endpoint data.
* @param Endpoint $this Endpoint object.
*/
return apply_filters( 'edd_reports_endpoint_data', $data, $this );
}
/**
* Retrieves the endpoint view (type).
*
* @since 3.0
*
* @return string Endpoint view.
*/
public function get_view() {
return $this->view;
}
/**
* Checks the endpoint view (type) against the list of available views..
*
* @since 3.0
*
* @param string $view_type Endpoint type.
*/
protected function check_view() {
$views = Reports\get_endpoint_views();
if ( ! array_key_exists( $this->get_view(), $views ) ) {
$this->errors->add(
'invalid_view',
sprintf( 'The \'%1$s\' view is invalid.', $this->get_view() ),
$this
);
}
}
/**
* Retrieves the ID of the report currently associated with the endpoint.
*
* @since 3.0
*
* @return string|null Report ID if set, otherwise null.
*/
public function get_report_id() {
return $this->report_id;
}
/**
* Sets the ID for the report currently associated with the endpoint at the point of render.
*
* @since 3.0
*
* @param string $report Report ID.
*/
protected function set_report_id( $report ) {
$this->report_id = $report;
}
/**
* Sets display-related properties for the Endpoint.
*
* @since 3.0
*
* @param array $endpoint Endpoint record from the registry.
*/
protected function set_display_props( $endpoint ) {
$view_type = $this->get_view();
if ( ! empty( $endpoint['views'][ $view_type ] ) ) {
$view_atts = $endpoint['views'][ $view_type ];
// display_args is optional.
if ( ! empty( $view_atts['display_args'] ) ) {
$this->set_display_args( $view_atts['display_args'] );
}
// display_callback
if ( ! empty( $view_atts['display_callback'] ) ) {
$this->set_display_callback( $view_atts['display_callback'] );
} else {
$this->flag_missing_view_arg( 'display_callback' );
}
// data_callback
if ( ! empty( $view_atts['data_callback'] ) ) {
$this->set_data_callback( $view_atts['data_callback'] );
} else {
$this->flag_missing_view_arg( 'data_callback' );
}
} else {
$message = sprintf( 'The \'%1$s\' view type is not defined for the \'%2$s\' endpoint.',
$view_type,
$this->get_id()
);
$this->errors->add( 'view_not_defined', $message, array(
'view_type' => $view_type,
'endpoint_id' => $this->get_id(),
) );
}
}
/**
* Retrieves the value of a given display argument if set.
*
* @since 3.0
*
* @param string $key Display argument key.
* @param string $default Optional. Default value to return in the event the argument isn't set.
* Default empty string.
* @return mixed|string Value of the display argument if set, otherwise an empty string.
*/
public function get_display_arg( $key, $default = '' ) {
$display_args = $this->get_display_args();
if ( isset( $display_args[ $key ] ) ) {
$value = $display_args[ $key ];
} else {
$value = $default;
}
return $value;
}
/**
* Retrieves the display arguments for the view (type).
*
* @since 3.0
*
* @return array Display arguments.
*/
public function get_display_args() {
/**
* Filters the display arguments for the current endpoint.
*
* @since 3.0
*
* @param array $display_args Display arguments.
* @param Endpoint $this Endpoint object.
*/
return apply_filters( 'edd_reports_endpoint_display_args', $this->display_args, $this );
}
/**
* Validates and sets the display_args prop.
*
* @since 3.0
*
* @param array|mixed $display_args Display arguments.
* @return void
*/
protected function set_display_args( $display_args ) {
if ( is_array( $display_args ) ) {
$this->display_args = $display_args;
} else {
$this->flag_invalid_view_arg_type( 'display_args', 'array' );
}
}
/**
* Retrieves the display callback for the endpoint view (type).
*
* @since 3.0
*
* @return callable Display callback.
*/
public function get_display_callback() {
/**
* Filters the display callback for the current endpoint.
*
* @since 3.0
*
* @param callable $display_callback Display callback.
* @param Endpoint $this Endpoint object.
*/
return apply_filters( 'edd_reports_endpoint_display_callback', $this->display_callback, $this );
}
/**
* Validates and sets the display_args prop.
*
* @since 3.0
*
* @param callable|mixed $display_callback Display callback.
* @return void
*/
private function set_display_callback( $display_callback ) {
if ( is_callable( $display_callback ) ) {
$this->display_callback = $display_callback;
} elseif ( is_string( $display_callback ) && '::' === substr( $display_callback, 0, 2 ) ) {
$method = str_replace( '::', '', $display_callback );
$display_callback = array( $this, $display_callback );
$this->set_display_callback( $display_callback );
} else {
$this->flag_invalid_view_arg_type( 'display_callback', 'callable' );
}
}
/**
* Retrieves the data callback for the endpoint view (type).
*
* @since 3.0
*
* @return callable Data callback.
*/
public function get_data_callback() {
/**
* Filters the data callback for the current endpoint.
*
* @since 3.0
*
* @param callable $data_callback Data callback.
* @param Endpoint $this Endpoint object.
*/
return apply_filters( 'edd_reports_endpoint_data_callback', $this->data_callback, $this );
}
/**
* Validates and sets the display_args prop.
*
* @since 3.0
*
* @param callable|mixed $data_callback Data callback.
* @return void
*/
private function set_data_callback( $data_callback ) {
if ( is_callable( $data_callback ) ) {
$this->data_callback = $data_callback;
} elseif ( is_string( $data_callback ) && '::' === substr( $data_callback, 0, 2 ) ) {
$method = str_replace( '::', '', $data_callback );
$data_callback = array( $this, $data_callback );
$this->set_data_callback( $data_callback );
} else {
$this->flag_invalid_view_arg_type( 'data_callback', 'callable' );
}
}
/**
* Flags an error for an invalid view argument type.
*
* @since 3.0
*
* @param string $argument Argument name.
* @return void
*/
protected function flag_invalid_view_arg_type( $argument, $expected_type ) {
$message = sprintf( 'The \'%1$s\' argument must be of type %2$s for the \'%3$s\' endpoint \'%4$s\' view.',
$argument,
$expected_type,
$this->get_view(),
$this->get_id()
);
$this->errors->add( 'invalid_view_arg_type', $message, array(
'view_type' => $this->get_view(),
'endpoint_id' => $this->get_id(),
) );
}
/**
* Flags an error for a missing required view argument.
*
* @since 3.0
*
* @param string $argument Argument name.
* @return void
*/
protected function flag_missing_view_arg( $argument ) {
$message = sprintf( 'The \'%1$s\' argument must be set for the \'%2$s\' endpoint \'%3$s\' view.',
$argument,
$this->get_id(),
$this->get_view()
);
$this->errors->add( "missing_{$argument}", $message, array(
'view_type' => $this->get_view(),
'endpoint_id' => $this->get_id(),
) );
}
/**
* Converts callback attributes signified as methods (prefixed with '::')
* to methods under the given object.
*
* This conversion can only really happen once the Endpoint is generated
* because the object context doesn't yet exist during registration.
*
* @since 3.0
*
* @param array $atts View attributes for an endpoint.
* @param object $object Optional. Object under which the method should be assigned.
* Default is the current Endpoint object.
* @return array (Maybe) adjusted list of view attributes.
*/
protected function maybe_convert_callbacks_to_methods( $atts, $object = null ) {
$callbacks = array( 'display_callback', 'data_callback' );
if ( null === $object ) {
$object = $this;
}
foreach ( $callbacks as $callback ) {
if ( ! empty( $atts[ $callback ] )
&& ( is_string( $atts[ $callback ] ) && '::' === substr( $atts[ $callback ], 0, 2 ) )
) {
$method = str_replace( '::', '', $atts[ $callback ] );
$atts[ $callback ] = array( $object, $method );
}
}
return $atts;
}
}

View File

@ -0,0 +1,292 @@
<?php
/**
* Reports API - Report Registry
*
* @package EDD
* @subpackage Reports
* @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;
use EDD\Utils;
use EDD\Reports;
/**
* Implements a singleton registry for registering reports.
*
* @since 3.0
*
* @see \EDD\Reports\Registry
* @see \EDD\Utils\Static_Registry
*
* @method array get_report( string $report_id )
* @method void remove_report( string $report_id )
*/
class Report_Registry extends Reports\Registry implements Utils\Static_Registry {
/**
* Item error label.
*
* @since 3.0
* @var string
*/
public static $item_error_label = 'report';
/**
* The one true Report registry instance.
*
* @since 3.0
* @var Report_Registry
*/
private static $instance;
/**
* Retrieves the one true Report registry instance.
*
* @since 3.0
*
* @return Report_Registry Report registry instance.
*/
public static function instance() {
if ( is_null( self::$instance ) ) {
self::$instance = new Report_Registry();
}
return self::$instance;
}
/**
* Handles magic method calls for report manipulation.
*
* @since 3.0
*
* @throws \EDD_Exception in get_report() if the item does not exist.
*
* @param string $name Method name.
* @param array $arguments Method arguments (if any)
* @return mixed Results of the method call (if any).
*/
public function __call( $name, $arguments ) {
$report_id_or_sort = isset( $arguments[0] )
? $arguments[0]
: '';
switch ( $name ) {
case 'get_report':
return parent::get_item( $report_id_or_sort );
case 'remove_report':
return parent::remove_item( $report_id_or_sort );
}
}
/**
* Adds a new report to the master registry.
*
* @since 3.0
*
* @throws \EDD_Exception if the 'label' or 'endpoints' attributes are empty.
* @throws \EDD_Exception if one or more endpoints are not of a valid specification.
*
* @param string $report_id Report ID.
* @param array $attributes {
* Reports attributes. All arguments are required unless otherwise noted.
*
* @type string $label Report label.
* @type int $priority Optional. Priority by which to register the report. Default 10.
* @type array $filters Filters available to the report.
* @type array $endpoints Optional. Endpoints to associate with the report.
* }
* @return bool True if the report was successfully registered, otherwise false.
*/
public function add_report( $report_id, $attributes ) {
$error = false;
$defaults = array(
'label' => '',
'priority' => 10,
'group' => 'core',
'capability' => 'view_shop_reports',
'filters' => array(
'dates',
'taxes',
)
);
$attributes['id'] = $report_id;
$attributes = array_merge( $defaults, $attributes );
try {
// Filters can be empty.
$this->validate_attributes( $attributes, $report_id, array( 'filters' ) );
} catch ( \EDD_Exception $exception ) {
$error = true;
throw $exception;
}
if ( isset( $attributes['endpoints'] ) && is_array( $attributes['endpoints'] ) ) {
foreach ( $attributes['endpoints'] as $view_group => $endpoints ) {
foreach ( $endpoints as $index => $endpoint ) {
if ( ! is_string( $endpoint ) && ! ( $endpoint instanceof \EDD\Reports\Data\Endpoint ) ) {
unset( $attributes['endpoints'][ $view_group ][ $index ] );
throw new Utils\Exception( sprintf( 'The \'%1$s\' report contains one or more invalidly defined endpoints.', $report_id ) );
}
}
}
}
if ( isset( $attributes['filters'] ) && is_array( $attributes['filters'] ) ) {
foreach ( $attributes['filters'] as $index => $filter ) {
if ( ! Reports\validate_filter( $filter ) ) {
$message = sprintf( 'The \'%1$s\' report contains one or more invalid filters.', $report_id );
unset( $attributes['filters'][ $index ] );
throw new Utils\Exception( $message );
}
}
}
if ( true === $error ) {
return false;
} else {
return parent::add_item( $report_id, $attributes );
}
}
/**
* Retrieves registered reports.
*
* @since 3.0
*
* @param string $sort Optional. How to sort the list of registered reports before retrieval.
* Accepts 'priority' or 'ID' (alphabetized by item ID), or empty (none).
* Default empty.
* @param string $group Optional. The reports group to retrieve reports for. Default 'core'.
* @return
*/
public function get_reports( $sort = '', $group = 'core' ) {
$reports = $this->get_items_sorted( $sort );
foreach ( $reports as $report_id => $atts ) {
if ( $group !== $atts['group'] ) {
unset( $reports[ $report_id ] );
}
}
return $reports;
}
/**
* Registers a new data endpoint to the master endpoints registry.
*
* @since 3.0
*
* @throws \EDD_Exception if the `$label` or `$views` attributes are empty.
* @throws \EDD_Exception if any of the required `$views` sub-attributes are empty.
*
* @see \EDD\Reports\Data\Endpoint_Registry::register_endpoint()
*
* @param string $endpoint_id Reports data endpoint ID.
* @param array $attributes Attributes of the endpoint. See Endpoint_Registry::register_endpoint()
* for more information on expected arguments.
* @return bool True if the endpoint was successfully registered, otherwise false.
*/
public function register_endpoint( $endpoint_id, $attributes ) {
/** @var \EDD\Reports\Data\Endpoint_Registry|\WP_Error $registry */
$registry = EDD()->utils->get_registry( 'reports:endpoints' );
if ( is_wp_error( $registry ) ) {
return false;
}
return $registry->register_endpoint( $endpoint_id, $attributes );
}
/**
* Unregisters a data endpoint from the master endpoints registry.
*
* @since 3.0
*
* @see \EDD\Reports\Data\Endpoint_Registry::unregister_endpoint()
*
* @param string $endpoint_id Endpoint ID.
*/
public function unregister_endpoint( $endpoint_id ) {
/** @var \EDD\Reports\Data\Endpoint_Registry|\WP_Error $registry */
$registry = EDD()->utils->get_registry( 'reports:endpoints' );
if ( ! is_wp_error( $registry ) ) {
$registry->unregister_endpoint( $endpoint_id );
}
}
/**
* Registers an endpoint view to the master endpoint views registry.
*
* @since 3.0
*
* @throws \EDD_Exception if all expected attributes are not set.
*
* @see \EDD\Reports\Data\Endpoint_View_Registry::register_endpoint_view()
*
* @param string $view_id View ID. Currently only core endpoint views can be added.
* @param array $attributes Attributes of the endpoint view. See Endpoint_View_Registry::register_endpoint_view()
* for more information on expected/allowed arguments.
* @return bool True if the endpoint view was successfully registered, otherwise false.
*/
public function register_endpoint_view( $view_id, $attributes ) {
/** @var \EDD\Reports\Data\Endpoint_View_Registry|\WP_Error $registry */
$registry = EDD()->utils->get_registry( 'reports:endpoints:views' );
if ( is_wp_error( $registry ) ) {
return false;
}
return $registry->register_endpoint_view( $view_id, $attributes );
}
/**
* Builds and retrieves a Report object.
*
* @since 3.0
*
* @param string|Report $report Report ID or object.
* @param bool $build_endpoints Optional. Whether to build the endpoints (includes
* registering any endpoint dependencies, such as
* registering meta boxes). Default true.
* @return Report|\WP_Error Report object on success, otherwise a WP_Error object.
*/
public function build_report( $report, $build_endpoints = true ) {
// If a report object was passed, just return it.
if ( $report instanceof Report ) {
return $report;
}
try {
$_report = $this->get_report( $report );
} catch( \EDD_Exception $exception ) {
edd_debug_log_exception( $exception );
return new \WP_Error( 'invalid_report', $exception->getMessage(), $report );
}
if ( ! empty( $_report ) ) {
$_report = new Report( $_report );
if ( true === $build_endpoints ) {
$_report->build_endpoints();
}
}
return $_report;
}
}

View File

@ -0,0 +1,447 @@
<?php
/**
* Reports API - Report object
*
* @package EDD
* @subpackage Reports
* @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;
use EDD\Reports;
use EDD\Utils;
/**
* Represents an encapsulated report for the Reports API.
*
* @since 3.0
*/
final class Report extends Base_Object {
/**
* Represents valid endpoints available for display.
*
* @since 3.0
* @var Endpoint[]
*/
private $endpoints = array();
/**
* Represents the raw endpoints passed to the Report constructor.
*
* @since 3.0
* @var array
*/
private $raw_endpoints = array();
/**
* Represents the capability needed to view the rendered report.
*
* @since 3.0
* @var string
*/
private $capability;
/**
* Represents the display callback used to output the report.
*
* @since 3.0
* @var callable
*/
private $display_callback = '\EDD\Reports\default_display_report';
/**
* Represents filters the report has opted into.
*
* @since 3.0
* @var array
*/
private $filters = array();
/**
* Represents the group to display the report under.
*
* @since 3.0
* @var string
*/
private $group;
/**
* Constructs the report object.
*
* @since 3.0
*
* @param array $args Arguments for building the report (usually taking
* the form of a report record from the registry).
*/
public function __construct( $args ) {
parent::__construct( $args );
if ( ! empty( $args['endpoints'] ) ) {
$this->raw_endpoints = $args['endpoints'];
} else {
$this->errors->add( 'missing_endpoints', 'No endpoints are defined for the report.', $args );
}
if ( ! empty( $args['capability'] ) ) {
$this->set_capability( $args['capability'] );
} else {
$this->errors->add( 'missing_capability', 'No capability is defined for the report.', $args );
}
if ( ! empty( $args['display_callback'] ) ) {
$this->set_display_callback( $args['display_callback'] );
}
if ( ! empty( $args['filters'] ) ) {
$this->set_filters( $args['filters'] );
}
if ( ! empty( $args['group'] ) ) {
$this->set_group( $args['group'] );
}
}
/**
* Triggers building the report's endpoints if defined and the current user
* has the ability to view them.
*
* This is abstracted away from instantiation to allow for building Report objects
* without always registering meta boxes and other endpoint dependencies for display.
*
* @since 3.0
*/
public function build_endpoints() {
if ( ! empty( $this->raw_endpoints ) && current_user_can( $this->get_capability() ) ) {
try {
$this->parse_endpoints( $this->raw_endpoints );
} catch ( \EDD_Exception $exception ) {
edd_debug_log_exception( $exception );
}
} else {
$this->errors->add( 'missing_endpoints', 'No endpoints are defined for the report.' );
}
}
/**
* Parses Endpoint objects for each endpoint in the report.
*
* @since 3.0
*
* @throws \EDD_Exception
*
* @param array $endpoints Endpoints, keyed by view type.
*/
public function parse_endpoints( $report_endpoints ) {
/** @var \EDD\Reports\Data\Endpoint_Registry|\WP_Error $registry */
$registry = EDD()->utils->get_registry( 'reports:endpoints' );
if ( is_wp_error( $registry ) ) {
throw new Utils\Exception( $registry->get_error_message() );
}
$view_groups = $this->parse_view_groups();
// Loop through all passed endpoints using view groups.
foreach ( $report_endpoints as $group => $endpoints ) {
// Skip any invalid views based on view group.
if ( ! array_key_exists( $group, $view_groups ) ) {
throw new Utils\Exception( sprintf(
'The \'%1$s\' view group does not correspond to a known endpoint view type.',
$group
) );
}
// Loop through all endpoints for each view group and build endpoint objects.
foreach ( $endpoints as $endpoint ) {
$endpoint = $registry->build_endpoint( $endpoint, $view_groups[ $group ], $this->get_id() );
$this->validate_endpoint( $group, $endpoint );
}
}
}
/**
* Parses the views whitelist to retrieve corresponding view groups.
*
* @since 3.0
*
* @return array List of view group and view slug pairs.
*/
public function parse_view_groups() {
$views = Reports\get_endpoint_views();
$view_groups = array();
foreach ( $views as $view_type => $atts ) {
if ( ! empty( $atts['group'] ) ) {
$view_group = $atts['group'];
$view_groups[ $view_group ] = $view_type;
}
}
return $view_groups;
}
/**
* Validates an endpoint for rendering.
*
* @since 3.0
*
* @see \EDD\Reports\Data\Report::$valid_endpoints
*
* @param string $view_group View group corresponding to the endpoint view.
* @param Data\Endpoint|\WP_Error $endpoint Endpoint object.
*/
public function validate_endpoint( $view_group, $endpoint ) {
if ( is_wp_error( $endpoint ) ) {
$this->errors->add(
$endpoint->get_error_code(),
$endpoint->get_error_message(),
$endpoint->get_error_data()
);
} elseif ( ! is_wp_error( $endpoint ) && $endpoint->has_errors() ) {
$message = sprintf( 'The \'%1$s\' endpoint is invalid.', $endpoint->get_id() );
$this->errors->add( 'invalid_endpoint', $message, $endpoint->get_errors() );
// Valid.
} else {
$this->endpoints[ $view_group ][ $endpoint->get_id() ] = $endpoint;
}
}
/**
* Retrieves a list of validated endpoints for the current report.
*
* @since 3.0
*
* @param string $view_group Optional. View group for the type of endpoints to retrieve.
* Default empty (all valid endpoints).
* @return Endpoint[] List of validated endpoints by view view group.
*/
public function get_endpoints( $view_group = '' ) {
if ( ! empty( $view_group ) && ! empty( $this->endpoints[ $view_group ] ) ) {
return $this->endpoints[ $view_group ];
} else {
return $this->endpoints;
}
}
/**
* Determines whether the report has any valid endpoints.
*
* @since 3.0
*
* @param string $view_group Optional. View group for the type of endpoints
* to check the existence of. Default empty.
* @return bool True if there is at least one valid endpoint, otherwise false.
*/
public function has_endpoints( $view_group = '' ) {
if ( ! empty( $view_group ) ) {
$has_endpoints = ! empty( $this->endpoints[ $view_group ] );
} else {
$has_endpoints = ! empty( $this->endpoints );
}
return $has_endpoints;
}
/**
* Retrieves a given endpoint by view group.
*
* @since 3.0
*
* @param string $endpoint_id Endpoint ID.
* @param string $view_group Endpoint view group.
* @return Endpoint|\WP_Error Endpoint object if it exists, otherwise a WP_Error object.
*/
public function get_endpoint( $endpoint_id, $view_group ) {
$endpoints = $this->get_endpoints( $view_group );
if ( isset( $endpoints[ $endpoint_id ] ) ) {
$endpoint = $endpoints[ $endpoint_id ];
} else {
$message = sprintf( 'The \'%1$s\' endpoint does not exist for the \'%2$s\' view group in the \'%3$s\' report.',
$endpoint_id,
$view_group,
$this->get_id()
);
$endpoint = new \WP_Error( 'invalid_report_endpoint', $message );
}
return $endpoint;
}
/**
* Retrieves the capability needed to view the rendered report.
*
* @since 3.0
*
* @return string Report capability.
*/
public function get_capability() {
return $this->capability;
}
/**
* Sets the capability needed for the current user to view the report.
*
* @since 3.0
*
* @param string $capability Capability.
*/
private function set_capability( $capability ) {
$this->capability = sanitize_key( $capability );
}
/**
* Displays the endpoint based on the view (type).
*
* @since 3.0
*
* @return void
*/
public function display() {
$callback = $this->get_display_callback();
if ( is_callable( $callback ) ) {
call_user_func( $callback, $this );
}
}
/**
* Retrieves the current report's display callback.
*
* @since 3.0
*
* @return callable Display callback.
*/
public function get_display_callback() {
return $this->display_callback;
}
/**
* Sets the display callback used to render the report.
*
* @since 3.0
*
* @param callable $callback Display callback.
*/
private function set_display_callback( $callback ) {
if ( is_callable( $callback ) ) {
$this->display_callback = $callback;
} else {
$this->flag_invalid_report_arg_type( 'display_callback', 'callable' );
}
}
/**
* Retrieves the list of filters registered for use with this report.
*
* @since 3.0
*
* @return array List of support filters.
*/
public function get_filters() {
return $this->filters;
}
/**
* Sets the endpoint filters supported by the current report's endpoints.
*
* @since 3.0
*
* @param array $filters Filters to set for this report.
*/
private function set_filters( $filters ) {
foreach ( $filters as $filter ) {
if ( Reports\validate_filter( $filter ) ) {
$this->filters[] = $filter;
} else {
$message = sprintf( 'The \'%1$s\' filter for the \'%2$s\' report is invalid.',
$filter,
$this->get_id()
);
$this->errors->add( 'invalid_report_filter', $message, $this );
}
}
$this->filters = array_unique( $this->filters );
}
/**
* Retrieves the display group for the current report.
*
* @since 3.0
*
* @return string Display group. Default 'reports'.
*/
public function get_group() {
return $this->group;
}
/**
* Sets the display group for the current report.
*
* @since 3.0
*
* @param string $group Report display group.
*/
private function set_group( $group ) {
$this->group = sanitize_key( $group );
}
/**
* Displays an entire group of an endpoints view.
*
* @since 3.0
*
* @param string $view_group Endpoints view group.
* @return void
*/
public function display_endpoint_group( $view_group ) {
$groups = $this->parse_view_groups();
if ( array_key_exists( $view_group, $groups ) ) {
$callback = Reports\get_endpoint_group_callback( $groups[ $view_group ] );
if ( is_callable( $callback ) ) {
call_user_func( $callback, $this );
}
}
}
/**
* Flags an error for an invalid report argument type.
*
* @since 3.0
*
* @param string $argument Argument name.
*/
protected function flag_invalid_report_arg_type( $argument, $expected_type ) {
$message = sprintf( 'The \'%1$s\' argument must be of type %2$s for the \'%3$s\' report.',
$argument,
$expected_type,
$this->get_id()
);
$this->errors->add( 'invalid_report_arg_type', $message, array(
'report_id' => $this->get_id(),
) );
}
}

View File

@ -0,0 +1,223 @@
<?php
/**
* Reports API - Table Endpoint Handler
*
* @package EDD
* @subpackage Reports
* @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;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Handler for building a table endpoint in the Reports API.
*
* @since 3.0
*/
final class Table_Endpoint extends Endpoint {
/**
* Endpoint view (type).
*
* @since 3.0
* @var string
*/
protected $view = 'table';
/**
* List table instance.
*
* @since 3.0
* @var WP_List_Table
*/
private $list_table;
/**
* Represents the full path to the list table class file.
*
* @since 3.0
* @var string
*/
private $class_file;
/**
* Sets up the table endpoint.
*
* @since 3.0
*
* @param array $args Table endpoint attributes.
*/
public function __construct( array $args ) {
$this->errors = new \WP_Error();
// ID and Label.
$this->set_props( $args );
// List table set up and dumping display args.
$this->setup_list_table( $args );
// Parse display attributes from defaults.
$args = $this->parse_display_props( $args );
parent::__construct( $args );
}
/**
* Sets display-related properties for the Endpoint.
*
* @since 3.0
*
* @param array $endpoint Endpoint record from the registry.
*/
private function parse_display_props( $endpoint ) {
$view_type = $this->get_view();
if ( ! empty( $endpoint['views'][ $view_type ] ) ) {
$view_atts = $endpoint['views'][ $view_type ];
$list_table = $this->get_list_table();
if ( null === $list_table ) {
return $endpoint;
}
$endpoint['views'][ $view_type ] = $this->maybe_convert_callbacks_to_methods( $view_atts, $list_table );
}
return $endpoint;
}
/**
* Sets attributes related to the list table.
*
* @since 3.0
*
* @param array $endpoint Table endpoint arguments.
*/
private function setup_list_table( $endpoint ) {
if ( ! empty( $endpoint['views'][ $this->view ]['display_args'] ) ) {
$display_args = $endpoint['views'][ $this->view ]['display_args'];
if ( ! empty( $display_args['class_name'] ) ) {
if ( ! empty( $display_args['class_file'] ) ) {
$this->set_class_file( $display_args['class_file'] );
$this->set_list_table( $display_args['class_name'] );
} else {
$this->errors->add(
'missing_table_class_file',
sprintf( 'The list table class file for the \'%1$s\' endpoint is missing.', $this->get_id() )
);
}
} else {
$this->errors->add(
'missing_table_class_name',
sprintf( 'The list table class name for the \'%1$s\' endpoint is missing.',
$this->get_id()
)
);
}
// Dump the display args as they're no longer needed.
$endpoint['views'][ $this->view ]['display_args'] = array();
}
}
/**
* Retrieves the list table class file.
*
* @since 3.0
*
* @return string|null Class file if set, otherwise null.
*/
public function get_class_file() {
return $this->class_file;
}
/**
* Sets the list table class file.
*
* @since 3.0
*
* @param string $file Class file.
*/
private function set_class_file( $file ) {
if ( false === strpos( $file, '..' ) && false === strpos( $file, './' ) ) {
$this->class_file = $file;
}
}
/**
* Retrieves the list table instance.
*
* @since 3.0
*
* @return WP_List_Table|null List table instance if set, otherwise null.
*/
public function get_list_table() {
return $this->list_table;
}
/**
* Sets the list table instance.
*
* @since 3.0
*
* @see get_class_file()
*
* @param string $class List table class name.
*/
private function set_list_table( $class ) {
if ( ! class_exists( $class ) ) {
$path_to_file = $this->get_class_file();
if ( file_exists( $path_to_file ) ) {
require_once $path_to_file;
}
}
$this->list_table = new $class;
}
/**
* Display logic for the current table endpoint.
*
* @since 3.0
*/
public function display() {
$callback = $this->get_display_callback();
if ( is_callable( $callback ) ) {
$table = $this->get_list_table();
if ( null !== $table ) {
// Prep the table data for display (prepare_items).
$this->get_data();
call_user_func_array( $callback, array(
$this, // Endpoint
$table, // Table
$this->get_display_args(), // Args
) );
}
}
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Reports API - Tile Endpoint Handler
*
* @package EDD
* @subpackage Reports
* @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;
/**
* Handler for building a tile endpoint in the Reports API.
*
* @since 3.0
*/
final class Tile_Endpoint extends Endpoint {
/**
* Endpoint view (type).
*
* @since 3.0
* @var string
*/
protected $view = 'tile';
/**
* Display logic for the current tile endpoint.
*
* Tiles are rendered via meta boxes, so this method is deliberately empty.
*
* @since 3.0
*/
public function display() {
$classnames = array(
'edd-reports-tile',
);
echo '<div id="' . esc_attr( $this->get_id() ) . '" class="' . esc_attr( implode( ' ', $classnames ) ) . '">';
parent::display();
echo '</div>';
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* Most Valuable Customers list table.
*
* @package EDD
* @subpackage Reports/Data/Customers
* @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\Customers;
use EDD\Reports as Reports;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
// Load \EDD_Customer_Reports_Table if not loaded
if ( ! class_exists( '\EDD_Customer_Reports_Table' ) ) {
require_once EDD_PLUGIN_DIR . 'includes/admin/customers/class-customer-table.php';
}
/**
* Most_Valuable_Customers_List_Table class.
*
* @since 3.0
*/
class Most_Valuable_Customers_List_Table extends \EDD_Customer_Reports_Table {
/**
* Query the database and fetch the top five customers of all time.
*
* @since 3.0
*
* @return array $data Customers.
*/
public function get_data() {
global $wpdb;
$data = array();
$dates = Reports\get_filter_value( 'dates' );
$date_range = Reports\parse_dates_for_range( $dates['range'] );
$column = Reports\get_taxes_excluded_filter() ? 'total - tax' : 'total';
$currency = Reports\get_filter_value( 'currencies' );
$currency_clause = '';
if ( empty( $currency ) || 'convert' === $currency ) {
$column = sprintf( '%s / rate', $column );
} else {
$currency_clause = $wpdb->prepare( " AND currency = %s ", $currency );
}
$start_date = sanitize_text_field( date( 'Y-m-d 00:00:00', strtotime( $date_range['start'] ) ) );
$end_date = sanitize_text_field( date( 'Y-m-d 23:59:59', strtotime( $date_range['end'] ) ) );
$sql = "SELECT customer_id, COUNT(id) AS order_count, SUM({$column}) AS total_spent
FROM {$wpdb->edd_orders}
WHERE status IN (%s, %s) AND date_created >= %s AND date_created <= %s AND type = 'sale'
{$currency_clause}
GROUP BY customer_id
ORDER BY total_spent DESC
LIMIT 5";
$results = $wpdb->get_results( $wpdb->prepare( $sql, sanitize_text_field( 'complete' ), sanitize_text_field( 'revoked' ), $start_date, $end_date ) );
foreach ( $results as $result ) {
$customer = edd_get_customer( (int) $result->customer_id );
// Skip if customer record not found.
if ( ! $customer ) {
continue;
}
$user_id = ! empty( $customer->user_id )
? intval( $customer->user_id )
: 0;
$data[] = array(
'id' => $customer->id,
'user_id' => $user_id,
'name' => $customer->name,
'email' => $customer->email,
'order_count' => absint( $result->order_count ),
'spent' => $result->total_spent,
'date_created' => $customer->date_created,
);
}
return $data;
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns.
*/
public function get_columns() {
$columns = parent::get_columns();
// Remove the checkbox if it exists.
if ( isset( $columns['cb'] ) ) {
unset( $columns['cb'] );
}
return $columns;
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,206 @@
<?php
/**
* Top Five Customers list table.
*
* @package EDD
* @subpackage Reports/Data/Customers
* @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\Customers;
use EDD\Reports as Reports;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
// Load \EDD_Customer_Reports_Table if not loaded
if ( ! class_exists( '\EDD_Customer_Reports_Table' ) ) {
require_once EDD_PLUGIN_DIR . 'includes/admin/customers/class-customer-table.php';
}
/**
* Top_Five_Customers_List_Table class.
*
* @since 3.0
*/
class Top_Five_Customers_List_Table extends \EDD_Customer_Reports_Table {
/**
* Query the database and fetch the top five customers of all time.
*
* @since 3.0
*
* @return array $data Customers.
*/
public function get_data() {
$data = array();
$taxes = Reports\get_taxes_excluded_filter();
if ( false === $taxes ) {
$args = array(
'number' => 5,
'order' => 'DESC',
'orderby' => 'purchase_value',
);
$customers = edd_get_customers( $args );
foreach ( $customers as $customer ) {
/** @var \EDD_Customer $customer */
$user_id = ! empty( $customer->user_id )
? intval( $customer->user_id )
: 0;
$data[] = array(
'id' => $customer->id,
'user_id' => $user_id,
'name' => $customer->name,
'email' => $customer->email,
'order_count' => $customer->purchase_count,
'spent' => $customer->purchase_value,
'date_created' => $customer->date_created,
);
}
} else {
global $wpdb;
// @todo DRY with Most_Valuable_Customers_List_Table
$column = Reports\get_taxes_excluded_filter() ? 'total - tax' : 'total';
$currency = Reports\get_filter_value( 'currencies' );
$currency_clause = '';
if ( empty( $currency ) || 'convert' === $currency ) {
$column = sprintf( '%s / rate', $column );
} else {
$currency_clause = $wpdb->prepare( 'AND status = %s', strtoupper( $currency ) );
}
$sql = "SELECT customer_id, COUNT(id) AS order_count, SUM({$column}) AS total_spent
FROM {$wpdb->edd_orders}
WHERE status IN (%s, %s) AND type = 'sale'
{$currency_clause}
GROUP BY customer_id
ORDER BY total_spent DESC
LIMIT 5";
$results = $wpdb->get_results( $wpdb->prepare( $sql, sanitize_text_field( 'complete' ), sanitize_text_field( 'revoked' ) ) );
foreach ( $results as $result ) {
$customer = edd_get_customer( (int) $result->customer_id );
// Skip if customer record not found.
if ( ! $customer ) {
continue;
}
$user_id = ! empty( $customer->user_id )
? intval( $customer->user_id )
: 0;
$data[] = array(
'id' => $customer->id,
'user_id' => $user_id,
'name' => $customer->name,
'email' => $customer->email,
'order_count' => absint( $result->order_count ),
'spent' => $result->total_spent,
'date_created' => $customer->date_created,
);
}
}
return $data;
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns.
*/
public function get_columns() {
$columns = parent::get_columns();
// Remove the checkbox if it exists.
if ( isset( $columns['cb'] ) ) {
unset( $columns['cb'] );
}
return $columns;
}
/**
* Overrides the `spent` column value to possibly display in the filtered currency.
*
* @since 3.0
*
* @param array $item
* @param string $column_name
*
* @return string
*/
public function column_default( $item, $column_name ) {
if ( 'spent' !== $column_name ) {
return parent::column_default( $item, $column_name );
}
$currency = '';
$selected_currency = Reports\get_filter_value( 'currencies' );
if ( ! empty( $selected_currency ) && 'convert' !== $selected_currency ) {
$currency = $selected_currency;
}
$value = edd_currency_filter( edd_format_amount( $item[ $column_name ] ), $currency );
return apply_filters( 'edd_customers_column_' . $column_name, $value, $item['id'] );
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,237 @@
<?php
/**
* Top Five Discounts list table.
*
* @package EDD
* @subpackage Reports/Data/Customers
* @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\Discounts;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
use EDD\Reports as Reports;
use EDD\Stats as Stats;
use EDD\Admin\List_Table;
/**
* Top_Five_Discounts_List_Table class.
*
* @since 3.0
*/
class Top_Five_Discounts_List_Table extends List_Table {
/**
* Query the database and fetch the top five discounts.
*
* @since 3.0
*
* @return array $data Discounts.
*/
public function get_data() {
$filter = Reports\get_filter_value( 'dates' );
$stats = new Stats();
$d = $stats->get_most_popular_discounts( array(
'number' => 5,
'range' => $filter['range'],
) );
$data = array();
foreach ( $d as $result ) {
if ( empty( $result->object ) ) {
continue;
}
$c = new \stdClass();
$c->id = $result->object->id;
$c->name = $result->object->name;
$c->status = $result->object->status;
$c->use_count = $result->count;
$c->code = $result->object->code;
$c->type = $result->object->type;
$c->amount = $result->object->amount;
$data[] = $c;
}
return $data;
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns
*/
public function get_columns() {
return array(
'name' => __( 'Name', 'easy-digital-downloads' ),
'code' => __( 'Code', 'easy-digital-downloads' ),
'use_count' => __( 'Uses', 'easy-digital-downloads' ),
'amount' => __( 'Amount', 'easy-digital-downloads' )
);
}
/**
* This function renders most of the columns in the list table.
*
* @since 3.0
*
* @param \stdClass $discount Discount object.
* @param string $column_name The name of the column
*
* @return string Column Name
*/
public function column_default( $discount, $column_name ) {
return property_exists( $discount, $column_name ) ? $discount->$column_name : '';
}
/**
* This function renders the amount column.
*
* @access public
* @since 3.0
*
* @param \stdClass $discount Data for the discount code.
* @return string Formatted amount.
*/
public function column_amount( $discount ) {
return edd_format_discount_rate( $discount->type, $discount->amount );
}
/**
* Render the Name Column
*
* @since 3.0
*
* @param \stdClass $discount Discount object.
* @return string Data shown in the Name column
*/
public function column_name( $discount ) {
$base = $this->get_base_url();
$state = '';
// Bail if current user cannot manage discounts
if ( ! current_user_can( 'manage_shop_discounts' ) ) {
return;
}
// State
if ( ( ! empty( $status ) && ( $status !== $discount->status ) ) || ( $discount->status !== 'active' ) ) {
$state = ' &mdash; ' . edd_get_discount_status_label( $discount->id );
}
// Wrap discount title in strong anchor
$discount_title = '<strong><a class="row-title" href="' . esc_url( add_query_arg( array(
'edd-action' => 'edit_discount',
'discount' => absint( $discount->id ),
), $base ) ) . '">' . stripslashes( $discount->name ) . '</a>' . esc_html( $state ) . '</strong>';
// Return discount title & row actions
return $discount_title;
}
/**
* Setup the final data for the table.
*
* @since 3.0
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->get_data();
}
/**
* Get the base URL for the discount list table
*
* @since 3.0
*
* @return string
*/
public function get_base_url() {
// Remove some query arguments
$base = remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() );
// Add base query args
return add_query_arg( array(
'page' => 'edd-discounts',
), $base );
}
/**
* Message to be displayed when there are no items
*
* @since 3.0
*/
public function no_items() {
esc_html_e( 'No discounts found.', 'easy-digital-downloads' );
}
/**
* Gets the name of the primary column.
*
* @since 3.0
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,341 @@
<?php
/**
* Earnings by Taxonomy list table.
*
* @package EDD
* @subpackage Reports/Data/File_Downloads
* @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\Downloads;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
use EDD\Reports as Reports;
use EDD\Admin\List_Table;
/**
* Earnings_By_Taxonomy_List_Table class.
*
* @since 3.0
*/
class Earnings_By_Taxonomy_List_Table extends List_Table {
/**
* Query the database and fetch the top five most downloaded products.
*
* @since 3.0
*
* @return array Taxonomies.
*/
public function get_data() {
global $wpdb;
$dates = Reports\get_filter_value( 'dates' );
$date_range = Reports\parse_dates_for_range( $dates['range'] );
$currency = Reports\get_filter_value( 'currencies' );
// Generate date query SQL if dates have been set.
$date_query_sql = '';
if ( ! empty( $date_range['start'] ) || ! empty( $date_range['end'] ) ) {
if ( ! empty( $date_range['start'] ) ) {
$date_query_sql .= $wpdb->prepare( 'AND oi.date_created >= %s', $date_range['start']->format( 'mysql' ) );
}
// Join dates with `AND` if start and end date set.
if ( ! empty( $date_range['start'] ) && ! empty( $date_range['end'] ) ) {
$date_query_sql .= ' AND ';
}
if ( ! empty( $date_range['end'] ) ) {
$date_query_sql .= $wpdb->prepare( 'oi.date_created <= %s', $date_range['end']->format( 'mysql' ) );
}
}
$taxonomies = edd_get_download_taxonomies();
$taxonomies = array_map( 'sanitize_text_field', $taxonomies );
$placeholders = implode( ', ', array_fill( 0, count( $taxonomies ), '%s' ) );
$taxonomy__in = $wpdb->prepare( "tt.taxonomy IN ({$placeholders})", $taxonomies );
$sql = "SELECT t.*, tt.*, tr.object_id
FROM {$wpdb->terms} AS t
INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id
INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id
WHERE {$taxonomy__in}";
$results = $wpdb->get_results( $sql );
// Build intermediate array to allow for better data processing.
$taxonomies = array();
foreach ( $results as $r ) {
$taxonomies[ absint( $r->term_id ) ]['name'] = esc_html( $r->name );
$taxonomies[ absint( $r->term_id ) ]['object_ids'][] = absint( $r->object_id );
$taxonomies[ absint( $r->term_id ) ]['parent'] = absint( $r->parent );
}
$data = array();
$parent_ids = array();
$column = Reports\get_taxes_excluded_filter() ? 'oi.total - oi.tax' : 'oi.total';
$join = " INNER JOIN {$wpdb->edd_orders} o ON o.id = oi.order_id ";
$currency_clause = '';
if ( empty( $currency ) || 'convert' === $currency ) {
$column = sprintf( '(%s) / oi.rate', $column );
} elseif ( array_key_exists( strtoupper( $currency ), edd_get_currencies() ) ) {
$currency_clause = $wpdb->prepare(
" AND o.currency = %s ",
strtoupper( $currency )
);
}
$statuses = edd_get_net_order_statuses();
$status_string = implode( ', ', array_fill( 0, count( $statuses ), '%s' ) );
$status_sql = $wpdb->prepare(
" AND oi.status IN('complete','partially_refunded')
AND o.status IN({$status_string})",
...$statuses
);
foreach ( $taxonomies as $k => $t ) {
$c = new \stdClass();
$c->id = $k;
$c->name = $taxonomies[ $k ]['name'];
$placeholders = implode( ', ', array_fill( 0, count( $taxonomies[ $k ]['object_ids'] ), '%d' ) );
$product_id__in = $wpdb->prepare( "oi.product_id IN({$placeholders})", $taxonomies[ $k ]['object_ids'] );
$sql = "SELECT SUM({$column}) as total
FROM {$wpdb->edd_order_items} oi
{$join}
WHERE {$product_id__in} {$currency_clause} {$date_query_sql} {$status_sql}";
$result = $wpdb->get_row( $sql ); // WPCS: unprepared SQL ok.
$earnings = null === $result && null === $result->total
? 0.00
: floatval( $result->total );
$complete_orders = "SELECT SUM(oi.quantity) as sales
FROM {$wpdb->edd_order_items} oi
{$join}
WHERE {$product_id__in} {$currency_clause} {$date_query_sql} {$status_sql}";
$partial_orders = "SELECT SUM(oi.quantity) as sales
FROM {$wpdb->edd_order_items} oi
LEFT JOIN {$wpdb->edd_order_items} ri
ON ri.parent = oi.id
{$join}
WHERE {$product_id__in} {$currency_clause} {$date_query_sql}
AND oi.status ='partially_refunded'
AND oi.quantity = - ri.quantity";
$sql_sales = $wpdb->get_row( "SELECT SUM(sales) AS sales FROM ({$complete_orders} UNION {$partial_orders})a" );
$sales = ! empty( $sql_sales->sales ) ? $sql_sales->sales : 0;
$c->sales = $sales;
$c->earnings = $earnings;
$c->parent = 0 === $t['parent']
? null
: $t['parent'];
$average_sales = 0;
$average_earnings = 0.00;
foreach ( $taxonomies[ $k ]['object_ids'] as $download ) {
$average_sales += edd_get_average_monthly_download_sales( $download );
$average_earnings += edd_get_average_monthly_download_earnings( $download );
}
$c->average_sales = $average_sales;
$c->average_earnings = $average_earnings;
$data[] = $c;
}
$sorted_data = array();
foreach ( $data as $d ) {
// Get parent level elements
if ( null === $d->parent ) {
$sorted_data[] = $d;
$objects = array_values( wp_filter_object_list( $data, array( 'parent' => $d->id ) ) );
foreach ( $objects as $o ) {
$sorted_data[] = $o;
}
}
}
// Sort by total earnings
usort( $sorted_data, function( $a, $b ) {
return ( $a->earnings < $b->earnings ) ? -1 : 1;
} );
return $sorted_data;
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns
*/
public function get_columns() {
return array(
'name' => __( 'Name', 'easy-digital-downloads' ),
'sales' => __( 'Total Sales', 'easy-digital-downloads' ),
'earnings' => __( 'Total Earnings', 'easy-digital-downloads' ),
'average_sales' => __( 'Monthly Sales Average', 'easy-digital-downloads' ),
'average_earnings' => __( 'Monthly Earnings Average', 'easy-digital-downloads' )
);
}
/**
* Render the Name Column.
*
* @since 3.0
*
* @param \stdClass $taxonomy Taxonomy object.
* @return string Data shown in the Name column.
*/
public function column_name( $taxonomy ) {
return 0 < $taxonomy->parent
? '&#8212; ' . $taxonomy->name
: $taxonomy->name;
}
/**
* Render the Sales Column.
*
* @since 3.0
*
* @param \stdClass $taxonomy Taxonomy object.
* @return string Data shown in the Sales column.
*/
public function column_sales( $taxonomy ) {
return $taxonomy->sales;
}
/**
* Render the Earnings Column.
*
* @since 3.0
*
* @param \stdClass $taxonomy Taxonomy object.
* @return string Data shown in the Earnings column.
*/
public function column_earnings( $taxonomy ) {
return edd_currency_filter( edd_format_amount( $taxonomy->earnings ) );
}
/**
* Render the Average Sales Column.
*
* @since 3.0
*
* @param \stdClass $taxonomy Taxonomy object.
* @return int Data shown in the Average Sales column.
*/
public function column_average_sales( $taxonomy ) {
return (int) round( $taxonomy->average_sales );
}
/**
* Render the Average Earnings Column.
*
* @since 3.0
*
* @param \stdClass $taxonomy Taxonomy object.
* @return string Data shown in the Average Earnings column.
*/
public function column_average_earnings( $taxonomy ) {
return edd_currency_filter( edd_format_amount( $taxonomy->average_earnings ) );
}
/**
* Setup the final data for the table.
*
* @since 3.0
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->get_data();
}
/**
* Message to be displayed when there are no items
*
* @since 3.0
*/
public function no_items() {
esc_html_e( 'No taxonomies found.', 'easy-digital-downloads' );
}
/**
* Gets the name of the primary column.
*
* @since 3.0
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,213 @@
<?php
/**
* Top Selling Downloads list table.
*
* @package EDD
* @subpackage Reports/Data/File_Downloads
* @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\Downloads;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
use EDD\Stats as Stats;
use EDD\Reports as Reports;
use EDD\Admin\List_Table;
/**
* Top_Selling_Downloads_List_Table class.
*
* @since 3.0
*/
class Top_Selling_Downloads_List_Table extends List_Table {
/**
* Query the database and fetch the top five most downloaded products.
*
* @since 3.0
*
* @return array Downloads.
*/
public function get_data() {
$filter = Reports\get_filter_value( 'dates' );
$stats = new Stats();
return $stats->get_most_valuable_order_items( array(
'number' => 10,
'range' => $filter['range'],
'currency' => '',
) );
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns
*/
public function get_columns() {
return array(
'name' => __( 'Name', 'easy-digital-downloads' ),
'price' => __( 'Price', 'easy-digital-downloads' ),
'sales' => __( 'Sales', 'easy-digital-downloads' ),
'earnings' => __( 'Net Earnings', 'easy-digital-downloads' ),
);
}
/**
* Render the Name Column.
*
* @since 3.0
*
* @param \stdClass $download Download object.
* @return string Data shown in the Name column.
*/
public function column_name( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
// Check for variable pricing
$retval = ! is_null( $download->price_id ) && is_numeric( $download->price_id )
? edd_get_download_name( $download->object->ID, $download->price_id )
: edd_get_download_name( $download->object->ID );
return $retval;
}
/**
* Render the Price Column.
*
* @since 3.0
*
* @param \stdClass $download Download object.
* @return string Data shown in the Price column.
*/
public function column_price( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
// Check for variable pricing
$retval = ! is_null( $download->price_id ) && is_numeric( $download->price_id )
? edd_price( $download->object->ID, false, $download->price_id )
: edd_price( $download->object->ID, false );
return $retval;
}
public function column_sales( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
return current_user_can( 'view_product_stats', $download->object->ID )
? $download->sales
: '&mdash;';
}
public function column_earnings( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
return current_user_can( 'view_product_stats', $download->object->ID )
? edd_currency_filter( edd_format_amount( $download->total ) )
: '&mdash;';
}
/**
* Setup the final data for the table.
*
* @since 3.0
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->get_data();
}
/**
* Get the base URL for the discount list table
*
* @since 3.0
*
* @return string
*/
public function get_base_url() {
return remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() );
}
/**
* Message to be displayed when there are no items
*
* @since 3.0
*/
public function no_items() {
esc_html_e( 'No downloads found.', 'easy-digital-downloads' );
}
/**
* Gets the name of the primary column.
*
* @since 3.0
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,227 @@
<?php
/**
* Top Five Most Downloaded Products list table.
*
* @package EDD
* @subpackage Reports/Data/File_Downloads
* @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\File_Downloads;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
use EDD\Reports as Reports;
use EDD\Stats as Stats;
use EDD\Admin\List_Table;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
/**
* Top_Five_Most_Downloaded_List_Table class.
*
* @since 3.0
*/
class Top_Five_Most_Downloaded_List_Table extends List_Table {
/**
* Query the database and fetch the top five most downloaded products.
*
* @since 3.0
*
* @return array Downloads.
*/
public function get_data() {
$filter = Reports\get_filter_value( 'dates' );
$stats = new Stats();
return $stats->get_most_downloaded_products( array(
'number' => 5,
'range' => $filter['range']
) );
}
/**
* Retrieve the table columns.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns
*/
public function get_columns() {
return array(
'name' => __( 'Name', 'easy-digital-downloads' ),
'download_count' => __( 'File Downloads', 'easy-digital-downloads' ),
'price' => __( 'Price', 'easy-digital-downloads' ),
'sales' => __( 'Sales', 'easy-digital-downloads' ),
'earnings' => __( 'Earnings', 'easy-digital-downloads' )
);
}
/**
* Render the Name Column.
*
* @since 3.0
*
* @param \stdClass $download Download object.
* @return string Data shown in the Name column.
*/
public function column_name( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
// Download title
return $download->object->get_name();
}
/**
* Render the Download Count Column.
*
* @since 3.0
*
* @param \stdClass $download Download object.
* @return string Data shown in the Download Count column.
*/
public function column_download_count( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
return $download->total;
}
/**
* Render the Price Column.
*
* @since 3.0
*
* @param \stdClass $download Download object.
* @return string Data shown in the Price column.
*/
public function column_price( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
if ( $download->object->has_variable_prices() ) {
return edd_price_range( $download->object->ID );
} else {
return edd_price( $download->object->ID, false );
}
}
public function column_sales( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
return current_user_can( 'view_product_stats', $download->object->ID )
? edd_get_download_sales_stats( $download->object->ID )
: '&mdash;';
}
public function column_earnings( $download ) {
if ( ! $download->object instanceof \EDD_Download ) {
return '&mdash;';
}
return current_user_can( 'view_product_stats', $download->object->ID )
? edd_currency_filter( edd_format_amount( edd_get_download_earnings_stats( $download->object->ID ) ) )
: '&mdash;';
}
/**
* Setup the final data for the table.
*
* @since 3.0
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->get_data();
}
/**
* Get the base URL for the discount list table
*
* @since 3.0
*
* @return string
*/
public function get_base_url() {
return remove_query_arg( edd_admin_removable_query_args(), edd_get_admin_base_url() );
}
/**
* Message to be displayed when there are no items
*
* @since 3.0
*/
public function no_items() {
esc_html_e( 'No downloads found.', 'easy-digital-downloads' );
}
/**
* Gets the name of the primary column.
*
* @since 3.0
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,205 @@
<?php
/**
* Gateway Stats list table.
*
* @package EDD
* @subpackage Reports/Data/Customers
* @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\Payment_Gateways;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
use EDD\Stats as Stats;
use EDD\Reports as Reports;
use EDD\Admin\List_Table;
/**
* Top_Five_Customers_List_Table class.
*
* @since 3.0
*/
class Gateway_Stats extends List_Table {
/**
* Get things started
*
* @since 1.5
* @see WP_List_Table::__construct()
*/
public function __construct() {
parent::__construct( array(
'singular' => 'report-gateway',
'plural' => 'report-gateways',
'ajax' => false,
) );
}
/**
* Gets the name of the primary column.
*
* @since 2.5
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'label';
}
/**
* Render each column.
*
* @since 1.5
*
* @param array $item Contains all the data of the downloads
* @param string $column_name The name of the column
*
* @return string Column Name
*/
public function column_default( $item, $column_name ) {
return $item[ $column_name ];
}
/**
* Column names.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns
*/
public function get_columns() {
return array(
'label' => __( 'Gateway', 'easy-digital-downloads' ),
'complete_sales' => __( 'Complete Sales', 'easy-digital-downloads' ),
'pending_sales' => __( 'Pending / Failed Sales', 'easy-digital-downloads' ),
'refunded_sales' => __( 'Refunds Issued', 'easy-digital-downloads' ),
'total_sales' => __( 'Total Sales', 'easy-digital-downloads' ),
);
}
/**
* Build all the reports data
*
* @since 1.5
* @return array All the data for customer reports
*/
public function get_data() {
$filter = Reports\get_filter_value( 'dates' );
$currency = Reports\get_filter_value( 'currencies' );
$reports_data = array();
$gateways = edd_get_payment_gateways();
foreach ( $gateways as $gateway_id => $gateway ) {
$stats = new Stats();
$complete_count = $stats->get_gateway_sales( array(
'range' => $filter['range'],
'gateway' => $gateway_id,
'status' => edd_get_gross_order_statuses(),
'type' => array( 'sale' ),
'currency' => $currency,
) );
$pending_count = $stats->get_gateway_sales( array(
'range' => $filter['range'],
'gateway' => $gateway_id,
'status' => edd_get_incomplete_order_statuses(),
'type' => array( 'sale' ),
'currency' => $currency,
) );
$refunded_count = $stats->get_gateway_sales( array(
'range' => $filter['range'],
'gateway' => $gateway_id,
'status' => array( 'complete' ),
'type' => array( 'refund' ),
'currency' => $currency,
) );
$total_count = $stats->get_gateway_sales( array(
'range' => $filter['range'],
'gateway' => $gateway_id,
'status' => 'any',
'type' => array( 'sale' ),
'currency' => $currency,
) );
$reports_data[] = array(
'ID' => $gateway_id,
'label' => '<a href="' . esc_url( edd_get_admin_url( array( 'page' => 'edd-payment-history', 'gateway' => sanitize_key( $gateway_id ) ) ) ) . '">' . esc_html( $gateway['admin_label'] ) . '</a>',
'complete_sales' => edd_format_amount( $complete_count, false ),
'pending_sales' => edd_format_amount( $pending_count, false ),
'refunded_sales' => edd_format_amount( $refunded_count, false ),
'total_sales' => edd_format_amount( $total_count, false ),
);
}
return $reports_data;
}
/**
* Setup the final data for the table
*
* @since 1.5
* @uses EDD_Gateway_Reports_Table::get_columns()
* @uses EDD_Gateway_Reports_Table::get_sortable_columns()
* @uses EDD_Gateway_Reports_Table::reports_data()
* @return void
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array(); // No hidden columns
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->get_data();
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}

View File

@ -0,0 +1,254 @@
<?php
/**
* Tax Collected by Location list table.
*
* @package EDD
* @subpackage Reports/Data/Customers
* @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\Taxes;
// Exit if accessed directly
defined( 'ABSPATH' ) || exit;
use EDD\Admin\List_Table;
use EDD\Reports;
/**
* Tax_Collected_by_Location class.
*
* @since 3.0
*/
class Tax_Collected_By_Location extends List_Table {
/**
* Get things started
*
* @since 1.5
* @see WP_List_Table::__construct()
*/
public function __construct() {
parent::__construct( array(
'singular' => 'report-tax-collected-by-location',
'plural' => 'report-tax-collected-by-locations',
'ajax' => false,
) );
}
/**
* Gets the name of the primary column.
*
* @since 2.5
* @access protected
*
* @return string Name of the primary column.
*/
protected function get_primary_column_name() {
return 'label';
}
/**
* Render each column.
*
* @since 1.5
*
* @param array $item Contains all the data of the downloads
* @param string $column_name The name of the column
*
* @return string Column Name
*/
public function column_default( $item, $column_name ) {
return $item[ $column_name ];
}
/**
* Column names.
*
* @since 3.0
*
* @return array $columns Array of all the list table columns
*/
public function get_columns() {
return array(
'country' => __( 'Country/Region', 'easy-digital-downloads' ),
'from' => __( 'From', 'easy-digital-downloads' ),
'to' => __( 'To', 'easy-digital-downloads' ),
'gross' => __( 'Gross', 'easy-digital-downloads' ),
'tax' => __( 'Tax', 'easy-digital-downloads' ),
'net' => __( 'Net', 'easy-digital-downloads' ),
);
}
/**
* Query data for the list table.
*
* @since 3.0
*
* @return array $data All the data for the list table.
*/
public function get_data() {
global $wpdb;
$data = array();
$tax_rates = edd_get_tax_rates( array(), OBJECT );
$date_filter = Reports\get_filter_value( 'dates' );
$date_range = Reports\parse_dates_for_range( $date_filter['range'] );
$currency = Reports\get_filter_value( 'currencies' );
$convert_currency = empty( $currency ) || 'convert' === $currency;
$format_currency = $convert_currency ? edd_get_currency() : strtoupper( $currency );
// Date query.
$date_query = '';
if ( ! empty( $date_range['start'] ) && '0000-00-00 00:00:00' !== $date_range['start'] ) {
$date_query .= $wpdb->prepare( " AND {$wpdb->edd_orders}.date_created >= %s", esc_sql( EDD()->utils->date( $date_range['start'], null, false )->startOfDay()->format( 'mysql' ) ) );
}
if ( ! empty( $date_range['end'] ) && '0000-00-00 00:00:00' !== $date_range['end'] ) {
$date_query .= $wpdb->prepare( " AND {$wpdb->edd_orders}.date_created <= %s", esc_sql( EDD()->utils->date( $date_range['end'], null, false )->endOfDay()->format( 'mysql' ) ) );
}
$from = empty( $date_range['start'] ) || '0000-00-00 00:00:00' === $date_range['start']
? '&mdash;'
: edd_date_i18n( EDD()->utils->date( $date_range['start'], null, false )->startOfDay()->timestamp );
$to = empty( $date_range['end'] ) || '0000-00-00 00:00:00' === $date_range['end']
? '&mdash;'
: edd_date_i18n( EDD()->utils->date( $date_range['end'], null, false )->endOfDay()->timestamp );
$tax_column = $convert_currency ? 'tax / rate' : 'tax';
$total_column = $convert_currency ? 'total / rate' : 'total';
$currency_sql = '';
if ( ! $convert_currency && array_key_exists( strtoupper( $currency ), edd_get_currencies() ) ) {
$currency_sql = $wpdb->prepare( " AND currency = %s ", strtoupper( $currency ) );
}
/*
* We need to first calculate the total tax collected for all orders so we can determine the amount of tax collected for the global rate
*
* The total determined here will be reduced by the amount collected for each specified tax rate/region.
*/
$all_orders = $wpdb->get_results( "
SELECT SUM({$tax_column}) as tax, SUM({$total_column}) as total
FROM {$wpdb->edd_orders}
WHERE 1=1 {$currency_sql} {$date_query}
", ARRAY_A );
foreach ( $tax_rates as $tax_rate ) {
$country_region = $tax_rate->name . '-' . $tax_rate->description;
if ( array_key_exists( $country_region, $data ) ) {
continue; // We've already pulled numbers for this country / region
}
$location = edd_get_country_name( $tax_rate->name );
if ( ! empty( $tax_rate->description ) ) {
$location .= ' &mdash; ' . edd_get_state_name( $tax_rate->name, $tax_rate->description );
}
$region = ! empty( $tax_rate->description )
? $wpdb->prepare( ' AND region = %s', esc_sql( $tax_rate->description ) )
: '';
$results = $wpdb->get_results( $wpdb->prepare( "
SELECT SUM($tax_column) as tax, SUM($total_column) as total, country, region
FROM {$wpdb->edd_orders}
INNER JOIN {$wpdb->edd_order_addresses} ON {$wpdb->edd_order_addresses}.order_id = {$wpdb->edd_orders}.id
WHERE {$wpdb->edd_order_addresses}.country = %s {$region} {$date_query} {$currency_sql}
", esc_sql( $tax_rate->name ) ), ARRAY_A );
$all_orders[0]['tax'] -= $results[0]['tax'];
$all_orders[0]['total'] -= $results[0]['total'];
$data[ $country_region ] = array(
'country' => $location,
'from' => $from,
'to' => $to,
'gross' => edd_currency_filter( edd_format_amount( floatval( $results[0]['total'] ) ), $format_currency ),
'tax' => edd_currency_filter( edd_format_amount( floatval( $results[0]['tax'] ) ), $format_currency ),
'net' => edd_currency_filter( edd_format_amount( floatval( $results[0]['total'] - $results[0]['tax'] ) ), $format_currency ),
);
}
if( $all_orders[0]['total'] > 0 && $all_orders[0]['tax'] > 0 ) {
$data[ 'global' ] = array(
'country' => __( 'Global Rate', 'easy-digital-downloads' ),
'from' => $from,
'to' => $to,
'gross' => edd_currency_filter( edd_format_amount( floatval( max( 0, $all_orders[0]['total'] ) ) ), $format_currency ),
'tax' => edd_currency_filter( edd_format_amount( floatval( max( 0, $all_orders[0]['tax'] ) ) ), $format_currency ),
'net' => edd_currency_filter( edd_format_amount( floatval( max( 0, $all_orders[0]['total'] - $all_orders[0]['tax'] ) ) ), $format_currency ),
);
}
return $data;
}
/**
* Setup the final data for the table
*
* @since 1.5
* @uses EDD_Gateway_Reports_Table::get_columns()
* @uses EDD_Gateway_Reports_Table::get_sortable_columns()
* @uses EDD_Gateway_Reports_Table::reports_data()
* @return void
*/
public function prepare_items() {
$columns = $this->get_columns();
$hidden = array(); // No hidden columns
$sortable = $this->get_sortable_columns();
$this->_column_headers = array( $columns, $hidden, $sortable );
$this->items = $this->get_data();
}
/**
* Return empty array to disable sorting.
*
* @since 3.0
*
* @return array
*/
public function get_sortable_columns() {
return array();
}
/**
* Return empty array to remove bulk actions.
*
* @since 3.0
*
* @return array
*/
public function get_bulk_actions() {
return array();
}
/**
* Hide pagination.
*
* @since 3.0
*
* @param string $which
*/
protected function pagination( $which ) {
}
/**
* Hide table navigation.
*
* @since 3.0
*
* @param string $which
*/
protected function display_tablenav( $which ) {
}
}