Skip to content

Instantly share code, notes, and snippets.

@3mrdev
Created June 9, 2022 00:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save 3mrdev/b1fd2f2a16f0bbdd6d66f4c313e16f47 to your computer and use it in GitHub Desktop.
Save 3mrdev/b1fd2f2a16f0bbdd6d66f4c313e16f47 to your computer and use it in GitHub Desktop.
Odoo XMLRPC Example using PHP
<?php
// like import file
require_once('lib/ripcord.php');
// define variables to use in future
$url = "http://localhost:8015";
$db = "o14_api";
$username = "admin";
$password = "admin";
$end_point = $url . "/xmlrpc/2/object";
?>
<?php
require_once("login.php"); // import login file
// create
$name = $_POST['name'];
$id = $model->execute_kw(
$db,//database name
$uid, // user id
$password, // user password
"student", // model / table name
"create", // rpc function
array(
array( // data to insert
"name"=>$name,
"birth_date"=>"2000-08-20",
"number"=>6,
"fees"=>400,
"is_register"=>True,
)
)
);
// header('Location: insert.php');
echo json_encode($id); // print user that created
?>
<?php
require_once('login.php');
// $id = (int) $_GET['id'];
$is_deleted = $model->execute_kw(
$db,
$uid,
$password,
"student",
"unlink",
array(6),
);
echo json_encode($is_deleted);
?>
<html>
<body>
<form action="create.php" method="POST">
Name: <input name="name" type="text"/>
<input type="submit" name="Save"/>
</form>
</body>
</html>
<?php
require_once('config.php');
$common = ripcord::client($url . "/xmlrpc/2/common");
$uid = $common->authenticate($db, $username, $password, array());
$model = ripcord::client($end_point);
?>
<?php
/**
* Ripcord is an easy to use XML-RPC library for PHP.
* @package Ripcord
* @author Auke van Slooten <auke@muze.nl>
* @copyright Copyright (C) 2010, Muze <www.muze.nl>
* @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
* @version Ripcord 0.9 - PHP 5
*/
/**
* The ripcord class contains a number of useful static methods. This makes it a bit easier to create a server or client, convert types
* and check for errors.
* @package Ripcord
*/
class ripcord
{
/**
* This method checks whether the given argument is an XML-RPC fault.
* @param mixed $fault
* @return bool
*/
public static function isFault($fault)
{
if ( isset($fault) && is_array($fault) ) {
return xmlrpc_is_fault($fault);
} else {
return false;
}
}
/**
* This method generates an XML-RPC fault with the given code and message.
* @param int $code
* @param string $message
* @return array
*/
public static function fault($code, $message)
{
return array('faultCode' => $code, 'faultString' => $message);
}
/**
* This method returns a new Ripcord server, which by default implements XML-RPC, Simple RPC and SOAP 1.1.
* The server will publish any methods passed through the $services argument. It can be configured through
* the $options argument.
* @param mixed $services Optional. Either an object or an array of objects. If the array has non-numeric keys, the key will be used as a namespace for the methods in the object.
* @param array $options Optional. An array of options to set for the Ripcord server.
* @see Ripcord_Server
*/
public static function server($services = null, $options = null, $documentor = null)
{
self::load('Ripcord_Server');
if ( !isset($documentor) )
{
$doc = array('name', 'css', 'wsdl', 'wsdl2');
$docOptions = array();
foreach ( $doc as $key )
{
if ( isset($options[$key]) )
{
$docOptions[$key] = $options[$key];
unset( $options[$key] );
}
}
$docOptions['version'] = $options['version'];
$documentor = self::documentor( $docOptions );
}
return new Ripcord_Server($services, $options, $documentor);
}
/**
* This method returns a new Ripcord client. By default this will be an XML-RPC client, but you can change this
* through the $options argument.
* @param string $url The url of the RPC server to connect with
* @param array $options Optional. An array of options to set for the Ripcord client.
* @see Ripcord_Client
*/
public static function client($url, $options = null, $transport = null )
{
self::load('Ripcord_Client');
if ( !isset($transport) )
{
$transport = new Ripcord_Transport_Stream();
}
return new Ripcord_Client($url, $options, $transport);
}
/**
* This method returns a new Ripcord documentor object.
* @param array $options Optional. An array of options to set for the Ripcord documentor.
* @param object docCommentParser Optional. An object that parses a docComment block. Must
* implement the Ripcord_Documentor_CommentParser interface.
* @see Ripcord_Client
*/
public static function documentor( $options = null, $docCommentParser = null )
{
self::load('Ripcord_Documentor');
if (!$docCommentParser) {
$docCommentParser = new Ripcord_Documentor_Parser_phpdoc();
}
return new Ripcord_Documentor( $options, $docCommentParser );
}
/**
* This method returns an XML-RPC datetime object from a given unix timestamp.
* @param int $timestamp
* @return object
*/
public static function datetime($timestamp)
{
$datetime = date("Ymd\TH:i:s", $timestamp);
xmlrpc_set_type($datetime, 'datetime');
return $datetime;
}
/**
* This method returns a unix timestamp from a given XML-RPC datetime object.
* It will throw a 'Variable is not of type datetime' Ripcord_Exception (code -6)
* if the given argument is not of the correct type.
* @param object $datetime
* @return int
*/
public static function timestamp($datetime)
{
if (xmlrpc_get_type($datetime)=='datetime')
{
return $datetime->timestamp;
} else {
throw Ripcord_Exception('Variable is not of type datetime', -6);
}
}
/**
* This method returns an XML-RPC base64 object from a given binary string.
* @param string $binary
* @return object
*/
public static function base64($binary)
{
xmlrpc_set_type($binary, 'base64');
return $binary;
}
/**
* This method returns a (binary) string from a given XML-RPC base64 object.
* It will throw a 'Variable is not of type base64' Ripcord_Exception (code -7)
* if the given argument is not of the correct type.
* @param object $base64
* @return string
*/
public static function binary($base64)
{
if (xmlrpc_get_type($base64)=='base64')
{
return $base64->scalar;
} else {
throw Ripcord_Exception('Variable is not of type base64', -7);
}
}
/**
* This method returns the type of the given parameter. This can be any of the XML-RPC data types, e.g.
* 'struct', 'int', 'string', 'base64', 'boolean', 'double', 'array' or 'datetime'.
* @param mixed $param
* @return string
*/
public static function getType($param)
{
return xmlrpc_get_type($param);
}
/**
* This method returns a new Ripcord client, configured to access a SOAP 1.1 server.
* @param string $url
* @param array $options Optional.
* @see Ripcord_Client
*/
public static function soapClient($url, $options = null, $transport = null)
{
$options['version'] = 'soap 1.1';
return self::client($url, $options, $transport);
}
/**
* This method returns a new Ripcord client, configured to access an XML-RPC server.
* @param string $url
* @param array $options Optional.
* @return object
* @see Ripcord_Client
*/
public static function xmlrpcClient($url, $options = null, $transport = null)
{
$options['version'] = 'xmlrpc';
return self::client($url, $options, $transport);
}
/**
* This method returns a new Ripcord client, configured to access a Simple RPC server.
* @param string $url
* @param array $options Optional.
* @return object
* @see Ripcord_Client
*/
public static function simpleClient($url, $options = null, $transport = null)
{
$options['version'] = 'simple';
return self::client($url, $options, $transport);
}
/**
* This method includes a ripcord class, using require_once. Used for autoloading ripcord classes.
* @param string $class The name of the class to load.
* @return boolean
*/
public static function load($class)
{
if (substr($class, 0, 8)=='Ripcord_')
{
$root = dirname(__FILE__).'/ripcord_';
$class = substr($class, 8);
$file = str_replace('.', '', $class);
$file = str_replace('_', '/', $file);
$file = strtolower($file);
while ($file && $file!='.')
{
if ( file_exists($root.$file.'.php') )
{
require_once($root.$file.'.php');
return true;
} else {
$file = dirname($file);
}
}
}
return false;
}
/**
* This method creates a new Ripcord_Client_Call object, which encodes the information needed for
* a method call to an rpc server. This is mostly used for the system.multiCall method.
* @param string $method The name of the method call to encode
* @param mixed $args,... The remainder of the arguments are encoded as parameters to the call
* @return object
*/
public static function encodeCall()
{
self::load('Ripcord_Client');
$params = func_get_args();
$method = array_shift($params);
return new Ripcord_Client_Call( $method, $params );
}
/*
* This method binds the first parameter to the output of a Ripcord client call. If
* the second argument is a Ripcord_Client_Call object, it binds the parameter to it,
* if not it simply assigns the second parameter to the first parameter.
* This means that doing:
* > ripcord::bind( $result, $client->someMethod() )
* will always result in $result eventually containing the return value of $client->someMethod().
* Whether multiCall mode has been enabled or not.
*/
public function bind(&$bound, $call)
{
if ( is_a( $call, 'Ripcord_Client_Call' ) )
{
$call->bound =& $bound;
} else {
$bound = $call;
}
return null;
}
/**
* Method {method} not found. - Thrown by the ripcord server when a requested method isn't found.
*/
const methodNotFound = -1;
/**
* Argument {index} is not a valid Ripcord call - Thrown by the client when passing incorrect arguments to system.multiCall.
*/
const notRipcordCall = -2;
/**
* Cannot recurse system.multiCall - Thrown by the ripcord server when system.multicall is called within itself.
*/
const cannotRecurse = -3;
/**
* Could not access {url} - Thrown by the transport object when unable to access the given url.
*/
const cannotAccessURL = -4;
/**
* PHP XMLRPC library is not installed - Thrown by the ripcord server and client when the xmlrpc library is not installed.
*/
const xmlrpcNotInstalled = -5;
/**
* Variable is not of type datetime - Thrown by the ripcord timestamp method.
*/
const notDatetime = -6;
/**
* Variable is not of type base64 - Thrown by the ripcord binary method.
*/
const notBase64 = -7;
/**
* Variable is not a classname or an object - Thrown by the ripcord server.
*/
const unknownServiceType = -8;
}
/**
* This interface is implemented by all exceptions thrown by Ripcord.
* @package Ripcord
*/
interface Ripcord_Exception {}
/**
* This class is used whenever an when a method passed to the server is invalid.
* - ripcord::methodNotFound (-1) Method {method} not found. - Thrown by the ripcord server when a requested method isn't found.
* @package Ripcord
*/
class Ripcord_BadMethodCallException extends BadMethodCallException implements Ripcord_Exception { }
/**
* This class is used whenever prerequisite requirements are not met.
* - ripcord::xmlrpcNotInstalled (-5) PHP XMLRPC library is not installed - Thrown by the ripcord server and client when the xmlrpc library is not installed.
* @package Ripcord
*/
class Ripcord_ConfigurationException extends Exception implements Ripcord_Exception { }
/**
* This class is used whenever an argument passed to a Ripcord method is invalid for any reason. Possible exceptions thrown are:
* - ripcord::notRipcordCall (-2) Argument {index} is not a valid Ripcord call - Thrown by the client when passing incorrect arguments to system.multiCall.
* - ripcord::cannotRecurse (-3) Cannot recurse system.multiCall - Thrown by the ripcord server when system.multicall is called within itself.
* - ripcord::notDateTime (-6) Variable is not of type datetime - Thrown by the ripcord timestamp method.
* - ripcord::notBase64 (-7) Variable is not of type base64 - Thrown by the ripcord binary method.
* - ripcord::unknownServiceType (-8) Variable is not a classname or an object - Thrown by the ripcord server.
* @package Ripcord
*/
class Ripcord_InvalidArgumentException extends InvalidArgumentException implements Ripcord_Exception { }
/**
* This class is used whenever something goes wrong in sending / receiving data. Possible exceptions thrown are:
* - ripcord::cannotAccessURL (-4) Could not access {url} - Thrown by the transport object when unable to access the given url.
* @package Ripcord
*/
class Ripcord_TransportException extends RuntimeException implements Ripcord_Exception { }
/**
* This class is used for exceptions generated from xmlrpc faults returned by the server. The code and message correspond
* to the code and message from the xmlrpc fault.
* @package Ripcord
*/
class Ripcord_RemoteException extends Exception implements Ripcord_Exception { }
if (function_exists('spl_autoload_register')) {
spl_autoload_register('ripcord::load');
}
?>
<?php
/**
* Ripcord is an easy to use XML-RPC library for PHP.
* @package Ripcord
* @author Auke van Slooten <auke@muze.nl>
* @copyright Copyright (C) 2010, Muze <www.muze.nl>
* @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
* @version Ripcord 0.9 - PHP 5
*/
/**
* Includes the static ripcord factory class and exceptions
*/
require_once(dirname(__FILE__).'/ripcord.php');
/**
* This class implements a simple RPC client, for XML-RPC, (simplified) SOAP 1.1 or Simple RPC. The client abstracts
* the entire RPC process behind native PHP methods. Any method defined by the rpc server can be called as if it was
* a native method of the rpc client.
*
* E.g.
* <code>
* <?php
* $client = ripcord::client( 'http://www.moviemeter.nl/ws' );
* $score = $client->film->getScore( 'e3dee9d19a8c3af7c92f9067d2945b59', 500 );
* ?>
* </code>
*
* The client has a simple interface for the system.multiCall method:
* <code>
* <?php
* $client = ripcord::client( 'http://ripcord.muze.nl/ripcord.php' );
* $client->system->multiCall()->start();
* ripcord::bind( $methods, $client->system->listMethods() );
* ripcord::bind( $foo, $client->getFoo() );
* $client->system->multiCall()->execute();
* ?>
* </code>
*
* The soap client can only handle the basic php types and doesn't understand xml namespaces. Use PHP's SoapClient
* for complex soap calls. This client cannot parse wsdl.
* If you want to skip the ripcord::client factory method, you _must_ provide a transport object explicitly.
*
* @link http://wiki.moviemeter.nl/index.php/API Moviemeter API documentation
* @package Ripcord
*/
class Ripcord_Client
{
/**
* The url of the rpc server
*/
private $_url = '';
/**
* The transport object, used to post requests.
*/
private $_transport = null;
/**
* A list of output options, used with the xmlrpc_encode_request method.
* @see Ripcord_Server::setOutputOption()
*/
private $_outputOptions = array(
"output_type" => "xml",
"verbosity" => "pretty",
"escaping" => array("markup"),
"version" => "xmlrpc",
"encoding" => "utf-8"
);
/**
* The namespace to use when calling a method.
*/
private $_namespace = null;
/**
* A reference to the root client object. This is so when you use namespaced sub clients, you can always
* find the _response and _request data in the root client.
*/
private $_rootClient = null;
/**
* A flag to indicate whether or not to preemptively clone objects passed as arguments to methods, see
* php bug #50282. Only correctly set in the rootClient.
*/
private $_cloneObjects = false;
/**
* A flag to indicate if we are in a multiCall block. Start this with $client->system->multiCall()->start()
*/
protected $_multiCall = false;
/**
* A list of deferred encoded calls.
*/
protected $_multiCallArgs = array();
/**
* The exact response from the rpc server. For debugging purposes.
*/
public $_response = '';
/**
* The exact request from the client. For debugging purposes.
*/
public $_request = '';
/**
* Whether or not to throw exceptions when an xml-rpc fault is returned by the server. Default is false.
*/
public $_throwExceptions = false;
/**
* Whether or not to decode the XML-RPC datetime and base64 types to unix timestamp and binary string
* respectively.
*/
public $_autoDecode = true;
/**
* The constructor for the RPC client.
* @param string $url The url of the rpc server
* @param array $options Optional. A list of outputOptions. See {@link Ripcord_Server::setOutputOption()}
* @param object $rootClient Optional. Used internally when using namespaces.
* @throws Ripcord_ConfigurationException (ripcord::xmlrpcNotInstalled) when the xmlrpc extension is not available.
*/
public function __construct( $url, array $options = null, $transport = null, $rootClient = null )
{
if ( !isset($rootClient) ) {
$rootClient = $this;
if ( !function_exists( 'xmlrpc_encode_request' ) )
{
throw new Ripcord_ConfigurationException('PHP XMLRPC library is not installed',
ripcord::xmlrpcNotInstalled);
}
$version = explode('.', phpversion() );
if ( (0 + $version[0]) == 5) {
if ( ( 0 + $version[1]) < 2 ) {
$this->_cloneObjects = true; // workaround for bug #50282
}
}
}
$this->_rootClient = $rootClient;
$this->_url = $url;
if ( isset($options) )
{
if ( isset($options['namespace']) )
{
$this->_namespace = $options['namespace'];
unset( $options['namespace'] );
}
$this->_outputOptions = $options;
}
if ( isset($transport) ) {
$this->_transport = $transport;
}
}
/**
* This method catches any native method called on the client and calls it on the rpc server instead. It automatically
* parses the resulting xml and returns native php type results.
* @throws Ripcord_InvalidArgumentException (ripcord::notRipcordCall) when handling a multiCall and the
* arguments passed do not have the correct method call information
* @throws Ripcord_RemoteException when _throwExceptions is true and the server returns an XML-RPC Fault.
*/
public function __call($name, $args)
{
if ( isset($this->_namespace) )
{
$name = $this->_namespace . '.' . $name;
}
if ( $name === 'system.multiCall' || $name == 'system.multicall' )
{
if ( !$args || ( is_array($args) && count($args)==0 ) )
{
// multiCall is called without arguments, so return the fetch interface object
return new Ripcord_Client_MultiCall( $this->_rootClient, $name );
} else if ( is_array( $args ) && (count( $args ) == 1) &&
is_array( $args[0] ) && !isset( $args[0]['methodName'] ) )
{
// multicall is called with a simple array of calls.
$args = $args[0];
}
$this->_rootClient->_multiCall = false;
$params = array();
$bound = array();
foreach ( $args as $key => $arg )
{
if ( !is_a( $arg, 'Ripcord_Client_Call' ) &&
(!is_array($arg) || !isset($arg['methodName']) ) )
{
throw new Ripcord_InvalidArgumentException(
'Argument '.$key.' is not a valid Ripcord call',
ripcord::notRipcordCall);
}
if ( is_a( $arg, 'Ripcord_Client_Call' ) )
{
$arg->index = count( $params );
$params[] = $arg->encode();
}
else
{
$arg['index'] = count( $params );
$params[] = array(
'methodName' => $arg['methodName'],
'params' => isset($arg['params']) ?
(array) $arg['params'] : array()
);
}
$bound[$key] = $arg;
}
$args = array( $params );
$this->_rootClient->_multiCallArgs = array();
}
if ( $this->_rootClient->_multiCall ) {
$call = new Ripcord_Client_Call( $name, $args );
$this->_rootClient->_multiCallArgs[] = $call;
return $call;
}
if ($this->_rootClient->_cloneObjects) { //workaround for php bug 50282
foreach( $args as $key => $arg) {
if (is_object($arg)) {
$args[$key] = clone $arg;
}
}
}
$request = xmlrpc_encode_request( $name, $args, $this->_outputOptions );
$response = $this->_transport->post( $this->_url, $request );
$result = xmlrpc_decode( $response );
$this->_rootClient->_request = $request;
$this->_rootClient->_response = $response;
if ( ripcord::isFault( $result ) && $this->_throwExceptions )
{
throw new Ripcord_RemoteException($result['faultString'], $result['faultCode']);
}
if ( isset($bound) && is_array( $bound ) )
{
foreach ( $bound as $key => $callObject )
{
if ( is_a( $callObject, 'Ripcord_Client_Call' ) )
{
$returnValue = $result[$callObject->index];
}
else
{
$returnValue = $result[$callObject['index']];
}
if ( is_array( $returnValue ) && count( $returnValue ) == 1 )
{
// XML-RPC specification says that non-fault results must be in a single item array
$returnValue = current($returnValue);
}
if ($this->_autoDecode)
{
$type = xmlrpc_get_type($returnValue);
switch ($type)
{
case 'base64' :
$returnValue = ripcord::binary($returnValue);
break;
case 'datetime' :
$returnValue = ripcord::timestamp($returnValue);
break;
}
}
if ( is_a( $callObject, 'Ripcord_Client_Call' ) ) {
$callObject->bound = $returnValue;
}
$bound[$key] = $returnValue;
}
$result = $bound;
}
return $result;
}
/**
* This method catches any reference to properties of the client and uses them as a namespace. The
* property is automatically created as a new instance of the rpc client, with the name of the property
* as a namespace.
* @param string $name The name of the namespace
* @return object A Ripcord Client with the given namespace set.
*/
public function __get($name)
{
$result = null;
if ( !isset($this->{$name}) )
{
$result = new Ripcord_Client(
$this->_url,
array_merge($this->_outputOptions, array(
'namespace' => $this->_namespace ?
$this->_namespace . '.' . $name : $name
) ),
$this->_transport,
$this->_rootClient
);
$this->{$name} = $result;
}
return $result;
}
}
/**
* This class provides the fetch interface for system.multiCall. It is returned
* when calling $client->system->multiCall() with no arguments. Upon construction
* it puts the originating client into multiCall deferred mode. The client will
* gather the requested method calls instead of executing them immediately. It
* will them execute all of them, in order, when calling
* $client->system->multiCall()->fetch().
* This class extends Ripcord_Client only so it has access to its protected _multiCall
* property.
*/
class Ripcord_Client_MultiCall extends Ripcord_Client
{
/*
* The reference to the originating client to put into multiCall mode.
*/
private $client = null;
/*
* This method creates a new multiCall fetch api object.
*/
public function __construct( $client, $methodName = 'system.multiCall' )
{
$this->client = $client;
$this->methodName = $methodName;
}
/*
* This method puts the client into multiCall mode. While in this mode all
* method calls are collected as deferred calls (Ripcord_Client_Call).
*/
public function start()
{
$this->client->_multiCall = true;
}
/*
* This method finally calls the clients multiCall method with all deferred
* method calls since multiCall mode was enabled.
*/
public function execute()
{
if ($this->methodName=='system.multiCall') {
return $this->client->system->multiCall( $this->client->_multiCallArgs );
} else { // system.multicall
return $this->client->system->multicall( $this->client->_multiCallArgs );
}
}
}
/**
* This class is used with the Ripcord_Client when calling system.multiCall. Instead of immediately calling the method on the rpc server,
* a Ripcord_Client_Call object is created with all the information needed to call the method using the multicall parameters. The call object is
* returned immediately and is used as input parameter for the multiCall call. The result of the call can be bound to a php variable. This
* variable will be filled with the result of the call when it is available.
* @package Ripcord
*/
class Ripcord_Client_Call
{
/**
* The method to call on the rpc server
*/
public $method = null;
/**
* The arguments to pass on to the method.
*/
public $params = array();
/**
* The index in the multicall request array, if any.
*/
public $index = null;
/**
* A reference to the php variable to fill with the result of the call, if any.
*/
public $bound = null;
/**
* The constructor for the Ripcord_Client_Call class.
* @param string $method The name of the rpc method to call
* @param array $params The parameters for the rpc method.
*/
public function __construct($method, $params)
{
$this->method = $method;
$this->params = $params;
}
/**
* This method allows you to bind a php variable to the result of this method call.
* When the method call's result is available, the php variable will be filled with
* this result.
* @param mixed $bound The variable to bind the result from this call to.
* @return object Returns this object for chaining.
*/
public function bind(&$bound)
{
$this->bound =& $bound;
return $this;
}
/**
* This method returns the correct format for a multiCall argument.
* @return array An array with the methodName and params
*/
public function encode() {
return array(
'methodName' => $this->method,
'params' => (array) $this->params
);
}
}
/**
* This interface describes the minimum interface needed for the transport object used by the
* Ripcord_Client
* @package Ripcord
*/
interface Ripcord_Transport
{
/**
* This method must post the request to the given url and return the results.
* @param string $url The url to post to.
* @param string $request The request to post.
* @return string The server response
*/
public function post( $url, $request );
}
/**
* This class implements the Ripcord_Transport interface using PHP streams.
* @package Ripcord
*/
class Ripcord_Transport_Stream implements Ripcord_Transport
{
/**
* A list of stream context options.
*/
private $options = array();
/**
* Contains the headers sent by the server.
*/
public $responseHeaders = null;
/**
* This is the constructor for the Ripcord_Transport_Stream class.
* @param array $contextOptions Optional. An array with stream context options.
*/
public function __construct( $contextOptions = null )
{
if ( isset($contextOptions) )
{
$this->options = $contextOptions;
}
}
/**
* This method posts the request to the given url.
* @param string $url The url to post to.
* @param string $request The request to post.
* @return string The server response
* @throws Ripcord_TransportException (ripcord::cannotAccessURL) when the given URL cannot be accessed for any reason.
*/
public function post( $url, $request )
{
$options = array_merge(
$this->options,
array(
'http' => array(
'method' => "POST",
'header' => "Content-Type: text/xml",
'content' => $request
)
)
);
$context = stream_context_create( $options );
$result = @file_get_contents( $url, false, $context );
$this->responseHeaders = $http_response_header;
if ( !$result )
{
throw new Ripcord_TransportException( 'Could not access ' . $url,
ripcord::cannotAccessURL );
}
return $result;
}
}
/**
* This class implements the Ripcord_Transport interface using CURL.
* @package Ripcord
*/
class Ripcord_Transport_CURL implements Ripcord_Transport
{
/**
* A list of CURL options.
*/
private $options = array();
/**
* A flag that indicates whether or not we can safely pass the previous exception to a new exception.
*/
private $skipPreviousException = false;
/**
* Contains the headers sent by the server.
*/
public $responseHeaders = null;
/**
* This is the constructor for the Ripcord_Transport_CURL class.
* @param array $curlOptions A list of CURL options.
*/
public function __construct( $curlOptions = null )
{
if ( isset($curlOptions) )
{
$this->options = $curlOptions;
}
$version = explode('.', phpversion() );
if ( ( (0 + $version[0]) == 5) && ( 0 + $version[1]) < 3 ) { // previousException supported in php >= 5.3
$this->_skipPreviousException = true;
}
}
/**
* This method posts the request to the given url
* @param string $url The url to post to.
* @param string $request The request to post.
* @throws Ripcord_TransportException (ripcord::cannotAccessURL) when the given URL cannot be accessed for any reason.
* @return string The server response
*/
public function post( $url, $request)
{
$curl = curl_init();
$options = (array) $this->options + array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $request,
CURLOPT_HEADER => true
);
curl_setopt_array( $curl, $options );
$contents = curl_exec( $curl );
$headerSize = curl_getinfo( $curl, CURLINFO_HEADER_SIZE );
$this->responseHeaders = substr( $contents, 0, $headerSize );
$contents = substr( $contents, $headerSize );
if ( curl_errno( $curl ) )
{
$errorNumber = curl_errno( $curl );
$errorMessage = curl_error( $curl );
curl_close( $curl );
$version = explode('.', phpversion() );
if (!$this->_skipPreviousException) { // previousException supported in php >= 5.3
$exception = new Ripcord_TransportException( 'Could not access ' . $url
, ripcord::cannotAccessURL
, new Exception( $errorMessage, $errorNumber )
);
} else {
$exception = new Ripcord_TransportException( 'Could not access ' . $url
. ' ( original CURL error: ' . $errorMessage . ' ) ',
ripcord::cannotAccessURL
);
}
throw $exception;
}
curl_close($curl);
return $contents;
}
}
?>
<?php
/**
* Ripcord is an easy to use XML-RPC library for PHP.
* @package Ripcord
* @author Auke van Slooten <auke@muze.nl>
* @copyright Copyright (C) 2010, Muze <www.muze.nl>
* @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
* @version Ripcord 0.9 - PHP 5
*/
/**
* This interface defines the minimum methods any documentor needs to implement.
* @package Ripcord
*/
interface Ripcord_Documentor_Interface
{
public function setMethodData( $methods );
public function handle( $rpcServer );
public function getIntrospectionXML();
}
/**
* This class implements the default documentor for the ripcord server. Any request to the server
* without a request_xml is handled by the documentor.
* @package Ripcord
*/
class Ripcord_Documentor implements Ripcord_Documentor_Interface
{
/**
* The object to parse the docComments.
*/
private $docCommentParser = null;
/**
* The name of the rpc server, used as the title and heading of the default HTML page.
*/
public $name = 'Ripcord: Simple RPC Server';
/**
* A url to an optional css file or a css string for an inline stylesheet.
*/
public $css = "
html {
font-family: georgia, times, serif;
font-size: 79%;
background-color: #EEEEEE;
}
h1 {
font-family: 'arial black', helvetica, sans-serif;
font-size: 2em;
font-weight: normal;
margin: -20px -21px 0.4em -20px;
padding: 40px 20px 20px;
background: #01648E; /* for non-css3 browsers */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00486E', endColorstr='#09799E'); /* for IE */
background: -webkit-gradient(linear, left top, left bottom, from(#00486E), to(#09799E)); /* for webkit browsers */
background: -moz-linear-gradient(top, #00486E, #09799E); /* for firefox 3.6+ */
color: white;
border-bottom: 4px solid black;
text-shadow: black 0.1em 0.1em 0.2em;
}
h2 {
font-family: arial, helvetica, sans-serif;
font-weight: bold;
font-size: 1.4em;
color: #444444;
text-shadow: #AAAAAA 0.1em 0.1em 0.2em;
margin-top: 2.5em;
border-bottom: 1px solid #09799E;
}
h3 {
font-family: arial, helvetica, sans-serif;
font-weight: normal;
font-size: 1.4em;
color: #555555;
text-shadow: #AAAAAA 0.1em 0.1em 0.2em;
margin-bottom: 0px;
}
div.signature {
font-family: courier, monospace;
margin-bottom: 1.4em;
}
ul, ol, li {
margin: 0px;
padding: 0px;
}
ul, ol {
color: #09799E;
margin-bottom: 1.4em;
}
ul li {
list-style: square;
}
ul li, ol li {
margin-left: 20px;
}
li span, li label {
color: black;
}
li.param label {
font-family: courier, monospace;
padding-right: 1.4em;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body {
background-color: white;
width: 830px;
margin: 10px auto;
padding: 20px;
-moz-box-shadow: 5px 5px 5px #ccc;
-webkit-box-shadow: 5px 5px 5px #ccc;
box-shadow: 5px 5px 5px #ccc;
}
code {
display: block;
background-color: #999999;
padding: 10px;
margin: 0.4em 0px 1.4em 0px;
color: white;
white-space: pre;
font-family: courier, monospace;
font-size: 1.2em;
}
.tag, .argName, .argType {
margin-right: 10px;
}
.argument {
margin-left: 20px;
}
.footer {
font-family: helvetica, sans-serif;
font-size: 0.9em;
font-weight: normal;
margin: 0px -21px -20px -20px;
padding: 20px;
background: #01648E; /* for non-css3 browsers */
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00486E', endColorstr='#09799E'); /* for IE */
background: -webkit-gradient(linear, left top, left bottom, from(#00486E), to(#09799E)); /* for webkit browsers */
background: -moz-linear-gradient(top, #00486E, #09799E); /* for firefox 3.6+ */
color: white;
}
.footer a {
color: white;
text-decoration: none;
}
";
/**
* The wsdl 1.0 description.
*/
public $wsdl = false;
/**
* The wsdl 2.0 description
*/
public $wsdl2 = false;
/**
* Which version of the XML vocabulary the server implements. Either 'xmlrpc', 'soap 1.1', 'simple' or 'auto'.
*/
public $version = 'auto';
/**
* The root URL of the rpc server.
*/
public $root = '';
/**
* Optional header text for the online documentation.
*/
public $header = '';
/**
* Optional footer text for the online documentation.
*/
public $footer = '';
/**
* A list of method data, containing all the user supplied methods the rpc server implements.
*/
private $methods = null;
/**
* The constructor for the Ripcord_Documentor class.
* @param array $options. Optional. Allows you to set the public properties of this class upon construction.
*/
public function __construct( $options = null, $docCommentParser = null )
{
$check = array( 'name', 'css', 'wsdl', 'wsdl2', 'root', 'version', 'header', 'footer' );
foreach ( $check as $name )
{
if ( isset($options[$name]) )
{
$this->{$name} = $options[$name];
}
}
$this->docCommentParser = $docCommentParser;
}
/**
* This method fills the list of method data with all the user supplied methods of the rpc server.
* @param array $methodData A list of methods with name and callback information.
*/
public function setMethodData( $methodData )
{
$this->methods = $methodData;
}
/**
* This method handles any request which isn't a valid rpc request.
* @param object $rpcServer A reference to the active rpc server.
*/
public function handle( $rpcServer )
{
$methods = $rpcServer->call('system.listMethods');
echo '<!DOCTYPE html>';
echo '<html><head><title>' . $this->name . '</title>';
if ( isset($this->css) )
{
if (strpos($this->css, "\n")!==false) {
echo '<style type="text/css">'.$this->css.'</style>';
} else {
echo '<link rel="stylesheet" type="text/css" href="' . $this->css . '">';
}
}
echo '</head><body>';
echo '<div class="content">';
echo '<h1>' . $this->name . '</h1>';
echo $this->header;
echo '<p>';
$showWSDL = false;
switch ( $this->version )
{
case 'xmlrpc':
echo 'This server implements the <a href="http://www.xmlrpc.com/spec">XML-RPC specification</a>';
break;
case 'simple':
echo 'This server implements the <a href="http://sites.google.com/a/simplerpc.org/simplerpc/Home/simplerpc-specification-v09">SimpleRPC 1.0 specification</a>';
break;
case 'auto';
echo 'This server implements the <a href="http://www.w3.org/TR/2000/NOTE-SOAP-20000508/">SOAP 1.1</a>, <a href="http://www.xmlrpc.com/spec">XML-RPC</a> and <a href="http://sites.google.com/a/simplerpc.org/simplerpc/Home/simplerpc-specification-v09">SimpleRPC 1.0</a> specification.';
$showWSDL = true;
break;
case 'soap 1.1':
echo 'This server implements the <a href="http://www.w3.org/TR/2000/NOTE-SOAP-20000508/">SOAP 1.1 specification</a>.';
$showWSDL = true;
break;
}
echo '</p>';
if ( $showWSDL && ( $this->wsdl || $this->wsdl2 ) )
{
echo '<ul>';
if ($this->wsdl)
{
echo '<li><a href="' . $this->root . '?wsdl">WSDL 1.1 Description</a></li>';
}
if ($this->wsdl2)
{
echo '<li><a href="' . $this->root . '?wsdl2">WSDL 2.0 Description</a></li>';
}
echo '</ul>';
}
$methods = $rpcServer->call( 'system.describeMethods' );
$allMethods = array();
$allFunctions = array();
foreach( $methods['methodList'] as $index => $method )
{
if ( strpos( $method['name'], '.' ) !== false )
{
$allMethods[ $method['name'] ] = $index;
} else {
$allFunctions[ $method['name'] ] = $index;
}
}
ksort( $allMethods );
ksort( $allFunctions );
$allMethods = $allFunctions + $allMethods;
echo '<div class="index"><h2>Methods</h2><ul>';
foreach ( $allMethods as $methodName => $methodIndex )
{
echo '<li><a href="#method_' . (int)$methodIndex . '">' . $methodName . '</a></li>';
}
echo '</ul></div>';
$currentClass = '';
$class = '';
echo '<div class="functions">';
foreach ( $allMethods as $methodName => $methodIndex )
{
$method = $methods['methodList'][$methodIndex];
$pos = strpos( $methodName, '.');
if ( $pos !== false )
{
$class = substr( $methodName, 0, $pos );
}
if ( $currentClass != $class )
{
echo '</div>';
echo '<div class="class_'.$class.'">';
$currentClass = $class;
}
echo '<h2 id="method_'.$methodIndex.'">' . $method['name'] . '</h2>';
if ( isset( $method['signatures'] ) )
{
foreach ( $method['signatures'] as $signature )
{
echo '<div class="signature">';
if ( is_array( $signature['returns'] ) ) {
$return = $signature['returns'][0];
echo '(' . $return['type'] . ') ';
}
echo $method['name'] . '(';
$paramInfo = false;
if ( is_array( $signature['params'] ) )
{
$paramInfo = $signature['params'];
$params = '';
foreach ( $signature['params'] as $param )
{
$params .= ', (' . $param['type'] . ') ' . $param['name'] . ' ';
}
echo substr($params, 1);
}
echo ')</div>';
if ( is_array( $paramInfo ) )
{
echo '<div class="params"><h3>Parameters</h3><ul>';
foreach ( $paramInfo as $param )
{
echo '<li class="param">';
echo '<label>(' . $param['type'] . ') ' . $param['name'] . '</label> ';
echo '<span>' . $param['description'] . '</span>';
echo '</li>';
}
echo '</ul></div>';
}
}
}
if ( $method['purpose'] )
{
echo '<div class="purpose">' . $method['purpose'] . '</div>';
}
if ( isset( $method['notes'] ) && is_array( $method['notes'] ) )
{
echo '<div class="notes"><h3>Notes</h3><ol>';
foreach ( $method['notes'] as $note )
{
echo '<li><span>' . $note. '</span></li>';
}
echo '</ol></div>';
}
if ( isset( $method['see'] ) && is_array( $method['see'] ) )
{
echo '<div class="see">';
echo '<h3>See</h3>';
echo '<ul>';
foreach ( $method['see'] as $link => $description)
{
echo '<li>';
if ( isset( $allMethods[$link] ) )
{
echo '<a href="#method_' . (int)$allMethods[$link] .'">' . $link . '</a> <span>' . $description . '</span>';
} else {
echo '<span>' . $link . ' ' . $description . '</span>';
}
echo '</li>';
}
echo '</ul></div>';
}
}
echo '</div>';
echo $this->footer;
echo '<div class="footer">';
echo 'Powered by <a href="http://ripcord.googlecode.com/">Ripcord : Simple RPC Server</a>.';
echo '</div>';
echo '</div></body></html>';
}
/**
* This method returns an XML document in the introspection format expected by
* xmlrpc_server_register_introspection_callback. It uses the php Reflection
* classes to gather information from the registered methods.
* Descriptions are added from phpdoc docblocks if found.
* @return string XML string with the introspection data.
*/
function getIntrospectionXML()
{
$xml = "<?xml version='1.0' ?><introspection version='1.0'><methodList>";
if ( isset($this->methods) && is_array( $this->methods ) )
{
foreach ($this->methods as $method => $methodData )
{
if ( is_array( $methodData['call'] ) )
{
$reflection = new ReflectionMethod(
$methodData['call'][0],
$methodData['call'][1]
);
}
else
{
$reflection = new ReflectionFunction( $methodData['call'] );
}
$description = $reflection->getDocComment();
if ( $description && $this->docCommentParser )
{
$data = $this->docCommentParser->parse( $description );
if ($data['description'])
{
$description = $data['description'];
}
}
if ($description)
{
$description = '<p>' . str_replace( array( "\r\n\r\n", "\n\n") , '</p><p>', $description)
. '</p>';
}
if ( is_array( $data ) )
{
foreach( $data as $key => $value )
{
switch( $key )
{
case 'category' :
case 'deprecated' :
case 'package' :
$description .= '<div class="' . $key . '"><span class="tag">'
. $key . '</span>' . $value .'</div>';
break;
default :
break;
}
}
}
$xml .= '<methodDescription name="' . $method . '"><purpose><![CDATA['
. $description . ']]></purpose>';
if ( is_array( $data ) && ( isset( $data['arguments'] ) || isset( $data['return'] ) ) )
{
$xml .= '<signatures><signature>';
if ( isset( $data['arguments'] ) && is_array($data['arguments']) )
{
$xml .= '<params>';
foreach ( $data['arguments'] as $name => $argument )
{
if ( $name[0] == '$' )
{
$name = substr( $name, 1 );
}
$xml .= '<value type="' . htmlspecialchars( $argument['type'] )
. '" name="' . htmlspecialchars( $name ) . '"><![CDATA[' . $argument['description']
. ']]></value>';
}
$xml .= '</params>';
}
if ( isset( $data['return'] ) && is_array( $data['return'] ) )
{
$xml .= '<returns><value type="' . htmlspecialchars($data['return']['type'])
. '"><![CDATA[' . $data['return']['description'] . ']]></value></returns>';
}
$xml .= '</signature></signatures>';
}
$xml .= '</methodDescription>';
}
}
$xml .= "</methodList></introspection>";
return $xml;
}
}
/**
* This interface describes the minimum interface needed for a comment parser object used by the
* Ripcord_Documentor
* @package Ripcord
*/
interface Ripcord_Documentor_Parser
{
/**
* This method parses a given docComment block and returns an array with information.
* @param string $commentBlock The docComment block.
* @return array The parsed information.
*/
public function parse( $commentBlock );
}
/**
* This class implements the Ripcord_Documentor_Parser interface, parsing the docComment
* as a phpdoc style docComment.
* @package Ripcord
*/
class Ripcord_Documentor_Parser_phpdoc implements Ripcord_Documentor_Parser
{
/**
* This method parses a given docComment block and returns an array with information.
* @param string $commentBlock The docComment block.
* @return array The parsed information.
*/
public function parse( $commentBlock)
{
$this->currentTag = 'description';
$description = preg_replace('/^(\s*(\/\*\*|\*\/|\*))/m', '', $commentBlock);
$info = array();
$lines = explode( "\n", $description );
foreach ( $lines as $line ) {
$info = $this->parseLine( $line, $info );
}
return $info; //array( 'description' => $description );
}
/**
* This method parses a single line from the comment block.
*/
private function parseLine( $line, $info )
{
$handled = false;
if (preg_match('/^\s*(@[a-z]+)\s(.*)$/i', $line, $matches))
{
$this->currentTag = substr($matches[1], 1);
$line = trim( substr($line, strlen($this->currentTag)+2 ) );
switch( $this->currentTag )
{
case 'param' :
if ( preg_match('/^\s*([[:alpha:]|]+)\s([[:alnum:]$_]+)(.*)$/i', $line, $matches) )
{
if ( !isset($info['arguments']) ) {
$info['arguments'] = array();
}
if ( !isset($info['arguments'][$matches[2]]) ) {
$info['arguments'][$matches[2]] = array('description' => '');
}
$info['arguments'][$matches[2]]['type'] = $matches[1];
$info['arguments'][$matches[2]]['description'] .= $this->parseDescription($matches[3]);
}
$handled = true;
break;
case 'return' :
if ( preg_match('/^\s*([[:alpha:]|]+)\s(.*)$/i', $line, $matches) )
{
if ( !isset($info['return']) ) {
$info['return'] = array( 'description' => '' );
}
$info['return']['type'] = $matches[1];
$info['return']['description'] .= $this->parseDescription($matches[2]);
}
$handled = true;
break;
}
}
if (!$handled) {
switch( $this->currentTag) {
case 'param' :
case 'return' :
if ( !isset( $info[$this->currentTag] ) ) {
$info[$this->currentTag] = array();
}
$info[$this->currentTag]['description'] .= $this->parseDescription($line);
break;
default:
if ( !isset( $info[$this->currentTag] ) ) {
$info[$this->currentTag] = '';
}
$info[$this->currentTag] .= $this->parseDescription($line);
break;
}
}
return $info;
}
/**
* This method parses only the text description part of a line of the comment block.
*/
private function parseDescription( $line ) {
while ( preg_match('/{@([^}]*)}/', $line, $matches) ) {
switch( $matches[1] ) {
case 'internal' :
$line = str_replace( $matches[0], '', $line );
break;
default :
$line = str_replace( $matches[0], $matches[1], $line );
break;
}
}
$line = str_replace( array( '\@', '{@*}' ), array( '@', '*/' ), $line );
return $line;
}
}
?>
<?php
/**
* Ripcord is an easy to use XML-RPC library for PHP.
* @package Ripcord
* @author Auke van Slooten <auke@muze.nl>
* @copyright Copyright (C) 2010, Muze <www.muze.nl>
* @license http://opensource.org/licenses/gpl-3.0.html GNU Public License
* @version Ripcord 0.9 - PHP 5
*/
/**
* Includes the static ripcord factory class and exceptions
*/
require_once(dirname(__FILE__).'/ripcord.php');
/**
* This class implements the Ripcord server. It is an OO wrapper around PHP's XML-RPC methods, with some added features.
* You can create an XML-RPC (or Simple RPC or a simple SOAP 1.1) server by defining a class with public methods and passing
* an object (or array of objects) of this class to the constructor of Ripcord_Server. Then simply call the run() method.
*
* A basic example:
* <code>
* <?php
* $myObject = new MyClass();
* $server = ripcord::server( $myObject );
* $server->run();
* ?>
* </code>
*
* An example with namespaces in the method names and a static class as rpc service.
* <code>
* <?php
* $myObject = new MyClass();
* $server = ripcord::server(
* array(
* 'namespace1' => $myObject,
* 'namespace2' => 'myOtherClass'
* )
* );
* $server->run();
* ?>
* </code>
*
* You don't need to instantiate a class to use it with Ripcord, in the above example 'myOtherClass' is the
* name of a PHP class to use. In addition you may also specify functions or methods directly, in any format
* that matches PHP's is_callable() criteria.
* @package Ripcord
*/
/*
TODO:
- create seperate interface for encoding / decoding requests
- create xmlrpc-epi class using xmlrpc_encode/decode for xml-rpc, simple-rpc and for now soap
- add json-rpc class (http://json-rpc.org/wiki/specification)
- pass list of protocol parsers/generators in the constructor of the server
- protocol must know how to handle the system.* methods
*/
class Ripcord_Server
{
/**
* Contains a reference to the Ripcord documentor object.
* @see Ripcord_Documentor
*/
private $documentor = null;
/**
* Contains a reference to the XML-RPC server created with xmlrpc_server_create.
*/
private $xmlrpc = null;
/**
* Contains a list of methods set for this server. Excludes the system.* methods automatically
* created by PHP's xmlrpc_server_create.
*/
private $methods = array();
/**
* Contains an array with outputOptions, used when calling methods on the xmlrpc server created with
* xmlrpc_server_create. These options can be overridden through the $options parameter of the
* Ripcord_Server constructor.
* @see Ripcord_Server::setOutputOption()
*/
private $outputOptions = array(
"output_type" => "xml",
"verbosity" => "pretty",
"escaping" => array("markup"),
"version" => "auto",
"encoding" => "utf-8"
);
/**
* Creates a new instance of the Ripcord server.
* @param mixed $services. Optional. An object or array of objects. The public methods in these objects will be exposed
* through the RPC server. If the services array has non-numeric keys, the key for each object will define its namespace.
* @param array $options. Optional. Allows you to override the default server settings. Accepted key names are:
* - 'documentor': allows you to specify an alternative HTML documentor class, or if set to false, no HTML documentor.
* - 'name' : The name of the server, used by the default HTML documentor.
* - 'css' : An url of a css file to link to in the HTML documentation.
* - 'wsdl' : The wsdl 1.0 description of this service (only usefull if you run the 'soap 1.1' version, or the 'auto' version
* - 'wsdl2' : The wsdl 2.0 description of this service
* In addition you can set any of the outputOptions for the xmlrpc server.
* @see Ripcord_Server::setOutputOption()
* @throws Ripcord_InvalidArgumentException (ripcord::unknownServiceType) when passed an incorrect service
* @throws Ripcord_ConfigurationException (ripcord::xmlrpcNotInstalled) when the xmlrpc extension in not available.
*/
function __construct($services = null, $options = null, $documentor = null)
{
if ( !function_exists( 'xmlrpc_server_create' ) )
{
throw new Ripcord_ConfigurationException('PHP XMLRPC library is not installed',
ripcord::xmlrpcNotInstalled );
}
libxml_disable_entity_loader(); // prevents XXE attacks
$this->xmlrpc = xmlrpc_server_create();
if (isset($services))
{
if (is_array($services))
{
foreach ($services as $serviceName => $service)
{
$this->addService($service, $serviceName);
}
} else {
$this->addService($services);
}
}
if ( isset($documentor) && is_object($documentor) ) {
$this->documentor = $documentor;
xmlrpc_server_register_introspection_callback( $this->xmlrpc,
array( $this->documentor, 'getIntrospectionXML') );
}
if ( isset($options) )
{
$this->outputOptions = array_merge($this->outputOptions, $options);
}
}
/**
* Allows you to add a service to the server after construction.
* @param object $service The object or class whose public methods must be added to the rpc server. May also be a function or method.
* @param string $serviceName Optional. The namespace for the methods.
* @throws Ripcord_InvalidArgumentException (ripcord::unknownServiceType) when passed an incorrect service
*/
public function addService($service, $serviceName = 0)
{
if ( is_object( $service ) )
{
$reflection = new ReflectionObject( $service );
}
else if ( is_string( $service ) && class_exists( $service ) )
{
$reflection = new ReflectionClass( $service );
}
else if ( is_callable( $service ) ) // method passed directly
{
$this->addMethod( $serviceName, $service );
return;
}
else
{
throw new Ripcord_InvalidArgumentException( 'Unknown service type ' . $serviceName,
ripcord::unknownServiceType );
}
if ( $serviceName && !is_numeric( $serviceName ) )
{
$serviceName .= '.';
}
else
{
$serviceName = '';
}
$methods = $reflection->getMethods();
if ( is_array( $methods ) )
{
foreach( $methods as $method )
{
if ( substr( $method->name, 0, 1 ) != '_'
&& !$method->isPrivate() && !$method->isProtected())
{
$rpcMethodName = $serviceName . $method->name;
$this->addMethod(
$rpcMethodName,
array( $service, $method->name )
);
}
}
}
}
/**
* Allows you to add a single method to the server after construction.
* @param string $name The name of the method as exposed through the rpc server
* @param callback $method The name of the method to call, or an array with classname or object and method name.
*/
public function addMethod($name, $method)
{
$this->methods[$name] = array(
'name' => $name,
'call' => $method
);
xmlrpc_server_register_method( $this->xmlrpc, $name, array( $this, 'call' ) );
}
/**
* Runs the rpc server. Automatically handles an incoming request.
*/
public function run()
{
if ($this->documentor) {
$this->documentor->setMethodData( $this->methods );
}
$request_xml = file_get_contents( 'php://input' );
if ( !$request_xml )
{
if ( ( $query = $_SERVER['QUERY_STRING'] )
&& isset($this->wsdl[$query]) && $this->wsdl[$query] )
{
header('Content-type: text/xml');
header('Access-Control-Allow-Origin: *');
$wsdl = $this->wsdl[$query];
header('Content-Length: '.strlen($wsdl) );
echo $wsdl;
}
else if ( $this->documentor )
{
header('Content-type: text/html; charset=' . $this->outputOptions['encoding']);
$this->documentor->handle( $this, $this->methods );
}
else
{
// FIXME: add check for json-rpc protocol, if set and none of the xml protocols are set, use that
header('Content-type: text/xml');
header('Access-Control-Allow-Origin: *');
$result = xmlrpc_encode_request(
null,
ripcord::fault( -1, 'No request xml found.' ),
$this->outputOptions
);
header('Content-Length: '.strlen( $result ) );
echo $result;
}
}
else
{
// FIXME: add check for the protocol of the request, could be json-rpc, then check if it is supported.
header('Content-type: text/xml');
header('Access-Control-Allow-Origin: *');
$result = $this->handle( $request_xml );
header('Content-Length: '.strlen($result) );
echo $result;
}
}
/**
* This method wraps around xmlrpc_decode_request, since it is borken in many ways. This wraps
* around all the ugliness needed to make it not dump core and not print expat warnings.
*/
private function parseRequest( $request_xml ) {
$xml = @simplexml_load_string($request_xml);
if (!$xml && !$xml->getNamespaces()) {
// FIXME: check for protocol json-rpc
//simplexml in combination with namespaces (soap) lets $xml evaluate to false
return xmlrpc_encode_request(
null,
ripcord::fault( -3, 'Invalid Method Call - Ripcord Server accepts only XML-RPC, SimpleRPC or SOAP 1.1 calls'),
$this->outputOptions
);
} else {
// prevent segmentation fault on incorrect xmlrpc request (without methodName)
$methodCall = $xml->xpath('//methodCall');
if ($methodCall) { //xml-rpc
$methodName = $xml->xpath('//methodName');
if (!$methodName) {
return xmlrpc_encode_request(
null,
ripcord::fault( -3, 'Invalid Method Call - No methodName given'),
$this->outputOptions
);
}
}
}
$method = null;
ob_start(); // xmlrpc_decode echo expat errors if the xml is not valid, can't stop it.
$params = xmlrpc_decode_request($request_xml, $method);
ob_end_clean(); // clean up any xml errors
return array( 'methodName' => $method, 'params' => $params );
}
/**
* This method implements the system.multiCall method without dumping core. The built-in method from the
* xmlrpc library dumps core when you have registered any php methods, fixed in php 5.3.2
*/
private function multiCall( $params = null ) {
if ( $params && is_array( $params ) )
{
$result = array();
$params = $params[0];
foreach ( $params as $param ) {
$method = $param['methodName'];
$args = $param['params'];
try {
// XML-RPC specification says that non-fault results must be in a single item array
$result[] = array( $this->call($method, $args) );
} catch( Exception $e) {
$result[] = ripcord::fault( $e->getCode(), $e->getMessage() );
}
}
$result = xmlrpc_encode_request( null, $result, $this->outputOptions );
} else {
$result = xmlrpc_encode_request(
null,
ripcord::fault( -2, 'Illegal or no params set for system.multiCall'),
$this->outputOptions
);
}
return $result;
}
/**
* Handles the given request xml
* @param string $request_xml The incoming request.
* @return string
*/
public function handle($request_xml)
{
$result = $this->parseRequest( $request_xml );
if (!$result || ripcord::isFault( $result ) )
{
return $result;
}
else
{
$method = $result['methodName'];
$params = $result['params'];
}
if ( $method == 'system.multiCall' || $method == 'system.multicall' ) {
// php's xml-rpc server (xmlrpc-epi) crashes on multicall, so handle it ourselves... fixed in php 5.3.2
$result = $this->multiCall( $params );
} else {
try {
$result = xmlrpc_server_call_method(
$this->xmlrpc, $request_xml, null, $this->outputOptions
);
} catch( Exception $e) {
$result = xmlrpc_encode_request(
null,
ripcord::fault( $e->getCode(), $e->getMessage() ),
$this->outputOptions
);
}
}
return $result;
}
/**
* Calls a method by its rpc name.
* @param string $method The rpc name of the method
* @param array $args The arguments to this method
* @return mixed
* @throws Ripcord_InvalidArgumentException (ripcord::cannotRecurse) when passed a recursive multiCall
* @throws Ripcord_BadMethodCallException (ripcord::methodNotFound) when the requested method isn't available.
*/
public function call( $method, $args = null )
{
if ( isset( $this->methods[$method] ) )
{
$call = $this->methods[$method]['call'];
return call_user_func_array( $call, $args);
} else {
if ( substr( $method, 0, 7 ) == 'system.' )
{
if ( $method == 'system.multiCall' ) {
throw new Ripcord_InvalidArgumentException(
'Cannot recurse system.multiCall', ripcord::cannotRecurse );
}
// system methods are handled internally by the xmlrpc server, so we've got to create a makebelieve request,
// there is no other way because of a badly designed API
$req = xmlrpc_encode_request( $method, $args, $this->outputOptions );
$result = xmlrpc_server_call_method( $this->xmlrpc, $req, null,
$this->outputOptions);
return xmlrpc_decode( $result );
} else {
throw new Ripcord_BadMethodCallException( 'Method '.$method.' not found.',
ripcord::methodNotFound );
}
}
}
/**
* Allows you to set specific output options of the server after construction.
* @param string $option The name of the option
* @param mixed $value The value of the option
* The options are:
* - output_type: Return data as either php native data or xml encoded. Can be either 'php' or 'xml'. 'xml' is the default.
* - verbosity: Determines the compactness of generated xml. Can be either 'no_white_space', 'newlines_only' or 'pretty'.
* 'pretty' is the default.
* - escaping: Determines how/whether to escape certain characters. 1 or more values are allowed. If multiple, they need
* to be specified as a sub-array. Options are: 'cdata', 'non-ascii', 'non-print' and 'markup'. Default is 'non-ascii',
* 'non-print' and 'markup'.
* - version: Version of the xml vocabulary to use. Currently, three are supported: 'xmlrpc', 'soap 1.1' and 'simple'. The
* keyword 'auto' is also recognized and tells the server to respond in whichever version the request cam in. 'auto' is
* the default.
* - encoding: The character encoding that the data is in. Can be any supported character encoding. Default is 'utf-8'.
*/
public function setOutputOption($option, $value)
{
if ( isset($this->outputOptions[$option]) )
{
$this->outputOptions[$option] = $value;
return true;
} else {
return false;
}
}
}
?>
<?php
require_once('login.php');
// search
// $result = $model->execute_kw(
// $db,$uid,$password,"student","search",
// array(
// array(
// // array("id","=",1),
// // array("name","=","Admin")
// ),
// )
// );
// read
// $data = $model->execute_kw(
// $db,
// $uid,
// $password,
// "student",
// "read",
// array($result),
// array('fields'=>array("name","age","number"))
// );
$result2 = $model->execute_kw(
$db,$uid,$password,"student","search_read",
array(
array(
// array("id","=",1),
array("name","ilike","ali")
),
),
array('fields'=>array("name","age","number"))
);
echo json_encode($result2);
?>
<?php
require_once('login.php'); // import login php file
// $no = (int) $_GET['id'];
// $name = $_GET['name'];
$is_update = $model->execute_kw(
$db, //database name
$uid,
$password,
"student",
"write",
array(
array(6),
array("name"=>"Alaa Ahmed Ibrahim Osman")
),
);
echo json_encode($is_update);
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment