move IXR XML RPC to composer dependency #1970

This is a monky patched version of kissyfrot/php-ixr until my PR has
been accepted upstream.

Plugins using the XML RPC client may need adjustments.
This commit is contained in:
Andreas Gohr 2021-09-16 15:22:50 +02:00
parent dc2ffedf4d
commit 7f8f24562b
34 changed files with 2316 additions and 1796 deletions

View File

@ -33,13 +33,13 @@ class XmlRpcServerTest extends DokuWikiTest
$pageName = ":wiki:dokuwiki";
$file = wikiFN($pageName);
$timestamp = filemtime($file);
$ixrModifiedTime = (new DateTime('@' . $timestamp))->format(IXR_Date::XMLRPC_ISO8601);
$ixrModifiedTime = (new DateTime('@' . $timestamp))->format(DateTime::ATOM);
$request = <<<EOD
<?xml version="1.0"?>
<methodCall>
<methodName>wiki.getPageInfo</methodName>
<param>
<param>
<value>
<string>$pageName</string>
</value>
@ -64,6 +64,6 @@ EOD;
EOD;
$this->server->serve($request);
$this->assertEquals(trim($expected), trim($this->server->output));
$this->assertXmlStringEqualsXmlString(trim($expected), trim($this->server->output));
}
}

View File

@ -1,44 +0,0 @@
<?php
require_once DOKU_INC.'inc/IXR_Library.php';
/**
* Class ixr_library_date_test
*/
class ixr_library_date_test extends DokuWikiTest {
function test_parseIso(){
// multiple tests
$tests = array(
// full datetime, different formats
array('2010-08-17T09:23:14', 1282036994),
array('20100817T09:23:14', 1282036994),
array('2010-08-17 09:23:14', 1282036994),
array('20100817 09:23:14', 1282036994),
array('2010-08-17T09:23:14Z', 1282036994),
array('20100817T09:23:14Z', 1282036994),
// with timezone
array('2010-08-17 09:23:14+0000', 1282036994),
array('2010-08-17 09:23:14+00:00', 1282036994),
array('2010-08-17 12:23:14+03:00', 1282036994),
// no seconds
array('2010-08-17T09:23', 1282036980),
array('20100817T09:23', 1282036980),
// no time
array('2010-08-17', 1282003200),
array(1282036980, 1282036980),
// array('20100817', 1282003200), #this will NOT be parsed, but is assumed to be timestamp
);
foreach($tests as $test){
$dt = new IXR_Date($test[0]);
$this->assertEquals($test[1], $dt->getTimeStamp());
}
}
}
//Setup VIM: ex: et ts=4 :

View File

@ -1,134 +0,0 @@
<?php
class ixr_library_ixr_message_test extends DokuWikiTest {
function test_untypedvalue1(){
$xml = '<?xml version="1.0" encoding="UTF-8"?><methodCall><methodName>wiki.getBackLinks</methodName><params><param><value> change </value></param></params></methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.getBackLinks');
$this->assertEquals($ixrmsg->params,array(' change '));
}
function test_untypedvalue2(){
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>wiki.getBackLinks</methodName>
<params>
<param>
<value> change </value>
</param>
</params>
</methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.getBackLinks');
$this->assertEquals($ixrmsg->params,array(' change '));
}
function test_stringvalue1(){
$xml = '<?xml version="1.0" encoding="UTF-8"?><methodCall><methodName>wiki.getBackLinks</methodName><params><param><value><string> change </string></value></param></params></methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.getBackLinks');
$this->assertEquals($ixrmsg->params,array(' change '));
}
function test_stringvalue2(){
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>wiki.getBackLinks</methodName>
<params>
<param>
<value>
<string> change </string>
</value>
</param>
</params>
</methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.getBackLinks');
$this->assertEquals($ixrmsg->params,array(' change '));
}
function test_emptyvalue1(){
$xml = '<?xml version="1.0" encoding="UTF-8"?><methodCall><methodName>wiki.getBackLinks</methodName><params><param><value><string></string></value></param></params></methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.getBackLinks');
$this->assertEquals($ixrmsg->params,array(''));
}
function test_emptyvalue2(){
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>wiki.getBackLinks</methodName>
<params>
<param>
<value>
<string></string>
</value>
</param>
</params>
</methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.getBackLinks');
$this->assertEquals($ixrmsg->params,array(''));
}
function test_struct(){
$xml = '<?xml version=\'1.0\'?>
<methodCall>
<methodName>wiki.putPage</methodName>
<params>
<param>
<value><string>start</string></value>
</param>
<param>
<value><string>test text</string></value>
</param>
<param>
<value><struct>
<member>
<name>sum</name>
<value><string>xmlrpc edit</string></value>
</member>
<member>
<name>minor</name>
<value><string>1</string></value>
</member>
</struct></value>
</param>
</params>
</methodCall>';
$ixrmsg = new IXR_Message($xml);
$ixrmsg->parse();
$this->assertEquals($ixrmsg->messageType,'methodCall');
$this->assertEquals($ixrmsg->methodName,'wiki.putPage');
$this->assertEquals($ixrmsg->params,array('start','test text',array('sum'=>'xmlrpc edit','minor'=>'1')));
}
}
//Setup VIM: ex: et ts=4 :

View File

@ -15,7 +15,8 @@
"aziraphale/email-address-validator": "^2",
"marcusschwarz/lesserphp": "^0.5.1",
"splitbrain/php-cli": "^1.1",
"splitbrain/slika": "^1.0"
"splitbrain/slika": "^1.0",
"kissifrot/php-ixr": "^1.8"
},
"config": {
"platform": {

51
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "cd951937a517d2f058327d0429efe7ff",
"content-hash": "09f5f3726c3993d80980df99ab2a59dc",
"packages": [
{
"name": "aziraphale/email-address-validator",
@ -98,6 +98,53 @@
},
"time": "2020-06-22T15:46:04+00:00"
},
{
"name": "kissifrot/php-ixr",
"version": "1.8.3",
"source": {
"type": "git",
"url": "https://github.com/kissifrot/php-ixr.git",
"reference": "4477cd1a67416ce5b6a2080f9a79d9eb50a965c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kissifrot/php-ixr/zipball/4477cd1a67416ce5b6a2080f9a79d9eb50a965c1",
"reference": "4477cd1a67416ce5b6a2080f9a79d9eb50a965c1",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"type": "library",
"autoload": {
"psr-4": {
"IXR\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Incutio Ltd 2010 - Simon Willison",
"homepage": "http://scripts.incutio.com/xmlrpc/"
}
],
"description": "Incutio XML-RPC library (IXR)",
"homepage": "http://scripts.incutio.com/xmlrpc/",
"keywords": [
"remote procedure call",
"rpc",
"xlm-rpc",
"xmlrpc"
],
"support": {
"issues": "https://github.com/kissifrot/php-ixr/issues",
"source": "https://github.com/kissifrot/php-ixr/tree/master"
},
"time": "2016-11-17T12:00:18+00:00"
},
{
"name": "marcusschwarz/lesserphp",
"version": "v0.5.5",
@ -570,5 +617,5 @@
"platform-overrides": {
"php": "7.2"
},
"plugin-api-version": "2.0.0"
"plugin-api-version": "2.1.0"
}

File diff suppressed because it is too large Load Diff

88
inc/Remote/IXR/Client.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace dokuwiki\Remote\IXR;
use dokuwiki\HTTP\HTTPClient;
use IXR\Message\Message;
use IXR\Request\Request;
/**
* This implements a XML-RPC client using our own HTTPClient
*
* Note: this now inherits from the IXR library's client and no longer from HTTPClient. Instead composition
* is used to add the HTTP client.
*/
class Client extends \IXR\Client\Client
{
/** @var HTTPClient */
protected $httpClient;
/** @var string */
protected $posturl = '';
/** @inheritdoc */
public function __construct($server, $path = false, $port = 80, $timeout = 15, $timeout_io = null)
{
parent::__construct($server, $path, $port, $timeout, $timeout_io);
if (!$path) {
// Assume we have been given an URL instead
$this->posturl = $server;
} else {
$this->posturl = 'http://' . $server . ':' . $port . $path;
}
$this->httpClient = new HTTPClient();
$this->httpClient->timeout = $timeout;
}
/** @inheritdoc */
public function query()
{
$args = func_get_args();
$method = array_shift($args);
$request = new Request($method, $args);
$length = $request->getLength();
$xml = $request->getXml();
$this->headers['Content-Type'] = 'text/xml';
$this->headers['Content-Length'] = $length;
$this->httpClient->headers = array_merge($this->httpClient->headers, $this->headers);
if (!$this->httpClient->sendRequest($this->posturl, $xml, 'POST')) {
$this->handleError(-32300, 'transport error - ' . $this->httpClient->error);
return false;
}
// Check HTTP Response code
if ($this->httpClient->status < 200 || $this->httpClient->status > 206) {
$this->handleError(-32300, 'transport error - HTTP status ' . $this->httpClient->status);
return false;
}
// Now parse what we've got back
$this->message = new Message($this->httpClient->resp_body);
if (!$this->message->parse()) {
// XML error
return $this->handleError(-32700, 'Parse error. Message not well formed');
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
return $this->handleError($this->message->faultCode, $this->message->faultString);
}
// Message must be OK
return true;
}
/**
* Direct access to the underlying HTTP client if needed
*
* @return HTTPClient
*/
public function getHTTPClient()
{
return $this->httpClient;
}
}

View File

@ -2,10 +2,15 @@
namespace dokuwiki\Remote;
use IXR\DataType\Base64;
use IXR\DataType\Date;
use IXR\Exception\ServerException;
use IXR\Server\Server;
/**
* Contains needed wrapper functions and registers all available XMLRPC functions.
*/
class XmlRpcServer extends \IXR_Server
class XmlRpcServer extends Server
{
protected $remote;
@ -28,34 +33,34 @@ class XmlRpcServer extends \IXR_Server
try {
$result = $this->remote->call($methodname, $args);
return $result;
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (AccessDeniedException $e) {
} catch (AccessDeniedException $e) {
if (!isset($_SERVER['REMOTE_USER'])) {
http_status(401);
return new \IXR_Error(-32603, "server error. not authorized to call method $methodname");
return new ServerException("server error. not authorized to call method $methodname", -32603);
} else {
http_status(403);
return new \IXR_Error(-32604, "server error. forbidden to call the method $methodname");
return new ServerException("server error. forbidden to call the method $methodname", -32604);
}
} catch (RemoteException $e) {
return new \IXR_Error($e->getCode(), $e->getMessage());
return new ServerException($e->getMessage(), $e->getCode());
}
}
/**
* @param string|int $data iso date(yyyy[-]mm[-]dd[ hh:mm[:ss]]) or timestamp
* @return \IXR_Date
* @return Date
*/
public function toDate($data)
{
return new \IXR_Date($data);
return new Date($data);
}
/**
* @param string $data
* @return \IXR_Base64
* @return Base64
*/
public function toFile($data)
{
return new \IXR_Base64($data);
return new Base64($data);
}
}

View File

@ -65,10 +65,6 @@ function load_autoload($name){
'JpegMeta' => DOKU_INC.'inc/JpegMeta.php',
'SimplePie' => DOKU_INC.'inc/SimplePie.php',
'FeedParser' => DOKU_INC.'inc/FeedParser.php',
'IXR_Server' => DOKU_INC.'inc/IXR_Library.php',
'IXR_Client' => DOKU_INC.'inc/IXR_Library.php',
'IXR_Error' => DOKU_INC.'inc/IXR_Library.php',
'IXR_IntrospectionServer' => DOKU_INC.'inc/IXR_Library.php',
'SafeFN' => DOKU_INC.'inc/SafeFN.class.php',
'Sitemapper' => DOKU_INC.'inc/Sitemapper.php',
'Mailer' => DOKU_INC.'inc/Mailer.class.php',

View File

@ -42,30 +42,75 @@ namespace Composer\Autoload;
*/
class ClassLoader
{
/** @var ?string */
private $vendorDir;
// PSR-4
/**
* @var array[]
* @psalm-var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, array<int, string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* @var array[]
* @psalm-var array<string, array<string, string[]>>
*/
private $prefixesPsr0 = array();
/**
* @var array[]
* @psalm-var array<string, string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var string[]
* @psalm-var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var bool[]
* @psalm-var array<string, bool>
*/
private $missingClasses = array();
/** @var ?string */
private $apcuPrefix;
/**
* @var self[]
*/
private static $registeredLoaders = array();
/**
* @param ?string $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
/**
* @return string[]
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
@ -75,28 +120,47 @@ class ClassLoader
return array();
}
/**
* @return array[]
* @psalm-return array<string, array<int, string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return array[]
* @psalm-return array<string, string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return string[] Array of classname => path
* @psalm-var array<string, string>
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
* @param string[] $classMap Class to filename map
* @psalm-param array<string, string> $classMap
*
* @return void
*/
public function addClassMap(array $classMap)
{
@ -111,9 +175,11 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
@ -156,11 +222,13 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
@ -204,8 +272,10 @@ class ClassLoader
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
* @param string $prefix The prefix
* @param string[]|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
@ -220,10 +290,12 @@ class ClassLoader
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param string[]|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
@ -243,6 +315,8 @@ class ClassLoader
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
@ -265,6 +339,8 @@ class ClassLoader
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
@ -285,6 +361,8 @@ class ClassLoader
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
@ -305,6 +383,8 @@ class ClassLoader
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
@ -324,6 +404,8 @@ class ClassLoader
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
@ -338,7 +420,7 @@ class ClassLoader
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
@ -347,6 +429,8 @@ class ClassLoader
return true;
}
return null;
}
/**
@ -401,6 +485,11 @@ class ClassLoader
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
@ -472,6 +561,10 @@ class ClassLoader
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
* @private
*/
function includeFile($file)
{

View File

@ -1,366 +1,337 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*/
class InstalledVersions
{
private static $installed = array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '4ee6ddae660f9ec72c1f6e2ddb72d6b29d6d8900',
'name' => 'splitbrain/dokuwiki',
),
'versions' =>
array (
'aziraphale/email-address-validator' =>
array (
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
'aliases' =>
array (
),
'reference' => 'fa25bc22c1c0b6491657c91473fae3e40719a650',
),
'geshi/geshi' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
0 => '1.0.x-dev',
1 => '9999999-dev',
),
'reference' => '3c12a7931d509c5e3557c5ed44c9a32e9c917c7d',
),
'marcusschwarz/lesserphp' =>
array (
'pretty_version' => 'v0.5.5',
'version' => '0.5.5.0',
'aliases' =>
array (
),
'reference' => '77ba82b5218ff228267d3b0e5ec8697be75e86a7',
),
'openpsa/universalfeedcreator' =>
array (
'pretty_version' => 'v1.8.4',
'version' => '1.8.4.0',
'aliases' =>
array (
),
'reference' => '099817dc9efef33ca2382b04daf9e191b77fed13',
),
'phpseclib/phpseclib' =>
array (
'pretty_version' => '2.0.31',
'version' => '2.0.31.0',
'aliases' =>
array (
),
'reference' => '233a920cb38636a43b18d428f9a8db1f0a1a08f4',
),
'simplepie/simplepie' =>
array (
'pretty_version' => '1.5.6',
'version' => '1.5.6.0',
'aliases' =>
array (
),
'reference' => '1c68e14ca3ac84346b6e6fe3c5eedf725d0f92c6',
),
'splitbrain/dokuwiki' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '4ee6ddae660f9ec72c1f6e2ddb72d6b29d6d8900',
),
'splitbrain/php-archive' =>
array (
'pretty_version' => '1.2.1',
'version' => '1.2.1.0',
'aliases' =>
array (
),
'reference' => '211a2198b73b233d7d2b6159462e11cd9a91348a',
),
'splitbrain/php-cli' =>
array (
'pretty_version' => '1.1.8',
'version' => '1.1.8.0',
'aliases' =>
array (
),
'reference' => '8c2c001b1b55d194402cf18aad2757049ac6d575',
),
'splitbrain/slika' =>
array (
'pretty_version' => '1.0.4',
'version' => '1.0.4.0',
'aliases' =>
array (
),
'reference' => 'fda87e816eb150f3608282da962788b4ad509c11',
),
),
);
private static $canGetVendors;
private static $installedByVendor = array();
private static $installed;
private static $canGetVendors;
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
public static function isInstalled($packageName)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return true;
}
}
return false;
}
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
public static function getRawData()
{
return self::$installed;
}
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
}
}
}
$installed[] = self::$installed;
return $installed;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View File

