<?php

namespace Ds\Component\Model\Attribute\Accessor;

use InvalidArgumentException;

/**
 * Trait AssigneeUuid
 *
 * @package Ds\Component\Model
 */
trait AssigneeUuid
{
    /**
     * Set assignee uuid
     *
     * @param string $assigneeUuid
     * @return object
     * @throws \InvalidArgumentException
     */
    public function setAssigneeUuid(?string $assigneeUuid)
    {
        if (null !== $assigneeUuid) {
            if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $assigneeUuid)) {
                throw new InvalidArgumentException('Assignee uuid is not valid.');
            }
        }

        $this->assigneeUuid = $assigneeUuid;

        return $this;
    }

    /**
     * Get assignee uuid
     *
     * @return string
     */
    public function getAssigneeUuid(): ?string
    {
        return $this->assigneeUuid;
    }
}


<?php

namespace Ds\Component\Security\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * Class Configuration
 *
 * @package Ds\Component\Security
 */
final class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder()
    {
        $builder = new TreeBuilder;
        $node = $builder->root('ds_security');
        $node
            ->children()
                ->arrayNode('token')
                    ->children()
                        ->booleanNode('uuid')
                            ->defaultFalse()
                        ->end()
                        ->booleanNode('ip')
                            ->defaultFalse()
                        ->end()
                        ->booleanNode('client')
                            ->defaultFalse()
                        ->end()
                        ->booleanNode('modifier')
                            ->defaultFalse()
                        ->end()
                        ->arrayNode('identity')
                            ->children()
                                ->booleanNode('roles')
                                    ->defaultFalse()
                                ->end()
                                ->booleanNode('type')
                                    ->defaultFalse()
                                ->end()
                                ->booleanNode('uuid')
                                    ->defaultFalse()
                                ->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end();

        return $builder;
    }
}


<?php

namespace Ds\Component\Acl\EventListener\Entity\Access;

use Ds\Component\Acl\Entity\Access;
use Ds\Component\Acl\Service\AccessService;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

/**
 * Class PermissionsListener
 *
 * @package Ds\Component\Acl
 */
final class PermissionsListener
{
    /**
     * @var \Ds\Component\Acl\Service\AccessService
     */
    private $accessService;

    /**
     * Constructor
     *
     * @param \Ds\Component\Acl\Service\AccessService $accessService
     */
    public function __construct(AccessService $accessService)
    {
        $this->accessService = $accessService;
    }

    /**
     * Remove the existing permissions on an update request
     *
     * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
     */
    public function kernelRequest(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if (!$request->isMethod(Request::METHOD_PUT)) {
            return;
        }

        if (!$request->attributes->has('data')) {
            return;
        }

        $data = $request->attributes->get('data');

        if (!$data instanceof Access) {
            return;
        }

        $access = $data;
        $manager = $this->accessService->getManager();

        foreach ($access->getPermissions() as $permission) {
            $manager->remove($permission);
        }

        $manager->flush();
    }
}


<?php

namespace Ds\Component\System\Service;

use Ds\Component\System\Model\Tenant;
use Ds\Component\System\Query\TenantParameters as Parameters;

/**
 * Class TenantService
 *
 * @package Ds\Component\System
 */
class TenantService implements Service
{
    use Base;

    /**
     * @const string
     */
    const MODEL = Tenant::class;

    /**
     * @const string
     */
    const RESOURCE_LIST = '/system/tenants';
    const RESOURCE_OBJECT = '/system/tenants/{id}';

    /**
     * @var array
     */
    private static $map = [
        'id',
        'uuid',
        'createdAt',
        'updatedAt',
        'data',
        'version'
    ];

    /**
     * Get tenant list
     *
     * @param \Ds\Component\System\Query\TenantParameters $parameters
     * @return array
     */
    public function getList(Parameters $parameters = null): array
    {
        $objects = $this->execute('GET', static::RESOURCE_LIST);
        $list = [];

        foreach ($objects as $object) {
            $model = static::toModel($object);
            $list[] = $model;
        }

        return $list;
    }

    /**
     * Get tenant
     *
     * @param string $id
     * @param \Ds\Component\System\Query\TenantParameters $parameters
     * @return \Ds\Component\System\Model\Tenant
     */
    public function get($id, Parameters $parameters = null)
    {
        $resource = str_replace('{id}', $id, static::RESOURCE_OBJECT);
        $object = $this->execute('GET', $resource);
        $model = static::toModel($object);

        return $model;
    }

    /**
     * Create tenant
     *
     * @param \Ds\Component\System\Model\Tenant $tenant
     * @param \Ds\Component\System\Query\TenantParameters $parameters
     * @return \Ds\Component\System\Model\Tenant
     */
    public function create(Tenant $tenant, Parameters $parameters = null)
    {
        $options = [];
        $options['json'] = (array) static::toObject($tenant);

        if ($parameters) {
            $options['query'] = (array) $parameters->toObject(true);
        }

        $object = $this->execute('POST', static::RESOURCE_LIST, $options);
        $tenant = static::toModel($object);

        return $tenant;
    }

    /**
     * Delete tenant
     *
     * @param string $id
     * @param \Ds\Component\System\Query\TenantParameters $parameters
     * @return \Ds\Component\System\Model\Tenant
     */
    public function delete($id, Parameters $parameters = null)
    {
        $options = [];

        if ($parameters) {
            $options['query'] = (array) $parameters->toObject(true);
        }

        $resource = str_replace('{id}', $id, static::RESOURCE_OBJECT);
        $this->execute('DELETE', $resource, $options);

        return $this;
    }
}


<?php

namespace Ds\Component\Encryption\Service;

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use LogicException;

/**
 * Class CipherService
 *
 * @package Ds\Component\Encryption
 */
final class CipherService
{
    /**
     * @var string
     */
    private $key;

    /**
     * Constructor
     *
     * @param string $key
     */
    public function __construct(string $key = null)
    {
        $this->key = $key;
    }

    /**
     * Encrypt data using secret key
     *
     * @param mixed $data
     * @param string $key
     * @return string
     */
    public function encrypt($data, string $key = null): string
    {
        $key = $this->createKey($key);
        $data = Crypto::encrypt(serialize($data), $key);

        return $data;
    }

    /**
     * Decrypt data using secret key
     *
     * @param string $data
     * @param string $key
     * @return mixed
     */
    public function decrypt(string $data, string $key = null)
    {
        $key = $this->createKey($key);
        $data = unserialize(Crypto::decrypt($data, $key));

        return $data;
    }

    /**
     * Create key
     *
     * @param string $key
     * @return \Defuse\Crypto\Key
     * @throws \LogicException If the key is not defined
     */
    private function createKey(string $key = null): Key
    {
        if (null === $key) {
            $key = $this->key;
        }

        if (null === $key) {
            throw new LogicException('Cipher secret key is not defined.');
        }

        $key = Key::loadFromAsciiSafeString($key);

        return $key;
    }
}


<?php

namespace Ds\Component\Parameter\Fixture;

use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;

/**
 * Class ParameterSequenceFixture
 *
 * @package Ds\Component\Parameter
 */
final class ParameterSequenceFixture implements FixtureInterface, OrderedFixtureInterface
{
    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $connection = $manager->getConnection();
        $platform = $connection->getDatabasePlatform()->getName();

        switch ($platform) {
            case 'postgresql':
                $connection->exec('ALTER SEQUENCE ds_parameter_id_seq RESTART WITH 1');
                break;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getOrder()
    {
        return -10;
    }
}


<?php

namespace Ds\Component\Database\Fixture;

use Ds\Component\Container\Attribute;
use Ds\Component\Database\Util\Objects;
use LogicException;

/**
 * Trait Yaml
 *
 * This trait provides the ability to load fixtures from YAML configuration files.
 *
 * @package Ds\Component\Database
 * @example A YAML resource configuration file
 * <code>
 * objects:
 *   - uuid: 1ea40809-0520-482f-a4d6-7973228b225a
 *     title: Item 1
 *   - uuid: 7f51d089-945a-4f80-9c05-4e2fe924d214
 *     title: Item 2
 *
 * prototype:
 *   uuid: ~
 *   title: Default title
 * </code>
 */
trait Yaml
{
    use Attribute\Container;

    /**
     * Parse resource files to objects
     *
     * @param string $path
     * @return array
     * @throws \DomainException
     * @throws \LogicException
     */
    protected function parse($path): array
    {
        $fixtures = array_key_exists('FIXTURES', $_ENV) ? $_ENV['FIXTURES'] : 'dev';
        $files = glob(str_replace('{fixtures}', $fixtures, $path));

        if (!$files) {
            throw new LogicException('Fixtures path "'.$path.'" yields no files.');
        }

        $objects = [];

        foreach ($files as $file) {
            foreach (Objects::parseFile($file) as $object) {
                $objects[] = $object;
            }
        }

        return $objects;
    }
}


<?php

namespace Ds\Component\Model\Attribute\Accessor;

use InvalidArgumentException;

/**
 * Trait EntityUuid
 *
 * @package Ds\Component\Model
 */
trait EntityUuid
{
    /**
     * Set entity uuid
     *
     * @param string $entityUuid
     * @return object
     * @throws \InvalidArgumentException
     */
    public function setEntityUuid(?string $entityUuid)
    {
        if (null !== $entityUuid) {
            if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', $entityUuid)) {
                throw new InvalidArgumentException('Entity uuid is not valid.');
            }
        }

