448 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?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(),
 | |
| 		) );
 | |
| 	}
 | |
| }
 |