diff --git a/civicrm.php b/civicrm.php index 8f702f27b00b4d3d81d23da308a822f7311a229b..59b11b6b70b5f5e68446cee38b46091e114dc989 100644 --- a/civicrm.php +++ b/civicrm.php @@ -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; + } diff --git a/wp-rest/Autoloader.php b/wp-rest/Autoloader.php new file mode 100644 index 0000000000000000000000000000000000000000..a5d6757c218b0ccdda162fec7a6a20e2bf669770 --- /dev/null +++ b/wp-rest/Autoloader.php @@ -0,0 +1,115 @@ +<?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 ); + + } + +} diff --git a/wp-rest/Civi/Mailing-Hooks.php b/wp-rest/Civi/Mailing-Hooks.php new file mode 100644 index 0000000000000000000000000000000000000000..7113088b3ba4f868b6ad0ed286af3c205979b3f5 --- /dev/null +++ b/wp-rest/Civi/Mailing-Hooks.php @@ -0,0 +1,136 @@ +<?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; + + } + +} diff --git a/wp-rest/Controller/AuthorizeIPN.php b/wp-rest/Controller/AuthorizeIPN.php new file mode 100644 index 0000000000000000000000000000000000000000..3f8cf64c9cb6216579366c928715adf0f731235d --- /dev/null +++ b/wp-rest/Controller/AuthorizeIPN.php @@ -0,0 +1,123 @@ +<?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() {} + +} diff --git a/wp-rest/Controller/Base.php b/wp-rest/Controller/Base.php new file mode 100644 index 0000000000000000000000000000000000000000..5115c490e7766d2e7b858765f56ba7f7d0a268ea --- /dev/null +++ b/wp-rest/Controller/Base.php @@ -0,0 +1,101 @@ +<?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 ); + + } + +} diff --git a/wp-rest/Controller/Cxn.php b/wp-rest/Controller/Cxn.php new file mode 100644 index 0000000000000000000000000000000000000000..7f7cca5c5621c3eb3441ca7060d5e1048eb85ade --- /dev/null +++ b/wp-rest/Controller/Cxn.php @@ -0,0 +1,125 @@ +<?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() {} + +} diff --git a/wp-rest/Controller/Open.php b/wp-rest/Controller/Open.php new file mode 100644 index 0000000000000000000000000000000000000000..db70e1070eba0d78e993004dd61d50f76b739dd9 --- /dev/null +++ b/wp-rest/Controller/Open.php @@ -0,0 +1,129 @@ +<?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 ); + + } + ] + ]; + + } + +} diff --git a/wp-rest/Controller/PayPalIPN.php b/wp-rest/Controller/PayPalIPN.php new file mode 100644 index 0000000000000000000000000000000000000000..0aaac0513b64798edf99cb6552e36f7167ff163e --- /dev/null +++ b/wp-rest/Controller/PayPalIPN.php @@ -0,0 +1,134 @@ +<?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() {} + +} diff --git a/wp-rest/Controller/PxIPN.php b/wp-rest/Controller/PxIPN.php new file mode 100644 index 0000000000000000000000000000000000000000..d68fc8d787ae3e87eb449f36b459e0b8d24d845a --- /dev/null +++ b/wp-rest/Controller/PxIPN.php @@ -0,0 +1,139 @@ +<?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() {} + +} diff --git a/wp-rest/Controller/Rest.php b/wp-rest/Controller/Rest.php new file mode 100644 index 0000000000000000000000000000000000000000..b3b97b205cd6abdb498449a4b6d45de31d623b66 --- /dev/null +++ b/wp-rest/Controller/Rest.php @@ -0,0 +1,522 @@ +<?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 ); + + } + +} diff --git a/wp-rest/Controller/Soap.php b/wp-rest/Controller/Soap.php new file mode 100644 index 0000000000000000000000000000000000000000..17402cc579a834ca8854014e683a53aad5011399 --- /dev/null +++ b/wp-rest/Controller/Soap.php @@ -0,0 +1,98 @@ +<?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() {} + +} diff --git a/wp-rest/Controller/Url.php b/wp-rest/Controller/Url.php new file mode 100644 index 0000000000000000000000000000000000000000..263a5410ab795f419fd8e67427d3e713323b4b48 --- /dev/null +++ b/wp-rest/Controller/Url.php @@ -0,0 +1,214 @@ +<?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( '&', '&', $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 ); + + } + ] + ]; + + } + +} diff --git a/wp-rest/Controller/Widget.php b/wp-rest/Controller/Widget.php new file mode 100644 index 0000000000000000000000000000000000000000..fd4ee14bc6a35341bc84f7f50478310e9eca2038 --- /dev/null +++ b/wp-rest/Controller/Widget.php @@ -0,0 +1,214 @@ +<?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 ); + + } + ] + ]; + + } + +} diff --git a/wp-rest/Endpoint/Endpoint-Interface.php b/wp-rest/Endpoint/Endpoint-Interface.php new file mode 100644 index 0000000000000000000000000000000000000000..9497cde5099ecbd7936571b0b73e318652abea7f --- /dev/null +++ b/wp-rest/Endpoint/Endpoint-Interface.php @@ -0,0 +1,35 @@ +<?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(); + +} diff --git a/wp-rest/Plugin.php b/wp-rest/Plugin.php new file mode 100644 index 0000000000000000000000000000000000000000..7cf3e9f92fb1282dffb50302d17c73733e9d1946 --- /dev/null +++ b/wp-rest/Plugin.php @@ -0,0 +1,125 @@ +<?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' ); + + } + +} diff --git a/wp-rest/README.md b/wp-rest/README.md new file mode 100644 index 0000000000000000000000000000000000000000..77234de84a195dc6fbb22e1e7d460ff7d44587f2 --- /dev/null +++ b/wp-rest/README.md @@ -0,0 +1,59 @@ +# 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._