        $this->entityUuid = $entityUuid;

        return $this;
    }

    /**
     * Get entity uuid
     *
     * @return string
     */
    public function getEntityUuid(): ?string
    {
        return $this->entityUuid;
    }
}


<?php

namespace Revolution\Mastodon\Traits;

trait AppsTrait
{
    /**
     * Register a new OAuth client app on the target instance
     *
     * @param string $client_name
     * @param string $redirect_uris
     * @param string $scopes
     * @param string $website
     *
     * @return array
     */
    public function createApp(string $client_name, string $redirect_uris, string $scopes, string $website = ''): array
    {
        $params = compact('client_name', 'redirect_uris', 'scopes', 'website');

        return $this->post('/apps', $params);
    }

    /**
     * @deprecated PSR
     * @see createApp()
     *
     * @param string $client_name
     * @param string $redirect_uris
     * @param string $scopes
     * @param string $website
     *
     * @return array
     */
    public function app_register(
        string $client_name,
        string $redirect_uris,
        string $scopes,
        string $website = ''
    ): array {
        return $this->createApp($client_name, $redirect_uris, $scopes, $website);
    }
}


<?php

namespace Revolution\Mastodon\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;

use GuzzleHttp\Client;

use Revolution\Mastodon\Contracts\Factory;
use Revolution\Mastodon\MastodonClient;

class MastodonServiceProvider extends ServiceProvider implements DeferrableProvider
{
    /**
     * Boot the service provider.
     */
    public function boot()
    {
        //
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Factory::class, function ($app) {
            return new MastodonClient(new Client());
        });

        $this->app->alias(
            MastodonClient::class,
            Factory::class
        );
    }

    /**
     * Get the services provided by the provider.
     *
     * @return string[]
     */
    public function provides()
    {
        return [
            MastodonClient::class,
            Factory::class,
        ];
    }
}


<?php

namespace Revolution\Mastodon;

use Closure;
use BadMethodCallException;

use Illuminate\Support\Arr;
use Illuminate\Support\Traits\Macroable;

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;

use Revolution\Mastodon\Contracts\Factory;

class MastodonClient implements Factory
{
    use Traits\AppsTrait;
    use Traits\AccountsTrait;
    use Traits\StatusesTrait;
    use Traits\StreamingTrait;

    use Macroable;

    /**
     * @var string
     */
    protected $api_version = 'v1';

    /**
     * @var Client
     */
    protected $client;

    /**
     * @var string
     */
    protected $domain;

    /**
     * @var string
     */
    protected $token;

    /**
     * @var string
     */
    protected $api_base = '/api/';

    /**
     * @var mixed|\Psr\Http\Message\ResponseInterface
     */
    protected $response;

    /**
     * constructor.
     *
     * @param Client $client
     */
    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * @param string $method
     * @param string $api
     * @param array  $options
     *
     * @return array
     */
    public function call(string $method, string $api, array $options = []): array
    {
        $url = $this->apiEndpoint() . $api;

        if (!empty($this->token)) {
            Arr::set($options, 'headers.Authorization', 'Bearer ' . $this->token);
        }

        $this->response = $this->client->request($method, $url, $options);

        return json_decode(optional($this->response)->getBody(), true);
    }

    /**
     * @param string $api
     * @param array  $query
     *
     * @return array
     */
    public function get(string $api, array $query = []): array
    {
        $options = [];

        if (!empty($query)) {
            $options['query'] = $query;
        }

        return $this->call('GET', $api, $options);
    }

    /**
     * @param string $api
     * @param array  $params
     *
     * @return array
     */
    public function post(string $api, array $params = []): array
    {
        $options = [];

        if (!empty($params)) {
            $options['form_params'] = $params;
        }

        return $this->call('POST', $api, $options);
    }

    /**
     * @return string
     */
    public function apiEndpoint(): string
    {
        return $this->domain . $this->api_base . $this->api_version;
    }

    /**
     * @deprecated PSR
     * @see apiEndpoint()
     *
     * @return string
     */
    public function api_endpoint(): string
    {
        return $this->apiEndpoint();
    }

    /**
     * @param ClientInterface $client
     *
     * @return Factory
     */
    public function setClient(ClientInterface $client): Factory
    {
        $this->client = $client;

        return $this;
    }

    /**
     * @param string $domain
     *
     * @return Factory
     */
    public function domain(string $domain): Factory
    {
        $this->domain = trim($domain, '/');

        return $this;
    }

    /**
     * @param string $token
     *
     * @return Factory
     */
    public function token(string $token): Factory
    {
        $this->token = $token;

        return $this;
    }

    /**
     * @param string $api_version
     *
     * @return Factory
     */
    public function apiVersion(string $api_version): Factory
    {
        $this->api_version = $api_version;

        return $this;
    }

    /**
     * @deprecated PSR
     * @see apiVersion()
     *
     * @param string $api_version
     *
     * @return Factory
     */
    public function api_version(string $api_version): Factory
    {
        return $this->apiVersion($api_version);
    }

    /**
     * @param string $api_base
     *
     * @return Factory
     */
    public function apiBase(string $api_base): Factory
    {
        $this->api_base = $api_base;

        return $this;
    }

    /**
     * @deprecated PSR
     * @see apiBase()
     *
     * @param string $api_base
     *
     * @return Factory
     */
    public function api_base(string $api_base): Factory
    {
        return $this->apiBase($api_base);
    }

    /**
     * @return mixed
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * Magic call method.
     *
     * @param string $method
     * @param array  $parameters
     *
     * @throws \BadMethodCallException
     *
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        if (method_exists($this->client, $method)) {
            return call_user_func_array([$this->client, $method], $parameters);
        }

        if (static::hasMacro($method)) {
            if (static::$macros[$method] instanceof Closure) {
                return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters);
            }
        }

        throw new BadMethodCallException(sprintf('Method [%s] does not exist.', $method));
    }
}


<?php

namespace Revolution\Mastodon\Traits;

trait StatusesTrait
{
    /**
     * Get a list of statuses by a user
     *
     * @param int $account_id
     * @param int $limit
     * @param int $since_id
     *
     * @return array
     */
    public function statuses(int $account_id, int $limit = 40, int $since_id = null): array
    {
        $url = "/accounts/${account_id}/statuses";

        $query = [
            'limit'    => $limit,
            'since_id' => $since_id,
        ];

        return $this->get($url, $query);
    }

    /**
     * @deprecated PSR
     * @see statuses()
     *
     * @param int $account_id
     * @param int $limit
     * @param int $since_id
     *
     * @return array
     */
    public function status_list(int $account_id, int $limit = 40, int $since_id = null): array
    {
        return $this->statuses($account_id, $limit, $since_id);
    }

    /**
     * Create new status
     *
     * @param string $status
     * @param array  $options
     *
     * @return array
     */
    public function createStatus(string $status, array $options = null): array
    {
        $url = '/statuses';

        if (empty($options)) {
            $options = [];
        }

        $params = array_merge(['status' => $status,], $options);

        return $this->post($url, $params);
    }

    /**
     * @deprecated PSR
     * @see createStatus()
     *
     * @param string $status
     * @param array  $options
     *
     * @return array
     */
    public function status_post(string $status, array $options = []): array
    {
        return $this->createStatus($status, $options);
    }

    /**
     * Retrieve status
     *
     * @param int $status_id
     *
     * @return array
     */
    public function status(int $status_id): array
    {
        $url = "/statuses/${status_id}";

        return $this->get($url);
    }

    /**
     * @deprecated PSR
     * @see status()
     *
     * @param int $status_id
     *
     * @return array
     */
    public function status_get(int $status_id): array
    {
        return $this->status($status_id);
    }
}


<?php

/*
 * This file is part of the 2amigos/yii2-multiselect-widget project.
 * (c) 2amigOS! <http://2amigos.us/>
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace dosamigos\multiselect;

use Yii;
use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\widgets\InputWidget;

/**
 * MultiSelect renders a [David Stutz Multiselect widget](http://davidstutz.github.io/bootstrap-multiselect/)
 *
 * @see http://davidstutz.github.io/bootstrap-multiselect/
 * @author Antonio Ramirez <amigo.cobos@gmail.com>
 * @link http://www.ramirezcobos.com/
 * @link http://www.2amigos.us/
 * @package dosamigos\widgets
 */
class MultiSelect extends InputWidget
{
    /**
     * @var array data for generating the list options (value=>display)
     */
    public $data = [];
    /**
     * @var array the options for the Bootstrap Multiselect JS plugin.
     *            Please refer to the Bootstrap Multiselect plugin Web page for possible options.
     * @see http://davidstutz.github.io/bootstrap-multiselect/#options
     */
    public $clientOptions = [];

