<?php

namespace Laracasts\Behat\ServiceContainer;

use Behat\Testwork\ServiceContainer\Extension;
use Behat\Testwork\ServiceContainer\ExtensionManager;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Behat\Behat\Context\ServiceContainer\ContextExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Laracasts\Behat\Context\Argument\LaravelArgumentResolver;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension;

class BehatExtension implements Extension
{

    /**
     * {@inheritdoc}
     */
    public function getConfigKey()
    {
        return 'laravel';
    }

    /**
     * {@inheritdoc}
     */
    public function initialize(ExtensionManager $extensionManager)
    {
        if (null !== $minkExtension = $extensionManager->getExtension('mink')) {
            $minkExtension->registerDriverFactory(new LaravelFactory);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        //
    }

    /**
     * {@inheritdoc}
     */
    public function configure(ArrayNodeDefinition $builder)
    {
        $builder
            ->children()
                ->scalarNode('bootstrap_path')
                    ->defaultValue('bootstrap/app.php')
                ->end()
                ->scalarNode('env_path')
                    ->defaultValue('.env.behat');
    }

    /**
     * {@inheritdoc}
     */
    public function load(ContainerBuilder $container, array $config)
    {
        $app = $this->loadLaravel($container, $config);

        $this->loadInitializer($container, $app);
        $this->loadLaravelArgumentResolver($container, $app);
    }

    /**
     * Boot up Laravel.
     *
     * @param ContainerBuilder $container
     * @param array            $config
     * @return mixed
     */
    private function loadLaravel(ContainerBuilder $container, array $config)
    {
        $laravel = new LaravelBooter($container->getParameter('paths.base'), $config['env_path']);

        $container->set('laravel.app', $app = $laravel->boot());

        return $app;
    }

    /**
     * Load the initializer.
     *
     * @param ContainerBuilder    $container
     * @param HttpKernelInterface $app
     */
    private function loadInitializer(ContainerBuilder $container, $app)
    {
        $definition = new Definition('Laracasts\Behat\Context\KernelAwareInitializer', [$app]);

        $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, ['priority' => 0]);
        $definition->addTag(ContextExtension::INITIALIZER_TAG, ['priority' => 0]);

        $container->setDefinition('laravel.initializer', $definition);
    }

    /**
     * Load argument resolver
     *
     * @param  ContainerBuilder $container
     * @param  Application $app
     */
    private function loadLaravelArgumentResolver(ContainerBuilder $container, $app)
    {
        $definition = new Definition(LaravelArgumentResolver::class, [
            new Reference('laravel.app')
        ]);
        $definition->addTag(ContextExtension::ARGUMENT_RESOLVER_TAG, ['priority' => 0]);
        $container->setDefinition('laravel.context.argument.service_resolver', $definition);
    }
}


<?php

namespace Laracasts\Behat\Context\Services;

use GuzzleHttp\Client;
use Config;
use Exception;

trait MailTrap
{

    /**
     * The MailTrap configuration.
     *
     * @var integer
     */
    protected $mailTrapInboxId;

    /**
     * The MailTrap API Key.
     *
     * @var string
     */
    protected $mailTrapApiKey;

    /**
     * The Guzzle client.
     *
     * @var Client
     */
    protected $client;

    /**
     * Get the configuration for MailTrap.
     *
     * @param integer|null $inboxId
     * @throws Exception
     */
    protected function applyMailTrapConfiguration($inboxId = null)
    {
        if (is_null($config = Config::get('services.mailtrap'))) {
            throw new Exception(
                'Set "secret" and "default_inbox" keys for "mailtrap" in "config/services.php."'
            );
        }

        $this->mailTrapInboxId = $inboxId ?: $config['default_inbox'];
        $this->mailTrapApiKey = $config['secret'];
    }

    /**
     * Fetch a MailTrap inbox.
     *
     * @param  integer|null $inboxId
     * @return mixed
     * @throws RuntimeException
     */
    protected function fetchInbox($inboxId = null)
    {
        if ( ! $this->alreadyConfigured()) {
            $this->applyMailTrapConfiguration($inboxId);
        }

        $body = $this->requestClient()
            ->get($this->getMailTrapMessagesUrl())
            ->getBody();

        return $this->parseJson($body);
    }

    /**
     *
     * Empty the MailTrap inbox.
     *
     * @AfterScenario @mail
     */
    public function emptyInbox()
    {
        $this->requestClient()->patch($this->getMailTrapCleanUrl());
    }

    /**
     * Get the MailTrap messages endpoint.
     *
     * @return string
     */
    protected function getMailTrapMessagesUrl()
    {
        return "/api/v1/inboxes/{$this->mailTrapInboxId}/messages";
    }

    /**
     * Get the MailTrap "empty inbox" endpoint.
     *
     * @return string
     */
    protected function getMailTrapCleanUrl()
    {
        return "/api/v1/inboxes/{$this->mailTrapInboxId}/clean";
    }

    /**
     * Determine if MailTrap config has been retrieved yet.
     *
     * @return boolean
     */
    protected function alreadyConfigured()
    {
        return $this->mailTrapApiKey;
    }

    /**
     * Request a new Guzzle client.
     *
     * @return Client
     */
    protected function requestClient()
    {
        if ( ! $this->client) {
            $this->client = new Client([
                'base_uri' => 'https://mailtrap.io',
                'headers' => ['Api-Token' => $this->mailTrapApiKey]
            ]);
        }

        return $this->client;
    }

    /**
     * @param $body
     * @return array|mixed
     * @throws RuntimeException
     */
    protected function parseJson($body)
    {
        $data = json_decode((string)$body, true);

        if (JSON_ERROR_NONE !== json_last_error()) {
            throw new RuntimeException('Unable to parse response body into JSON: '.json_last_error());
        }

        return $data === null ? array() : $data;
    }
}


<?php

namespace Laracasts\Behat\Context;

use Behat\Behat\Context\Context;
use Behat\Behat\Context\Initializer\ContextInitializer;
use Behat\Behat\EventDispatcher\Event\ScenarioTested;
use Laracasts\Behat\ServiceContainer\LaravelBooter;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\HttpKernelInterface;

class KernelAwareInitializer implements EventSubscriberInterface, ContextInitializer
{

    /**
     * The app kernel.
     *
     * @var HttpKernelInterface
     */
    private $kernel;

    /**
     * The Behat context.
     *
     * @var Context
     */
    private $context;

