Community Documentation

Services 6.x-2.0 PHP xmlrpc example with api key

Last updated April 29, 2010. Created by bsenftner on April 18, 2010.
Log in to edit this page.

Here's some working logic that I cobbled together from various partial examples online and the deploy module. It's rough, but works. The key difference in this example from others is the fact that this version only uses Drupal API routines, rather than the 'experimental' PHP routines such as xmlrpc_encode_request() ( see http://php.net/manual/en/function.xmlrpc-encode-request.php ) and xmlrpc_decode(). Such routines may not exist in some environments and are also subject to change.

<?php
/***************************************************/
class DrupalXmlrpc {

  function
__construct( $domain = '', $apiKey = '', $endPoint = '', $verbose = FALSE )
  {
   
// set local domain or IP address
    // this needs to match the domain set when you created the API key
   
$this->domain = $domain;
  
   
// set API key
   
$this->kid = $apiKey;
  
   
// set target web service endpoint
   
$this->endpoint = $endPoint;

   
// extended debugging
   
$this->verbose = $verbose;
  
   
// call system.connect to get our required anonymous sessionId:
   
$retVal = $this->send( 'system.connect', array() );
   
$this->session_id = $retVal['sessid'];

    if (
$this->verbose) {
      
$func = 'DrupalXmlrpc->__construct:';
       if (
$this->session_id)
           
error_log( $func.' got anonymous session id fine' );
       else
error_log( $func.' failed to get anonymous session id!' );
    }
  }

 
/***********************************************************************
  * Function for sending xmlrpc requests
  */
 
public function send( $methodName, $functionArgs = array() )
  {
   
$protocolArgs = array();
   
// only the system.connect method does not require a sessionId:
   
if ($methodName == 'system.connect') {
      
$protocolArgs = array( $this->endpoint, $methodName );
    }
    else {
      
$timestamp = (string)time();
      
$nonce = $this->getUniqueCode("10");
  
      
// prepare a hash
      
$hash_parameters = array( $timestamp, $this->domain, $nonce, $methodName );
      
$hash = hash_hmac("sha256", implode(';', $hash_parameters), $this->kid);
  
      
// prepared the arguments for this service:
       // note, the sessid needs to be the one returned by user.login
      
$protocolArgs = array( $this->endpoint, $methodName, $hash, $this->domain, $timestamp, $nonce, $this->session_id );
    }

   
$params = array_merge( $protocolArgs, $functionArgs );
    return
call_user_func_array( 'xmlrpc', $params );
  }

 
/***************************************************
   * login and return user object
   */
 
public function userLogin( $userName = '', $userPass = '' )
  {
    if (
$this->verbose)
        
error_log( 'DrupalXmlrpc->userLogin() called with userName "'.$userName.'" and pass "'.$userPass.'"' );

   
// clear out any lingering xmlrpc errors:
   
xmlrpc_error( NULL, NULL, TRUE );

   
$retVal = $this->send( 'user.login', array($userName, $userPass) );
    if (!
$retVal && xmlrpc_errno()) {
       if (
$this->verbose)
         
error_log( 'userLogin() failed! errno "'.xmlrpc_errno().'" msg "'.xmlrpc_error_msg().'"' );
       return
FALSE;
    }
    else {
      
// remember our logged in session id:
      
$this->session_id = $retVal['sessid'];

      
// we might need the user object later, so save it:
      
$user = new stdClass();
      
$user = (object)$response['user'];
      
$this->authenticated_user = $user;
       return
$user;
    }
  }

 
/***************************************************
   * logout, returns 0 for okay, or -1 for error.
   */
 
public function userLogout()
  {
   
$retVal = $this->send( 'user.logout', array() );
    if (!
$retVal) {
       if (
$this->verbose)
         
error_log( 'userLogout() failed! errno "'.xmlrpc_errno().'" msg "'.xmlrpc_error_msg().'"' );
       return -
1;
    }

    return
0; // meaning okay
 
}

 
/***************************************************
  * Function for generating a random string, used for
  * generating a token for the XML-RPC session
  */
 
private function getUniqueCode($length = "")
  {
   
$code = md5(uniqid(rand(), true));
    if (
$length != "")
         return
substr($code, 0, $length);
    else return
$code;
  }
}
?>

Use it like this:

<?php
      
// create a drupal session:
      
$localDomain   = 'whatever domain you used when you created this api key';
      
$apiKey        = 'Services generated api key string';
      
$endPoint      = 'url to your destination endpoint';
      
$drupalSession = new DrupalXmlrpc( $localDomain, $apiKey, $endPoint );
       if (
$drupalSession->session_id) {

         
$userName   = 'username used to login to remote system';
         
$userPass   = 'password of user on remote system';
         
$drupalUser = $drupalSession->userLogin( $userName, $userPass );
          if (
$drupalUser) {

            
$result = $drupalSession->send( 'method.name-of-remote-function, array(remote-functions-parameters-in-an-array) );

             if ($result == -1)
                $retVal = SOME_SYMBOL_MEANING_REMOTE_WORK_FAILED;
 
             $drupalSession->userLogout(); // all done communicating, so logout
          }
          else {
             $retVal = SOME_SYMBOL_MEANING_LOGIN_FAILED;
          }
       }
       else {
          $retVal = SOME_SYMBOL_MEANING_CONNECTION_FAILED;
       }
?>

I hope this is useful for others.

Comments

PHP Warning

PHP Warning: call_user_func_array() [function.call-user-func-array]: First argument is expected to be a valid callback, 'xmlrpc' was given in ......./xmlrpc.php on line 67

below the lines 66-67 :

$params = array_merge( $protocolArgs, $functionArgs );
return call_user_func_array( 'xmlrpc', $params );

$params is an array similar this Array ( [0] => localhost/drupal-6.16/services/xmlrpc [1] => system.connect )

someone can help me ?
thanks

Maxi

You really should be asking

You really should be asking at the Services.module issue queue.

When you post your issue over there, be sure to include your Drupal version & Services version. Your first error on line 67 should not happen, because 'xmlrpc' is a valid function name, it's part of the Drupal API: http://api.drupal.org/api/function/xmlrpc

The second part is simply an issue of you creating the $params array incorrectly. Do it just like you're sending the params to a function, as in $params = array( param1, param2, ... paramN ), without the associative array keys. And if they are strings, be sure to quote them...

Same here...

Yeah, I'm getting the same error here. Could it be a PHP5.3 thing? Did you ever get it solved?

PHP Warning:  call_user_func_array() expects parameter 1 to be a valid callback, function 'xmlrpc' not found or invalid function name

@Blake: This question does belong under the example, since - if you take as a given that the user has set up and tested the Services module already, and it works (as, in my case, it does), it's related to a problem with this particular example code. If the example isn't correct, it should be removed. If there's a caveat or something related to the environment, it should be noted here for others (like me) who're trying to get it to work!

This example does not work...

Here is a revised example that does function. Note that the class requires includes from Drupal; you can bundle them or link toyour site install. Make sure permissions are set in THREE places:

1. In Site Permissions: For the user to access/update the content
2. In Site Permissions: For the user to use/access the services themselves
3. In the API Key Permissions: enable the services for each API client

main.php

<?php
require('service_class.php');

// create a drupal session:
$localDomain   = 'www.sendingdomain.com';
$apiKey        = 'a89sad9asd8asjda89jd9a8sjd98aj98jsad98j';
$endPoint      = 'http://www.drupalsite.com/services/xmlrpc';
$userName = '';
$userPass = '';
$title = '';
$description '';

$drupalSession = new DrupalXmlrpc( $localDomain, $apiKey, $endPoint );

if ( isset(
$drupalSession->session_id) ) {

   
$request = (object)array(
         
'nid' => '3753',
    );
   
   
// Add build node function
   
$create = (object)array(
       
'type' => 'page',
       
'title' => $title,
       
'body' => $description,
    );
   
   
$drupalUser = $drupalSession->userLogin( $userName, $userPass );

    if ( isset(
$drupalUser->uid ) ) {

         
//$result = $drupalSession->send( 'node_resource.retrieve', array('3749') );

            // Some variables go into the basic array, others are structs.
            // Send whatever the service is looking for: in the case of node.get, array.
            // In the case of user.load, struct.
           
            
$result = $drupalSession->send( 'node.get', $request, NULL );
            
//$result = $drupalSession->send( 'user.load', NULL, $user );
             //$result = $drupalSession->send( 'node.save', NULL, $create );

                
if ($result == -1)
                   
$retVal = SOME_SYMBOL_MEANING_REMOTE_WORK_FAILED;
             
       
$drupalSession->userLogout(); // all done communicating, so logout
         
}
          else {
                
$retVal = SOME_SYMBOL_MEANING_LOGIN_FAILED;
          }
} else {
   
$retVal = CONNECTION_FAILED;
}
?>

service_class.php

<?php
/***************************************************/
require_once('includes/bootstrap.inc');
require_once(
'includes/common.inc');
require_once(
'includes/xmlrpc.inc');

class
DrupalXmlrpc {

  function
__construct( $domain = '', $apiKey = '', $endPoint = '', $verbose = FALSE )
  {
   
// set local domain or IP address
    // this needs to match the domain set when you created the API key
   
$this->domain = $domain;
  
   
// set API key
   
$this->kid = $apiKey;
  
   
// set target web service endpoint
   
$this->endpoint = $endPoint;

   
// extended debugging
   
$this->verbose = $verbose;
  
   
// call system.connect to get our required anonymous sessionId:
   
$retVal = $this->send( 'system.connect', array() );
   
$this->session_id = $retVal['sessid'];

    if (
$this->verbose) {
      
$func = 'DrupalXmlrpc->__construct:';
       if (
$this->session_id)
           
error_log( $func.' got anonymous session id fine' );
       else
error_log( $func.' failed to get anonymous session id!' );
    }
  }

 
/***********************************************************************
  * Function for sending xmlrpc requests
  */
 
public function send( $methodName, $functionArgs = array(), $functionObjects = array() )
  {
   
$protocolArgs = array();

   
// only the system.connect method does not require a sessionId:
   
if ($methodName == 'system.connect') {
      
$protocolArgs = array( $this->endpoint, $methodName );
    }
    else {
      
$timestamp = (string)time();
      
$nonce = $this->getUniqueCode("10");
  
      
// prepare a hash
      
$hash_parameters = array( $timestamp, $this->domain, $nonce, $methodName );
      
$hash = hash_hmac("sha256", implode(';', $hash_parameters), $this->kid);
  
      
// prepared the arguments for this service:
       // note, the sessid needs to be the one returned by user.login
      
$protocolArgs = array( $this->endpoint, $methodName, $hash, $this->domain, $timestamp, $nonce, $this->session_id );
   
   
// this won't function in the case of structs, - in some cases, each one needs to be an arg, not
    // have the whole passed array appended
       
   
if( isset($functionObjects) ) {
       
array_push( $protocolArgs, $functionObjects );
       }
    if( isset(
$functionArgs) ) {
       
array_splice( $protocolArgs, count($protocolArgs), 0, $functionArgs );
    }
  }
  return
call_user_func_array( "_xmlrpc", $protocolArgs );

}

 
/***************************************************
   * login and return user object
   */
 
public function userLogin( $userName = '', $userPass = '' )
  {
    if (
$this->verbose)
        
error_log( 'DrupalXmlrpc->userLogin() called with userName "'.$userName.'" and pass "'.$userPass.'"' );

   
// clear out any lingering xmlrpc errors:
   
xmlrpc_error( NULL, NULL, TRUE );

   
$retVal = $this->send( 'user.login', array( $userName, $userPass ), NULL );
   
    if (!
$retVal && xmlrpc_errno()) {
       if (
$this->verbose)
         
error_log( 'userLogin() failed! errno "'.xmlrpc_errno().'" msg "'.xmlrpc_error_msg().'"' );
       return
FALSE;
    }
    else {
      
// remember our logged in session id:
      
$this->session_id = $retVal['sessid'];

      
// we might need the user object later, so save it:
      
$user = new stdClass();
      
$user = (object)$retVal['user'];
      
$this->authenticated_user = $user;
       return
$user;
    }
  }

 
/***************************************************
   * logout, returns 0 for okay, or -1 for error.
   */
 
public function userLogout()
  {
   
$retVal = $this->send( 'user.logout' );
    if (!
$retVal) {
       if (
$this->verbose)
         
error_log( 'userLogout() failed! errno "'.xmlrpc_errno().'" msg "'.xmlrpc_error_msg().'"' );
       return -
1;
    }

    return
0; // meaning okay
 
}

 
/***************************************************
  * Function for generating a random string, used for
  * generating a token for the XML-RPC session
  */
 
private function getUniqueCode($length = "")
  {
   
$code = md5(uniqid(rand(), true));
    if (
$length != "")
         return
substr($code, 0, $length);
    else return
$code;
  }
}
?>

thank you Reinette

thank you Reinette,

I had it working without sessions, but then suddenly my server stopped wanting to accept requests without sessions (yes I unchecked the box in settings and everything), go figure. Anyhow after scouring the net, this post saved the day. Your code sample worked for me.

Muchas Muchas Gracias,

James

The error I'm getting back is (Drupal 5)

userLogin() failed! errno "-32602" msg "Server error. Invalid method parameters.

anyone have some info on how to remedy this problem?

thank you

the problem was simply that I did not realize that system connect was not automatically supported as a service!

resolved.

About this page

Drupal version
Drupal 6.x
Audience
Contributors, Programmers

Develop for Drupal

Drupal’s online documentation is © 2000-2013 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License. Comments on documentation pages are used to improve content and then deleted.
nobody click here