    /**
     * Initializes the widget.
     * @throws InvalidConfigException
     */
    public function init()
    {
        if (empty($this->data)) {
            throw new  InvalidConfigException('"Multiselect::$data" attribute cannot be blank or an empty array.');
        }
        parent::init();
    }

    /**
     * @inheritdoc
     * @throws InvalidParamException
     */
    public function run()
    {
        if ($this->hasModel()) {
            echo Html::activeDropDownList($this->model, $this->attribute, $this->data, $this->options);
        } else {
            echo Html::dropDownList($this->name, $this->value, $this->data, $this->options);
        }
        $this->registerPlugin();
    }

    /**
     * Registers MultiSelect Bootstrap plugin and the related events
     * @throws InvalidParamException
     */
    protected function registerPlugin()
    {
        $view = $this->getView();

        MultiSelectAsset::register($view);

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

        $options = $this->clientOptions !== false && !empty($this->clientOptions)
            ? Json::encode($this->clientOptions)
            : '';

        $js = "jQuery('#$id').multiselect($options);";
        $view->registerJs($js);
    }
}


<?php
/**
 * @title            QR Code
 * @desc             Compatible to vCard 4.0 or higher.
 *
 * @author           Pierre-Henry Soria <ph7software@gmail.com>
 * @copyright        (c) 2012-2018, Pierre-Henry Soria. All Rights Reserved.
 * @license          GNU General Public License <http://www.gnu.org/licenses/gpl.html>
 * @version          1.2
 */

class QRCode
{
    const API_URL = 'https://chart.googleapis.com/chart?chs=';

    private $sData;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->sData = 'BEGIN:VCARD' . "\n";
        $this->sData .= 'VERSION:4.0' . "\n";
    }

    /**
     * The name of the person.
     *
     * @param string $sName
     *
     * @return self
     */
    public function name($sName)
    {
        $this->sData .= 'N:' . $sName . "\n";
        return $this;
    }

    /**
     * The full name of the person.
     *
     * @param string $sFullName
     *
     * @return self this
     */
    public function fullName($sFullName)
    {
        $this->sData .= 'FN:' . $sFullName . "\n";
        return $this;
    }

    /**
     * @param string $sAddress
     *
     * @return self
     */
    public function address($sAddress)
    {
        $this->sData .= 'ADR:' . $sAddress . "\n";

        return $this;
    }

    /**
     * @param string $sNickname
     *
     * @return self
     */
    public function nickName($sNickname)
    {
        $this->sData .= 'NICKNAME:' . $sNickname . "\n";
        return $this;
    }

    /**
     * @param string $sMail
     *
     * @return self
     */
    public function email($sMail)
    {
        $this->sData .= 'EMAIL;TYPE=PREF,INTERNET:' . $sMail . "\n";
        return $this;
    }

    /**
     * @param string $sVal
     *
     * @return self
     */
    public function workPhone($sVal)
    {
        $this->sData .= 'TEL;TYPE=WORK:' . $sVal . "\n";
        return $this;
    }

    /**
     * @param string $sVal
     *
     * @return self
     */
    public function homePhone($sVal)
    {
        $this->sData .= 'TEL;TYPE=HOME:' . $sVal . "\n";
        return $this;
    }

    /**
     * @param string $sUrl
     *
     * @return self
     */
    public function url($sUrl)
    {
        $sUrl = (substr($sUrl, 0, 4) != 'http') ? 'http://' . $sUrl : $sUrl;
        $this->sData .= 'URL:' . $sUrl . "\n";
        return $this;
    }

    /**
     * @param string $sPhone
     * @param string $sText
     *
     * @return self
     */
    public function sms($sPhone, $sText)
    {
        $this->sData .= 'SMSTO:' . $sPhone . ':' . $sText . "\n";
        return $this;
    }

    /**
     * @param string $sBirthday Date in the format YYYY-MM-DD or ISO 8601
     *
     * @return self
     */
    public function birthday($sBirthday)
    {
        $this->sData .= 'BDAY:' . $sBirthday . "\n";
        return $this;
    }

    /**
     * @param string $sBirthDate Date in the format YYYY-MM-DD or ISO 8601
     *
     * @return self
     */
    public function anniversary($sBirthDate)
    {
        $this->sData .= 'ANNIVERSARY:' . $sBirthDate . "\n";
        return $this;
    }

    /**
     * @param string $sSex F = Female. M = Male
     *
     * @return self
     */
    public function gender($sSex)
    {
        $this->sData .= 'GENDER:' . $sSex . "\n";
        return $this;
    }

    /**
     * A list of "tags" that can be used to describe the object represented by this vCard.
     *
     * @param string $sCategories
     *
     * @return self
     */
    public function categories($sCategories)
    {
        $this->sData .= 'CATEGORIES:' . $sCategories . "\n";
        return $this;
    }

    /**
     * The instant messenger (Instant Messaging and Presence Protocol).
     *
     * @param string $sVal
     *
     * @return self
     */
    public function impp($sVal)
    {
        $this->sData .= 'IMPP:' . $sVal . "\n";
        return $this;
    }

    /**
     * Photo (avatar).
     *
     * @param string $sImgUrl URL of the image.
     *
     * @return self
     *
     * @throws InvalidArgumentException If the image format is invalid.
     */
    public function photo($sImgUrl)
    {
        $bIsImgExt = strtolower(substr(strrchr($sImgUrl, '.'), 1)); // Get the file extension.

        if ($bIsImgExt == 'jpeg' || $bIsImgExt == 'jpg' || $bIsImgExt == 'png' || $bIsImgExt == 'gif') {
            $sExt = strtoupper($bIsImgExt);
        } else {
            throw new InvalidArgumentException('Invalid format Image!');
        }

        $this->sData .= 'PHOTO;VALUE=URL;TYPE=' . $sExt . ':' . $sImgUrl . "\n";

        return $this;
    }

    /**
     * The role, occupation, or business category of the vCard object within an organization.
     *
     * @param string $sRole e.g., Executive
     *
     * @return self
     */
    public function role($sRole)
    {
        $this->sData .= 'ROLE:' . $sRole . "\n";
        return $this;
    }

    /**
     * The organization / company.
     *
     * The name and optionally the unit(s) of the organization
     * associated with the vCard object. This property is based on the X.520 Organization Name
     * attribute and the X.520 Organization Unit attribute.
     *
     * @param string $sOrg e.g., Google;GMail Team;Spam Detection Squad
     *
     * @return self
     */
    public function organization($sOrg)
    {
        $this->sData .= 'ORG:' . $sOrg . "\n";
        return $this;
    }

    /**
     * The supplemental information or a comment that is associated with the vCard.
     *
     * @param string $sText
     *
     * @return self
     */
    public function note($sText)
    {
        $this->sData .= 'NOTE:' . $sText . "\n";
        return $this;
    }

    /**
     * @param string $sTitle
     * @param string $sUrl
     *
     * @return self
     */
    public function bookmark($sTitle, $sUrl)
    {
        $this->sData .= 'MEBKM:TITLE:' . $sTitle . ';URL:' . $sUrl . "\n";
        return $this;
    }

    /**
     * Geo location.
     *
     * @param string $sLat Latitude
     * @param string $sLon Longitude
     * @param integer $iHeight Height
     *
     * @return self
     */
    public function geo($sLat, $sLon, $iHeight)
    {
        $this->sData .= 'GEO:' . $sLat . ',' . $sLon . ',' . $iHeight . "\n";
        return $this;
    }

    /**
     * The language that the person speaks.
     *
     * @param string $sLang e.g., en-US
     *
     * @return self
     */
    public function lang($sLang)
    {
        $this->sData .= 'LANG:' . $sLang . "\n";
        return $this;
    }

    /**
     * @param string $sType
     * @param string $sSsid
     * @param string $sPwd
     *
     * @return self
     */
    public function wifi($sType, $sSsid, $sPwd)
    {
        $this->sData .= 'WIFI:T:' . $sType . ';S' . $sSsid . ';' . $sPwd . "\n";
        return $this;
    }

    /**
     * Generate the QR code.
     *
     * @return self
     */
    public function finish()
    {
        $this->sData .= 'END:VCARD';
        $this->sData = urlencode($this->sData);
        return $this;
    }

    /**
     * Get the URL of QR Code.
     *
     * @param integer $iSize Default 150
     * @param string $sECLevel Default L
     * @param integer $iMargin Default 1
     *
     * @return string The API URL configure.
     */
    public function get($iSize = 150, $sECLevel = 'L', $iMargin = 1)
    {
        return self::API_URL . $iSize . 'x' . $iSize . '&cht=qr&chld=' . $sECLevel . '|' . $iMargin . '&chl=' . $this->sData;
    }

    /**
     * The HTML code for displaying the QR Code.
     *
     * @return void
     */
    public function display()
    {
        echo '
_cleanUrl($this->get()) . '" alt="QR Code" />
';
    }

    /**
     * Clean URL.
     *
     * @param string $sUrl
     *
     * @return string
     */
    private function _cleanUrl($sUrl)
    {
        return str_replace('&', '&', $sUrl);
    }
}


<?php

namespace Basho\Riak\TimeSeries;

/**
 * Data structure for Cells of a TimeSeries row
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class Cell
{
    const STRING_TYPE = 'varchar';
    const INT_TYPE = 'sint64';
    const DOUBLE_TYPE = 'double';
    const BOOL_TYPE = 'boolean';
    const TIMESTAMP_TYPE = 'timestamp';
    const BLOB_TYPE = 'blob';

    protected $name;
    protected $value = null;
    protected $type = null;

    public function __construct($name)
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return string
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * @param string $value
     *
     * @return $this
     */
    public function setValue($value = null)
    {
        $this->type = Cell::STRING_TYPE;
        $this->value = $value;

        return $this;
    }

    /**
     * @param string $value
     *
     * @return $this
     */
    public function setBlobValue($value = null)
    {
        $this->type = Cell::BLOB_TYPE;
        $this->value = $value;

        return $this;
    }

    /**
     * @param int $value
     *
     * @return $this
     */
    public function setIntValue($value = null)
    {
        if ($value != null && !is_int($value)) {
            throw new \InvalidArgumentException('Expected an integer value.');
        }

        $this->type = Cell::INT_TYPE;
        $this->value = $value;

        return $this;
    }

    /**
     * @param int $value
     *
     * @return $this
     */
    public function setTimestampValue($value = null)
    {
        if ($value != null && !is_int($value)) {
            throw new \InvalidArgumentException('Expected an integer value.');
        }

        $this->type = Cell::TIMESTAMP_TYPE;
        $this->value = $value;

        return $this;
    }

    /**
     * @param \DateTime $value
     *
     * @return $this
     */
    public function setDateTimeValue($value = null)
    {
        if ($value != null && !$value instanceof \DateTime) {
            throw new \InvalidArgumentException('Expected a \DateTime object.');
        }

        $this->type = Cell::TIMESTAMP_TYPE;
        $this->value = $value->getTimestamp();

        return $this;
    }

    /**
     * @param bool $value
     *
     * @return $this
     */
    public function setBooleanValue($value = null)
    {
        if ($value != null && !is_bool($value)) {
            throw new \InvalidArgumentException('Expected an boolean value.');
        }

        $this->type = Cell::BOOL_TYPE;
        $this->value = $value;

        return $this;
    }

    /**
     * @param double $value
     *
     * @return $this
     */
    public function setDoubleValue($value = null)
    {
        if ($value != null && !is_double($value)) {
            throw new \InvalidArgumentException('Expected an double value.');
        }

        $this->type = Cell::DOUBLE_TYPE;
        $this->value = $value;

        return $this;
    }

    /**
     * Convenience method for inclusion in HTTP api path
     *
     * @return string
     */
    public function __toString()
    {
        return rawurlencode($this->getName()) . '/' . rawurlencode($this->getValue());
    }
}


<?php

namespace Basho\Riak;

/**
 * Extend this class to implement your own API bridge.
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
abstract class Api
{
    /**
     * Request string to be sent
     *
     * @var string
     */
    protected $request = '';

    /**
     * @var Command\Response|null
     */
    protected $response = null;

    /**
     * @var Command|null
     */
    protected $command = null;

    /**
     * @var Node|null
     */
    protected $node = null;

    protected $success = null;

    protected $error = '';

    protected $config = [];

    public function __construct(array $config = [])
    {
        $this->config = $config;
    }

    /**
     * @return string
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * @return Command|null
     */
    public function getCommand()
    {
        return $this->command;
    }

    /**
     * @param Command|null $command
     *
     * @return $this
     */
    protected function setCommand($command)
    {
        $this->command = $command;

        return $this;
    }

    /**
     * @return Node|null
     */
    public function getNode()
    {
        return $this->node;
    }

    /**
     * @param Node|null $node
     *
     * @return $this
     */
    public function setNode($node)
    {
        $this->node = $node;

        return $this;
    }

    /**
     * @return string
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * Prepare the api connection
     *
     * @param Command $command
     * @param Node $node
     *
     * @return $this
     */
    public function prepare(Command $command, Node $node)
    {
        $this->setCommand($command);
        $this->setNode($node);

        return $this;
    }

    /**
     * @return Command\Response|null
     */
    public function getResponse()
    {
        return $this->response;
    }

    /**
     * @return null
     */
    public function getSuccess()
    {
        return $this->success;
    }

    /**
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * send
     *
     * @return bool
     */
    abstract public function send();

    /**
     * Closes the connection to the Riak Interface
     *
     * @return null
     */
    abstract public function closeConnection();
}


<?php

namespace Basho\Riak\Command\Builder;

use Basho\Riak\Bucket;
use Basho\Riak\Location;

/**
 * Allows easy code sharing for Location getters / setters within the Command Builders
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
trait LocationTrait
{
    // location depends on bucket
    use BucketTrait;

    /**
     * @var Location|null
     */
    protected $location = NULL;

    /**
     * @return Location|null
     */
    public function getLocation()
    {
        return $this->location;
    }

    /**
     * @param $key
     * @param $name
     * @param string $type
     *
     * @return $this
     */
    public function buildLocation($key, $name, $type = 'default')
    {
        $this->bucket = new Bucket($name, $type);
        $this->location = new Location($key, $this->bucket);

        return $this;
    }

    /**
     * @param Location $location
     *
     * @return $this
     */
    public function atLocation(Location $location)
    {
        $this->bucket = $location->getBucket();
        $this->location = $location;

        return $this;
    }
}


<?php

namespace Basho\Riak\Command\Builder;

use Basho\Riak;
use Basho\Riak\Command;

/**
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class UpdateMap extends Command\Builder implements Command\BuilderInterface
{
    use LocationTrait;

    protected $remove = [];

    /**
     * @var array
     */
    protected $registers = [];

    /**
     * @var array
     */
    protected $flags = [];

    /**
     * @var IncrementCounter[]
     */
    protected $counters = [];

    /**
     * @var UpdateSet[]
     */
    protected $sets = [];

    /**
     * @var UpdateMap[]
     */
    protected $maps = [];

    /**
     * Similar to Vector Clocks, the context allows us to determine the state of a CRDT Set
     *
     * @var string
     */
    protected $context = '';

    public function removeRegister($key)
    {
        $this->remove($key, Riak\DataType\Map::REGISTER);

        return $this;
    }

    protected function remove($key, $type)
    {
        $this->remove[] = sprintf('%s_%s', $key, $type);
    }

    public function removeFlag($key)
    {
        $this->remove($key, Riak\DataType\Map::FLAG);

        return $this;
    }

    public function removeCounter($key)
    {
        $this->remove($key, Riak\DataType\Counter::TYPE);

        return $this;
    }

    public function removeSet($key)
    {
        $this->remove($key, Riak\DataType\Set::TYPE);

        return $this;
    }

    public function removeMap($key)
    {
        $this->remove($key, Riak\DataType\Map::TYPE);

        return $this;
    }

    /**
     * @param $key
     * @param $value
     *
     * @return $this
     */
    public function updateRegister($key, $value)
    {
        $this->update($key, Riak\DataType\Map::REGISTER, $value);

        return $this;
    }

    protected function update($key, $type, $value)
    {
        $property = "{$type}s";
        $this->{$property}[sprintf('%s_%s', $key, $type)] = $value;
    }

    /**
     * @param $key
     * @param bool $state
     *
     * @return $this
     */
    public function updateFlag($key, $state = TRUE)
    {
        $this->update($key, Riak\DataType\Map::FLAG, $state);

        return $this;
    }

    /**
     * @param $key
     * @param IncrementCounter $builder
     *
     * @return $this
     */
    public function updateCounter($key, IncrementCounter $builder)
    {
        $this->update($key, Riak\DataType\Counter::TYPE, $builder);

        return $this;
    }

    /**
     * @param $key
     * @param UpdateSet $builder
     *
     * @return $this
     */
    public function updateSet($key, UpdateSet $builder)
    {
        $this->update($key, Riak\DataType\Set::TYPE, $builder);

        return $this;
    }

    /**
     * @param $key
     * @param UpdateMap $builder
     *
     * @return $this
     */
    public function updateMap($key, UpdateMap $builder)
    {
        $this->update($key, Riak\DataType\Map::TYPE, $builder);

        return $this;
    }

    /**
     * @param $context
     *
     * @return $this
     */
    public function withContext($context)
    {
        $this->context = $context;

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @return Command\DataType\Map\Store
     */
    public function build()
    {
        $this->validate();

        return new Command\DataType\Map\Store($this);
    }

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $this->required('Bucket');

        $count_remove = count($this->remove);
        $count_registers = count($this->registers);
        $count_flags = count($this->flags);
        $count_counters = count($this->counters);
        $count_sets = count($this->sets);
        $count_maps = count($this->maps);

        if ($count_remove + $count_registers + $count_flags + $count_counters + $count_sets + $count_maps < 1) {
            throw new Exception('At least one add, remove, or update operation needs to be defined.');
        }

        if ($count_remove) {
            $this->required('Location');
            $this->required('Context');
        }

        // if we are performing a remove on a nested set, Location and context are required
        if ($count_sets) {
            foreach ($this->sets as $set) {
                if (count($set->getRemoveAll())) {
                    $this->required('Location');
                    $this->required('Context');
                    break;
                }
            }
        }

        // if we are performing a remove, Location and context are required
        if ($count_remove) {
            $this->required('Location');
            $this->required('Context');
        }
    }

    public function getRemove()
    {
        return $this->remove;
    }

    /**
     * @return array
     */
    public function getRegisters()
    {
        return $this->registers;
    }

    /**
     * @return array
     */
    public function getFlags()
    {
        return $this->flags;
    }

    /**
     * @return IncrementCounter[]
     */
    public function getCounters()
    {
        return $this->counters;
    }

    /**
     * @return UpdateSet[]
     */
    public function getSets()
    {
        return $this->sets;
    }

    /**
     * @return UpdateMap[]
     */
    public function getMaps()
    {
        return $this->maps;
    }

    /**
     * getContext
     *
     * @return string
     */
    public function getContext()
    {
        return $this->context;
    }
}


