Skip to content
Snippets Groups Projects
Verified Commit 806158d3 authored by Christian Wach's avatar Christian Wach :soccer: Committed by Kevin Cristiano
Browse files

First pass at adding REST API compatibility

parent da891c3d
No related branches found
No related tags found
No related merge requests found
Showing with 2289 additions and 0 deletions
......@@ -121,6 +121,17 @@ if ( file_exists( CIVICRM_SETTINGS_PATH ) ) {
// Prevent CiviCRM from rendering its own header
define( 'CIVICRM_UF_HEAD', TRUE );
/**
* Setting this to 'true' will replace all mailing URLs calls to 'extern/url.php'
* and 'extern/open.php' with their REST counterpart 'civicrm/v3/url' and 'civicrm/v3/open'.
*
* Use for test purposes, may affect mailing
* performance, see Plugin->replace_tracking_urls() method.
*/
if ( ! defined( 'CIVICRM_WP_REST_REPLACE_MAILING_TRACKING' ) ) {
define( 'CIVICRM_WP_REST_REPLACE_MAILING_TRACKING', false );
}
/**
* Define CiviCRM_For_WordPress Class.
......@@ -512,6 +523,9 @@ class CiviCRM_For_WordPress {
include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.basepage.php';
$this->basepage = new CiviCRM_For_WordPress_Basepage;
// Include REST API autoloader class
require_once( CIVICRM_PLUGIN_DIR . 'wp-rest/Autoloader.php' );
}
......@@ -636,6 +650,12 @@ class CiviCRM_For_WordPress {
// Register hooks for clean URLs.
$this->register_hooks_clean_urls();
// Set up REST API.
CiviCRM_WP_REST\Autoloader::add_source( $source_path = trailingslashit( CIVICRM_PLUGIN_DIR . 'wp-rest' ) );
// Init REST API.
new CiviCRM_WP_REST\Plugin;
}
......
<?php
/**
* Autoloader class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST;
class Autoloader {
/**
* Instance.
*
* @since 0.1
* @var string
*/
private static $instance = null;
/**
* Namespace.
*
* @since 0.1
* @var string
*/
private $namespace = 'CiviCRM_WP_REST';
/**
* Autoloader directory sources.
*
* @since 0.1
* @var array
*/
private static $source_directories = [];
/**
* Constructor.
*
* @since 0.1
*/
private function __construct() {
$this->register_autoloader();
}
/**
* Creates an instance of this class.
*
* @since 0.1
*/
private static function instance() {
if ( ! self::$instance ) self::$instance = new self;
}
/**
* Adds a directory source.
*
* @since 0.1
* @param string $source The source path
*/
public static function add_source( string $source_path ) {
// make sure we have an instance
self::instance();
if ( ! is_readable( trailingslashit( $source_path ) ) )
return \WP_Error( 'civicrm_wp_rest_error', __( 'The source ' . $source . ' is not readable.' ) );
self::$source_directories[] = $source_path;
}
/**
* Registers the autoloader.
*
* @since 0.1
* @return bool Wehather the autoloader has been registered or not
*/
private function register_autoloader() {
return spl_autoload_register( [ $this, 'autoload' ] );
}
/**
* Loads the classes.
*
* @since 0.1
* @param string $class_name The class name to load
*/
private function autoload( $class_name ) {
if ( false === strpos( $class_name, $this->namespace ) ) return;
$parts = explode( '\\', $class_name );
// remove namespace and join class path
$class_path = str_replace( '_', '-', implode( DIRECTORY_SEPARATOR, array_slice( $parts, 1 ) ) );
array_map( function( $source_path ) use ( $class_path ) {
$path = $source_path . $class_path . '.php';
if ( ! file_exists( $path ) ) return;
require $path;
}, static::$source_directories );
}
}
<?php
/**
* CiviCRM Mailing_Hooks class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Civi;
class Mailing_Hooks {
/**
* Mailing Url endpoint.
*
* @since 0.1
* @var string
*/
public $url_endpoint;
/**
* Mailing Open endpoint.
*
* @since 0.1
* @var string
*/
public $open_endpoint;
/**
* Constructor.
*
* @since 0.1
*/
public function __construct() {
$this->url_endpoint = rest_url( 'civicrm/v3/url' );
$this->open_endpoint = rest_url( 'civicrm/v3/open' );
}
/**
* Register hooks.
*
* @since 0.1
*/
public function register_hooks() {
add_filter( 'civicrm_alterMailParams', [ $this, 'do_mailing_urls' ], 10, 2 );
}
/**
* Filters the mailing html and replaces calls to 'extern/url.php' and
* 'extern/open.php' with their REST counterparts 'civicrm/v3/url' and 'civicrm/v3/open'.
*
* @uses 'civicrm_alterMailParams'
*
* @since 0.1
* @param array &$params Mail params
* @param string $context The Context
* @return array $params The filtered Mail params
*/
public function do_mailing_urls( &$params, $context ) {
if ( $context == 'civimail' ) {
$params['html'] = $this->replace_html_mailing_tracking_urls( $params['html'] );
$params['text'] = $this->replace_text_mailing_tracking_urls( $params['text'] );
}
return $params;
}
/**
* Replace html mailing tracking urls.
*
* @since 0.1
* @param string $contnet The mailing content
* @return string $content The mailing content
*/
public function replace_html_mailing_tracking_urls( string $content ) {
$doc = \phpQuery::newDocument( $content );
foreach ( $doc[ '[href*="civicrm/extern/url.php"], [src*="civicrm/extern/open.php"]' ] as $element ) {
$href = pq( $element )->attr( 'href' );
$src = pq( $element )->attr( 'src' );
// replace extern/url
if ( strpos( $href, 'civicrm/extern/url.php' ) ) {
$query_string = strstr( $href, '?' );
pq( $element )->attr( 'href', $this->url_endpoint . $query_string );
}
// replace extern/open
if ( strpos( $src, 'civicrm/extern/open.php' ) ) {
$query_string = strstr( $src, '?' );
pq( $element )->attr( 'src', $this->open_endpoint . $query_string );
}
unset( $href, $src, $query_string );
}
return $doc->html();
}
/**
* Replace text mailing tracking urls.
*
* @since 0.1
* @param string $contnet The mailing content
* @return string $content The mailing content
*/
public function replace_text_mailing_tracking_urls( string $content ) {
// replace extern url
$content = preg_replace( '/http.*civicrm\/extern\/url\.php/i', $this->url_endpoint, $content );
// replace open url
$content = preg_replace( '/http.*civicrm\/extern\/open\.php/i', $this->open_endpoint, $content );
return $content;
}
}
<?php
/**
* AuthorizeIPN controller class.
*
* Replacement for CiviCRM's 'extern/authorizeIPN.php'.
*
* @see https://docs.civicrm.org/sysadmin/en/latest/setup/payment-processors/authorize-net/#shell-script-testing-method
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class AuthorizeIPN extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'authorizeIPN';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::ALLMETHODS,
'callback' => [ $this, 'get_item' ]
]
] );
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter request params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters( 'civi_wp_rest/controller/authorizeIPN/params', $request->get_params(), $request );
$authorize_IPN = new \CRM_Core_Payment_AuthorizeNetIPN( $params );
// log notification
\Civi::log()->alert( 'payment_notification processor_name=AuthNet', $params );
/**
* Filter AuthorizeIPN object.
*
* @param CRM_Core_Payment_AuthorizeNetIPN $authorize_IPN
* @param array $params
* @param WP_REST_Request $request
*/
$authorize_IPN = apply_filters( 'civi_wp_rest/controller/authorizeIPN/instance', $authorize_IPN, $params, $request );
try {
if ( ! method_exists( $authorize_IPN, 'main' ) || ! $this->instance_of_crm_base_ipn( $authorize_IPN ) )
return $this->civi_rest_error( get_class( $authorize_IPN ) . ' must implement a "main" method.' );
$result = $authorize_IPN->main();
} catch ( \CRM_Core_Exception $e ) {
\Civi::log()->error( $e->getMessage() );
\Civi::log()->error( 'error data ', [ 'data' => $e->getErrorData() ] );
\Civi::log()->error( 'REQUEST ', [ 'params' => $params ] );
return $this->civi_rest_error( $e->getMessage() );
}
return rest_ensure_response( $result );
}
/**
* Checks whether object is an instance of CRM_Core_Payment_AuthorizeNetIPN or CRM_Core_Payment_BaseIPN.
*
* Needed because the instance is being filtered through 'civi_wp_rest/controller/authorizeIPN/instance'.
*
* @since 0.1
* @param CRM_Core_Payment_AuthorizeNetIPN|CRM_Core_Payment_BaseIPN $object
* @return bool
*/
public function instance_of_crm_base_ipn( $object ) {
return $object instanceof \CRM_Core_Payment_BaseIPN || $object instanceof \CRM_Core_Payment_AuthorizeNetIPN;
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {}
}
<?php
/**
* Base controller class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
use CiviCRM_WP_REST\Endpoint\Endpoint_Interface;
abstract class Base extends \WP_REST_Controller implements Endpoint_Interface {
/**
* Route namespace.
*
* @since 0.1
* @var string
*/
protected $namespace = 'civicrm/v3';
/**
* Gets the endpoint namespace.
*
* @since 0.1
* @return string $namespace
*/
public function get_namespace() {
return $this->namespace;
}
/**
* Gets the rest base route.
*
* @since 0.1
* @return string $rest_base
*/
public function get_rest_base() {
return '/' . $this->rest_base;
}
/**
* Retrieves the endpoint ie. '/civicrm/v3/rest'.
*
* @since 0.1
* @return string $rest_base
*/
public function get_endpoint() {
return '/' . $this->get_namespace() . $this->get_rest_base();
}
/**
* Checks whether the requested route is equal to this endpoint.
*
* @since 0.1
* @param WP_REST_Request $request
* @return bool $is_current_endpoint True if it's equal, false otherwise
*/
public function is_current_endpoint( $request ) {
return $this->get_endpoint() == $request->get_route();
}
/**
* Authorization status code.
*
* @since 0.1
* @return int $status
*/
protected function authorization_status_code() {
$status = 401;
if ( is_user_logged_in() ) $status = 403;
return $status;
}
/**
* Wrapper for WP_Error.
*
* @since 0.1
* @param string $message
* @param mixed $data Error data
* @return WP_Error $error
*/
protected function civi_rest_error( $message, $data = [] ) {
return new \WP_Error( 'civicrm_rest_api_error', $message, empty( $data ) ? [ 'status' => $this->authorization_status_code() ] : $data );
}
}
<?php
/**
* Cxn controller class.
*
* CiviConnect endpoint, replacement for CiviCRM's 'extern/cxn.php'.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class Cxn extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'cxn';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::ALLMETHODS,
'callback' => [ $this, 'get_item' ]
]
] );
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter request params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters( 'civi_wp_rest/controller/cxn/params', $request->get_params(), $request );
// init connection server
$cxn = \CRM_Cxn_BAO_Cxn::createApiServer();
/**
* Filter connection server object.
*
* @param Civi\Cxn\Rpc\ApiServer $cxn
* @param array $params
* @param WP_REST_Request $request
*/
$cxn = apply_filters( 'civi_wp_rest/controller/cxn/instance', $cxn, $params, $request );
try {
$result = $cxn->handle( $request->get_body() );
} catch ( Civi\Cxn\Rpc\Exception\CxnException $e ) {
return $this->civi_rest_error( $e->getMessage() );
} catch ( Civi\Cxn\Rpc\Exception\ExpiredCertException $e ) {
return $this->civi_rest_error( $e->getMessage() );
} catch ( Civi\Cxn\Rpc\Exception\InvalidCertException $e ) {
return $this->civi_rest_error( $e->getMessage() );
} catch ( Civi\Cxn\Rpc\Exception\InvalidMessageException $e ) {
return $this->civi_rest_error( $e->getMessage() );
} catch ( Civi\Cxn\Rpc\Exception\GarbledMessageException $e ) {
return $this->civi_rest_error( $e->getMessage() );
}
/**
* Bypass WP and send request from Cxn.
*/
add_filter( 'rest_pre_serve_request', function( $served, $response, $request, $server ) use ( $result ) {
// Civi\Cxn\Rpc\Message->send()
$result->send();
return true;
}, 10, 4 );
return rest_ensure_response( $result );
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {}
}
<?php
/**
* Open controller class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class Open extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'open';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'args' => $this->get_item_args()
],
'schema' => [ $this, 'get_item_schema' ]
] );
}
/**
* Get item.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
$queue_id = $request->get_param( 'q' );
// track open
\CRM_Mailing_Event_BAO_Opened::open( $queue_id );
// serve tracker file
add_filter( 'rest_pre_serve_request', [ $this, 'serve_tracker_file' ], 10, 4 );
}
/**
* Serves the tracker gif file.
*
* @since 0.1
* @param bool $served Whether the request has been served
* @param WP_REST_Response $result
* @param WP_REST_Request $request
* @param WP_REST_Server $server
* @return bool $served Whether the request has been served
*/
public function serve_tracker_file( $served, $result, $request, $server ) {
// tracker file path
$file = CIVICRM_PLUGIN_DIR . 'civicrm/i/tracker.gif';
// set headers
$server->send_header( 'Content-type', 'image/gif' );
$server->send_header( 'Cache-Control', 'must-revalidate, post-check=0, pre-check=0' );
$server->send_header( 'Content-Description', 'File Transfer' );
$server->send_header( 'Content-Disposition', 'inline; filename=tracker.gif' );
$server->send_header( 'Content-Length', filesize( $file ) );
$buffer = readfile( $file );
return true;
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'civicrm/v3/open',
'description' => 'CiviCRM Open endpoint',
'type' => 'object',
'required' => [ 'q' ],
'properties' => [
'q' => [
'type' => 'integer'
]
]
];
}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {
return [
'q' => [
'type' => 'integer',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
]
];
}
}
<?php
/**
* PayPalIPN controller class.
*
* PayPal IPN endpoint, replacement for CiviCRM's 'extern/ipn.php'.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class PayPalIPN extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'ipn';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::ALLMETHODS,
'callback' => [ $this, 'get_item' ]
]
] );
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter request params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters( 'civi_wp_rest/controller/ipn/params', $request->get_params(), $request );
if ( $request->get_method() == 'GET' ) {
// paypal standard
$paypal_IPN = new \CRM_Core_Payment_PayPalIPN( $params );
// log notification
\Civi::log()->alert( 'payment_notification processor_name=PayPal_Standard', $params );
} else {
// paypal pro
$paypal_IPN = new \CRM_Core_Payment_PayPalProIPN( $params );
// log notification
\Civi::log()->alert( 'payment_notification processor_name=PayPal', $params );
}
/**
* Filter PayPalIPN object.
*
* @param CRM_Core_Payment_PayPalIPN|CRM_Core_Payment_PayPalProIPN $paypal_IPN
* @param array $params
* @param WP_REST_Request $request
*/
$paypal_IPN = apply_filters( 'civi_wp_rest/controller/ipn/instance', $paypal_IPN, $params, $request );
try {
if ( ! method_exists( $paypal_IPN, 'main' ) || ! $this->instance_of_crm_base_ipn( $paypal_IPN ) )
return $this->civi_rest_error( get_class( $paypal_IPN ) . ' must implement a "main" method.' );
$result = $paypal_IPN->main();
} catch ( \CRM_Core_Exception $e ) {
\Civi::log()->error( $e->getMessage() );
\Civi::log()->error( 'error data ', [ 'data' => $e->getErrorData() ] );
\Civi::log()->error( 'REQUEST ', [ 'params' => $params ] );
return $this->civi_rest_error( $e->getMessage() );
}
return rest_ensure_response( $result );
}
/**
* Checks whether object is an instance of CRM_Core_Payment_BaseIPN|CRM_Core_Payment_PayPalProIPN|CRM_Core_Payment_PayPalIPN.
*
* Needed because the instance is being filtered through 'civi_wp_rest/controller/ipn/instance'.
*
* @since 0.1
* @param CRM_Core_Payment_BaseIPN|CRM_Core_Payment_PayPalProIPN|CRM_Core_Payment_PayPalIPN $object
* @return bool
*/
public function instance_of_crm_base_ipn( $object ) {
return $object instanceof \CRM_Core_Payment_BaseIPN || $object instanceof \CRM_Core_Payment_PayPalProIPN || $object instanceof \CRM_Core_Payment_PayPalIPN;
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {}
}
<?php
/**
* PxIPN controller class.
*
* PxPay IPN endpoint, replacement for CiviCRM's 'extern/pxIPN.php'.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class PxIPN extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'pxIPN';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::ALLMETHODS,
'callback' => [ $this, 'get_item' ]
]
] );
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter payment processor params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters(
'civi_wp_rest/controller/pxIPN/params',
$this->get_payment_processor_args( $request ),
$request
);
// log notification
\Civi::log()->alert( 'payment_notification processor_name=Payment_Express', $params );
try {
$result = \CRM_Core_Payment_PaymentExpressIPN::main( ...$params );
} catch ( \CRM_Core_Exception $e ) {
\Civi::log()->error( $e->getMessage() );
\Civi::log()->error( 'error data ', [ 'data' => $e->getErrorData() ] );
\Civi::log()->error( 'REQUEST ', [ 'params' => $params ] );
return $this->civi_rest_error( $e->getMessage() );
}
return rest_ensure_response( $result );
}
/**
* Get payment processor necessary params.
*
* @since 0.1
* @param WP_REST_Resquest $request
* @return array $args
*/
public function get_payment_processor_args( $request ) {
// get payment processor types
$payment_processor_types = civicrm_api3( 'PaymentProcessor', 'getoptions', [
'field' => 'payment_processor_type_id'
] );
// payment processor params
$params = apply_filters( 'civi_wp_rest/controller/pxIPN/payment_processor_params', [
'user_name' => $request->get_param( 'userid' ),
'payment_processor_type_id' => array_search(
'DPS Payment Express',
$payment_processor_types['values']
),
'is_active' => 1,
'is_test' => 0
] );
// get payment processor
$payment_processor = civicrm_api3( 'PaymentProcessor', 'get', $params );
$args = $payment_processor['values'][$payment_processor['id']];
$method = empty( $args['signature'] ) ? 'pxpay' : 'pxaccess';
return [
$method,
$request->get_param( 'result' ),
$args['url_site'],
$args['user_name'],
$args['password'],
$args['signature']
];
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {}
}
<?php
/**
* Rest controller class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class Rest extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'rest';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::ALLMETHODS,
'callback' => [ $this, 'get_items' ],
'permission_callback' => [ $this, 'permissions_check' ],
'args' => $this->get_item_args()
],
'schema' => [ $this, 'get_item_schema' ]
] );
}
/**
* Check get permission.
*
* @since 0.1
* @param WP_REST_Request $request
* @return bool
*/
public function permissions_check( $request ) {
if ( ! $this->is_valid_api_key( $request ) )
return $this->civi_rest_error( __( 'Param api_key is not valid.' ) );
if ( ! $this->is_valid_site_key() )
return $this->civi_rest_error( __( 'Param key is not valid.' ) );
return true;
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_items( $request ) {
/**
* Filter formatted api params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters( 'civi_wp_rest/controller/rest/api_params', $this->get_formatted_api_params( $request ), $request );
try {
$items = civicrm_api3( ...$params );
} catch ( \CiviCRM_API3_Exception $e ) {
return $this->civi_rest_error( $e->getMessage() );
}
if ( ! isset( $items ) || empty( $items ) )
return rest_ensure_response( [] );
/**
* Filter civi api result.
*
* @since 0.1
* @param array $items
* @param WP_REST_Request $request
*/
$data = apply_filters( 'civi_wp_rest/controller/rest/api_result', $items, $params, $request );
// only collections of items, ie any action but 'getsingle'
if ( isset( $data['values'] ) ) {
$data['values'] = array_reduce( $items['values'] ?? $items, function( $items, $item ) use ( $request ) {
$response = $this->prepare_item_for_response( $item, $request );
$items[] = $this->prepare_response_for_collection( $response );
return $items;
}, [] );
}
$response = rest_ensure_response( $data );
// check wheather we need to serve xml or json
if ( ! in_array( 'json', array_keys( $request->get_params() ) ) ) {
/**
* Adds our response holding Civi data before dispatching.
*
* @since 0.1
* @param WP_HTTP_Response $result Result to send to client
* @param WP_REST_Server $server The REST server
* @param WP_REST_Request $request The request
* @return WP_HTTP_Response $result Result to send to client
*/
add_filter( 'rest_post_dispatch', function( $result, $server, $request ) use ( $response ) {
return $response;
}, 10, 3 );
// serve xml
add_filter( 'rest_pre_serve_request', [ $this, 'serve_xml_response' ], 10, 4 );
} else {
// return json
return $response;
}
}
/**
* Get formatted api params.
*
* @since 0.1
* @param WP_REST_Resquest $request
* @return array $params
*/
public function get_formatted_api_params( $request ) {
$args = $request->get_params();
// destructure entity and action
[ 'entity' => $entity, 'action' => $action ] = $args;
// unset unnecessary args
unset( $args['entity'], $args['action'], $args['key'], $args['api_key'] );
if ( ! isset( $args['json'] ) || is_numeric( $args['json'] ) ) {
$params = $args;
} else {
$params = is_string( $args['json'] ) ? json_decode( $args['json'], true ) : [];
}
// ensure check permissions is enabled
$params['check_permissions'] = true;
return [ $entity, $action, $params ];
}
/**
* Matches the item data to the schema.
*
* @since 0.1
* @param object $item
* @param WP_REST_Request $request
*/
public function prepare_item_for_response( $item, $request ) {
return rest_ensure_response( $item );
}
/**
* Serves XML response.
*
* @since 0.1
* @param bool $served Whether the request has already been served
* @param WP_REST_Response $result
* @param WP_REST_Request $request
* @param WP_REST_Server $server
*/
public function serve_xml_response( $served, $result, $request, $server ) {
// get xml from response
$xml = $this->get_xml_formatted_data( $result->get_data() );
// set content type header
$server->send_header( 'Content-Type', 'text/xml' );
echo $xml;
return true;
}
/**
* Formats CiviCRM API result to XML.
*
* @since 0.1
* @param array $data The CiviCRM api result
* @return string $xml The formatted xml
*/
protected function get_xml_formatted_data( array $data ) {
// xml document
$xml = new \DOMDocument();
// result set element <ResultSet>
$result_set = $xml->createElement( 'ResultSet' );
// xmlns:xsi attribute
$result_set->setAttribute( 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance' );
// count attribute
if ( isset( $data['count'] ) ) $result_set->setAttribute( 'count', $data['count'] );
// build result from result => values
if ( isset( $data['values'] ) ) {
array_map( function( $item ) use ( $result_set, $xml ) {
// result element <Result>
$result = $xml->createElement( 'Result' );
// format item
$result = $this->get_xml_formatted_item( $item, $result, $xml );
// append result to result set
$result_set->appendChild( $result );
}, $data['values'] );
} else {
// result element <Result>
$result = $xml->createElement( 'Result' );
// format item
$result = $this->get_xml_formatted_item( $data, $result, $xml );
// append result to result set
$result_set->appendChild( $result );
}
// append result set
$xml->appendChild( $result_set );
return $xml->saveXML();
}
/**
* Formats a single api result to xml.
*
* @since 0.1
* @param array $item The single api result
* @param DOMElement $parent The parent element to append to
* @param DOMDocument $doc The document
* @return DOMElement $parent The parent element
*/
public function get_xml_formatted_item( array $item, \DOMElement $parent, \DOMDocument $doc ) {
// build field => values
array_map( function( $field, $value ) use ( $parent, $doc ) {
// entity field element
$element = $doc->createElement( $field );
// handle array values
if ( is_array( $value ) ) {
array_map( function( $key, $val ) use ( $element, $doc ) {
// child element, append underscore '_' otherwise createElement
// will throw an Invalid character exception as elements cannot start with a number
$child = $doc->createElement( '_' . $key, $val );
// append child
$element->appendChild( $child );
}, array_keys( $value ), $value );
} else {
// assign value
$element->nodeValue = $value;
}
// append element
$parent->appendChild( $element );
}, array_keys( $item ), $item );
return $parent;
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'civicrm/v3/rest',
'description' => 'CiviCRM API3 WP rest endpoint wrapper',
'type' => 'object',
'required' => [ 'entity', 'action', 'params' ],
'properties' => [
'is_error' => [
'type' => 'integer'
],
'version' => [
'type' => 'integer'
],
'count' => [
'type' => 'integer'
],
'values' => [
'type' => 'array'
]
]
];
}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {
return [
'key' => [
'type' => 'string',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return $this->is_valid_site_key();
}
],
'api_key' => [
'type' => 'string',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return $this->is_valid_api_key( $request );
}
],
'entity' => [
'type' => 'string',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return is_string( $value );
}
],
'action' => [
'type' => 'string',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return is_string( $value );
}
],
'json' => [
'type' => ['integer', 'string', 'array'],
'required' => false,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value ) || is_array( $value ) || $this->is_valid_json( $value );
}
]
];
}
/**
* Checks if string is a valid json.
*
* @since 0.1
* @param string $param
* @return bool
*/
protected function is_valid_json( $param ) {
$param = json_decode( $param, true );
if ( ! is_array( $param ) ) return false;
return ( json_last_error() == JSON_ERROR_NONE );
}
/**
* Validates the site key.
*
* @since 0.1
* @return bool $is_valid_site_key
*/
private function is_valid_site_key() {
return \CRM_Utils_System::authenticateKey( false );
}
/**
* Validates the api key.
*
* @since 0.1
* @param WP_REST_Resquest $request
* @return bool $is_valid_api_key
*/
private function is_valid_api_key( $request ) {
$api_key = $request->get_param( 'api_key' );
if ( ! $api_key ) return false;
$contact_id = \CRM_Core_DAO::getFieldValue( 'CRM_Contact_DAO_Contact', $api_key, 'id', 'api_key' );
// validate contact and login
if ( $contact_id ) {
$wp_user = $this->get_wp_user( $contact_id );
$this->do_user_login( $wp_user );
return true;
}
return false;
}
/**
* Get WordPress user data.
*
* @since 0.1
* @param int $contact_id The contact id
* @return bool|WP_User $user The WordPress user data
*/
protected function get_wp_user( int $contact_id ) {
try {
// Get CiviCRM domain group ID from constant, if set.
$domain_id = defined( 'CIVICRM_DOMAIN_ID' ) ? CIVICRM_DOMAIN_ID : 0;
// If this fails, get it from config.
if ( $domain_id === 0 ) {
$domain_id = CRM_Core_Config::domainID();
}
// Call API.
$uf_match = civicrm_api3( 'UFMatch', 'getsingle', [
'contact_id' => $contact_id,
'domain_id' => $domain_id,
] );
} catch ( \CiviCRM_API3_Exception $e ) {
return $this->civi_rest_error( $e->getMessage() );
}
$wp_user = get_userdata( $uf_match['uf_id'] );
return $wp_user;
}
/**
* Logs in the WordPress user, needed to respect CiviCRM ACL and permissions.
*
* @since 0.1
* @param WP_User $user
*/
protected function do_user_login( \WP_User $user ) {
if ( is_user_logged_in() ) return;
wp_set_current_user( $user->ID, $user->user_login );
wp_set_auth_cookie( $user->ID );
do_action( 'wp_login', $user->user_login, $user );
}
}
<?php
/**
* Soap controller class.
*
* Soap endpoint, replacement for CiviCRM's 'extern/soap.php'.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class Soap extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'soap';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::ALLMETHODS,
'callback' => [ $this, 'get_item' ]
]
] );
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter request params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters( 'civi_wp_rest/controller/soap/params', $request->get_params(), $request );
// init soap server
$soap_server = new \SoapServer(
NULL,
[
'uri' => 'urn:civicrm',
'soap_version' => SOAP_1_2,
]
);
$crm_soap_server = new \CRM_Utils_SoapServer();
$soap_server->setClass( 'CRM_Utils_SoapServer', \CRM_Core_Config::singleton()->userFrameworkClass );
$soap_server->setPersistence( SOAP_PERSISTENCE_SESSION );
/**
* Bypass WP and send request from Soap server.
*/
add_filter( 'rest_pre_serve_request', function( $served, $response, $request, $server ) use ( $soap_server ) {
$soap_server->handle();
return true;
}, 10, 4 );
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {}
}
<?php
/**
* Url controller class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class Url extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'url';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'args' => $this->get_item_args()
],
'schema' => [ $this, 'get_item_schema' ]
] );
}
/**
* Get items.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter formatted api params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters( 'civi_wp_rest/controller/url/params', $this->get_formatted_params( $request ), $request );
// track url
$url = \CRM_Mailing_Event_BAO_TrackableURLOpen::track( $params['queue_id'], $params['url_id'] );
/**
* Filter url.
*
* @param string $url
* @param array $params
* @param WP_REST_Request $request
*/
$url = apply_filters( 'civi_wp_rest/controller/url/before_parse_url', $url, $params, $request );
// parse url
$url = $this->parse_url( $url, $params );
$this->do_redirect( $url );
}
/**
* Get formatted api params.
*
* @since 0.1
* @param WP_REST_Resquest $request
* @return array $params
*/
protected function get_formatted_params( $request ) {
$args = $request->get_params();
$params = [
'queue_id' => isset( $args['qid'] ) ? $args['qid'] ?? '' : $args['q'] ?? '',
'url_id' => $args['u']
];
// unset unnecessary args
unset( $args['qid'], $args['u'], $args['q'] );
if ( ! empty( $args ) ) {
$params['query'] = http_build_query( $args );
}
return $params;
}
/**
* Parses the url.
*
* @since 0.1
* @param string $url
* @param array $params
* @return string $url
*/
protected function parse_url( $url, $params ) {
// CRM-18320 - Fix encoded ampersands
$url = str_replace( '&amp;', '&', $url );
// CRM-7103 - Look for additional query variables and append them
if ( isset( $params['query'] ) && strpos( $url, '?' ) ) {
$url .= '&' . $params['query'];
} elseif ( isset( $params['query'] ) ) {
$url .= '?' . $params['query'];
}
return apply_filters( 'civi_wp_rest/controller/url/parsed_url', $url, $params );
}
/**
* Do redirect.
*
* @since 0.1
* @param string $url
*/
protected function do_redirect( $url ) {
wp_redirect( $url );
exit;
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'civicrm_api3/v3/url',
'description' => 'CiviCRM API3 wrapper',
'type' => 'object',
'required' => [ 'qid', 'u' ],
'properties' => [
'qid' => [
'type' => 'integer'
],
'q' => [
'type' => 'integer'
],
'u' => [
'type' => 'integer'
]
]
];
}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {
return [
'qid' => [
'type' => 'integer',
'required' => false,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
],
'q' => [
'type' => 'integer',
'required' => false,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
],
'u' => [
'type' => 'integer',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
]
];
}
}
<?php
/**
* Widget controller class.
*
* Widget endpoint, replacement for CiviCRM's 'extern/widget.php'
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Controller;
class Widget extends Base {
/**
* The base route.
*
* @since 0.1
* @var string
*/
protected $rest_base = 'widget';
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes() {
register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
[
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'get_item' ],
'args' => $this->get_item_args()
],
'schema' => [ $this, 'get_item_schema' ]
] );
}
/**
* Get item.
*
* @since 0.1
* @param WP_REST_Request $request
*/
public function get_item( $request ) {
/**
* Filter mandatory params.
*
* @since 0.1
* @param array $params
* @param WP_REST_Request $request
*/
$params = apply_filters(
'civi_wp_rest/controller/widget/params',
$this->get_mandatory_params( $request ),
$request
);
$jsonvar = 'jsondata';
if ( ! empty( $request->get_param( 'format' ) ) ) $jsonvar .= $request->get_param( 'cpageId' );
$data = \CRM_Contribute_BAO_Widget::getContributionPageData( ...$params );
$response = 'var ' . $jsonvar . ' = ' . json_encode( $data ) . ';';
/**
* Adds our response data before dispatching.
*
* @since 0.1
* @param WP_HTTP_Response $result Result to send to client
* @param WP_REST_Server $server The REST server
* @param WP_REST_Request $request The request
* @return WP_HTTP_Response $result Result to send to client
*/
add_filter( 'rest_post_dispatch', function( $result, $server, $request ) use ( $response ) {
return rest_ensure_response( $response );
}, 10, 3 );
// serve javascript
add_filter( 'rest_pre_serve_request', [ $this, 'serve_javascript' ], 10, 4 );
}
/**
* Get mandatory params from request.
*
* @since 0.1
* @param WP_REST_Resquest $request
* @return array $params The widget params
*/
protected function get_mandatory_params( $request ) {
$args = $request->get_params();
return [
$args['cpageId'],
$args['widgetId'],
$args['includePending'] ?? false
];
}
/**
* Serve jsondata response.
*
* @since 0.1
* @param bool $served Whether the request has already been served
* @param WP_REST_Response $result
* @param WP_REST_Request $request
* @param WP_REST_Server $server
* @return bool $served
*/
public function serve_javascript( $served, $result, $request, $server ) {
// set content type header
$server->send_header( 'Expires', gmdate( 'D, d M Y H:i:s \G\M\T', time() + 60 ) );
$server->send_header( 'Content-Type', 'application/javascript' );
$server->send_header( 'Cache-Control', 'max-age=60, public' );
echo $result->get_data();
return true;
}
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema() {
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'civicrm_api3/v3/widget',
'description' => 'CiviCRM API3 wrapper',
'type' => 'object',
'required' => [ 'cpageId', 'widgetId' ],
'properties' => [
'cpageId' => [
'type' => 'integer',
'minimum' => 1
],
'widgetId' => [
'type' => 'integer',
'minimum' => 1
],
'format' => [
'type' => 'integer'
],
'includePending' => [
'type' => 'boolean'
]
]
];
}
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args() {
return [
'cpageId' => [
'type' => 'integer',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
],
'widgetId' => [
'type' => 'integer',
'required' => true,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
],
'format' => [
'type' => 'integer',
'required' => false,
'validate_callback' => function( $value, $request, $key ) {
return is_numeric( $value );
}
],
'includePending' => [
'type' => 'boolean',
'required' => false,
'validate_callback' => function( $value, $request, $key ) {
return is_string( $value );
}
]
];
}
}
<?php
/**
* Endpoint Interface class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST\Endpoint;
interface Endpoint_Interface {
/**
* Registers routes.
*
* @since 0.1
*/
public function register_routes();
/**
* Item schema.
*
* @since 0.1
* @return array $schema
*/
public function get_item_schema();
/**
* Item arguments.
*
* @since 0.1
* @return array $arguments
*/
public function get_item_args();
}
<?php
/**
* Main plugin class.
*
* @since 0.1
*/
namespace CiviCRM_WP_REST;
use CiviCRM_WP_REST\Civi\Mailing_Hooks;
class Plugin {
/**
* Constructor.
*
* @since 0.1
*/
public function __construct() {
$this->register_hooks();
$this->setup_objects();
}
/**
* Register hooks.
*
* @since 1.0
*/
protected function register_hooks() {
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
add_filter( 'rest_pre_dispatch', [ $this, 'bootstrap_civi' ], 10, 3 );
}
/**
* Bootstrap CiviCRM when hitting a the 'civicrm' namespace.
*
* @since 0.1
* @param mixed $result
* @param WP_REST_Server $server REST server instance
* @param WP_REST_Request $request The request
* @return mixed $result
*/
public function bootstrap_civi( $result, $server, $request ) {
if ( false !== strpos( $request->get_route(), 'civicrm' ) ) civi_wp()->initialize();
return $result;
}
/**
* Setup objects.
*
* @since 0.1
*/
private function setup_objects() {
if ( CIVICRM_WP_REST_REPLACE_MAILING_TRACKING ) {
// register mailing hooks
$mailing_hooks = ( new Mailing_Hooks )->register_hooks();
}
}
/**
* Registers Rest API routes.
*
* @since 0.1
*/
public function register_rest_routes() {
// rest endpoint
$rest_controller = new Controller\Rest;
$rest_controller->register_routes();
// url controller
$url_controller = new Controller\Url;
$url_controller->register_routes();
// open controller
$open_controller = new Controller\Open;
$open_controller->register_routes();
// authorizenet controller
$authorizeIPN_controller = new Controller\AuthorizeIPN;
$authorizeIPN_controller->register_routes();
// paypal controller
$paypalIPN_controller = new Controller\PayPalIPN;
$paypalIPN_controller->register_routes();
// pxpay controller
$paypalIPN_controller = new Controller\PxIPN;
$paypalIPN_controller->register_routes();
// civiconnect controller
$cxn_controller = new Controller\Cxn;
$cxn_controller->register_routes();
// widget controller
$widget_controller = new Controller\Widget;
$widget_controller->register_routes();
// soap controller
$soap_controller = new Controller\Soap;
$soap_controller->register_routes();
/**
* Opportunity to add more rest routes.
*
* @since 0.1
*/
do_action( 'civi_wp_rest/plugin/rest_routes_registered' );
}
}
# CiviCRM WP REST API Wrapper
This is a WordPress plugin that aims to expose CiviCRM's [extern](https://github.com/civicrm/civicrm-core/tree/master/extern) scripts as WordPress REST endpoints.
This plugin requires:
- PHP 7.1+
- WordPress 4.7+
- CiviCRM to be installed and activated.
### Endpoints
1. `civicrm/v3/rest` - a wrapper around `civicrm_api3()`
**Parameters**:
- `key` - **required**, the site key
- `api_key` - **required**, the contact api key
- `entity` - **required**, the API entity
- `action` - **required**, the API action
- `json` - **optional**, json formatted string with the API parameters/argumets, or `1` as in `json=1`
By default all calls to `civicrm/v3/rest` return XML formatted results, to get `json` formatted result pass `json=1` or a json formatted string with the API parameters, like in the example 2 below.
**Examples**:
1. `https://example.com/wp-json/civicrm/v3/rest?entity=Contact&action=get&key=<site_key>&api_key=<api_key>&group=Administrators`
2. `https://example.com/wp-json/civicrm/v3/rest?entity=Contact&action=get&key=<site_key>&api_key=<api_key>&json={"group": "Administrators"}`
2. `civicrm/v3/url` - a substition for `civicrm/extern/url.php` mailing tracking
3. `civicrm/v3/open` - a substition for `civicrm/extern/open.php` mailing tracking
4. `civicrm/v3/authorizeIPN` - a substition for `civicrm/extern/authorizeIPN.php` (for testing Authorize.net as per [docs](https://docs.civicrm.org/sysadmin/en/latest/setup/payment-processors/authorize-net/#shell-script-testing-method))
**_Note_**: this endpoint has **not been tested**
5. `civicrm/v3/ipn` - a substition for `civicrm/extern/ipn.php` (for PayPal Standard and Pro live transactions)
**_Note_**: this endpoint has **not been tested**
6. `civicrm/v3/cxn` - a substition for `civicrm/extern/cxn.php`
7. `civicrm/v3/pxIPN` - a substition for `civicrm/extern/pxIPN.php`
**_Note_**: this endpoint has **not been tested**
8. `civicrm/v3/widget` - a substition for `civicrm/extern/widget.php`
9. `civicrm/v3/soap` - a substition for `civicrm/extern/soap.php`
**_Note_**: this endpoint has **not been tested**
### Settings
Set the `CIVICRM_WP_REST_REPLACE_MAILING_TRACKING` constant to `true` to replace mailing url and open tracking calls with their counterpart REST endpoints, `civicrm/v3/url` and `civicrm/v3/open`.
_Note: use this setting with caution, it may affect performance on large mailings, see `CiviCRM_WP_REST\Civi\Mailing_Hooks` class._
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment