custom XMLRPC method plus authentication of user & WooCommerce order

I’m working with a custom XML-RPC method to capture some information for a plugin I am building. When activated the user has to authenticate themselves by entering there user/password given to them when purchasing the plugin on my WP site.

The plugin uses this code:

Read More
if ( isset( $_POST['username'] ) && isset( $_POST['password'] ) ) {

        /* check against remote server */
        require_once( ABSPATH . WPINC . '/class-IXR.php' );
        $this->client = new IXR_Client( trailingslashit( CUSTOMLOGIN_UPDATE_API ) . 'xmlrpc.php' ); //*/

        $url = ( ( is_multisite() ) ? network_site_url() : site_url() );

        $client_request_args = array(
            'username'  => $_POST['username'],
            'password'  => $_POST['password'],
            'plugin'    => CUSTOMLOGINPRO_BASENAME,
            'url'       => $url
        );

        if ( !$this->client->query( 'thefrosty.is_user_authorized', $client_request_args ) ) {
            add_action( 'admin_notices', array( $this, 'error_notice' ) );
            return false;
        }

        $this->settings = get_option( CUSTOMLOGINPRO . '_settings', array() );
        $this->settings['api-key'] = $this->client->getResponse();
        update_option( CUSTOMLOGINPRO . '_settings', $this->settings );
        header( 'Location: ' . admin_url( 'options-general.php?page=' . CUSTOMLOGINPRO ) );
        die();

    } 

The form submits and looks like its kinda working, getting a 200 error when working locally, but tested with another plugin that send same error code, but is sending responses from host (no worries yet).

What I am stuck on is the back end code. I’ve created a class in a core plugin on my WP install. I am assuming this is where it needs to be. I am missing some fault code and error message responses, not sure how to do those. Just looking for a little push in the right direction..

As you can see I am also testing the user against a WooCommerce order (code from WC, should be good) but then trying to update the order meta meta might need to fixes to save the users URLs.

class frosty_core {

function __construct() {
    add_filter( 'xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
}

/**
 * Create our custom XML-rpc method.
 * @ref http://kovshenin.com/2010/04/custom-xml-rpc-methods-in-wordpress-2151/
 */
function xmlrpc_methods( $methods ) {
    $methods['thefrosty.is_user_authorized'] = array( $this, 'thefrosty_plugin_callback' );
    return $methods;
}

/**
 * XML-prc mothod
 */
function thefrosty_plugin_callback( $args ) {

    // Parse the arguments, assuming they're in the correct order
    $username       = $args[0];
    $password       = $args[1];
    $plugin_name    = $args[2];
    $url            = $args[3];

    global $wp_xmlrpc_server;

    // Let's run a check to see if credentials are okay
    if ( !$user = $wp_xmlrpc_server->login($username, $password) ) {
        return $wp_xmlrpc_server->error;
    }

    if ( !class_exists( 'woocommerce' ) ) return 'error, please try again later';

    /* Get the user ID by name */
    $current_user_id = get_userdatabylogin( $username );

    /* woocommerce/shortcodes/shortcode-my_account.php */
    $args = array(
        'numberposts'     => -1,
        'meta_key'        => '_customer_user',
        'meta_value'      => $current_user_id,
        'post_type'       => 'shop_order',
        'post_status'     => 'publish' 
    );
    $customer_orders = get_posts( $args );      
    $match = false;

    foreach ( $customer_orders as $customer_order ) :
        $order = &new woocommerce_order();
        $order->populate( $customer_order );

        $status = get_term_by( 'slug', $order->status, 'shop_order_status' );
        if ( 'completed' !== $status->name ) return; //error, order not completed //*/

        if ( $plugin_name !== $order->items->name ) return; // you have not purchased this plugin //*/

        $match  = true;
        $apikey = $order->order_key;
    endforeach;

    if ( isset( $match ) && $match ) {
        /* woocommerce/admin/writepanels/writepanel-order_data.php */
        add_filter( 'update_order_item', create_function( '$order_items', '
            $new_meta   = &new order_item_meta();
            $meta_name  = "active_urls";
            $meta_value = esc_url( $url );

            $new_meta->add( $meta_name, $meta_value );
            return $order_items["item_meta"] => $new_meta->meta;' ) );

        return $apikey;
    }
    else return false;
}

}

Related posts

Leave a Reply

2 comments

  1. I sent it to you on Twitter, but here it is again.

    I made this little class to help me do XML-RPC faster.

    abstract class MZAXMLRPC {
        protected $calls = Array();
        protected $namespace = "myxmlrpc";
    
        function __construct($namespace){
            $this->namespace = $namespace;
            $reflector = new ReflectionClass($this);
            foreach ( $reflector->getMethods(ReflectionMethod::IS_PUBLIC) as $method){
                if ($method->isUserDefined() && $method->getDeclaringClass()->name != get_class()){
                    $this->calls[] = $method->name;
                }
            }
            add_filter('xmlrpc_methods', array($this, 'xmlrpc_methods'));
        }
    
        public function xmlrpc_methods($methods)
        {
            foreach ($this->calls as $call){
                $methods[$this->namespace . "." . $call] = array($this, "dispatch");
            }
            return $methods;
        }
    
        public function dispatch($args){
            global $wp_xmlrpc_server;
    
            $username   = $args[1];
            $password   = $args[2];
            $data = $args[3];
    
            if ( !$wp_xmlrpc_server->login($username, $password) )
                return $wp_xmlrpc_server->error;
    
            $call = $this->get_called_method();
    
            if (method_exists($this, $call)){
                $status = call_user_func_array(array($this, $call), array($data));
                return $status;
            }else{
                return "Method not allowed";
            }
    
        }
    
        private function get_called_method(){
            global $wp_xmlrpc_server;
            $call = $wp_xmlrpc_server->message->methodName;
            $pieces = explode(".", $call);
            return $pieces[1];
        }
    
    }
    

    This is an abstract class. You won’t instantiate objects out of it. What you’ll do is create a new class that inherits from MZAXMLRPC, and in it you’ll make public methods for each XML-RPC call you want to expose.

    The constructor uses Reflection to find all the user-defined public methods of the child class that is inheriting from it. Then it will tell WordPress that we’re accepting XML-RPC calls for this methods. The XML-RPC methods are exposed with the name $namespace.$public_method_name.

    All those new XML-RPC will arrive to the same method, dispatch. This method will fist validate the user/pass for the remote call, then it will check that effectively there is a method declared to take care of the XML-RPC call. If all validates ok it will dispatch the call to the appropriate method of the child class, passing along all the data that came through the XML-RPC server.

    Enough gibberish already! The only thing you need to do is:

    class MY_XMLRPC extends MZAXMLRPC{
    
        public function QuoteUpload($data){
    
            if (!isset($data["author"]))
                return "Missing 'author' parameter";
    
            if (!isset($data["quote"]))
                return "Missing 'quote' parameter";
    
            $author = $data["author"];
            $quote = $data["quote"];
    
            $new_post = array(
                'post_status' => 'publish',
                'post_type' => 'mzaquotes',
                'post_title' => $quote
            );
    
            $new_post_id = wp_insert_post($new_post);
            wp_set_post_terms( $new_post_id, $author, 'quote_author' );
    
            return "OK";
        }
    }
    
    new MY_XMLRPC('my_function');
    

    This little piece of code will expose a XML-RPC method called my_function.QuoteUpload. The parent class will deal with authentication and WordPress APIs for you.

  2. Nothing looks wrong with your code. You’re definitely adding the XMLRPC endpoint correctly. The only issue I see is with this code:

     if ( isset( $match ) && $match ) {
        /* woocommerce/admin/writepanels/writepanel-order_data.php */
        add_filter( 'update_order_item', create_function( '$order_items', '
            $new_meta   = &new order_item_meta();
            $meta_name  = "active_urls";
            $meta_value = esc_url( $url );
    
            $new_meta->add( $meta_name, $meta_value );
            return $order_items["item_meta"] => $new_meta->meta;' ) );
    
        return $apikey;
    }
    

    It might be that I don’t know WooCommerce well enough … but you should be invoking some kind of function here, not adding a filter. Your code will only be applied if the filter is hit … and in a typical XMLRPC request, it won’t be.