<?php

namespace Basho\Riak\Command\Builder;

use Basho\Riak\Command;

/**
 * Used to query a secondary index in Riak.
 *
 * <code>
 * $command = (new Command\Builder\QueryIndex($riak))
 *   ->buildBucket('users')
 *   ->withIndex('users_name', 'Knuth')
 *   ->build();
 *
 * $response = $command->execute();
 *
 * $index_results = $response->getIndexResults();
 * </code>
 *
 * @author Alex Moore <amoore at basho d0t com>
 */
class QueryIndex extends Command\Builder implements Command\BuilderInterface
{
    use BucketTrait;
    use IndexTrait;

    /**
     * {@inheritdoc}
     *
     * @return Command\Indexes\Query
     */
    public function build()
    {
        $this->validate();

        return new Command\Indexes\Query($this);
    }

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $this->required('Bucket');
        $this->required('IndexName');

        if($this->isMatchQuery()) {
            $this->required('MatchValue');
        }
        else {
            $this->required('LowerBound');
            $this->required('UpperBound');
        }
    }
}


<?php

namespace Basho\Riak\Command\Builder;

/**
 * Allows easy code sharing for Bucket getters / setters within the Command Builders
 *
 * @author Alex Moore <amoore at basho d0t com>
 */
trait IndexTrait
{
    /**
     * The name of the index to query.
     *
     * @var string|null
     */
    protected $indexName = NULL;

    /**
     * The index match value for scalar queries.
     *
     * @var string|integer|null
     */
    protected $match = NULL;

    /**
     * The index lower bound value for range queries.
     *
     * @var string|integer|null
     */
    protected $lowerBound = NULL;

    /**
     * The index upper bound value for range queries.
     *
     * @var string|integer|null
     */
    protected $upperBound = NULL;

    /**
     * The continuation string for this query.
     * Used to page results when combined with MaxResults.
     *
     * @var string|null
     */
    protected $continuation = NULL; //Binary

    /**
     * The maximum number of results returned by the query.
     *
     * @var integer|null
     */
    protected $maxResults; // Int

    /**
     * The option to return the index keys with the Riak object keys.
     *
     * @var boolean|null
     */
    protected $returnTerms; // Bool

    /**
     * The option to sort, or not sort, the results of a non-paginated secondary index query.
     * If MaxResults is set, this property is ignored.
     * By default results are sorted first by index value, then by key value.
     *
     * @var boolean|null
     */
    protected $paginationSort; // Bool

    /**
     * The option to filter result terms with a Regex.
     *
     * @var string|null
     */
    protected $termFilter; // string
    /**
     *
     * @var integer|null
     */
    protected $timeout; // timeout

    /**
     * Gets the index name
     *
     * @return string|null
     */
    public function getIndexName()
    {
        return $this->indexName;
    }

    /**
     * @return string|integer|null
     */
    public function getMatchValue()
    {
        return $this->match;
    }

    /**
     * @return string|integer|null
     */
    public function getLowerBound()
    {
        return $this->lowerBound;
    }

    /**
     * @return string|integer|null
     */
    public function getUpperBound()
    {
        return $this->upperBound;
    }

    /**
     * @return boolean
     */
    public function isMatchQuery()
    {
        return isset($this->match);
    }

    /**
     * @return boolean
     */
    public function isRangeQuery()
    {
        return isset($this->lowerBound) && isset($this->upperBound);
    }

    /**
     * @return null|string
     */
    public function getContinuation()
    {
        return $this->continuation;
    }

    /**
     * @return int|null
     */
    public function getMaxResults()
    {
        return $this->maxResults;
    }

    /**
     * @return bool|null
     */
    public function getReturnTerms()
    {
        return $this->returnTerms;
    }

    /**
     * @return bool|null
     */
    public function getPaginationSort()
    {
        return $this->paginationSort;
    }

    /**
     * @return null|string
     */
    public function getTermFilter()
    {
        return $this->termFilter;
    }

    /**
     * @return int|null
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * @param $param
     * @return bool
     */
    public function isParamSet($param)
    {
        return !empty($this->$param);
    }

    /**
     * Adds the index name information to the Command
     *
     * @param $indexName
     *
     * @return $this
     */
    public function withIndexName($indexName)
    {
        $this->indexName = $indexName;
        return $this;
    }

    /**
     * Adds the scalar index query information to the Command
     *
     * @param $value
     *
     * @return $this
     */
    public function withScalarValue($value)
    {
        $this->match = $value;
        $this->lowerBound = null;
        $this->upperBound = null;
        return $this;
    }

    /**
     * Adds the range index query information to the Command
     *
     * @param $lowerBound
     * @param $upperBound
     *
     * @return $this
     */
    public function withRangeValue($lowerBound, $upperBound)
    {
        $this->lowerBound = $lowerBound;
        $this->upperBound = $upperBound;
        $this->match = null;

        return $this;
    }

    /**
     * @param null|string $continuation
     *
     * @return $this
     */
    public function withContinuation($continuation)
    {
        $this->continuation = $continuation;
        return $this;
    }

    /**
     * @param int|null $maxResults
     *
     * @return $this
     */
    public function withMaxResults($maxResults)
    {
        $this->maxResults = $maxResults;
        return $this;
    }

    /**
     * @param bool|null $returnTerms
     *
     * @return $this
     */
    public function withReturnTerms($returnTerms = true)
    {
        $this->returnTerms = $returnTerms;
        return $this;
    }

    /**
     * @param bool|null $paginationSort
     *
     * @return $this
     */
    public function withPaginationSort($paginationSort)
    {
        $this->paginationSort = $paginationSort;
        return $this;
    }

    /**
     * @param null|string $termFilter
     *
     * @return $this
     */
    public function withTermFilter($termFilter)
    {
        $this->termFilter = $termFilter;
        return $this;
    }

    /**
     * @param int|null $timeout
     *
     * @return $this
     */
    public function withTimeout($timeout)
    {
        $this->timeout = $timeout;
        return $this;
    }
}


<?php

namespace Basho\Riak;

