315 lines
8.3 KiB
PHP
315 lines
8.3 KiB
PHP
|
<?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 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|