@ -11,4 +11,5 @@ return array(
'splitbrain\\phpcli\\' => array($vendorDir . '/splitbrain/php-cli/src'),
'splitbrain\\PHPArchive\\' => array($vendorDir . '/splitbrain/php-archive/src'),
'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'IXR\\' => array($vendorDir . '/kissifrot/php-ixr/src'),
);

View File

@ -23,6 +23,10 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b
array (
'phpseclib\\' => 10,
),
'I' =>
array (
'IXR\\' => 4,
),
);
public static $prefixDirsPsr4 = array (
@ -46,6 +50,10 @@ class ComposerStaticInita19a915ee98347a0c787119619d2ff9b
array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
),
'IXR\\' =>
array (
0 => __DIR__ . '/..' . '/kissifrot/php-ixr/src',
),
);
public static $prefixesPsr0 = array (

View File

@ -87,6 +87,56 @@
"homepage": "http://qbnz.com/highlighter/",
"install-path": "../geshi/geshi"
},
{
"name": "kissifrot/php-ixr",
"version": "1.8.3",
"version_normalized": "1.8.3.0",
"source": {
"type": "git",
"url": "https://github.com/kissifrot/php-ixr.git",
"reference": "4477cd1a67416ce5b6a2080f9a79d9eb50a965c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kissifrot/php-ixr/zipball/4477cd1a67416ce5b6a2080f9a79d9eb50a965c1",
"reference": "4477cd1a67416ce5b6a2080f9a79d9eb50a965c1",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"time": "2016-11-17T12:00:18+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"IXR\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Incutio Ltd 2010 - Simon Willison",
"homepage": "http://scripts.incutio.com/xmlrpc/"
}
],
"description": "Incutio XML-RPC library (IXR)",
"homepage": "http://scripts.incutio.com/xmlrpc/",
"keywords": [
"remote procedure call",
"rpc",
"xlm-rpc",
"xmlrpc"
],
"support": {
"issues": "https://github.com/kissifrot/php-ixr/issues",
"source": "https://github.com/kissifrot/php-ixr/tree/master"
},
"install-path": "../kissifrot/php-ixr"
},
{
"name": "marcusschwarz/lesserphp",
"version": "v0.5.5",

View File

@ -1,107 +1,116 @@
<?php return array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
<?php return array(
'root' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'dc2ffedf4d1aa49db448d4fc9029d50705d0c3c6',
'name' => 'splitbrain/dokuwiki',
'dev' => true,
),
'reference' => '4ee6ddae660f9ec72c1f6e2ddb72d6b29d6d8900',
'name' => 'splitbrain/dokuwiki',
),
'versions' =>
array (
'aziraphale/email-address-validator' =>
array (
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
'aliases' =>
array (
),
'reference' => 'fa25bc22c1c0b6491657c91473fae3e40719a650',
'versions' => array(
'aziraphale/email-address-validator' => array(
'pretty_version' => '2.0.1',
'version' => '2.0.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../aziraphale/email-address-validator',
'aliases' => array(),
'reference' => 'fa25bc22c1c0b6491657c91473fae3e40719a650',
'dev_requirement' => false,
),
'geshi/geshi' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../geshi/geshi',
'aliases' => array(
0 => '1.0.x-dev',
1 => '9999999-dev',
),
'reference' => '3c12a7931d509c5e3557c5ed44c9a32e9c917c7d',
'dev_requirement' => false,
),
'kissifrot/php-ixr' => array(
'pretty_version' => '1.8.3',
'version' => '1.8.3.0',
'type' => 'library',
'install_path' => __DIR__ . '/../kissifrot/php-ixr',
'aliases' => array(),
'reference' => '4477cd1a67416ce5b6a2080f9a79d9eb50a965c1',
'dev_requirement' => false,
),
'marcusschwarz/lesserphp' => array(
'pretty_version' => 'v0.5.5',
'version' => '0.5.5.0',
'type' => 'library',
'install_path' => __DIR__ . '/../marcusschwarz/lesserphp',
'aliases' => array(),
'reference' => '77ba82b5218ff228267d3b0e5ec8697be75e86a7',
'dev_requirement' => false,
),
'openpsa/universalfeedcreator' => array(
'pretty_version' => 'v1.8.4',
'version' => '1.8.4.0',
'type' => 'library',
'install_path' => __DIR__ . '/../openpsa/universalfeedcreator',
'aliases' => array(),
'reference' => '099817dc9efef33ca2382b04daf9e191b77fed13',
'dev_requirement' => false,
),
'phpseclib/phpseclib' => array(
'pretty_version' => '2.0.31',
'version' => '2.0.31.0',
'type' => 'library',
'install_path' => __DIR__ . '/../phpseclib/phpseclib',
'aliases' => array(),
'reference' => '233a920cb38636a43b18d428f9a8db1f0a1a08f4',
'dev_requirement' => false,
),
'simplepie/simplepie' => array(
'pretty_version' => '1.5.6',
'version' => '1.5.6.0',
'type' => 'library',
'install_path' => __DIR__ . '/../simplepie/simplepie',
'aliases' => array(),
'reference' => '1c68e14ca3ac84346b6e6fe3c5eedf725d0f92c6',
'dev_requirement' => false,
),
'splitbrain/dokuwiki' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'dc2ffedf4d1aa49db448d4fc9029d50705d0c3c6',
'dev_requirement' => false,
),
'splitbrain/php-archive' => array(
'pretty_version' => '1.2.1',
'version' => '1.2.1.0',
'type' => 'library',
'install_path' => __DIR__ . '/../splitbrain/php-archive',
'aliases' => array(),
'reference' => '211a2198b73b233d7d2b6159462e11cd9a91348a',
'dev_requirement' => false,
),
'splitbrain/php-cli' => array(
'pretty_version' => '1.1.8',
'version' => '1.1.8.0',
'type' => 'library',
'install_path' => __DIR__ . '/../splitbrain/php-cli',
'aliases' => array(),
'reference' => '8c2c001b1b55d194402cf18aad2757049ac6d575',
'dev_requirement' => false,
),
'splitbrain/slika' => array(
'pretty_version' => '1.0.4',
'version' => '1.0.4.0',
'type' => 'library',
'install_path' => __DIR__ . '/../splitbrain/slika',
'aliases' => array(),
'reference' => 'fda87e816eb150f3608282da962788b4ad509c11',
'dev_requirement' => false,
),
),
'geshi/geshi' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
0 => '1.0.x-dev',
1 => '9999999-dev',
),
'reference' => '3c12a7931d509c5e3557c5ed44c9a32e9c917c7d',
),
'marcusschwarz/lesserphp' =>
array (
'pretty_version' => 'v0.5.5',
'version' => '0.5.5.0',
'aliases' =>
array (
),
'reference' => '77ba82b5218ff228267d3b0e5ec8697be75e86a7',
),
'openpsa/universalfeedcreator' =>
array (
'pretty_version' => 'v1.8.4',
'version' => '1.8.4.0',
'aliases' =>
array (
),
'reference' => '099817dc9efef33ca2382b04daf9e191b77fed13',
),
'phpseclib/phpseclib' =>
array (
'pretty_version' => '2.0.31',
'version' => '2.0.31.0',
'aliases' =>
array (
),
'reference' => '233a920cb38636a43b18d428f9a8db1f0a1a08f4',
),
'simplepie/simplepie' =>
array (
'pretty_version' => '1.5.6',
'version' => '1.5.6.0',
'aliases' =>
array (
),
'reference' => '1c68e14ca3ac84346b6e6fe3c5eedf725d0f92c6',
),
'splitbrain/dokuwiki' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '4ee6ddae660f9ec72c1f6e2ddb72d6b29d6d8900',
),
'splitbrain/php-archive' =>
array (
'pretty_version' => '1.2.1',
'version' => '1.2.1.0',
'aliases' =>
array (
),
'reference' => '211a2198b73b233d7d2b6159462e11cd9a91348a',
),
'splitbrain/php-cli' =>
array (
'pretty_version' => '1.1.8',
'version' => '1.1.8.0',
'aliases' =>
array (
),
'reference' => '8c2c001b1b55d194402cf18aad2757049ac6d575',
),
'splitbrain/slika' =>
array (
'pretty_version' => '1.0.4',
'version' => '1.0.4.0',
'aliases' =>
array (
),
'reference' => 'fda87e816eb150f3608282da962788b4ad509c11',
),
),
);

View File

@ -0,0 +1,8 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true

6
vendor/kissifrot/php-ixr/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.project
.idea
.vscode
.phpunit.result.cache
composer.lock
vendor

28
vendor/kissifrot/php-ixr/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,28 @@
IXR - The Incutio XML-RPC Library
Copyright (c) 2010, Incutio Ltd.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of Incutio Ltd. nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

37
vendor/kissifrot/php-ixr/README.md vendored Normal file
View File

@ -0,0 +1,37 @@
#Incutio XML-RPC library (IXR)
**Note**: _This is a fork of the original Incutio XML-RPC library (IXR) SVN repo hosted on <https://code.google.com/p/php-ixr/>_
All credits go to Incutio.
**Docs and Homepage:** <http://scripts.incutio.com/xmlrpc/>
#Introduction
The Incutio XML-RPC library (IXR) is designed primarily for ease of use. It incorporates both client and server classes, and is designed to hide as much of the workings of XML-RPC from the user as possible. A key feature of the library is automatic type conversion from PHP types to XML-RPC types and vice versa. This should enable developers to write web services with very little knowledge of the underlying XML-RPC standard.
Don't however be fooled by it's simple surface. The library includes a wide variety of additional XML-RPC specifications and has all of the features required for serious web service implementations.
#Background / History
The original XML-RPC library was developed back in 2002 and updated through 2010 by Incutio for a number of projects the company was working on at the time. It has become fairly dated but is still used extensively by a wide range of commercial and open-source projects.
This fork makes it usable on more recent systems (PHP 5.4+ ones)
#Composer
A [Composer](http://getcomposer.org/) file has been added to this repository.
This package is published to [Packagist](https://packagist.org/), but if you don't want to use it simply add
"repositories": [
{
"type": "vcs",
"url": "https://github.com/kissifrot/php-ixr"
}
],
"require": {
"kissifrot/php-ixr": "1.8.*"
}
To your composer.json file

27
vendor/kissifrot/php-ixr/composer.json vendored Normal file
View File

@ -0,0 +1,27 @@
{
"name": "kissifrot/php-ixr",
"type": "library",
"description": "Incutio XML-RPC library (IXR)",
"keywords": ["xmlrpc","xlm-rpc", "remote procedure call", "rpc"],
"homepage": "http://scripts.incutio.com/xmlrpc/",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Incutio Ltd 2010 - Simon Willison",
"homepage": "http://scripts.incutio.com/xmlrpc/"
}
],
"require": {
"php": ">=5.4.0",
"ext-xml": "*"
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"autoload": {
"psr-4": {
"IXR\\tests\\": "tests/",
"IXR\\": "src/"
}
}
}

View File

@ -0,0 +1,209 @@
<?php
namespace IXR\Client;
use IXR\Message\Error;
use IXR\Message\Message;
use IXR\Request\Request;
/**
* IXR_Client
*
* @package IXR
* @since 1.5.0
*
*/
class Client
{
protected $server;
protected $port;
protected $path;
protected $useragent;
protected $response;
/** @var bool|Message */
protected $message = false;
protected $debug = false;
/** @var int Connection timeout in seconds */
protected $timeout;
/** @var null|int Timeout for actual data transfer; in seconds */
protected $timeout_io = null;
protected $headers = [];
/**
* @var null|Error
*
* Storage place for an error message
*/
private $error = null;
public function __construct($server, $path = false, $port = 80, $timeout = 15, $timeout_io = null)
{
if (!$path) {
// Assume we have been given a URL instead
$bits = parse_url($server);
$this->server = $bits['host'];
$this->port = isset($bits['port']) ? $bits['port'] : 80;
$this->path = isset($bits['path']) ? $bits['path'] : '/';
// Make absolutely sure we have a path
if (!$this->path) {
$this->path = '/';
}
if (!empty($bits['query'])) {
$this->path .= '?' . $bits['query'];
}
} else {
$this->server = $server;
$this->path = $path;
$this->port = $port;
}
$this->useragent = 'The Incutio XML-RPC PHP Library';
$this->timeout = $timeout;
$this->timeout_io = $timeout_io;
}
public function query()
{
$args = func_get_args();
$method = array_shift($args);
$request = new Request($method, $args);
$length = $request->getLength();
$xml = $request->getXml();
$r = "\r\n";
$request = "POST {$this->path} HTTP/1.0$r";
// Merged from WP #8145 - allow custom headers
$this->headers['Host'] = $this->server;
$this->headers['Content-Type'] = 'text/xml';
$this->headers['User-Agent'] = $this->useragent;
$this->headers['Content-Length'] = $length;
foreach ($this->headers as $header => $value) {
$request .= "{$header}: {$value}{$r}";
}
$request .= $r;
$request .= $xml;
// Now send the request
if ($this->debug) {
echo '<pre class="ixr_request">' . htmlspecialchars($request) . "\n</pre>\n\n";
}
if ($this->timeout) {
try {
$fp = fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);
} catch (\Exception $e) {
$fp = false;
}
} else {
try {
$fp = fsockopen($this->server, $this->port, $errno, $errstr);
} catch (\Exception $e) {
$fp = false;
}
}
if (!$fp) {
return $this->handleError(-32300, 'transport error - could not open socket');
}
if (null !== $this->timeout_io) {
stream_set_timeout($fp, $this->timeout_io);
}
fputs($fp, $request);
$contents = '';
$debugContents = '';
$gotFirstLine = false;
$gettingHeaders = true;
while (!feof($fp)) {
$line = fgets($fp, 4096);
if (!$gotFirstLine) {
// Check line for '200'
if (strstr($line, '200') === false) {
return $this->handleError(-32300, 'transport error - HTTP status code was not 200');
}
$gotFirstLine = true;
}
if (trim($line) == '') {
$gettingHeaders = false;
}
if (!$gettingHeaders) {
// merged from WP #12559 - remove trim
$contents .= $line;
}
if ($this->debug) {
$debugContents .= $line;
}
}
if ($this->debug) {
echo '<pre class="ixr_response">' . htmlspecialchars($debugContents) . "\n</pre>\n\n";
}
// Now parse what we've got back
$this->message = new Message($contents);
if (!$this->message->parse()) {
// XML error
return $this->handleError(-32700, 'Parse error. Message not well formed');
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
return $this->handleError($this->message->faultCode, $this->message->faultString);
}
// Message must be OK
return true;
}
public function getResponse()
{
// methodResponses can only have one param - return that
return $this->message->params[0];
}
public function isError()
{
return (is_object($this->error));
}
protected function handleError($errorCode, $errorMessage)
{
$this->error = new Error($errorCode, $errorMessage);
return false;
}
public function getError()
{
return $this->error;
}
public function getErrorCode()
{
return $this->error->code;
}
public function getErrorMessage()
{
return $this->error->message;
}
/**
* Gets the current timeout set for data transfer
* @return int|null
*/
public function getTimeoutIo()
{
return $this->timeout_io;
}
/**
* Sets the timeout for data transfer
* @param int $timeout_io
* @return $this
*/
public function setTimeoutIo($timeout_io)
{
$this->timeout_io = $timeout_io;
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace IXR\Client;
/**
* IXR_ClientMulticall
*
* @package IXR
* @since 1.5.0
*/
class ClientMulticall extends Client
{
private $calls = [];
public function __construct($server, $path = false, $port = 80)
{
parent::__construct($server, $path, $port);
$this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';
}
public function addCall()
{
$args = func_get_args();
$methodName = array_shift($args);
$struct = [
'methodName' => $methodName,
'params' => $args
];
$this->calls[] = $struct;
}
public function query()
{
// Prepare multicall, then call the parent::query() method
return parent::query('system.multicall', $this->calls);
}
}

View File

@ -0,0 +1,235 @@
<?php
namespace IXR\Client;
use IXR\Exception\ClientException;
use IXR\Message\Message;
use IXR\Request\Request;
/**
* Client for communicating with a XML-RPC Server over HTTPS.
*
* @author Jason Stirk <jstirk@gmm.com.au> (@link http://blog.griffin.homelinux.org/projects/xmlrpc/)
* @version 0.2.0 26May2005 08:34 +0800
* @copyright (c) 2004-2005 Jason Stirk
* @package IXR
*/
class ClientSSL extends Client
{
/**
* Filename of the SSL Client Certificate
* @access private
* @since 0.1.0
* @var string
*/
private $_certFile;
/**
* Filename of the SSL CA Certificate
* @access private
* @since 0.1.0
* @var string
*/
private $_caFile;
/**
* Filename of the SSL Client Private Key
* @access private
* @since 0.1.0
* @var string
*/
private $_keyFile;
/**
* Passphrase to unlock the private key
* @access private
* @since 0.1.0
* @var string
*/
private $_passphrase;
/**
* Constructor
* @param string $server URL of the Server to connect to
* @since 0.1.0
*/
public function __construct($server, $path = false, $port = 443, $timeout = false, $timeout_io = null)
{
parent::__construct($server, $path, $port, $timeout, $timeout_io);
$this->useragent = 'The Incutio XML-RPC PHP Library for SSL';
// Set class fields
$this->_certFile = false;
$this->_caFile = false;
$this->_keyFile = false;
$this->_passphrase = '';
}
/**
* Set the client side certificates to communicate with the server.
*
* @since 0.1.0
* @param string $certificateFile Filename of the client side certificate to use
* @param string $keyFile Filename of the client side certificate's private key
* @param string $keyPhrase Passphrase to unlock the private key
* @throws ClientException
*/
public function setCertificate($certificateFile, $keyFile, $keyPhrase = '')
{
// Check the files all exist
if (is_file($certificateFile)) {
$this->_certFile = $certificateFile;
} else {
throw new ClientException('Could not open certificate: ' . $certificateFile);
}
if (is_file($keyFile)) {
$this->_keyFile = $keyFile;
} else {
throw new ClientException('Could not open private key: ' . $keyFile);
}
$this->_passphrase = (string)$keyPhrase;
}
public function setCACertificate($caFile)
{
if (is_file($caFile)) {
$this->_caFile = $caFile;
} else {
throw new ClientException('Could not open CA certificate: ' . $caFile);
}
}
/**
* Sets the connection timeout (in seconds)
* @param int $newTimeOut Timeout in seconds
* @returns void
* @since 0.1.2
*/
public function setTimeOut($newTimeOut)
{
$this->timeout = (int)$newTimeOut;
}
/**
* Returns the connection timeout (in seconds)
* @returns int
* @since 0.1.2
*/
public function getTimeOut()
{
return $this->timeout;
}
/**
* Set the query to send to the XML-RPC Server
* @since 0.1.0
*/
public function query()
{
$args = func_get_args();
$method = array_shift($args);
$request = new Request($method, $args);
$length = $request->getLength();
$xml = $request->getXml();
$this->debugOutput('<pre>' . htmlspecialchars($xml) . PHP_EOL . '</pre>');
//This is where we deviate from the normal query()
//Rather than open a normal sock, we will actually use the cURL
//extensions to make the calls, and handle the SSL stuff.
$curl = curl_init('https://' . $this->server . $this->path);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
//Since 23Jun2004 (0.1.2) - Made timeout a class field
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, $this->timeout);
if (null !== $this->timeout_io) {
curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout_io);
}
if ($this->debug) {
curl_setopt($curl, CURLOPT_VERBOSE, 1);
}
curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);
if($this->port !== 443) {
curl_setopt($curl, CURLOPT_PORT, $this->port);
}
curl_setopt($curl, CURLOPT_HTTPHEADER, [
"Content-Type: text/xml",
"Content-length: {$length}"
]);
// Process the SSL certificates, etc. to use
if (!($this->_certFile === false)) {
// We have a certificate file set, so add these to the cURL handler
curl_setopt($curl, CURLOPT_SSLCERT, $this->_certFile);
curl_setopt($curl, CURLOPT_SSLKEY, $this->_keyFile);
if ($this->debug) {
$this->debugOutput('SSL Cert at : ' . $this->_certFile);
$this->debugOutput('SSL Key at : ' . $this->_keyFile);
}
// See if we need to give a passphrase
if (!($this->_passphrase === '')) {
curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->_passphrase);
}
if ($this->_caFile === false) {
// Don't verify their certificate, as we don't have a CA to verify against
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
} else {
// Verify against a CA
curl_setopt($curl, CURLOPT_CAINFO, $this->_caFile);
}
}
// Call cURL to do it's stuff and return us the content
$contents = curl_exec($curl);
curl_close($curl);
// Check for 200 Code in $contents
if (!strstr($contents, '200 OK')) {
//There was no "200 OK" returned - we failed
return $this->handleError(-32300, 'transport error - HTTP status code was not 200');
}
if ($this->debug) {
$this->debugOutput('<pre>' . htmlspecialchars($contents) . PHP_EOL . '</pre>');
}
// Now parse what we've got back
// Since 20Jun2004 (0.1.1) - We need to remove the headers first
// Why I have only just found this, I will never know...
// So, remove everything before the first <
$contents = substr($contents, strpos($contents, '<'));
$this->message = new Message($contents);
if (!$this->message->parse()) {
// XML error
return $this->handleError(-32700, 'parse error. not well formed');
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
return $this->handleError($this->message->faultCode, $this->message->faultString);
}
// Message must be OK
return true;
}
/**
* Debug output, if debug is enabled
* @param $message
*/
private function debugOutput($message)
{
if ($this->debug) {
echo $message . PHP_EOL;
}
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace IXR\DataType;
/**
* IXR_Base64
*
* @package IXR
* @since 1.5.0
*/
class Base64
{
private $data;
public function __construct($data)
{
$this->data = $data;
}
public function getXml()
{
return '<base64>' . base64_encode($this->data) . '</base64>';
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace IXR\DataType;
/**
* IXR_Date
*
* @package IXR
* @since 1.5.0
*/
class Date
{
/** @var \DateTime */
private $dateTime;
public function __construct($time)
{
// $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$this->parseTimestamp($time);
} else {
$this->parseIso($time);
}
}
private function parseTimestamp($timestamp)
{
$date = new \DateTime();
$this->dateTime = $date->setTimestamp($timestamp);
}
/**
* Parses more or less complete iso dates and much more, if no timezone given assumes UTC
*
* @param string $iso
* @throws \Exception when no valid date is given
*/
protected function parseIso($iso) {
$this->dateTime = new \DateTime($iso, new \DateTimeZone('UTC'));
}
public function getIso()
{
return $this->dateTime->format(\DateTime::ATOM);
}
public function getXml()
{
return '<dateTime.iso8601>' . $this->getIso() . '</dateTime.iso8601>';
}
public function getTimestamp()
{
return (int)$this->dateTime->format('U');
}
}

View File

@ -0,0 +1,121 @@
<?php
namespace IXR\DataType;
class Value
{
private $data;
private $type;
public function __construct($data, $type = null)
{
$this->data = $data;
if (!$type) {
$type = $this->calculateType();
}
$this->type = $type;
if ($type === 'struct') {
// Turn all the values in the array in to new IXR_Value objects
foreach ($this->data as $key => $value) {
$this->data[$key] = new Value($value);
}
}
if ($type === 'array') {
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
$this->data[$i] = new Value($this->data[$i]);
}
}
}
public function calculateType()
{
if ($this->data === true || $this->data === false) {
return 'boolean';
}
if (is_integer($this->data)) {
return 'int';
}
if (is_double($this->data)) {
return 'double';
}
// Deal with IXR object types base64 and date
if (is_object($this->data) && $this->data instanceof Date) {
return 'date';
}
if (is_object($this->data) && $this->data instanceof Base64) {
return 'base64';
}
// If it is a normal PHP object convert it in to a struct
if (is_object($this->data)) {
$this->data = get_object_vars($this->data);
return 'struct';
}
if (!is_array($this->data)) {
return 'string';
}
// We have an array - is it an array or a struct?
if ($this->isStruct($this->data)) {
return 'struct';
} else {
return 'array';
}
}
public function getXml()
{
// Return XML for this value
switch ($this->type) {
case 'boolean':
return '<boolean>' . (((bool)$this->data) ? '1' : '0') . '</boolean>';
case 'int':
return '<int>' . $this->data . '</int>';
case 'double':
return '<double>' . $this->data . '</double>';
case 'string':
return '<string>' . htmlspecialchars($this->data) . '</string>';
case 'array':
$return = '<array><data>' . "\n";
foreach ($this->data as $item) {
$return .= ' <value>' . $item->getXml() . "</value>\n";
}
$return .= '</data></array>';
return $return;
break;
case 'struct':
$return = '<struct>' . "\n";
foreach ($this->data as $name => $value) {
$name = htmlspecialchars($name);
$return .= " <member><name>$name</name><value>";
$return .= $value->getXml() . "</value></member>\n";
}
$return .= '</struct>';
return $return;
case 'date':
case 'base64':
return $this->data->getXml();
default:
return false;
}
}
/**
* Checks whether or not the supplied array is a struct or not
*
* @param array $array
* @return boolean
*/
public function isStruct($array)
{
$expected = 0;
foreach ($array as $key => $value) {
if ((string)$key != (string)$expected) {
return true;
}
$expected++;
}
return false;
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace IXR\Exception;
class ClientException extends \Exception
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace IXR\Exception;
class ServerException extends \Exception
{
}

View File

@ -0,0 +1,43 @@
<?php
namespace IXR\Message;
/**
* IXR_Error
*
* @package IXR
* @since 1.5.0
*/
class Error
{
public $code;
public $message;
public function __construct($code, $message)
{
$this->code = $code;
$this->message = htmlspecialchars($message);
}
public function getXml()
{
$xml = <<<EOD
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>{$this->code}</int></value>
</member>
<member>
<name>faultString</name>
<value><string>{$this->message}</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
EOD;
return $xml;
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace IXR\Message;
use IXR\DataType\Date;
class Message
{
public $message;
public $messageType; // methodCall / methodResponse / fault
public $faultCode;
public $faultString;
public $methodName;
public $params;
// Current variable stacks
private $_arraystructs = []; // The stack used to keep track of the current array/struct
private $_arraystructstypes = []; // Stack keeping track of if things are structs or array
private $_currentStructName = []; // A stack as well
private $_param;
private $_value;
private $_currentTag;
private $_currentTagContents;
// The XML parser
private $_parser;
public function __construct($message)
{
$this->message =& $message;
}
public function parse()
{
// first remove the XML declaration
// merged from WP #10698 - this method avoids the RAM usage of preg_replace on very large messages
$header = preg_replace('/<\?xml.*?\?' . '>/s', '', substr($this->message, 0, 100), 1);
$this->message = trim(substr_replace($this->message, $header, 0, 100));
if ('' == $this->message) {
return false;
}
// Then remove the DOCTYPE
$header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($this->message, 0, 200), 1);
$this->message = trim(substr_replace($this->message, $header, 0, 200));
if ('' == $this->message) {
return false;
}
// Check that the root tag is valid
$root_tag = substr($this->message, 0, strcspn(substr($this->message, 0, 20), "> \t\r\n"));
if ('<!DOCTYPE' === strtoupper($root_tag)) {
return false;
}
if (!in_array($root_tag, ['<methodCall', '<methodResponse', '<fault'])) {
return false;
}
// Bail if there are too many elements to parse
$element_limit = 30000;
if ($element_limit && 2 * $element_limit < substr_count($this->message, '<')) {
return false;
}
$this->_parser = xml_parser_create();
// Set XML parser to take the case of tags in to account
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
// Set XML parser callback functions
xml_set_object($this->_parser, $this);
xml_set_element_handler($this->_parser, 'tagOpen', 'tagClose');
xml_set_character_data_handler($this->_parser, 'cdata');
$chunk_size = 262144; // 256Kb, parse in chunks to avoid the RAM usage on very large messages
$final = false;
do {
if (strlen($this->message) <= $chunk_size) {
$final = true;
}
$part = substr($this->message, 0, $chunk_size);
$this->message = substr($this->message, $chunk_size);
if (!xml_parse($this->_parser, $part, $final)) {
return false;
}
if ($final) {
break;
}
} while (true);
xml_parser_free($this->_parser);
// Grab the error messages, if any
if ($this->messageType === 'fault') {
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
/**
* Opening tag handler
* @param $parser
* @param $tag
* @param $attr
*/
public function tagOpen($parser, $tag, $attr)
{
$this->_currentTagContents = '';
$this->currentTag = $tag;
switch ($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$this->messageType = $tag;
break;
/* Deal with stacks of arrays and structs */
case 'data': // data is to all intents and puposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = [];
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = [];
break;
}
}
/**
* Character Data handler
* @param $parser
* @param $cdata
*/
public function cdata($parser, $cdata)
{
$this->_currentTagContents .= $cdata;
}
/**
* Closing tag handler
* @param $parser
* @param $tag
*/
public function tagClose($parser, $tag)
{
$valueFlag = false;
switch ($tag) {
case 'int':
case 'i4':
$value = (int)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'double':
$value = (double)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'string':
$value = (string)($this->_currentTagContents);
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new Date(trim($this->_currentTagContents));
$valueFlag = true;
break;
case 'value':
// "If no type is indicated, the type is string."
if (trim($this->_currentTagContents) != '') {
$value = (string)$this->_currentTagContents;
$valueFlag = true;
}
break;
case 'boolean':
$value = (boolean)trim($this->_currentTagContents);
$valueFlag = true;
break;
case 'base64':
$value = base64_decode($this->_currentTagContents);
$valueFlag = true;
break;
/* Deal with stacks of arrays and structs */
case 'data':
case 'struct':
$value = array_pop($this->_arraystructs);
array_pop($this->_arraystructstypes);
$valueFlag = true;
break;
case 'member':
array_pop($this->_currentStructName);
break;
case 'name':
$this->_currentStructName[] = trim($this->_currentTagContents);
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
break;
}
if ($valueFlag) {
if (count($this->_arraystructs) > 0) {
// Add value to struct or array
if ($this->_arraystructstypes[count($this->_arraystructstypes) - 1] === 'struct') {
// Add to struct
$this->_arraystructs[count($this->_arraystructs) - 1][$this->_currentStructName[count($this->_currentStructName) - 1]] = $value;
} else {
// Add to array
$this->_arraystructs[count($this->_arraystructs) - 1][] = $value;
}
} else {
// Just add as a paramater
$this->params[] = $value;
}
}
$this->_currentTagContents = '';
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace IXR\Request;
use IXR\DataType\Value;
/**
* IXR_Request
*
* @package IXR
* @since 1.5.0
*/
class Request
{
private $method;
private $args;
private $xml;
public function __construct($method, $args)
{
$this->method = $method;
$this->args = $args;
$this->xml = <<<EOD
<?xml version="1.0"?>
<methodCall>
<methodName>{$this->method}</methodName>
<params>
EOD;
foreach ($this->args as $arg) {
$this->xml .= '<param><value>';
$v = new Value($arg);
$this->xml .= $v->getXml();
$this->xml .= "</value></param>\n";
}
$this->xml .= '</params></methodCall>';
}
public function getLength()
{
return strlen($this->xml);
}
public function getXml()
{
return $this->xml;
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace IXR\Server;
use IXR\Message\Error;
/**
* Extension of the {@link Server} class to easily wrap objects.
*
* Class is designed to extend the existing XML-RPC server to allow the
* presentation of methods from a variety of different objects via an
* XML-RPC server.
* It is intended to assist in organization of your XML-RPC methods by allowing
* you to "write once" in your existing model classes and present them.
*
* @author Jason Stirk <jstirk@gmm.com.au>
* @version 1.0.1 19Apr2005 17:40 +0800
* @copyright Copyright (c) 2005 Jason Stirk
* @package IXR
*/
class ClassServer extends Server
{
private $_objects;
private $_delim;
public function __construct($delim = '.', $wait = false)
{
parent::__construct([], false, $wait);
$this->_delim = $delim;
$this->_objects = [];
}
public function addMethod($rpcName, $functionName)
{
$this->callbacks[$rpcName] = $functionName;
}
public function registerObject($object, $methods, $prefix = null)
{
if (is_null($prefix)) {
$prefix = get_class($object);
}
$this->_objects[$prefix] = $object;
// Add to our callbacks array
foreach ($methods as $method) {
if (is_array($method)) {
$targetMethod = $method[0];
$method = $method[1];
} else {
$targetMethod = $method;
}
$this->callbacks[$prefix . $this->_delim . $method] = [$prefix, $targetMethod];
}
}
public function call($methodname, $args)
{
if (!$this->hasMethod($methodname)) {
return new Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
}
$method = $this->callbacks[$methodname];
// Perform the callback and send the response
if (count($args) == 1) {
// If only one paramater just send that instead of the whole array
$args = $args[0];
}
// See if this method comes from one of our objects or maybe self
if (is_array($method) || (substr($method, 0, 5) == 'this:')) {
if (is_array($method)) {
$object = $this->_objects[$method[0]];
$method = $method[1];
} else {
$object = $this;
$method = substr($method, 5);
}
// It's a class method - check it exists
if (!method_exists($object, $method)) {
return new Error(-32601, 'server error. requested class method "' . $method . '" does not exist.');
}
// Call the method
$result = $object->$method($args);
} else {
// It's a function - does it exist?
if (!function_exists($method)) {
return new Error(-32601, 'server error. requested function "' . $method . '" does not exist.');
}
// Call the function
$result = $method($args);
}
return $result;
}
}

View File

@ -0,0 +1,171 @@
<?php
namespace IXR\Server;
use IXR\DataType\Base64;
use IXR\DataType\Date;
use IXR\Message\Error;
/**
* IXR_IntrospectionServer
*
* @package IXR
* @since 1.5.0
*/
class IntrospectionServer extends Server
{
private $signatures;
private $help;
public function __construct()
{
$this->setCallbacks();
$this->setCapabilities();
$this->capabilities['introspection'] = [
'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',
'specVersion' => 1
];
$this->addCallback(
'system.methodSignature',
'this:methodSignature',
['array', 'string'],
'Returns an array describing the return type and required parameters of a method'
);
$this->addCallback(
'system.getCapabilities',
'this:getCapabilities',
['struct'],
'Returns a struct describing the XML-RPC specifications supported by this server'
);
$this->addCallback(
'system.listMethods',
'this:listMethods',
['array'],
'Returns an array of available methods on this server'
);
$this->addCallback(
'system.methodHelp',
'this:methodHelp',
['string', 'string'],
'Returns a documentation string for the specified method'
);
}
public function addCallback($method, $callback, $args, $help)
{
$this->callbacks[$method] = $callback;
$this->signatures[$method] = $args;
$this->help[$method] = $help;
}
public function call($methodname, $args)
{
// Make sure it's in an array
if ($args && !is_array($args)) {
$args = [$args];
}
// Over-rides default call method, adds signature check
if (!$this->hasMethod($methodname)) {
return new Error(-32601,
'server error. requested method "' . $this->message->methodName . '" not specified.');
}
$method = $this->callbacks[$methodname];
$signature = $this->signatures[$methodname];
array_shift($signature);
// Check the number of arguments
if (count($args) != count($signature)) {
return new Error(-32602, 'server error. wrong number of method parameters');
}
// Check the argument types
$ok = true;
$argsbackup = $args;
for ($i = 0, $j = count($args); $i < $j; $i++) {
$arg = array_shift($args);
$type = array_shift($signature);
switch ($type) {
case 'int':
case 'i4':
if (is_array($arg) || !is_int($arg)) {
$ok = false;
}
break;
case 'base64':
case 'string':
if (!is_string($arg)) {
$ok = false;
}
break;
case 'boolean':
if ($arg !== false && $arg !== true) {
$ok = false;
}
break;
case 'float':
case 'double':
if (!is_float($arg)) {
$ok = false;
}
break;
case 'date':
case 'dateTime.iso8601':
if (!($arg instanceof Date)) {
$ok = false;
}
break;
}
if (!$ok) {
return new Error(-32602, 'server error. invalid method parameters');
}
}
// It passed the test - run the "real" method call
return parent::call($methodname, $argsbackup);
}
public function methodSignature($method)
{
if (!$this->hasMethod($method)) {
return new Error(-32601, 'server error. requested method "' . $method . '" not specified.');
}
// We should be returning an array of types
$types = $this->signatures[$method];
$return = [];
foreach ($types as $type) {
switch ($type) {
case 'string':
$return[] = 'string';
break;
case 'int':
case 'i4':
$return[] = 42;
break;
case 'double':
$return[] = 3.1415;
break;
case 'dateTime.iso8601':
$return[] = new Date(time());
break;
case 'boolean':
$return[] = true;
break;
case 'base64':
$return[] = new Base64('base64');
break;
case 'array':
$return[] = ['array'];
break;
case 'struct':
$return[] = ['struct' => 'struct'];
break;
}
}
return $return;
}
public function methodHelp($method)
{
return $this->help[$method];
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace IXR\Server;
use IXR\DataType\Value;
use IXR\Exception\ServerException;
use IXR\Message\Error;
use IXR\Message\Message;
class Server
{
protected $callbacks = [];
protected $message;
protected $capabilities;
public function __construct($callbacks = false, $data = false, $wait = false)
{
$this->setCapabilities();
if ($callbacks) {
$this->callbacks = $callbacks;
}
$this->setCallbacks();
if (!$wait) {
$this->serve($data);
}
}
public function serve($data = false)
{
if (!$data) {
if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Content-Type: text/plain'); // merged from WP #9093
throw new ServerException('XML-RPC server accepts POST requests only.');
}
$data = file_get_contents('php://input');
}
$this->message = new Message($data);
if (!$this->message->parse()) {
$this->error(-32700, 'parse error. not well formed');
}
if ($this->message->messageType != 'methodCall') {
$this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');
}
$result = $this->call($this->message->methodName, $this->message->params);
// Is the result an error?
if ($result instanceof Error) {
$this->error($result);
}
// Encode the result
$r = new Value($result);
$resultxml = $r->getXml();
// Create the XML
$xml = <<<EOD
<methodResponse>
<params>
<param>
<value>
$resultxml
</value>
</param>
</params>
</methodResponse>
EOD;
// Send it
$this->output($xml);
}
protected function call($methodname, $args)
{
if (!$this->hasMethod($methodname)) {
return new Error(-32601, 'server error. requested method ' . $methodname . ' does not exist.');
}
$method = $this->callbacks[$methodname];
// Perform the callback and send the response
if (is_array($args) && count($args) == 1) {
// If only one parameter just send that instead of the whole array
$args = $args[0];
}
try {
// Are we dealing with a function or a method?
if (is_string($method) && substr($method, 0, 5) === 'this:') {
// It's a class method - check it exists
$method = substr($method, 5);
return $this->$method($args);
}
return call_user_func($method, $args);
} catch (\BadFunctionCallException $exception) {
return new Error(-32601, "server error. requested callable '{$method}' does not exist.");
}
}
public function error($error, $message = false)
{
// Accepts either an error object or an error code and message
if ($message && !is_object($error)) {
$error = new Error($error, $message);
}
$this->output($error->getXml());
}
public function output($xml)
{
$xml = '<?xml version="1.0"?>' . "\n" . $xml;
$length = strlen($xml);
header('Connection: close');
header('Content-Length: ' . $length);
header('Content-Type: text/xml');
header('Date: ' . date('r'));
echo $xml;
exit;
}
protected function hasMethod($method)
{
return in_array($method, array_keys($this->callbacks));
}
protected function setCapabilities()
{
// Initialises capabilities array
$this->capabilities = [
'xmlrpc' => [
'specUrl' => 'http://www.xmlrpc.com/spec',
'specVersion' => 1
],
'faults_interop' => [
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
'specVersion' => 20010516
],
'system.multicall' => [
'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
'specVersion' => 1
],
];
}
public function getCapabilities($args)
{
return $this->capabilities;
}
public function setCallbacks()
{
$this->callbacks['system.getCapabilities'] = 'this:getCapabilities';
$this->callbacks['system.listMethods'] = 'this:listMethods';
$this->callbacks['system.multicall'] = 'this:multiCall';
}
public function listMethods($args)
{
// Returns a list of methods - uses array_reverse to ensure user defined
// methods are listed before server defined methods
return array_reverse(array_keys($this->callbacks));
}
public function multiCall($methodcalls)
{
// See http://www.xmlrpc.com/discuss/msgReader$1208
$return = [];
foreach ($methodcalls as $call) {
$method = $call['methodName'];
$params = $call['params'];
if ($method == 'system.multicall') {
$result = new Error(-32600, 'Recursive calls to system.multicall are forbidden');
} else {
$result = $this->call($method, $params);
}
if ($result instanceof Error) {
$return[] = [
'faultCode' => $result->code,
'faultString' => $result->message
];
} else {
$return[] = [$result];
}
}
return $return;
}
}