/**
 * Core data structure for a Riak Bucket.
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class Bucket
{
    /**
     * The default bucket type in Riak.
     */
    const DEFAULT_TYPE = "default";

    /**
     * Bucket properties
     *
     * @var array
     */
    protected $properties = [];

    /**
     * Name of bucket
     */
    protected $name = '';

    /**
     * Buckets are grouped by type, inheriting the properties defined on the type
     */
    protected $type = '';

    /**
     * @param        $name
     * @param string $type
     * @param array $properties
     */
    public function __construct($name, $type = self::DEFAULT_TYPE, array $properties = [])
    {
        $this->name = $name;
        $this->type = $type;
        $this->properties = $properties;
    }

    public function __toString()
    {
        return $this->getNamespace();
    }

    /**
     * Bucket namespace
     *
     * This is a human readable namespace for the bucket.
     *
     * @return string
     */
    public function getNamespace()
    {
        return "/{$this->type}/{$this->name}/";
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param $key
     *
     * @return string
     */
    public function getProperty($key)
    {
        $properties = $this->getProperties();
        if (isset($properties[$key])) {
            return $properties[$key];
        }

        return '';
    }

    /**
     * @return array
     */
    public function getProperties()
    {
        return $this->properties;
    }

    /**
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }
}


<?php

namespace Basho\Riak\Command\Builder\MapReduce;

use Basho\Riak;
use Basho\Riak\Command;

/**
 * Builds the command to fetch a collection of objects from Riak using Yokozuna search
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class FetchObjects extends Command\Builder implements Command\BuilderInterface
{
    /**
     * MR inputs used by query phase
     *
     * @var array|string
     */
    protected $inputs = [];

    /**
     * MR Query phases
     *  - options include map, reduce or link
     *
     * @var array
     */
    protected $query = [];

    /**
     * Timeout for MR query
     *
     * @var int
     */
    protected $timeout = 0;

    /**
     * {@inheritdoc}
     *
     * @return Command\MapReduce\Fetch;
     */
    public function build()
    {
        $this->validate();

        if (is_array($this->inputs) && count($this->inputs) == 1) {
            $this->inputs = $this->inputs[0];
        }

        return new Command\MapReduce\Fetch($this);
    }

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $this->required('Inputs');
        $this->required('Query');
    }

    /**
     * addBucketInput
     *
     * @param Riak\Bucket $bucket
     *
     * @return $this
     */
    public function addBucketInput(Riak\Bucket $bucket)
    {
        // default bucket type cannot be passed to the MR api due to a bug
        if ($bucket->getType() == 'default') {
            $input = $bucket->getName();
        } else {
            $input = [$bucket->getType(), $bucket->getName()];
        }

        $this->inputs[] = $input;

        return $this;
    }

    /**
     * addLocationInput
     *
     * @param Riak\Location $location
     *
     * @return $this
     */
    public function addLocationInput(Riak\Location $location)
    {
        // default bucket type cannot be passed to the MR api due to a bug
        if ($location->getBucket()->getType() == 'default') {
            $input = [$location->getBucket()->getName(), $location->getKey()];
        } else {
            $input = [$location->getBucket()->getName(), $location->getKey(), '', $location->getBucket()->getType()];
        }

        $this->inputs[] = $input;

        return $this;
    }

    /**
     * withInput
     *
     * Sets a single input value as a string. Can be:
     *  - a bucket with the default bucket type
     *  - a 2i index name
     *  - a Search/Yokozuna index name
     *
     * @param $input
     *
     * @return $this
     */
    public function withInput($input)
    {
        $this->inputs = $input;

        return $this;
    }

    /**
     * withInputs
     *
     * Sets an array of inputs for the MR request
     *
     * @param array $inputs
     *
     * @return $this
     */
    public function withInputs(array $inputs)
    {
        $this->inputs = $inputs;

        return $this;
    }

    public function getInputs()
    {
        return $this->inputs;
    }

    /**
     * @return string
     */
    public function getQuery()
    {
        return $this->query;
    }

    /**
     * buildMapPhase
     *
     * Allows you to define the Map Query Phase by using parameters
     *
     * @param string $bucket
     * @param string $key
     * @param string $source
     * @param string $language
     * @param bool $keep
     *
     * @return $this
     */
    public function buildMapPhase($bucket = '', $key = '', $source = '', $language = 'javascript', $keep = false)
    {
        $this->setPhase('map', $this->assemblePhaseData($bucket, $key, $source, $language), $keep);

        return $this;
    }

    /**
     * setPhase
     *
     * @param $type
     * @param $data
     * @param bool $keep
     */
    protected function setPhase($type, $data, $keep = false)
    {
        $data['keep'] = $keep;
        $object = new \StdClass();
        $object->{$type} = $data;
        $this->query[] = $object;
    }

    /**
     * assemblePhaseData
     *
     * Assembles the parameters into a data structure to define a Query Phase
     *
     * @param string $bucket
     * @param string $key
     * @param string $source
     * @param string $language
     * @param string $tag
     *
     * @return array
     */
    protected function assemblePhaseData($bucket = '', $key = '', $source = '', $language = '', $tag = '')
    {
        $data = [];

        if ($language) {
            $data['language'] = $language;
        }

        if ($bucket) {
            $data['bucket'] = $bucket;
        }

        if ($key) {
            $data['key'] = $key;
        }

        if ($source) {
            $data['source'] = $source;
        }

        if ($tag) {
            $data['tag'] = $tag;
        }

        return $data;
    }

    /**
     * buildReducePhase
     *
     * Allows you to define the Reduce Query Phase by using parameters
     *
     * @param string $bucket
     * @param string $key
     * @param string $source
     * @param string $language
     * @param bool $keep
     *
     * @return $this
     */
    public function buildReducePhase($bucket = '', $key = '', $source = '', $language = 'javascript', $keep = true)
    {
        $this->setPhase('reduce', $this->assemblePhaseData($bucket, $key, $source, $language), $keep);

        return $this;
    }

    /**
     * buildReducePhase
     *
     * Allows you to define the Reduce Query Phase by using parameters
     *
     * @param string $bucket
     * @param string $tag
     * @param bool $keep
     *
     * @internal param string $key
     * @internal param string $source
     * @internal param string $language
     * @return $this
     */
    public function buildLinkPhase($bucket = '', $tag = '', $keep = false)
    {
        $this->setPhase('link', $this->assemblePhaseData($bucket, '', '', '', $tag), $keep);

        return $this;
    }

    /**
     * withQuery
     *
     * Sets the Query Phase/s for the MR request. This value gets converted directly into a JSON array and passed to Riak
     *
     * @param array $query
     *
     * @return $this
     */
    public function withQuery(array $query)
    {
        $this->query = $query;

        return $this;
    }
}


<?php

namespace Basho\Riak\Command\Builder;

use Basho\Riak;
use Basho\Riak\Command;

/**
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class UpdateSet extends Command\Builder implements Command\BuilderInterface
{
    use LocationTrait;

    /**
     * @var array
     */
    protected $add_all = [];

    /**
     * @var array
     */
    protected $remove_all = [];

    /**
     * Similar to Vector Clocks, the context allows us to determine the state of a CRDT Set
     *
     * @var string
     */
    protected $context = '';

    /**
     * @param mixed $element
     *
     * @return $this
     */
    public function add($element)
    {
        settype($element, 'string');
        $this->add_all[] = $element;

        return $this;
    }

    /**
     * @param mixed $element
     *
     * @return $this
     */
    public function remove($element)
    {
        settype($element, 'string');
        $this->remove_all[] = $element;

        return $this;
    }

    /**
     * @param $context
     *
     * @return $this
     */
    public function withContext($context)
    {
        $this->context = $context;

        return $this;
    }

    /**
     * @return array
     */
    public function getAddAll()
    {
        return $this->add_all;
    }

    /**
     * @return array
     */
    public function getRemoveAll()
    {
        return $this->remove_all;
    }

    /**
     * getContext
     *
     * @return string
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * {@inheritdoc}
     *
     * @return Command\DataType\Set\Store
     */
    public function build()
    {
        $this->validate();

        return new Command\DataType\Set\Store($this);
    }

    /**
     * {@inheritdoc}
     */
    public function validate()
    {
        $this->required('Bucket');

        $count_add = count($this->add_all);
        $count_remove = count($this->remove_all);

        if ($count_add + $count_remove < 1) {
            throw new Exception('At least one element to add or remove needs to be defined.');
        }

        // if we are performing a remove, Location and context are required
        if ($count_remove) {
            $this->required('Location');
            $this->required('Context');
        }
    }
}


<?php

namespace Basho\Riak\Node;

use Basho\Riak\Node;

/**
 * This class follows the Builder design pattern and is the preferred method for creating Basho\Riak\Node objects for
 * connecting to your Riak node cluster.
 *
 *
 *  // simple local development / testing cluster
 *  use Basho\Riak\Node;
 *
 *  $nodes = (new Node\Builder)
 *      ->buildLocalhost([10018, 10028, 10038, 10048, 10058]);
 *
 *
 *
 *  // password authentication to production cluster
 *  use Basho\Riak\Node;
 *
 *  $nodes = (new Node\Builder)
 *      ->onPort(8098)
 *      ->usingPasswordAuthentication('riakuser', 'riakpassword')
 *      ->withCertificateAuthorityFile(getcwd() . '/path/to/cacert.pem')
 *      ->buildCluster(['riak1.company.int','riak2.company.int','riak3.company.int']);
 *
 *
 *
 *  // certificate authentication to production load balanced cluster
 *  use Basho\Riak\Node;
 *
 *  $node = (new Node\Builder)
 *      ->atHost('riak.company.int')
 *      ->onPort(8098)
 *      ->usingCertificateAuthentication(getcwd() . '/path/to/client.crt')
 *      ->withCertificateAuthorityFile(getcwd() . '/path/to/cacert.pem')
 *      ->build();
 *
 *
 *
 *  // pam authentication to production load balanced cluster
 *  use Basho\Riak\Node;
 *
 *  $node = (new Node\Builder)
 *      ->atHost('riak.company.int')
 *      ->onPort(8098)
 *      ->usingPamAuthentication('riakuser')
 *      ->withCertificateAuthorityFile(getcwd() . '/path/to/cacert.pem')
 *      ->build();
 *
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class Builder
{
    /**
     * Internal storage
     *
     * @var Config|null
     */
    protected $config = null;

    public function __construct()
    {
        $this->config = new Config();
    }

    /**
     * usingTrustAuthentication
     *
     * Build nodes with trust authentication
     *
     * User authentication and access rules are only available in Riak versions 2 and above. To use this feature, TSL
     * is required to communicate with your Riak nodes.
     *
     * @param string $user
     *
     * @return $this
     */
    public function usingTrustAuthentication($user = '')
    {
        $this->config->setUser($user);
        $this->config->setAuth(true);

        return $this;
    }

    /**
     * usingPasswordAuthentication
     *
     * Build nodes with password authentication
     *
     * User authentication and access rules are only available in Riak versions 2 and above. To use this feature, TSL
     * is required to communicate with your Riak nodes.
     *
     * @param $user
     * @param $pass
     * @return $this
     */
    public function usingPasswordAuthentication($user, $pass = '')
    {
        $this->config->setUser($user);
        $this->config->setPass($pass);
        $this->config->setAuth(true);

        return $this;
    }

    /**
     * usingCertificateAuthentication
     *
     * Build nodes with certificate authentication
     *
     * User authentication and access rules are only available in Riak versions 2 and above. To use this feature, TSL
     * is required to communicate with your Riak nodes.
     *
     * CURRENTLY NOT SUPPORTED OVER THE RIAK HTTP API
     *
     * @param $certificate
     * @param string $password
     *
     * @return $this
     * @throws Builder\Exception
     */
    public function usingCertificateAuthentication($certificate, $password = '')
    {
        $this->config->setCertificate($certificate);
        $this->config->setCertificatePassword($password);
        $this->config->setAuth(true);

        throw new Node\Builder\Exception('Riak over HTTP does not support Certificate Authentication.');

        //return $this;
    }

    /**
     * usingPamAuthentication
     *
     * Build nodes with PAM authentication
     *
     * User authentication and access rules are only available in Riak versions 2 and above. To use this feature, TSL
     * is required to communicate with your Riak nodes.
     *
     * @param $user
     *
     * @return $this
     */
    public function usingPamAuthentication($user)
    {
        $this->config->setUser($user);
        $this->config->setAuth(true);

        return $this;
    }

    /**
     * withCertificateAuthorityDirectory
     *
     * Path to CA file. A Certificate Authority file is required for any secure connections to Riak
     *
     * @param $ca_file
     *
     * @return $this
     *
     */
    public function withCertificateAuthorityFile($ca_file)
    {
        $this->config->setCaFile($ca_file);

        return $this;
    }

    /**
     * withCertificateAuthorityDirectory
     *
     * Directory where the CA file can be found. A Certificate Authority file is required for any secure connections to
     * Riak
     *
     * @param $ca_directory
     *
     * @return $this
     *
     */
    public function withCertificateAuthorityDirectory($ca_directory)
    {
        $this->config->setCaDirectory($ca_directory);

        return $this;
    }

    public function withPrivateKey($private_key, $password = '')
    {
        $this->config->setPrivateKey($private_key);
        $this->config->setPrivateKeyPassword($password);

        return $this;
    }

    /**
     * Client side connection timeout for requests
     *
     * @param $timeout
     */
    public function withConnectionTimeout($timeout) {
        $this->config->setConnectionTimeout($timeout);
    }

    /**
     * Client side socket read/write timeout for requests
     *
     * @param $timeout
     */
    public function withStreamTimeout($timeout) {
        $this->config->setStreamTimeout($timeout);
    }

    /**
     * @return Config|null
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Build distributed cluster
     *
     * Build node objects configured to listen on the same port but different hosts. Commonly used in
     * staging and production environments where you have multiple Riak nodes on multiple machines / vms.
     *
     * @param array $hosts
     * @return Node[]
     */
    public function buildCluster(array $hosts = ['localhost'])
    {
        $nodes = [];
        foreach ($hosts as $host) {
            $nodes[] = $this->atHost($host)->build();
        }

        return $nodes;
    }

    /**
     * Build node
     *
     * Validate configuration for a single node object, then build it
     *
     * @return Node
     */
    public function build()
    {
        $this->validate();

        return new Node(clone $this->config);
    }

    /**
     * Builder configuration validation
     *
     * Checks the current configuration of the Node Builder for errors. This method should be executed before each Node
     * is built.
     *
     * @throws Builder\Exception
     */
    protected function validate()
    {
        // verify we have a host address and port
        if (!$this->config->getHost() || !$this->config->getPort()) {
            throw new Node\Builder\Exception('Node host address and port number are required.');
        }

        if ($this->config->getUser() && $this->config->getCertificate()) {
            throw new Node\Builder\Exception('Connect with password OR certificate authentication, not both.');
        }

        if ($this->config->isAuth() && !$this->config->getCaDirectory() && !$this->config->getCaFile()) {
            throw new Node\Builder\Exception('Certificate authority file is required for authentication.');
        }
    }

    /**
     * Build with host address
     *
     * Build node objects with configuration to use a specific host address
     *
     * @param $host
     * @return $this
     */
    public function atHost($host)
    {
        $this->config->setHost($host);

        return $this;
    }

    /**
     * Build local node cluster
     *
     * Build multiple node objects configured with the same host address but different ports. Commonly used in
     * development environments where you have multiple Riak nodes on a single machine / vm.
     *
     * @param array $ports
     * @return Node[]
     */
    public function buildLocalhost(array $ports = [8087])
    {
        $nodes = [];
        $this->atHost('localhost');
        foreach ($ports as $port) {
            $nodes[] = $this->onPort($port)->build();
        }

        return $nodes;
    }

    /**
     * Build node objects with configuration to use a specific port number
     *
     * @param $port
     * @return $this
     */
    public function onPort($port)
    {
        $this->config->setPort($port);

        return $this;
    }
}


<?php

namespace Basho\Riak\Command;

use Basho\Riak;
use Basho\Riak\Command;
use Basho\Riak\DataType;

/**
 * This class follows the Builder design pattern and is the preferred method for creating Basho\Riak\Command
 * objects for interacting with your Riak data cluster.
 *
 * <code>
 * use Basho\Riak\Command;
 * use Basho\Riak\Bucket;
 * use Basho\Riak\Location;
 *
 * $bucket = new Bucket('users');
 *
 * $command = (new Command\Builder(Command::STORE_OBJECT))
 *      ->withObject(new Object('test_data'))
 *      ->atLocation(new Location('test_key', $bucket))
 *      ->build();
 * </code>
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
abstract class Builder
{
    /**
     * @var Riak|null
     */
    protected $riak = null;

    /**
     * Command parameters
     *
     * @var array
     */
    protected $parameters = [];

    /**
     * Command request headers
     *
     * @var array
     */
    protected $headers = [];

    protected $verbose = false;

    protected $connectionTimeout = 0;

    public function __construct(Riak $riak)
    {
        $this->riak = $riak;
    }

    /**
     * Command build
     *
     * Validates then returns the built command object.
     */
    abstract public function build();

    public function withParameter($key, $value = true)
    {
        $this->parameters[$key] = $value;

        return $this;
    }

    public function withParameters($parameters = [])
    {
        $this->parameters = $parameters;

        return $this;
    }

    public function withVerboseMode($verbose = true)
    {
        $this->verbose = $verbose;

        return $this;
    }

    public function withConnectionTimeout($seconds = 60)
    {
        $this->connectionTimeout = $seconds;

        return $this;
    }

    public function getParameters()
    {
        return $this->parameters;
    }

    public function getConnection()
    {
        return $this->riak;
    }

    public function getVerbose()
    {
        return $this->verbose;
    }

    /**
     * @return int
     */
    public function getConnectionTimeout()
    {
        return $this->connectionTimeout;
    }

    /**
     * Validate command
     *
     * Method validates if the builder has the parameters / objects required to successfully execute the command
     *
     * @return bool
     * @throws Builder\Exception
     */
    protected function validate()
    {
        throw new Command\Builder\Exception('Invalid builder.');
    }

    /**
     * Used to verify a property within the builder is not null and is instantiated
     *
     * @param $objectName
     *
     * @throws Builder\Exception
     */
    protected function required($objectName)
    {
        $method = "get{$objectName}";
        $class = "Basho\\Riak\\{$objectName}";
        $value = $this->$method();
        if (is_null($value)) {
            throw new Builder\Exception("Expected non-empty value for {$objectName}");
        }
        if (is_object($value) && $value instanceof $class === false) {
            throw new Builder\Exception("Expected instance of {$class}, received instance of " . get_class($value));
        }
        if (is_array($value) && count($value) == 0) {
            throw new Builder\Exception("Expected non-empty array value for {$objectName}");
        }
    }
}


<?php

namespace Basho\Riak;

/**
 * Immutable data structure storing the location of an Object or DataType
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class Location
{
    /**
     * Kv Object / CRDT key
     *
     * @var string
     */
    protected $key = '';

    /**
     * @var Bucket|null
     */
    protected $bucket = NULL;

    /**
     * @param $key
     * @param Bucket $bucket
     */
    public function __construct($key, Bucket $bucket)
    {
        $this->key = $key;
        $this->bucket = $bucket;
    }

    /**
     * Generate an instance of the Location object using the Location header string value returned from Riak
     *
     * @param $location_string
     *
     * @return Location
     */
    public static function fromString($location_string)
    {
        preg_match('/^\/types\/([^\/]+)\/buckets\/([^\/]+)\/keys\/([^\/]+)$/', $location_string, $matches);

        return new self($matches[3], new Bucket($matches[2], $matches[1]));
    }

    public function __toString()
    {
        return $this->bucket . $this->key;
    }

    /**
     * @return Bucket|null
     */
    public function getBucket()
    {
        return $this->bucket;
    }

    /**
     * @return string
     */
    public function getKey()
    {
        return $this->key;
    }
}


<?php

/*
Copyright 2014 Basho Technologies, Inc.

Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
specific language governing permissions and limitations under the License.
*/

namespace Basho\Riak\DataType;

use Basho\Riak\DataType;

/**
 * Data structure for map crdt
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class Map extends DataType
{
    /**
     * {@inheritdoc}
     */
    const TYPE = 'map';

    /**
     * Used within a composite key to identify a flag (bool) element
     *
     * @var string
     */
    const FLAG = 'flag';

    /**
     * Used within a composite key to identify a register (string) element
     *
     * @var string
     */
    const REGISTER = 'register';

    /**
     * @var string
     */
    private $context;

    /**
     * @param array $data
     * @param $context
     */
    public function __construct(array $data, $context)
    {
        $this->data = $data;
        $this->context = $context;
    }

    /**
     * @param $key
     *
     * @return string
     */
    public function getRegister($key)
    {
        return $this->getDataByKey($key, static::REGISTER);
    }

    protected function getDataByKey($key, $type)
    {
        $compKey = $this->getCompKey($key, $type);
        if (!isset($this->data[$compKey])) {
            throw new Exception("{$type} {$key} not found within Map.");
        }

        return $this->data[$compKey];
    }

    /**
     * Fetches the composite key used with
     *
     * @param $key
     * @param $type
     *
     * @return string
     */
    protected function getCompKey($key, $type)
    {
        return sprintf('%s_%s', $key, $type);
    }

    /**
     * @param $key
     *
     * @return bool
     */
    public function getFlag($key)
    {
        return $this->getDataByKey($key, static::FLAG) == 'enabled' ? TRUE : FALSE;
    }

    /**
     * @param $key
     *
     * @return Counter
     */
    public function getCounter($key)
    {
        return new Counter($this->getDataByKey($key, Counter::TYPE));
    }

    /**
     * @param $key
     *
     * @return Set
     */
    public function getSet($key)
    {
        return new Set($this->getDataByKey($key, Set::TYPE), $this->context);
    }

    /**
     * @param $key
     *
     * @return Map
     */
    public function getMap($key)
    {
        return new Map($this->getDataByKey($key, Map::TYPE), $this->context);
    }

    public function getContext()
    {
        return $this->context;
    }
}


<?php

namespace Basho\Riak\Command\Builder;

use Basho\Riak\Api\Http;
use Basho\Riak\Object as RObject;

/**
 * Allows easy code sharing for Object getters / setters within the Command Builders
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
trait ObjectTrait
{
    /**
     * @var \Basho\Riak\Object|null
     */
    protected $object = NULL;

    /**
     * @return Object|null
     */
    public function getObject()
    {
        return $this->object;
    }

    /**
     * Mint a new Object instance with supplied params and attach it to the Command
     *
     * @param string $data
     * @param array $headers
     *
     * @return $this
     */
    public function buildObject($data = NULL, $headers = NULL)
    {
        $this->object = new RObject($data, $headers);

        return $this;
    }

    /**
     * Attach an already instantiated Object to the Command
     *
     * @param \Basho\Riak\Object $object
     *
     * @return $this
     */
    public function withObject(RObject $object)
    {
        $this->object = $object;

        return $this;
    }

    /**
     * Mint a new Object instance with a json encoded string
     *
     * @param mixed $data
     *
     * @return $this
     */
    public function buildJsonObject($data)
    {
        $this->object = new RObject();
        $this->object->setData($data);
        $this->object->setContentType(Http::CONTENT_TYPE_JSON);

        return $this;
    }
}


<?php

namespace Basho;

use Basho\Riak\Api;
use Basho\Riak\Api\Http;
use Basho\Riak\Command;
use Basho\Riak\Exception;
use Basho\Riak\Node;

/**
 * This class maintains the list of nodes in the Riak cluster.
 *
 * <code>
 * $nodes = (new Node\Builder)
 *   ->atHost('localhost')
 *   ->onPort(8098)
 *   ->build()
 *
 * $riak = new Riak($nodes);
 *
 * $command = (new Command\Builder\FetchObject($riak))
 *   ->buildLocation('username', 'users')
 *   ->build();
 *
 * $response = $command->execute();
 *
 * $user = $response->getObject();
 * </code>
 *
 * @author Christopher Mancini <cmancini at basho d0t com>
 */
class Riak
{
    const VERSION = "2.0.3";

    /**
     * Riak server ring
     *
     * @var Node[]
     */
    protected $nodes = [];

    /**
     * Configuration options for this client
     *
     * @var array
     */
    protected $config = [
        'prefix'               => 'riak',
        'mapred_prefix'        => 'mapred',
        'index_prefix'         => 'buckets',
        'dns_server'           => '8.8.8.8',
        'max_connect_attempts' => 3,
    ];

    /**
     * The actively connected Riak Node from the ring
     *
     * @var int
     */
    protected $activeNodeIndex = 0;

    /**
     * API Bridge class to use
     *
     * @var Api|null
     */
    protected $api = NULL;

    /**
     * List of nodes marked inactive
     *
     * @var array
     */
    protected $inactiveNodes = [];

    /**
     * Connection attempts made that failed
     *
     * @var int
     */
    protected $attempts = 0;

    /**
     * Construct a new Client object, defaults to port 8098.
     *
     * @param Node[] $nodes an array of Basho\Riak\Node objects
     * @param array $config a configuration object
     * @param Api $api
     *
     * @throws Exception
     */
    public function __construct(array $nodes, array $config = [], Api $api = NULL)
    {
        // wash any custom keys if any
        $this->nodes = array_values($nodes);
        $this->setActiveNodeIndex($this->pickNode());

        if (!empty($config)) {
            // use php array merge so passed in config overrides defaults
            $this->config = array_merge($this->config, $config);
        }

        if ($api) {
            $this->api = $api;
        } else {
            // default to HTTP bridge class
            $this->api = new Http($this->config);
        }
    }

    /**
     * Pick a random Node from the ring
     *
     * You can pick your friends, you can pick your node, but you can't pick your friend's node.  :)
     *
     * @return int
     * @throws Exception
     */
    protected function pickNode()
    {
        $nodes       = $this->getNodes();
        $index = mt_rand(0, count($nodes) - 1);
        return array_keys($nodes)[$index];
    }

    /**
     * @return Node[]
     */
    public function getNodes()
    {
        return $this->nodes;
    }

    /**
     * Get value from connection config
     *
     * @param $key
     *
     * @return mixed
     */
    public function getConfigValue($key)
    {
        return $this->config[$key];
    }

    /**
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Execute a Riak command
     *
     * @param Command $command
     * @return Command\Response
     * @throws Exception
     */
    public function execute(Command $command)
    {
        $response = $this->getActiveNode()->execute($command, $this->api);

        // if more than 1 node configured, lets try a different node up to max connection attempts
        if (empty($response) && count($this->nodes) > 1 && $this->attempts < $this->getConfigValue('max_connect_attempts')) {
            $response = $this->pickNewNode()->execute($command);
        } elseif (empty($response) && $this->attempts >= $this->getConfigValue('max_connect_attempts')) {
            throw new Exception('Nodes unreachable. Error Msg: ' . $this->api->getError());
        } elseif ($response == false) {
            throw new Exception('Command failed to execute against Riak. Error Msg: ' . $this->api->getError());
        }

        return $response;
    }

    /**
     * @return Node
     */
    public function getActiveNode()
    {
        $nodes = $this->getNodes();

        return $nodes[$this->getActiveNodeIndex()];
    }

    /**
     * @return int
     */
    public function getActiveNodeIndex()
    {
        return $this->activeNodeIndex;
    }

    /**
     * @param int $activeNodeIndex
     */
    public function setActiveNodeIndex($activeNodeIndex)
    {
        $this->activeNodeIndex = $activeNodeIndex;
    }

    /**
     * @return Api|null
     */
    public function getApi()
    {
        return $this->api;
    }

    /**
     * Accessor for the last request issued to the API. For debugging purposes.
     *
     * @return string
     */
    public function getLastRequest()
    {
        return $this->api->getRequest();
    }

    /**
     * Pick new active node
     *
     * Used when the currently active node fails to complete a command / query
     *
     * @return $this
     * @throws Exception
     */
    public function pickNewNode()
    {
        // mark current active node as inactive and increment attempts
        $this->getActiveNode()->setInactive(true);
        $this->attempts++;
        $this->inactiveNodes[$this->getActiveNodeIndex()] = $this->getActiveNode();

        // move active node to inactive nodes structure to prevent selecting again
        unset($this->nodes[$this->getActiveNodeIndex()]);
        $this->setActiveNodeIndex($this->pickNode());

        return $this;
    }
}