    /**
     * Construct the initializer.
     *
     * @param HttpKernelInterface $kernel
     */
    public function __construct(HttpKernelInterface $kernel)
    {
        $this->kernel = $kernel;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        return [
            ScenarioTested::AFTER => ['rebootKernel', -15]
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function initializeContext(Context $context)
    {
        $this->context = $context;

        $this->setAppOnContext($this->kernel);
    }

    /**
     * Set the app kernel to the feature context.
     */
    private function setAppOnContext()
    {
        if ($this->context instanceof KernelAwareContext) {
            $this->context->setApp($this->kernel);
        }
    }

    /**
     * After each scenario, reboot the kernel.
     */
    public function rebootKernel()
    {
        if ($this->context instanceof KernelAwareContext)
        {
            $this->kernel->flush();

            $laravel = new LaravelBooter($this->kernel->basePath(), $this->kernel->environmentFile());

            $this->context->getSession('laravel')->getDriver()->reboot($this->kernel = $laravel->boot());

            $this->setAppOnContext();
        }
    }

}


<?php
namespace Laracasts\Behat\Context\Argument;

use \ReflectionClass;
use Illuminate\Foundation\Application;
use Behat\Behat\Context\Argument\ArgumentResolver;

class LaravelArgumentResolver implements ArgumentResolver
{
    /** @var Application Laravel application instance */
    private $app;

    /**
     * @param Application $app
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
    }

    /**
     * Resolves context constructor arguments.
     *
     * @param ReflectionClass $classReflection
     * @param mixed[]         $arguments
     *
     * @return mixed[]
     */
    public function resolveArguments(ReflectionClass $classReflection, array $arguments)
    {
        $resolvedArguments = [];

        foreach ($arguments as $key => $argument) {
            $resolvedArguments[$key] = $this->resolveArgument($argument);
        }

        return $resolvedArguments;
    }

    /**
     * Resolve argument
     *
     * @param  string $arg
     * @return object
     */
    private function resolveArgument($arg)
    {
        if (substr($arg, 0, 1) === '@') {
            return $this->app->make(substr($arg, 1));
        }

        return $arg;
    }
}


<?php
/**
 * Spiral Framework.
 *
 * @license   MIT
 * @author    Anton Titov (Wolfy-J)
 */
declare(strict_types=1);

namespace Spiral\Streams;

use Psr\Http\Message\StreamInterface;
use Spiral\Streams\Exception\WrapperException;

/**
 * Spiral converter of PSR-7 streams to virtual filenames. Static as hell.
 */
final class StreamWrapper
{
    /** @var bool */
    private static $registered = false;

    /**
     * Uris associated with StreamInterfaces.
     *
     * @var array
     */
    private static $uris = [];

    /** @var array */
    private static $modes = [
        'r'   => 33060,
        'rb'  => 33060,
        'r+'  => 33206,
        'rb+' => 33206,
        'w'   => 33188,
        'wb'  => 33188
    ];

    /** @var StreamInterface */
    private $stream = null;

    /** @var int */
    private $mode = 0;

    /**
     * Stream context.
     *
     * @var resource
     */
    public $context = null;

    /**
     * Check if StreamInterface ended.
     *
     * @return bool
     */
    public function stream_eof()
    {
        return $this->stream->eof();
    }

    /**
     * Open pre-mocked StreamInterface by it's unique uri.
     *
     * @param string  $path
     * @param int     $mode
     * @param int     $options
     * @param string &$opened_path
     * @return bool
     */
    public function stream_open($path, $mode, $options, &$opened_path)
    {
        if (!isset(self::$uris[$path])) {
            return false;
        }

        $this->stream = self::$uris[$path];
        $this->mode = $mode;

        $this->stream->rewind();

        return true;
    }

    /**
     * Read data from StreamInterface.
     *
     * @param int $count
     * @return string
     */
    public function stream_read($count)
    {
        return $this->stream->read($count);
    }

    /**
     * Seek into StreamInterface.
     *
     * @param int $offset
     * @param int $whence = SEEK_SET
     * @return bool
     */
    public function stream_seek($offset, $whence = SEEK_SET)
    {
        //Note, MongoDB native streams DO NOT support seeking at the moment
        //@see https://jira.mongodb.org/browse/PHPLIB-213
        $this->stream->seek($offset, $whence);

        return true;
    }

    /**
     * Get StreamInterface stats.
     *
     * @see stat()
     * @return array|null
     */
    public function stream_stat()
    {
        return $this->getStreamStats($this->stream);
    }

    /**
     * Get StreamInterface position.
     *
     * @return int
     */
    public function stream_tell()
    {
        //Note, MongoDB native streams DO NOT support seeking at the moment
        //@see https://jira.mongodb.org/browse/PHPLIB-213
        return $this->stream->tell();
    }

    /**
     * Write content into StreamInterface.
     *
     * @param string $data
     *
     * @return int
     */
    public function stream_write($data)
    {
        return $this->stream->write($data);
    }

    /**
     * Get stats based on wrapped StreamInterface by it's mocked uri.
     *
     * @see stat()
     * @param string $path
     * @param int    $flags
     * @return array|null
     */
    public function url_stat($path, $flags)
    {
        if (!isset(self::$uris[$path])) {
            return null;
        }

        return $this->getStreamStats(self::$uris[$path]);
    }

    /**
     * Helper method used to correctly resolve StreamInterface stats.
     *
     * @param StreamInterface $stream
     * @return array
     */
    private function getStreamStats(StreamInterface $stream)
    {
        $mode = $this->mode;
        if (empty($mode)) {
            if ($stream->isReadable()) {
                $mode = 'r';
            }

            if ($stream->isWritable()) {
                $mode = !empty($mode) ? 'r+' : 'w';
            }
        }

        return [
            'dev'     => 0,
            'ino'     => 0,
            'mode'    => self::$modes[$mode],
            'nlink'   => 0,
            'uid'     => 0,
            'gid'     => 0,
            'rdev'    => 0,
            'size'    => (string)$stream->getSize(),
            'atime'   => 0,
            'mtime'   => 0,
            'ctime'   => 0,
            'blksize' => 0,
            'blocks'  => 0
        ];
    }

    /**
     * Register stream wrapper.
     */
    public static function register()
    {
        if (self::$registered) {
            return;
        }

        stream_wrapper_register('spiral', __CLASS__);
        self::$registered = true;
    }

    /**
     * Check if given uri or stream has been allocated.
     *
     * @param string|StreamInterface $file
     * @return bool
     */
    public static function has($file)
    {
        if ($file instanceof StreamInterface) {
            $file = 'spiral://' . spl_object_hash($file);
        }

        return isset(self::$uris[$file]);
    }

    /**
     * Create StreamInterface associated resource.
     *
     * @param StreamInterface $stream
     * @return resource
     *
     * @throws WrapperException
     */
    public static function getResource(StreamInterface $stream)
    {
        $mode = null;
        if ($stream->isReadable()) {
            $mode = 'r';
        }

        if ($stream->isWritable()) {
            $mode = !empty($mode) ? 'r+' : 'w';
        }

        if (empty($mode)) {
            throw new WrapperException("Stream is not available in read or write modes");
        }

        return fopen(self::getFilename($stream), $mode);
    }

    /**
     * Register StreamInterface and get unique url for it.
     *
     * @param StreamInterface $stream
     * @return string
     */
    public static function getFilename(StreamInterface $stream)
    {
        self::register();

        $uri = 'spiral://' . spl_object_hash($stream);
        self::$uris[$uri] = $stream;

        return $uri;
    }

    /**
     * Free uri dedicated to specified StreamInterface. Method is useful for long living
     * applications. You must close resource by yourself!
     *
     * @param string|StreamInterface $file String uri or StreamInterface.
     */
    public static function release($file)
    {
        if ($file instanceof StreamInterface) {
            $file = 'spiral://' . spl_object_hash($file);
        }

        unset(self::$uris[$file]);
    }
}


<?php
/**
 * @link https://github.com/webtoolsnz/yii2-widgets
 * @copyright Copyright (c) 2015 Webtools Ltd
 */

namespace webtoolsnz\widgets;

use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
use yii\widgets\InputWidget;
use yii\helpers\FormatConverter;
use Yii;

class DateRangePicker extends InputWidget
{
    /**
     * Display format for date field
     *
     * @var String
     */
    public $dateFormat;

    public $parentSelectorHide = '.form-group';

    /*
    * @var String
    */
    public $start_attribute;
    public $end_attribute;

    /**
     * @var array
     */
    public $clientOptions = [];

    /**
     * @var array
     */
    public $options = [];

    /**
     * @var String
     */
    public $value = '';

    /**
     * @var String
     */
    public $separator = ' - ';

    /**
     * @var bool
     */
    public $displayIcon = false;

    /**
     * @throws \yii\base\InvalidConfigException
     */
    public function init()
    {
        if (!isset($this->options['id'])) {
            $this->options['id'] = $this->getId();
            $this->name = $this->getId();
        }

        if (!$this->hasModel()) {
            throw new InvalidConfigException("DateRangePicker requires a model, check docs for usage.");
        }

        Html::addCssClass($this->options, ['date-range-picker', 'form-control']);

        if (!$this->dateFormat) {
            $this->dateFormat = Yii::$app->formatter->dateFormat;
        }
        if ($this->getDate('start') && $this->getDate('end')) {
            $this->clientOptions['startDate'] = new JsExpression('moment("'.$this->getDate('start').'")');
            $this->clientOptions['endDate'] =  new JsExpression('moment("'.$this->getDate('end').'")');
        }

        $dateFormat = $this->dateFormat;
        if (strncmp($this->dateFormat, 'php:', 4) === 0) {
            $dateFormat = FormatConverter::convertDatePhpToIcu(substr($this->dateFormat, 4));
        }
        $this->clientOptions['locale'] = [
            'format' => MomentFormatConverter::convert($dateFormat)
        ];

    }

    /**
     * @inheritdoc
     */
    public function run()
    {
        /*if ($this->hasModel()) {
            $input = Html::activeTextInput($this->model, $this->attribute, $this->options);
        } else {*/
            $input = Html::textInput($this->name, $this->value, $this->options);
        //}

        if ($this->displayIcon) {
            $icon = Html::tag('span', null, ['class' => 'glyphicon glyphicon-calendar']);
            $addon = Html::tag('div', $icon, ['class' => 'input-group-addon']);
            $input = Html::tag('div', $addon.$input, ['class' => 'input-group']);
        }

        echo $input;

        $this->registerClientScript();
    }

    /**
     * Registers wysihtml5 plugin and the related events
     */
    protected function registerClientScript()
    {
        $view = $this->getView();

        DateRangePickerAsset::register($view);

        $id = $this->options['id'];

        $options = Json::encode($this->clientOptions);
        $startInputId = Json::encode('#'.Html::getInputId($this->model, $this->start_attribute));
        $endInputId = Json::encode('#'.Html::getInputId($this->model, $this->end_attribute));

        $fn = new JsExpression(<<<JS
function(ev, picker) {
    elS.val(picker.startDate.format('YYYY-MM-DD'));
    elE.val(picker.endDate.format('YYYY-MM-DD'));
}
JS
);
        $selector = Json::encode($this->parentSelectorHide);
        $view->registerJs(<<<JS
(function () {
    var elS = $($startInputId),
        elE = $($endInputId);
    jQuery('#$id')
        .daterangepicker($options)
        .on('apply.daterangepicker', $fn);

    elS.parents($selector).hide();
    elE.parents($selector).hide();
})();
JS
);

    }

    public function getDate($date = 'start')
    {
        // get formatted date value

        $value = Html::getAttributeValue(
            $this->model,
            $date == 'start' ? $this->start_attribute : $this->end_attribute
        );

        return $value;
    }

    public function hasModel()
    {
        return ($this->model instanceof Model);
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL\Types;

/**
 * \brief
 *      The XML-RPC "i8" type.
 *
 * This type represents a big integer.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class BigInteger extends \fpoirotte\XRL\Types\AbstractInteger
{
    /// \copydoc fpoirotte::XRL::Types::AbstractType::__toString()
    public function __toString()
    {
        return gmp_strval($this->value);
    }

    /// XML-RPC type for this class.
    const XMLRPC_TYPE   = '{http://ws.apache.org/xmlrpc/namespaces/extensions}biginteger';

    /// \copydoc fpoirotte::XRL::Types::AbstractType::set()
    public function set($value)
    {
        static $hasGMP = null;

        if (null === $hasGMP) {
            $hasGMP = function_exists("gmp_init");
        }

        if (!$hasGMP) {
            throw new \RuntimeException("The GMP extension is required for this operation to work");
        }

        if (is_object($value) && $value instanceof \GMP) {
            // It is already a GMP integer.
        } else {
            $value = @gmp_init($value, 10);
        }

        if ($value === false) {
            throw new \InvalidArgumentException("A valid big integer was expected");
        }

        $this->value = $value;
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL\Types;

/**
 * \brief
 *      The XML-RPC "dom" type.
 *
 * The "dom" type extension, as described in
 * http://ws.apache.org/xmlrpc/types.html
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Dom extends \fpoirotte\XRL\Types\AbstractType
{
    /// \copydoc fpoirotte::XRL::Types::AbstractType::__toString()
    public function __toString()
    {
        return $this->value->asXML();
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::set()
    public function set($value)
    {
        if (!is_object($value)) {
            throw new \InvalidArgumentException('Expected an object');
        }

        if ($value instanceof \DOMNode) {
            $value = simplexml_import_dom($value);
        }

        if ($value instanceof \XMLWriter) {
            $value = new \SimpleXMLElement($value->outputMemory(), LIBXML_NONET);
        }

        if (!($value instanceof \SimpleXMLElement)) {
            throw new \InvalidArgumentException('Expected a SimpleXMLElement object');
        }

        $this->value = $value;
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::write()
    public function write(\XMLWriter $writer, \DateTimeZone $timezone, $stringTag)
    {
        $xml = $this->value->asXML();

        // Not all combinations of PHP/libxml support stripping
        // the XML declaration. So, we do it ourselves here.
        if (strlen($xml) >= 6 && !strncmp($xml, '<?xml', 5) &&
            strpos(" \t\r\n", $xml[5]) !== false) {
            $xml = (string) substr($xml, strpos($xml, '?>') + 2);
        }

        $writer->startElementNS(
            'ex',
            'dom',
            'http://ws.apache.org/xmlrpc/namespaces/extensions'
        );
        $writer->writeRaw($xml);
        $writer->fullEndElement();
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::parse()
    protected static function parse($value, \DateTimeZone $timezone = null)
    {
        return simplexml_load_string($value, '\\SimpleXMLElement', LIBXML_NONET);
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL\Types;

/**
 * \brief
 *      The XML-RPC "i8" type.
 *
 * The "i8" type extension, as described in
 * http://ws.apache.org/xmlrpc/types.html
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class I8 extends \fpoirotte\XRL\Types\AbstractInteger
{
    /// XML-RPC type for this class.
    const XMLRPC_TYPE   = '{http://ws.apache.org/xmlrpc/namespaces/extensions}i8';

    /// Integer size in bits.
    const INTEGER_BITS  = 64;

    /// \copydoc fpoirotte::XRL::Types::AbstractType::set()
    public function set($value)
    {
        try {
            // Try to use PHP's integer type to hold the value.
            // This will only work on 64-bit versions of PHP.
            parent::set($value);
        } catch (\InvalidArgumentException $e) {
            // Try to use a GMP resource/object instead,
            // but make sure the value does not overflow/underflow.
            $value  = new \fpoirotte\XRL\Types\BigInteger($value);
            $gmp    = $value->get();
            if (gmp_cmp($gmp, "-9223372036854775808") < 0 || gmp_cmp($gmp, "9223372036854775807") > 0) {
                throw $e;
            }
            $this->value = $value;
        }
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      An XML-RPC encoder that transparently converts
 *      PHP types to their XML-RPC counterpart.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class NativeEncoder implements \fpoirotte\XRL\EncoderInterface
{
    /// Sub-encoder.
    protected $encoder;

    /**
     * Creates a new encoder.
     *
     * \param fpoirotte::XRL::EncoderInterface $encoder
     *      Sub-encoder to use.
     */
    public function __construct(\fpoirotte\XRL\EncoderInterface $encoder)
    {
        $this->encoder = $encoder;
    }

    /**
     * Determine if a string contains a valid sequence of UTF-8 encoded
     * Unicode codepoints.
     *
     * \param string $text
     *      Some text to test for UTF-8 validity.
     *
     * \retval bool
     *      \c true if the $text contains a valid UTF-8 sequence
     *      which does not make use of non-characters/reserved characters,
     *      \c null if it contains a valid UTF-8 containing
     *      non-characters/reserved characters, or \c false otherwise.
     */
    protected static function isUTF8($text)
    {
        /*
            Based on http://w3.org/International/questions/qa-forms-utf-8.html,
            but rewritten to avoid a dependency on PCRE.

            Also, the regular expression in the post above seems to limit
            valid inputs to printable characters (for compatibility reasons?).
            See also section 2.2 of http://www.w3.org/TR/xml11 and section 2.2
            of http://www.w3.org/TR/xml/ for a possible explanation.

            The code below does not suffer from such limitations.
        */
        $len = strlen($text);
        $res = true;
        for ($i = 0; $i < $len; $i++) {
            // U+0000 - U+007F
            $byte1 = ord($text[$i]);
            if ($byte1 >= 0 && $byte1 <= 0x7F) {
                continue;
            }

            // Look for 2nd byte
            if (++$i >= $len) {
                return false;
            }
            $byte2 = ord($text[$i]);

            // U+0080 - U-07FF
            if ($byte1 >= 0xC2 && $byte1 <= 0xDF) {
                if ($byte2 >= 0x80 && $byte2 <= 0xBF) {
                    continue;
                }
                return false;
            }

            // Look for 3nd byte
            if (++$i >= $len) {
                return false;
            }
            $byte3 = ord($text[$i]);

            // U+0800 - U+0FFF
            if ($byte1 == 0xE0) {
                if ($byte2 >= 0xA0 && $byte2 <= 0xBF &&
                    $byte3 >= 0x80 && $byte3 <= 0xBF) {
                    continue;
                }
                return false;
            }

            // U+1000 - U+CFFF & U+E000 - U+FFFF
            if ($byte1 >= 0xE1 && $byte1 <= 0xEF && $byte1 !== 0xED) {
                if ($byte2 >= 0x80 && $byte2 <= 0xBF &&
                    $byte3 >= 0x80 && $byte3 <= 0xBF) {
                    $codepoint = (($byte1 & 0x0F) << 12) + (($byte2 & 0x3F) << 6) + ($byte3 & 0x3F);
                    if (($codepoint >= 0xE000 && $codepoint <= 0xF8FF) ||   // Private range
                        ($codepoint >= 0xFDD0 && $codepoint <= 0xFDEF) ||   // Non-characters
                        $codepoint == 0xFFFE || $codepoint == 0xFFFF) {     // Non-characters
                        $res = null;
                    }
                    continue;
                }
                return false;
            }

            // U+D000 - U+D7FF
            if ($byte1 == 0xED) {
                if ($byte2 >= 0x80 && $byte2 <= 0x9F &&
                    $byte3 >= 0x80 && $byte3 <= 0xBF) {
                    continue;
                }
                return false;
            }

            // Look for 4nd byte
            if (++$i >= $len) {
                return false;
            }
            $byte4 = ord($text[$i]);

            // U+10000 - U+3FFFF
            if ($byte1 == 0xF0) {
                if ($byte2 >= 0x90 && $byte2 <= 0xBF &&
                    $byte3 >= 0x80 && $byte3 <= 0xBF &&
                    $byte4 >= 0x80 && $byte4 <= 0xBF) {
                    $codepoint = (($byte1 & 0x07) << 18) +
                                 (($byte2 & 0x3F) << 12) +
                                 (($byte3 & 0x3F) << 6) +
                                 ($byte4 & 0x3F);
                    //  Non-characters                                    Reserved range
                    if ($codepoint == 0x1FFFE || $codepoint == 0x1FFFF || $codepoint >= 0x2FFFE) {
                        $res = null;
                    }
                    continue;
                }
                return false;
            }

            // U+40000 - U+FFFFF
            if ($byte1 >= 0xF1 && $byte1 <= 0xF3) {
                if ($byte2 >= 0x80 && $byte2 <= 0xBF &&
                    $byte3 >= 0x80 && $byte3 <= 0xBF &&
                    $byte4 >= 0x80 && $byte4 <= 0xBF) {
                    $codepoint = (($byte1 & 0x07) << 18) +
                                 (($byte2 & 0x3F) << 12) +
                                 (($byte3 & 0x3F) << 6) +
                                 ($byte4 & 0x3F);
                    //  Reserved range           Non characters & private ranges
                    if ($codepoint < 0xE0000 || $codepoint >= 0xEFFFE) {
                        $res = null;
                    }
                    continue;
                }
                return false;
            }

            // U+100000 - U+10FFFF
            if ($byte1 == 0xF4) {
                if ($byte2 >= 0x80 && $byte2 <= 0x8F &&
                    $byte3 >= 0x80 && $byte3 <= 0xBF &&
                    $byte4 >= 0x80 && $byte4 <= 0xBF) {
                    // This part contains only non-characters & private ranges.
                    $res = null;
                    continue;
                }
                return false;
            }

            // Byte #1 contained an invalid value.
            return false;
        }

        // No decoding error detected, but the given input may contain
        // non-characters/reserved characters, depending on the value of $res.
        return $res;
    }

    protected static function isBinaryString($text)
    {
        if (!static::isUTF8($text)) {
            return true;
        }

        // Based on XML 1.1, section 2.2 (compatibility characters).
        // We're a bit more lax as long as the whole string forms
        // a valid UTF-8 codepoints sequence.
        $restrictedChars =
            "\x00\x01\x02\x03\x04\x05\x06\x07\x08" .
            "\x0B\x0C" .
            "\x0E\x0F\x10" .
            "\x11\x12\x13\x14\15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" .
            "\x7F";

        // If the text contains restricted characters,
        // encode it as a binary string.
        if (strcspn($text, $restrictedChars) !== strlen($text)) {
            return true;
        }

        // Otherwise, it is probably safe to encode the string "as is".
        return false;
    }

    /**
     * Convert the given PHP value to an XML-RPC type.
     *
     * \param mixed $value
     *      The PHP value to convert.
     *
     * \retval fpoirotte::XRL::Types::AbstractType
     *      The value encapsulated in an XML-RPC type.
     *
     * \note
     *      This implementation supports the "nil" XML-RPC extension
     *      (http://ontosys.com/xml-rpc/extensions.php).
     *
     * \note
     *      An empty PHP array will always be encoded as an empty
     *      XML-RPC array.
     *
     * \note
     *      The following PHP types are currently supported:
     *      -   \c null (encoded using the "nil" XML-RPC type).
     *      -   integer (encoded using the "i4" or "i8" XML-RPC type,
     *          depending on the actual size required to store the value).
     *      -   boolean (encoded using the "boolean" XML-RPC type).
     *      -   string (encoded using either the default type
     *          of XML-RPC [string] or "base64" if the string
     *          contains invalid UTF-8 sequences, such as when
     *          encoding binary data).
     *      -   double (encoded using the "double" XML-RPC type).
     *      -   array (encoded using either the "array" or "struct"
     *          XML-RPC type):
     *          -   "array" is used for numerically-indexed arrays
     *              where the keys are in the range [0..len(array)-1],
     *              aka "lists".
     *          -   "struct" is used for all other arrays, aka "hashes".
     *      -   DateTime objects (encoded using the "dateTime.iso8601"
     *          XML-RPC type).
     *      -   GMP objects/resources (encoded as "i4", "i8" or "BigInteger",
     *          depending on their actual storage size).
     *      -   XML objects (SimpleXML, DOM and XMLWriter objects)
     *          are encoded as XML DOM fragments.
     *      -   objects that support serialization (encoded as an XML-RPC
     *          "string" or "base64" type depending on the object's
     *          representation in serialized form).
     */
    public static function convert($value)
    {
        switch (gettype($value)) {
            case 'NULL':
                // Support for the <nil> extension
                // (http://ontosys.com/xml-rpc/extensions.php)
                return new \fpoirotte\XRL\Types\Nil(null);

            case 'boolean':
                return new \fpoirotte\XRL\Types\Boolean($value);

            case 'integer':
                try {
                    return new \fpoirotte\XRL\Types\I4($value);
                } catch (\InvalidArgumentException $e) {
                }
                return new \fpoirotte\XRL\Types\I8($value);

            case 'double':
                return new \fpoirotte\XRL\Types\Double($value);

            case 'string':
                // We try to encode it as a regular string if possible.
                if (static::isBinaryString($value)) {
                    return new \fpoirotte\XRL\Types\Base64($value);
                }
                return new \fpoirotte\XRL\Types\StringType($value);

            case 'array':
                $newValue = array_map("static::convert", $value);
                try {
                    return new \fpoirotte\XRL\Types\ArrayType($newValue);
                } catch (\InvalidArgumentException $e) {
                }
                return new \fpoirotte\XRL\Types\Struct($newValue);

            case 'object':
            case 'resource':
                // A special treatment is applied afterwards.
                break;
        }

        // Only objects & resources remain after this point.
        if ($value instanceof \fpoirotte\XRL\Types\AbstractType) {
            return $value;
        }

        if ($value instanceof \GMP) {
            $candidates = array(
                '\\fpoirotte\\XRL\\Types\\I4',
                '\\fpoirotte\\XRL\\Types\\I8',
                '\\fpoirotte\\XRL\\Types\\BigInteger',
            );
            foreach ($candidates as $candidate) {
                try {
                    return new $candidate($value);
                } catch (\InvalidArgumentException $e) {
                }
            }
        }

        if ($value instanceof \DateTime) {
            return new \fpoirotte\XRL\Types\DateTimeIso8601($value);
        }

        if (($value instanceof \DOMNode) ||
            ($value instanceof \XMLWriter) ||
            ($value instanceof \SimpleXMLElement)) {
            return new \fpoirotte\XRL\Types\Dom($value);
        }

        if ($value instanceof \Exception) {
            return new \fpoirotte\XRL\Types\Struct(
                array(
                    'faultCode'     => new \fpoirotte\XRL\Types\IntType($value->getCode()),
                    'faultString'   => new \fpoirotte\XRL\Types\StringType(
                        get_class($value).': '.$value->getMessage()
                    ),
                )
            );
        }

        if (is_object($value) && (
            ($value instanceof \Serializable) ||
            method_exists($value, '__sleep'))) {
            $value = serialize($value);

            // We try to encode it as a regular string if possible.
            if (static::isBinaryString($value)) {
                return new \fpoirotte\XRL\Types\Base64($value);
            }
            return new \fpoirotte\XRL\Types\StringType($value);
        }

        throw new \InvalidArgumentException('Unconvertible type');
    }

    /// \copydoc fpoirotte::XRL::EncoderInterface::encodeRequest()
    public function encodeRequest(\fpoirotte\XRL\Request $request)
    {
        $newParams = array_map('static::convert', $request->getParams());
        return $this->encoder->encodeRequest(
            new \fpoirotte\XRL\Request($request->getProcedure(), $newParams)
        );
    }

    /// \copydoc fpoirotte::XRL::EncoderInterface::encodeError()
    public function encodeError(\Exception $error)
    {
        return $this->encoder->encodeError($error);
    }

    /// \copydoc fpoirotte::XRL::EncoderInterface::encodeResponse()
    public function encodeResponse($response)
    {
        return $this->encoder->encodeResponse(static::convert($response));
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      A class that formats messages
 *      before sending them to a stream.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Output
{
    /// Stream to send the messages to.
    protected $stream;

    /**
     * Create a new outputter.
     *
     * \param resource $stream
     *      The PHP stream this outputter
     *      will write to.
     */
    public function __construct($stream)
    {
        if (@stream_get_meta_data($stream) === null) {
            throw new \InvalidArgumentException('Not a valid stream');
        }

        $this->stream = $stream;
    }

    /**
     * Write some message to the stream,
     * in a \c printf() fashion.
     *
     * \param string $format
     *      A format string to use to send the message.
     *
     * \note
     *      You may pass additional parameters to this
     *      method. They will serve as arguments for
     *      the format string.
     *
     * \note
     *      You don't need to add an end-of-line sequence
     *      to the format string, one will automatically
     *      be added for you by this method.
     */
    public function write($format /* , ... */)
    {
        $args = func_get_args();
        array_shift($args);
        // Protection against format attacks.
        if (!count($args)) {
            $args[] = $format;
            $format = "%s";
        }
        vfprintf($this->stream, $format . PHP_EOL, $args);
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      This class represents the response
 *      to an XML-RPC request.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Response implements \fpoirotte\XRL\ResponseInterface
{
    /// The response to an XML-RPC request, as serialized XML.
    protected $result;

    /**
     * Create the response to an XML-RPC request.
     *
     * \param mixed $xmlResult
     *      The result of the request as a string,
     *      encoded using XML-RPC types.
     */
    public function __construct($xmlResult)
    {
        if (!is_string($xmlResult)) {
            throw new \InvalidArgumentException('Not a valid response');
        }

        $this->result = $xmlResult;
    }

    /// \copydoc fpoirotte::XRL::ResponseInterface::__toString()
    public function __toString()
    {
        return (string) $this->result;
    }

    /**
     * Adds an HTTP header to the current response.
     *
     * \param string $header
     *      Header to add.
     *
     * \retval fpoirotte::XRL::ResponseInterface
     *      Returns this response.
     *
     * @codeCoverageIgnore
     */
    protected function addHeader($header)
    {
        header($header);
        return $this;
    }

    /**
     * A function that echoes its input and exits.
     *
     * \param string $result
     *      Some string to echo before exiting.
     *
     * @codeCoverageIgnore
     */
    protected function finalize($result)
    {
        exit($result);
    }

    /// \copydoc fpoirotte::XRL::ResponseInterface::publish()
    public function publish()
    {
        $result = (string) $this->result;
        $this->addHeader('Content-Type: text/xml')
             ->addHeader('Content-Length: ' . strlen($result))
             ->finalize($result);
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL\Types;

/**
 * \brief
 *      The XML-RPC "string" type.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class StringType extends \fpoirotte\XRL\Types\AbstractType
{
    /// \copydoc fpoirotte::XRL::Types::AbstractType::set()
    public function set($value)
    {
        if (!is_string($value)) {
            throw new \InvalidArgumentException('A string value was expected');
        }
        $this->value = $value;
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::write()
    public function write(\XMLWriter $writer, \DateTimeZone $timezone, $stringTag)
    {
        if ($stringTag) {
            $writer->writeElement('string', $this->value);
        } else {
            $writer->text($this->value);
        }
    }
}


<?php declare(strict_types=1);

namespace ApiClients\Tools\CommandBus;

use League\Tactician\CommandBus as Tactician;
use League\Tactician\Handler\CommandHandlerMiddleware;
use React\EventLoop\LoopInterface;
use React\Promise\CancellablePromiseInterface;
use function React\Promise\resolve;
use function WyriHaximus\React\futurePromise;

final class CommandBus implements CommandBusInterface
{
    /**
     * @var Tactician
     */
    private $commandBus;

    /**
     * @var LoopInterface
     */
    private $loop;

    /**
     * @param LoopInterface            $loop
     * @param CommandHandlerMiddleware $commandHandlerMiddleware
     */
    public function __construct(LoopInterface $loop, CommandHandlerMiddleware $commandHandlerMiddleware)
    {
        $this->loop = $loop;
        $this->commandBus = new Tactician([
            $commandHandlerMiddleware,
        ]);
    }

    /**
     * Executes the given command and optionally returns a value.
     *
     * @param object $command
     *
     * @return CancellablePromiseInterface
     */
    public function handle($command): CancellablePromiseInterface
    {
        return futurePromise($this->loop, $command)->then(function ($command) {
            return resolve($this->commandBus->handle($command));
        });
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      An XML node as read from an XML reader.
 *
 * This class is used to memorize the last XML node
 * that was read from an XML reader, so as to push
 * the node back in case it could not be processed
 * by an XML-RPC decoder.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Node
{
    /// Fields that make up this node.
    protected $properties;

    /// Error codes from http://www.xmlsoft.org/html/libxml-xmlerror.html.
    const XML_ERR_INTERNAL_ERROR        = 1;
    const XML_ERR_NO_MEMORY             = 2;
    const XML_ERR_DOCUMENT_EMPTY        = 4;
    const XML_ERR_DOCUMENT_END          = 5;
    const XML_ERR_INVALID_CHAR          = 9;
    const XML_ERR_UNKNOWN_ENCODING      = 31;
    const XML_ERR_UNSUPPORTED_ENCODING  = 32;

    /**
     * Create a new XML node.
     *
     * \param XMLReader $reader
     *      XML reader object that will be used to create
     *      this node.
     *
     * \param bool $validate
     *      Whether an exception should be raised (\c true)
     *      or not (\c false) if the current node is not valid.
     *
     * \param bool $subtrees
     *      Whether to explore subtrees (\c true) or not (\c false).
     */
    public function __construct(\XMLReader $reader, $validate, $subtrees)
    {
        do {
            // We must silence read()/next() as old PHPs (5.3.x) emit warnings
            // which get caught by PHPUnit and other custom error handlers
            // when the methods fail and this is known to cause some issues.
            if (($subtrees && !@$reader->read()) ||
                (!$subtrees && !@$reader->next())) {
                $error = libxml_get_last_error();

                // We reached the end of the document.
                // This is not an error per-se,
                // but it causes read() to fail anyway.
                // We throw a special error which gets caught
                // and dealt with appropriately by the caller.
                if ($error === false) {
                    throw new \InvalidArgumentException('End of document');
                }

                switch ($error->code) {
                    case self::XML_ERR_UNKNOWN_ENCODING:
                    case self::XML_ERR_UNSUPPORTED_ENCODING:
                        throw new \fpoirotte\XRL\Faults\UnsupportedEncodingException();

                    // Internal & memory errors are too hard to recreate
                    // and are thus excluded from code coverage analysis.
                    // @codeCoverageIgnoreStart
                    case self::XML_ERR_INTERNAL_ERROR:
                    case self::XML_ERR_NO_MEMORY:
                        throw new \fpoirotte\XRL\Faults\InternalErrorException();
                    // @codeCoverageIgnoreEnd

                    case self::XML_ERR_INVALID_CHAR:
                        throw new \fpoirotte\XRL\Faults\InvalidCharacterException();

                    // Generic error handling.
                    default:
                        throw new \fpoirotte\XRL\Faults\NotWellFormedException();
                }
            }

            if ($validate && !$reader->isValid()) {
                $errors = '';
                foreach (libxml_get_errors() as $error) {
                    $message    = trim($error->message);
                    $file       = $error->file;
                    $line       = $error->line;
                    $column     = $error->column;
                    $errors .= "$message in '$file' on line $line, column $column\n";
                }
                if ($errors) {
                    throw new \fpoirotte\XRL\Faults\InvalidXmlRpcException(trim($errors));
                } else {
                    // Use a generic error message in case the specific reason
                    // for the error cannot be pin-pointed.
                    throw new \fpoirotte\XRL\Faults\InvalidXmlRpcException();
                }
            }

            $subtrees = true;
        } while ($reader->nodeType === \XMLReader::SIGNIFICANT_WHITESPACE);

        $fields = array(
            'isEmptyElement',
            'localName',
            'namespaceURI',
            'nodeType',
            'value',
        );

        $this->properties = array();
        foreach ($fields as $field) {
            $this->properties[$field] = $reader->$field;
        }
        $name = $reader->localName;
        if ($reader->namespaceURI !== '') {
            $name = '{' . $reader->namespaceURI . '}' . $name;
        }
        $this->properties['name'] = $name;
    }

    /**
     * Magic method that returns one of the fields
     * of this node.
     *
     * \param string $field
     *      The name of the field to return.
     *
     * \throw UnexpectedValueException
     *      Raised when the given field does not exist.
     *
     * \note
     *      Currently, the following fields are valid:
     *      -   \c isEmptyElement
     *      -   \c localName
     *      -   \c name (= "{namespaceURI}localName" or just "localName")
     *      -   \c namespaceURI
     *      -   \c nodeType
     *      -   \c value
     */
    public function __get($field)
    {
        if (!isset($this->properties[$field])) {
            throw new \UnexpectedValueException("Unknown property '$field'");
        }

        return $this->properties[$field];
    }

    /**
     * Try to expand the current node if it's an empty one
     * and return whether the expansion worked or not.
     *
     * \retval bool
     *      \c true if this node was an empty one and it
     *      was successfully expanded, \c false otherwise.
     */
    public function emptyNodeExpansionWorked()
    {
        if ($this->properties['nodeType'] == \XMLReader::ELEMENT &&
            $this->properties['isEmptyElement'] == true) {
            $this->properties['nodeType'] = \XMLReader::END_ELEMENT;
            $this->properties['isEmptyElement'] = false;
            return true;
        }
        return false;
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL\Types;

/**
 * \brief
 *      Abstract class for various "date-time" types.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
abstract class AbstractDateTime extends \fpoirotte\XRL\Types\AbstractType
{
    /// \copydoc fpoirotte::XRL::Types::AbstractType::__toString()
    public function __toString()
    {
        return $this->value->format(static::XMLRPC_FORMAT);
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::set()
    public function set($value)
    {
        if (!is_object($value) || !($value instanceof \DateTime)) {
            throw new \InvalidArgumentException('A date and time value was expected');
        }
        $this->value = $value;
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::write()
    public function write(\XMLWriter $writer, \DateTimeZone $timezone, $stringTag)
    {
        // PHP has serious issues with timezone handling.
        // As a workaround, we use format(), specifying a UNIX timestamp
        // as the format to use, which we then reinject in a new DateTime.
        $date = new \DateTime('@'.$this->value->format('U'), $timezone);
        if (strpos(static::XMLRPC_TYPE, '}') !== false) {
            list($ns, $tagName) = explode('}', static::XMLRPC_TYPE, 2);
            $ns = (string) substr($ns, 1);
            return $writer->writeElementNS('ex', $tagName, $ns, $date->format(static::XMLRPC_FORMAT));
        }
        return $writer->writeElement(static::XMLRPC_TYPE, $date->format(static::XMLRPC_FORMAT));
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::parse()
    protected static function parse($value, \DateTimeZone $timezone = null)
    {
        if ($timezone === null) {
            $timezone = new \DateTimeZone(@date_default_timezone_get());
        }

        $result = \DateTime::createFromFormat(static::XMLRPC_FORMAT, $value, $timezone);

        if (!is_object($result) || strcasecmp($value, $result->format(static::XMLRPC_FORMAT))) {
            throw new \InvalidArgumentException('Invalid date/time');
        }

        return $result;
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/// \example server.php

/**
 * \brief
 *      A simple XML-RPC server.
 *
 * This class uses dynamic properties to manager
 * XML-RPC procedures:
 * \code
 *      // This registers the procedure "foo" on the XML-RPC server.
 *      // The function "bar" will be called to handle calls to "foo".
 *      $server->foo = 'bar';
 *
 *      // This returns the callable used to handle
 *      // calls to "foo", wrapped in an object
 *      // implementing the "\\fpoirotte\\XRL\\CallableInterface"
 *      // interface.
 *      $foo = $server->foo;
 *
 *      // This tests whether the "foo" procedure
 *      // has been registered on the server.
 *      if (isset($server->foo)) {
 *          ...
 *      }
 *
 *      // This unregisters the "foo" procedure
 *      // from the XML-RPC server.
 *      unset($server->foo);
 * \endcode
 *
 * You may also count how many XML-RPC procedures
 * are currently registered on the server:
 * \code
 *      $nbProcedures = count($server);
 * \endcode
 *
 * Last but not least, you may also iterate over
 * the server's registered XML-RPC procedures:
 * \code
 *      foreach ($server as $procedure) {
 *          ...
 *      }
 * \endcode
 *
 * \see
 *      The code in \ref server.php contains a complete example of a working
 *      XML-RPC server which may be queried using the corresponding client
 *      (\ref client.php) or XRL's command-line query tool.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Server implements \Countable, \IteratorAggregate, \ArrayAccess
{
    /// Registered "procedures".
    protected $XRLFunctions;

    /// Encoder for the request.
    protected $XRLEncoder;

    /// Decoder for the response.
    protected $XRLDecoder;

    /**
     * Create a new XML-RPC server.
     *
     * \param fpoirotte::XRL::EncoderInterface $encoder
     *      (optional) Encoder to use to build responses.
     *      If omitted, an encoder that accepts native PHP types,
     *      does not use indentation, but uses the \<string\> tags
     *      is automatically created using the machine's timezone.
     *
     * \param fpoirotte::XRL::DecoderInterface $decoder
     *      (optional) Decoder to use to parse responses.
     *      If omitted, a decoder that performs XML validation and
     *      converts values to native PHP types is automatically
     *      created using the machine's timezone.
     *
     * \throw InvalidArgumentException
     *      The given timezone is invalid.
     */
    public function __construct(
        \fpoirotte\XRL\EncoderInterface $encoder = null,
        \fpoirotte\XRL\DecoderInterface $decoder = null
    ) {
        if ($encoder === null) {
            $encoder = new \fpoirotte\XRL\NativeEncoder(
                new \fpoirotte\XRL\Encoder(null, false, true)
            );
        }

        if ($decoder === null) {
            $decoder = new \fpoirotte\XRL\NativeDecoder(
                new \fpoirotte\XRL\Decoder(null, true)
            );
        }

        $this->XRLEncoder   = $encoder;
        $this->XRLDecoder   = $decoder;
        $this->XRLFunctions = array();
    }

    /**
     * Register a new procedure with this XML-RPC server.
     *
     * \param string $func
     *      A valid name for the procedure.
     *      Names starting with the string "XRL" (case-insensitive)
     *      are reserved.
     *
     * \param mixed $callback
     *      Any valid PHP callback.
     *
     * \note
     *      See the "Payload format" section at
     *      http://xmlrpc.scripting.com/spec.html
     *      for information on valid procedure names.
     *
     * \note
     *      Several syntaxes can be used to refer to a PHP callback, see
     *      http://php.net/language.pseudo-types.php#language.types.callback
     *      for the full list of supported constructs.
     */
    public function __set($func, $callback)
    {
        $this->XRLFunctions[$func] = new \fpoirotte\XRL\CallableObject($callback);
    }

    /**
     * \copydoc fpoirotte::XRL::Server::__set()
     *
     * This method is an alias for fpoirotte::XRL::Server::__set().
     */
    public function offsetSet($func, $callback)
    {
        $this->XRLFunctions[$func] = new \fpoirotte\XRL\CallableObject($callback);
    }

    /**
     * Return a procedure previously registered
     * with this XML-RPC server.
     *
     * \param string $func
     *      The name of the registered XML-RPC procedure
     *      to return.
     *
     * \retval mixed
     *      The callable responsible for the XML-RPC
     *      procedure registered with the given name,
     *      as an object implementing the
     *      fpoirotte::XRL::CallableInterface interface,
     *      or \c null if the given name does not refer
     *      to an XML-RPC procedure known to this server.
     *
     * \note
     *      In case the given procedure has not been
     *      registered, a PHP notice will be issued.
     */
    public function __get($func)
    {
        return $this->XRLFunctions[$func];
    }

    /**
     * \copydoc fpoirotte::XRL::Server::__get()
     *
     * This method is an alias for fpoirotte::XRL::Server::__get().
     */
    public function offsetGet($func)
    {
        return $this->XRLFunctions[$func];
    }

    /**
     * Test whether a procedure has been registered
     * with the given name on this server.
     *
     * \param string $func
     *      Name of the procedure whose existence
     *      must be verified.
     *
     * \retval bool
     *      \c true if the procedure exists,
     *      \c false otherwise.
     */
    public function __isset($func)
    {
        return isset($this->XRLFunctions[$func]);
    }

    /**
     * \copydoc fpoirotte::XRL::Server::__isset()
     *
     * This method is an alias for fpoirotte::XRL::Server::__isset().
     */
    public function offsetExists($func)
    {
        return isset($this->XRLFunctions[$func]);
    }

    /**
     * Unregister a procedure.
     *
     * \param string $func
     *      The name of the procedure to unregister.
     *
     * \note
     *      No warning will be emitted if the given
     *      procedure has not been registered
     *      on this XML-RPC server.
     */
    public function __unset($func)
    {
        unset($this->XRLFunctions[$func]);
    }

    /**
     * \copydoc fpoirotte::XRL::Server::__unset()
     *
     * This method is an alias for fpoirotte::XRL::Server::__unset().
     */
    public function offsetUnset($func)
    {
        unset($this->XRLFunctions[$func]);
    }

    /**
     * Return the number of XML-RPC procedures
     * currently registered on this server.
     *
     * \retval int
     *      Number of currently registered
     *      procedures on this server.
     */
    public function count()
    {
        return count($this->XRLFunctions);
    }

    /**
     * Get an iterator over this server's
     * registered XML-RPC procedures.
     *
     * \retval ArrayIterator
     *      An iterator over this server's
     *      registered procedures.
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->XRLFunctions);
    }

    /**
     * Expose the public methods of a class or object,
     * with an optional prefix.
     *
     * \param mixed $other
     *      Class or object to expose.
     *      For classes, only public static methods are exposed.
     *      For objects, public non-static methods are exposed,
     *      except for the constructor.
     *
     * \param string $prefix
     *      Prefix under which the class/object's methods
     *      will be exposed. Defaults to the empty string
     *      (ie. the methods are exposed without any prefix).
     *
     * \return
     *      This method does not return any value.
     *
     * \note
     *      To expose all public methods of an object (both
     *      static and non-static), call this method twice:
     *      - once with the actual object
     *      - and a second time with get_class($object)
     */
    public function expose($other, $prefix = '')
    {
        if (!is_string($other) && !is_object($other)) {
            throw new \InvalidArgumentException('Invalid adoption');
        }

        if (!is_string($prefix)) {
            throw new \InvalidArgumentException('Invalid prefix');
        }

        $prefix = rtrim($prefix, '.');
        if ($prefix !== '') {
            $prefix .= '.';
        }

        if (is_object($other)) {
            // An object was passed.
            $class = get_class($other);
            foreach (get_class_methods($class) as $method) {
                // Only adopt public methods of the object,
                // excluding the constructor and static methods.
                // To also register static methods, call this method
                // a second time with get_class($other).
                $reflector = new \ReflectionMethod($class, $method);
                if ($reflector->isPublic() && !$reflector->isConstructor() &&
                    !$reflector->isStatic()) {
                    $this[$prefix . $method] = array($other, $method);
                }
            }
        } else {
            // A class was passed.
            foreach (get_class_methods($other) as $method) {
                // Only adopt methods which are both public and static.
                $reflector = new \ReflectionMethod($other, $method);
                if ($reflector->isPublic() && $reflector->isStatic()) {
                    $this[$prefix . $method] = array($other, $method);
                }
            }
        }
    }

    /**
     * Handle an XML-RPC request and return a response for it.
     *
     * \param string $URI
     *      (optional) URI to the XML-RPC request to process,
     *      If omitted, this method will try to retrieve the request
     *      directly from the data POST'ed to this script.
     *
     * \retval fpoirotte::XRL::ResponseInterface
     *      The response for that request. This response
     *      may indicate either success or failure of the
     *      Remote Procedure Call.
     *
     * \note
     *      Use the "data://" wrapper to pass the serialized
     *      request as raw data.
     *
     * \see
     *      See http://php.net/wrappers.data.php for information
     *      on how to use the "data://" wrapper.
     */
    public function handle($URI = null)
    {
        if ($URI === null) {
            $URI = 'php://input';
        }

        try {
            $request    = $this->XRLDecoder->decodeRequest($URI);
            $procedure  = $request->getProcedure();
            // Necessary to keep references.
            $params     = $request->getParams();

            $result     = $this->call($procedure, $params);
            $response   = $this->XRLEncoder->encodeResponse($result);
        } catch (\Exception $result) {
            $response   = $this->XRLEncoder->encodeError($result);
        }

        return new \fpoirotte\XRL\Response($response);
    }

    /**
     * Call an XML-RPC procedure.
     *
     * \param string $procedure
     *      Name of the procedure to call.
     *
     * \param array $params
     *      Parameters for that procedure.
     *
     * \retval mixed
     *      The procedure's return value.
     */
    public function call($procedure, array $params)
    {
        if (!is_string($procedure)) {
            throw new \BadFunctionCallException('Expected a string');
        }

        if (!isset($this->XRLFunctions[$procedure])) {
            throw new \fpoirotte\XRL\Faults\MethodNotFoundException();
        }

        $callable = $this->XRLFunctions[$procedure];
        return $callable(...$params);
    }
}


<?php
namespace webtoolsnz\widgets;

use Yii;
use webtoolsnz\widgets\SignatureAsset;
use yii\helpers\Html;
use yii\widgets\InputWidget;

class SignatureWidget extends InputWidget
{
    /**
     * @var array
     */
    public $inputOptions = [];

    /**
     * @var null
     */
    public $widgetId = null;

    /**
     * @throws \yii\base\InvalidConfigException
     */
    public function init()
    {
        parent::init();

        $this->widgetId = sprintf('signature_pad_%s', $this->options['id']);
    }

    /**
     * @inheritdoc
     */
    public function run()
    {
        Html::addCssClass($this->inputOptions, 'form-control');
        $this->inputOptions['id'] = $this->widgetId;

        if ($this->hasModel()) {
            echo Html::activeHiddenInput($this->model, $this->attribute, $this->inputOptions);
        } else {
            echo Html::hiddenInput($this->name, $this->value, $this->inputOptions);
        }

        $this->registerClientScript();
    }

    /**
     * Registers widget assets
     */
    protected function registerClientScript()
    {
        $view = $this->getView();
        SignatureAsset::register($view);

        $options = json_encode([]);

        $js[] = "$('#{$this->widgetId}').signatureWidget($options)";
        $view->registerJs(implode("\n", $js));
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL\Types;

/**
 * \brief
 *      The XML-RPC "boolean" type.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Boolean extends \fpoirotte\XRL\Types\AbstractType
{
    /// \copydoc fpoirotte::XRL::Types::AbstractType::__toString()
    public function __toString()
    {
        return ($this->value ? 'true' : 'false');
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::set()
    public function set($value)
    {
        if (!is_bool($value)) {
            throw new \InvalidArgumentException('Expected boolean value');
        }
        $this->value = $value;
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::write()
    public function write(\XMLWriter $writer, \DateTimeZone $timezone, $stringTag)
    {
        $writer->writeElement('boolean', (int) $this->value);
    }

    /// \copydoc fpoirotte::XRL::Types::AbstractType::parse()
    protected static function parse($value, \DateTimeZone $timezone = null)
    {
        if ($value === '0') {
            return false;
        } elseif ($value === '1') {
            return true;
        }
        return $value;
    }
}


<?php
/**
 * @link https://github.com/webtoolsnz/yii2-widgets
 * @copyright Copyright (c) 2015 Webtools Ltd
 */

namespace webtoolsnz\widgets;

use yii\helpers\Html;
use yii\helpers\Json;
use yii\widgets\InputWidget;
use Yii;

/**
 * RadioButton renders a bootstrap style button group for radio options
 *
 * @author Byron Adams <byron@webtools.co.nz>
 * @package webtoolsnz\yii2-widgets
 */
class RadioButtonGroup extends InputWidget
{
    const STATE_ACTIVE = 'activeState';
    const STATE_DEFAULT = 'defaultState';

    /**
     * @var array the options for the radioButton plugin.
     */
    public $itemOptions = [];

    /**
     * @var array
     */
    public $inputOptions = [];

    /**
     * @var bool use strict comparison (===) when comparing values
     */
    public $comparisonStrict = false;

    /**
     * @var bool setting this to true is the same as using comparisonStrict only when value is null or an empty string
     */
    public $ignoreEmpty = false;

    /**
     * @var array
     */
    public $items = [];

    /**
     * @var null
     */
    public $widgetId = null;

    /**
     * @var bool
     */
    public $disabled = false;

    /**
     * @throws \yii\base\InvalidConfigException
     */
    public function init()
    {
        parent::init();

        $this->widgetId = sprintf('radio_button_%s', $this->options['id']);

        $this->itemOptions = array_merge([
            self::STATE_ACTIVE => 'active btn btn-success',
            self::STATE_DEFAULT => 'btn btn-default',
            'buttons' => [],
        ], $this->itemOptions);

        $this->inputOptions = array_merge(['id' => $this->options['id']], $this->inputOptions);
    }

    public function getButtonClass($value)
    {
        $selectedValue = $this->hasModel() ? Html::getAttributeValue($this->model, $this->attribute) : $this->value;
        $state = ($this->comparisonStrict || (($selectedValue === null || $selectedValue === '') && $this->ignoreEmpty)
            ? $value === $selectedValue
            : $value == $selectedValue) ? self::STATE_ACTIVE : self::STATE_DEFAULT;
        $buttonOptions = isset($this->itemOptions['buttons'][$value]) ? $this->itemOptions['buttons'][$value] : null;

        if ($buttonOptions && isset($buttonOptions[$state])) {
            $class = $buttonOptions[$state];
        } else {
            $class = $this->itemOptions[$state];
        }

        return $class;
    }

    /**
     * @inheritdoc
     */
    public function run()
    {
        $buttons = '';

        foreach ($this->items as $value => $label) {
            $buttons .= Html::button($label, [
                'data-value' => $value,
                'class' => $this->getButtonClass($value),
                'disabled' => $this->disabled,
            ]);
        }

        $class = 'radio-button-group';
        if (isset($this->options['class'])) {
            $class .= ' '.$this->options['class'];
        }

        echo Html::tag(
            'div',
            Html::tag('div', $buttons, [
                'class' => 'btn-group',
                'id' => $this->widgetId,
                'data-field' => '#'.$this->options['id'],
            ]),
            [
                'class' => $class,
            ]
        );

        if ($this->hasModel()) {
            echo Html::activeHiddenInput($this->model, $this->attribute, $this->inputOptions);
        } else {
            echo Html::hiddenInput($this->name, $this->value, $this->inputOptions);
        }

        $this->registerClientScript();
    }

    /**
     * Registers widget assets
     */
    protected function registerClientScript()
    {
        $view = $this->getView();
        RadioButtonGroupAsset::register($view);

        $id = $this->widgetId;
        $options =  Json::encode($this->itemOptions);

        $js[] = "jQuery('#$id').radioButtonGroup($options);";
        $view->registerJs(implode("\n", $js));
    }
}


<?php
/**
 * @link https://github.com/webtoolsnz/yii2-widgets
 * @copyright Copyright (c) 2015 Webtools Ltd
 */

namespace webtoolsnz\widgets;

use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\widgets\InputWidget;
use Yii;

/**
 * CheckboxButtonGroup renders a bootstrap style button group for checkbox options
 *
 * @author Byron Adams <byron@webtools.co.nz>
 * @package webtoolsnz\yii2-widgets
 */
class CheckboxButtonGroup extends InputWidget
{
    const STATE_ACTIVE = 'activeState';
    const STATE_DEFAULT = 'defaultState';

    /**
     * @var array the options for the radioButton plugin.
     */
    public $itemOptions = [];

    /**
     * @var array
     */
    public $items = [];

    /**
     * @var null
     */
    public $widgetId = null;

    /**
     * @throws \yii\base\InvalidConfigException
     */
    public function init()
    {
        parent::init();

        $this->widgetId = sprintf('checkbox_button_%s', $this->options['id']);

        $this->itemOptions = array_merge([
            self::STATE_ACTIVE => 'active btn btn-success',
            self::STATE_DEFAULT => 'btn btn-default',
            'buttons' => [],
        ], $this->itemOptions);
    }

    /**
     * @param $value
     * @param $selectedValue
     * @return string
     */
    public function getButtonClass($value, $selectedValue)
    {
        $selectedValue = is_array($selectedValue) ? in_array($value, $selectedValue) : $selectedValue;
        $state = ($value == $selectedValue ? self::STATE_ACTIVE : self::STATE_DEFAULT);
        $buttonOptions = isset($this->itemOptions['buttons'][$value]) ? $this->itemOptions['buttons'][$value] : null;

        if ($buttonOptions && isset($buttonOptions[$state])) {
            $class = $buttonOptions[$state];
        } else {
            $class = $this->itemOptions[$state];
        }

        return $class;
    }

    /**
     * render the html input for the widget
     */
    protected function renderInput()
    {
        if ($this->hasModel()) {
            $content = Html::activeCheckboxList(
                $this->model,
                $this->attribute,
                $this->items,
                $this->options
            );
        } else {
            $content = Html::checkboxList(
                $this->name,
                $this->value,
                $this->items,
                $this->options
            );
        }

        return Html::tag(
            'div',
            $content,
            [
                'id' => $this->widgetId.'-checkbox',
                'class' => 'checkbox_button_group'.'_checkbox',
            ]
        );
    }

    /**
     * Executes the widget.
     * @return string the result of widget execution to be outputted.
     */
    public function run()
    {
        $this->registerClientScript();
        return Html::tag(
            'div',
            $this->renderInput().$this->renderButtons(),
            [
                'id' => $this->widgetId
            ]
        );
    }

    /**
     * Registers widget assets
     */
    protected function registerClientScript()
    {
        $view = $this->getView();
        CheckboxButtonGroupAsset::register($view);

        $id = $this->widgetId;
        $options =  Json::encode(
            ArrayHelper::merge(
                $this->itemOptions,
                [
                    'checkboxes' =>  '#'.$this->widgetId.'-checkbox'
                ]
            )
        );
        $js = "jQuery('#$id').checkboxButtonGroup($options);";
        $view->registerJs($js);

        $js = [
            "jQuery('#$id-buttons').show();",
            "jQuery('#$id-checkbox').hide();"
        ];

        $view->registerJs(
            implode("\n", $js),
            \yii\web\View::POS_END
        );
    }

    /**
     * @return string
     */
    private function renderButtons()
    {
        $buttons = '';

        foreach ($this->items as $value => $label) {
            $buttonOptions = ArrayHelper::merge(
                    [
                    'data-value' => $value,
                    'class' => $this->getButtonClass(
                        $value,
                        $this->hasModel()
                            ? Html::getAttributeValue($this->model, $this->attribute)
                            : $this->value
                    ),
                ],
                isset($this->itemOptions['buttons'][$value]) ? $this->itemOptions['buttons'][$value] : []
            );
            ArrayHelper::remove($buttonOptions, self::STATE_ACTIVE);
            ArrayHelper::remove($buttonOptions, self::STATE_DEFAULT);

            $buttons .= Html::button($label, $buttonOptions);
        }

        $class = 'btn-group checkbox-button-group';
        if (isset($this->options['class'])) {
            $class .= ' '.$this->options['class'];
        }

        return Html::tag('div', $buttons, [
            'class' => $class,
            'style' => 'display: none;',
            'id' => $this->widgetId.'-buttons',
        ]);
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      A decoder that transparently converts
 *      XML-RPC types to native PHP types.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class NativeDecoder implements \fpoirotte\XRL\DecoderInterface
{
    /// Sub-decoder.
    protected $decoder;

    /**
     * Creates a new decoder.
     *
     * \param fpoirotte::XRL::DecoderInterface $decoder
     *      Sub-decoder to use.
     */
    public function __construct(\fpoirotte\XRL\DecoderInterface $decoder)
    {
        $this->decoder = $decoder;
    }

    /// \copydoc fpoirotte::XRL::DecoderInterface::decodeRequest()
    public function decodeRequest($URI)
    {
        $request    = $this->decoder->decodeRequest($URI);
        $closure    = function ($p) {
            return $p->get();
        };
        $params     = array_map($closure, $request->getParams());
        return new \fpoirotte\XRL\Request($request->getProcedure(), $params);
    }

    /// \copydoc fpoirotte::XRL::DecoderInterface::decodeResponse()
    public function decodeResponse($URI)
    {
        return $this->decoder->decodeResponse($URI)->get();
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      A class that adds various capabilities
 *      to an existing XML-RPC server.
 *
 * \see
 *      http://scripts.incutio.com/xmlrpc/introspection.html
 *      for documentation on introspection-related capabilities.
 *
 * \see
 *      http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
 *      for documentation on interoperability faults.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class CapableServer
{
    /// Original XML-RPC server.
    protected $server;

    /// Whitelist of XML-RPC methods to announce.
    protected $whitelist;

    /**
     * Wrap an XML-RPC server to add capabilities support.
     *
     * \param fpoirotte::XRL::Server $server
     *      Server to wrap.
     *
     * \param array $whitelist
     *      (optional) Whitelist of methods that can
     *      be announced by the newly upgraded server.
     *      Pass an empty array to disable announces.
     *      By default, all methods are announced.
     */
    protected function __construct(\fpoirotte\XRL\Server $server, array $whitelist = null)
    {
        $this->server       = $server;
        $this->whitelist    = $whitelist;
    }

    /**
     * Turn a regular XML-RPC server into one
     * that supports various XML-RPC capabilities.
     *
     * \param fpoirotte::XRL::Server $server
     *      An XML-RPC to upgrade.
     *
     * \param array $whitelist
     *      (optional) Whitelist of methods that can
     *      be announced by the newly upgraded server.
     *      Pass an empty array to disable announces.
     *      By default, all methods are announced.
     *
     * \retval fpoirotte::XRL::Server
     *      The upgraded server.
     */
    public static function enable(\fpoirotte\XRL\Server $server, array $whitelist = null)
    {
        $wrapper = new static($server, $whitelist);
        $server->expose($wrapper, 'system');
        return $server;
    }

    /**
     * Extract parameter and return type information
     * from a documentation comment.
     *
     * \param string $doc
     *      Documentation comment the types
     *      will be extracted from.
     *
     * \retval array
     *      Array with type information.
     *      The "retval" key contains information about the return type,
     *      while the array in the "params" key contains information
     *      about each parameter's type (indexed by its name).
     */
    protected static function extractTypes($doc)
    {
        $doc    = trim(substr($doc, 3, -2), " \t\r\n*");
        $doc    = str_replace(array("\r\n", "\r"), "\n", $doc);
        $lines  = explode("\n", $doc . "\n");

        $tag    = null;
        $tags   = array(
            'params'    => array(),
            'retval'    => 'null',
        );
        $buffer = '';

        foreach ($lines as $line) {
            $line = trim($line, " \r\n\t*");

            if ($tag !== null && $line === '') {
                switch ($tag) {
                    case 'param':
                        $type = (string) substr($buffer, 0, strcspn($buffer, " \r\n\t"));
                        $buffer = ltrim(substr($buffer, strcspn($buffer, " \r\n\t")));
                        if (strncmp($buffer, '$', 1)) {
                            break;
                        }

                        $name = (string) substr($buffer, 1, strcspn($buffer, " \r\n\t") - 1);
                        $tags['params'][$name] = $type;
                        break;

                    case 'retval':
                        $type = (string) substr($buffer, 0, strcspn($buffer, " \r\n\t"));
                        $tags['retval'] = $type;
                        break;
                }

                $tag    = null;
                $buffer = '';
                continue;
            }

            if ($tag === null) {
                // \command or @command.
                if (!strncmp($line, '\\', 1) || !strncmp($line, '@', 1)) {
                    $tag    = (string) substr($line, 1, strcspn($line, " \r\n\t") - 1);
                    $buffer = ltrim(substr($line, strcspn($line, " \r\n\t")));
                }
            } else {
                // Continuation of previous paragraph.
                $buffer .= "\n$line";
            }
        }
        return $tags;
    }

    /**
     * Return XML-RPC type that comes the closest
     * to some type used in documentation comments.
     *
     * \param string $type
     *      A type used in a documentation comment.
     *
     * \retval string
     *      The closest XML-RPC, or \c null if none
     *      could be derived.
     *
     * @codeCoverageIgnore
     */
    protected static function adaptType($type)
    {
        switch ($type) {
            case 'integer':
                return 'int';

            case 'float':
                return 'double';

            case 'null':
                return 'nil';

            case 'DateTime':
                return 'dateTime.iso8601';

            case 'boolean':
                return 'bool';

            case 'int':
            case 'double':
            case 'string':
            case 'bool':
            case 'array':
                return $type;
        }
        return null;
    }

    /**
     * Get the server's capabilities.
     *
     * \retval array
     *      Array of capabilities supported by this server.
     *      Each entry contains two keys:
     *      - specURL (URL to that capability's specification)
     *      - specVersion (version of the capability)
     */
    public function getCapabilities()
    {
        return array(
            'xmlrpc' => array(
                'specUrl'       => 'http://www.xmlrpc.com/spec',
                'specVersion'   => 1,
            ),

            'introspect' => array(
                'specUrl'       => 'http://xmlrpc-c.sourceforge.net/xmlrpc-c/introspection.html',
                'specVersion'   => 1,
            ),

            'faults_interop' => array(
                'specUrl'       => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
                'specVersion'   => 20010516,
            ),
        );
    }

    /**
     * List the methods supported by the server.
     *
     * \retval array
     *      Array with the names of available methods.
     */
    public function listMethods()
    {
        $methods = array_keys($this->server->getIterator()->getArrayCopy());
        if ($this->whitelist !== null) {
            $methods = array_values(array_intersect($methods, $this->whitelist));
        }
        return $methods;
    }

    /**
     * Return possible signatures for a method.
     *
     * \param string $method
     *      Name of the method.
     *
     * \retval array
     *      An array containing:
     *      - The return type of the method as its first value
     *      - The types for the method's parameters in the following values
     */
    public function methodSignature($method)
    {
        if (!is_string($method) || !isset($this->server[$method])) {
            throw new \InvalidArgumentException('Invalid method');
        }

        $reflector  = $this->server[$method]->getReflector();
        $doc        = $reflector->getDocComment();
        if ($doc === false) {
            return 'undef';
        }

        $tags       = static::extractTypes($doc);
        $returnType = static::adaptType($tags['retval']);
        if ($returnType === null) {
            return 'undef';
        }

        $params     = array();
        foreach ($reflector->getParameters() as $param) {
            if (!isset($tags['params'][$param->getName()])) {
                return 'undef';
            }
            $type       = static::adaptType($tags['params'][$param->getName()]);
            if ($type === null) {
                return 'undef';
            }
            $params[]   = $type;
        }

        return array(array_merge(array($returnType), $params));
    }

    /**
     * Get help about a procedure.
     *
     * \param string $method
     *      Name of the procedure.
     *
     * \retval string
     *      Human readable help message for the given procedure.
     */
    public function methodHelp($method)
    {
        if (!is_string($method) || !isset($this->server[$method])) {
            throw new \InvalidArgumentException('Invalid method');
        }

        $reflector  = $this->server[$method]->getReflector();
        $doc        = $reflector->getDocComment();
        if ($doc === false) {
            return '';
        }

        // Remove comment delimiters.
        $doc    = substr($doc, 2, -2);

        // Normalize line endings.
        $doc    = str_replace(array("\r\n", "\r"), "\n", $doc);

        // Trim leading/trailing whitespace and '*' for every line.
        $help   = array_map(
            function ($l) {
                return trim(trim($l), '*');
            },
            explode("\n", $doc)
        );

        // Count number of empty columns on non-empty lines
        // before the actual start of the text.
        $cols = min(
            array_map(
                function ($l) {
                    return strspn($l, " \t");
                },
                array_filter($help, 'strlen')
            )
        );

        // Remove those columns from the result.
        $help = array_map(
            function ($l) use ($cols) {
                return (string) substr($l, $cols);
            },
            $help
        );

        // Produce the final output.
        return implode("\n", $help);
    }

    /**
     * Perform several calls to XML-RPC methods
     * in a single go.
     *
     * \param array $requests
     *      Array of requests, each described as a struct
     *      with the following information:
     *      - "methodName": name of the method to call
     *      - "params": array of parameters for the method
     *
     * \retval array
     *      Array of responses, one for each request,
     *      in the same order.
     *      Each response may be either a fault or an array
     *      with a single element (the call's result).
     *
     * \note
     *      Recursive calls to system.multicall are forbidden.
     */
    public function multicall(array $requests)
    {
        $responses = array();
        foreach ($requests as $request) {
            try {
                if (!is_array($request)) {
                    throw new \BadFunctionCallException('Expected struct');
                }
                if (!isset($request['methodName'])) {
                    throw new \BadFunctionCallException('Missing methodName');
                }
                if (!isset($request['params'])) {
                    throw new \BadFunctionCallException('Missing params');
                }
                if (!is_array($request['params'])) {
                    throw new \BadFunctionCallException('Invalid params');
                }
                if ($request['methodName'] === 'system.multicall') {
                    throw new \BadFunctionCallException('Recursive call');
                }

                $result = $this->server->call($request['methodName'], $request['params']);
                // Results are wrapped in an array to make it possible
                // to distinguish between faults and regular structs.
                $responses[] = array($result);
            } catch (\Exception $error) {
                $responses[] = $error;
            }
        }
        return $responses;
    }
}


<?php
/*
 * This file is part of XRL, a simple XML-RPC Library for PHP.
 *
 * Copyright (c) 2012, XRL Team. All rights reserved.
 * XRL is licensed under the 3-clause BSD License.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace fpoirotte\XRL;

/**
 * \brief
 *      An helper class that wraps XRL's autoloader.
 *
 * \authors François Poirotte <clicky@erebot.net>
 */
class Autoload
{
    /**
     * Load an XRL class or interface.
     *
     * \param string $class
     *      Name of the class/interface to load.
     *
     * \note
     *      The autoloader will only try to load classes/interfaces
     *      whose name starts with "fpoirotte\\XRL\\" (case-sensitive).
     *
     * \warning
     *      The autoloader will throw an exception (which will
     *      most likely result in a fatal error in your application)
     *      in case the class or interface's name contains a colon.
     *      This is a protection against a possible remote inclusion
     *      vulnerability introduced in PHP 5.3.8 using is_a().
     *
     * \retval bool
     *      \c true if the class or interface could be loaded,
     *      \c false otherwise.
     */
    public static function load($class)
    {
        // This code only applies to PHP 5.3.7 & 5.3.8.
        // It prevents a case of remote code execution (CVE 2011-3379).
        if (strpos($class, ':') !== false) {
            // @codeCoverageIgnoreStart
            throw new \Exception('Possible remote execution attempt');
            // @codeCoverageIgnoreEnd
        }

        $class = ltrim($class, '\\');
        if (strncmp($class, 'fpoirotte\\XRL\\', 14)) {
            return false;
        }

        $class = substr($class, 14);
        if (!strncmp($class, 'tests\\', 6)) {
            $path = dirname(__DIR__);
        } else {
            $path = __DIR__;
        }

        $class = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $class);
        include($path . DIRECTORY_SEPARATOR . $class . '.php');
        $res = (class_exists($class, false) || interface_exists($class, false));
        return $res;
    }

    /**
     * Register XRL's autoloader.
     *
     * \return
     *      This method does not return any meaningful value.
     */
    public static function register()
    {
        static $registered = false;

        if (!$registered) {
            spl_autoload_register(array(__CLASS__, "load"));
        }
    }
}