<?php

namespace Dhii\EventManager\WordPress;

use Dhii\EventManager\CreateEventCapableTrait;
use Dhii\EventManager\EventCacheTrait;
use Dhii\EventManager\HashCallableCapableTrait;
use Dhii\EventManager\NormalizeEventCapableTrait;
use Dhii\EventManager\WordPress\Exception\CreateStoppedPropagationExceptionCapableTrait;
use Dhii\Exception\CreateInternalExceptionCapableTrait;
use Dhii\Exception\CreateInvalidArgumentExceptionCapableTrait;
use Dhii\Exception\CreateOutOfRangeExceptionCapableTrait;
use Dhii\Exception\InternalException;
use Dhii\I18n\StringTranslatingTrait;
use Dhii\Util\Normalization\NormalizeIntCapableTrait;
use Dhii\Util\Normalization\NormalizeStringCapableTrait;
use Exception;
use Psr\EventManager\EventInterface;
use Psr\EventManager\EventManagerInterface;
use ReflectionMethod;

/**
 * A PSR-14 compliant WordPress event manager implementation.
 *
 * This implementation treats all events as filters. This is because in WordPress, all hooks are indeed filters -
 * {@link https://goo.gl/9kPjcE actions are simply filters} that ignore the return value. As such, functions such as
 * {@link https://goo.gl/Ro65KV `doing_action()`} might not give a reliable result. It is recommended to use the
 * filter counterparts of such functions.
 *
 * Handlers attached via {@see attach()} will always receive a single argument - an {@see EventInterface} instance.
 *
 * For filters triggered by {@link https://goo.gl/vB77MC `apply_filters()`} and other similar WordPress functions,
 * the first event parameter is used as the filter result.
 *
 * For filters triggered by the event manager, the result is the event instance. Data can then be read from the event
 * via {@see EventInterface::getParam()} and {@see EventInterface::getParams()}.
 *
 * Propagation can be stopped for all handlers attached via {@see attach()}. However, handlers attached through the
 * WordPress functional API will not respect the request to stop propagation. This implementation _can_ wrap all
 * attached handlers and add this functionality - but it comes at a performance cost. The option can be enabled during
 * construction by passing an argument of `true` for the `$wrapAllHandlers` parameter.
 *
 * @since [*next-version*]
 */
class WpEventManager implements EventManagerInterface
{
    /*
     * Provides functionality for adding handlers to a WordPress hook.
     *
     * @since [*next-version*]
     */
    use AddWpHookCapableTrait;

    /*
     * Provides functionality for removing handlers from a WordPress hook.
     *
     * @since [*next-version*]
     */
    use RemoveWpHookCapableTrait;

    /*
     * Provides functionality for clearing all handlers from a WordPress hook.
     *
     * @since [*next-version*]
     */
    use ClearWpHookCapableTrait;

    /*
     * Provides functionality for triggering WordPress hooks.
     *
     * @since [*next-version*]
     */
    use RunWpHookCapableTrait;

    /*
     * Provides functionality for wrapping WordPress handlers for PSR-14 event manager functionality.
     *
     * @since [*next-version*]
     */
    use WpHandlerWrapperCacheTrait;

    /*
     * Provides functionality for creating wrapper handlers for WordPress event handlers.
     *
     * @since [*next-version*]
     */
    use CreateWpHandlerWrapperCapableTrait;

    /*
     * Provides callable hashing functionality.
     *
     * @since [*next-version*]
     */
    use HashCallableCapableTrait;

    /*
     * Provides event instance caching functionality.
     *
     * @since [*next-version*]
     */
    use EventCacheTrait;

    /*
     * Provides functionality for enabling the auto-clearing of previously cached events.
     *
     * @since [*next-version*]
     */
    use AttachMethodHandlerCapableTrait;

    /*
     * Provides functionality for replacing WordPress hook instances.
     *
     * @since [*next-version*]
     */
    use ReplaceWpHookCapableTrait;

    /*
     * Provides event normalization functionality.
     *
     * @since [*next-version*]
     */
    use NormalizeEventCapableTrait;

    /*
     * Provides awareness of an argument count.
     *
     * @since [*next-version*]
     */
    use NumArgsAwareTrait;

    /*
     * Provides integer normalization functionality.
     *
     * @since [*next-version*]
     */
    use NormalizeIntCapableTrait;

    /*
     * Provides string normalization functionality.
     *
     * @since [*next-version*]
     */
    use NormalizeStringCapableTrait;

    /*
     * Provides functionality for creating event instances.
     *
     * @since [*next-version*]
     */
    use CreateEventCapableTrait;

    /*
     * Provides functionality for creating invalid argument exception instances.
     *
     * @since [*next-version*]
     */
    use CreateInvalidArgumentExceptionCapableTrait;

    /*
     * Provides functionality for creating out-of-range exception instances.
     *
     * @since [*next-version*]
     */
    use CreateOutOfRangeExceptionCapableTrait;

    /*
     * Provides functionality for creating internal exception instances.
     *
     * @since [*next-version*]
     */
    use CreateInternalExceptionCapableTrait;

    /*
     * Provides functionality for creating stopped propagation exception instances.
     *
     * @since [*next-version*]
     */
    use CreateStoppedPropagationExceptionCapableTrait;

    /*
     * Provides string translating functionality.
     *
     * @since [*next-version*]
     */
    use StringTranslatingTrait;

    /**
     * The default priority for event handlers.
     *
     * @since [*next-version*]
     */
    const DEFAULT_PRIORITY = 10;

    /**
     * The default argument count to pass to the WordPress action and filter hooking functions.
     *
     * @since [*next-version*]
     */
    const DEFAULT_NUM_ARGS = PHP_INT_MAX;

    /**
     * The hook name to which to attach the CCE handler.
     *
     * @since [*next-version*]
     */
    const WP_ALL_HOOK = 'all';

    /**
     * If true, hook instances are replaced with wrapping instances that detect stopped propagation.
     *
     * @since [*next-version*]
     *
     * @var bool
     */
    protected $replaceWpHooks;

    /**
     * Constructor.
     *
     * @since [*next-version*]
     *
     * @param bool $enablePropagation If true, {@see \WP_HOOK} instances are replaced with wrapping instances that can
     *                                detect thrown {@see Exception\StoppedPropagationExceptionInterface} exceptions and
     *                                stop propagation. If false, stopping propagation will not affect handlers
     *                                registered through WordPress functions.
     *
     * @param int  $numArgs           The argument count value to pass to the native WordPress action and filter
     *                                hooking functions. Must be positive.
     *
     * @throws InternalException If a problem occurred while registering the WP_Hook replacer handler.
     */
    public function __construct($enablePropagation = false, $numArgs = self::DEFAULT_NUM_ARGS)
    {
        $this->_setNumArgs($numArgs);

        try {
            $this->_attachMethodHandler(static::WP_ALL_HOOK, '_removeCachedEvent', 0, $numArgs);
        } catch (Exception $e) {
            throw $this->_createInternalException(
                $this->__('Failed to register the clear-cache-event handler to the `all` hook'),
                null,
                $e
            );
        }

        try {
            if ($this->replaceWpHooks = $enablePropagation) {
                $this->_attachMethodHandler(static::WP_ALL_HOOK, '_replaceWpHook', 1, $numArgs);
            }
        } catch (Exception $e) {
            throw $this->_createInternalException(
                $this->__('Failed to register the `WP_Hook` replacer handler to the `all` hook'),
                null,
                $e
            );
        }
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function attach($event, $callback, $priority = null)
    {
        $event   = $this->_normalizeEvent($event)->getName();
        $handler = $this->_getWpHandlerWrapper($event, $callback);

        $this->_addWpHook($event, $handler, $priority, $this->_getNumArgs());
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function detach($event, $callback, $priority = null)
    {
        $event   = $this->_normalizeEvent($event)->getName();
        $handler = $this->_getWpHandlerWrapper($event, $callback);

        $this->_removeWpHook($event, $handler, $priority);
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function clearListeners($event)
    {
        $this->_clearWpHooks($this->_normalizeEvent($event)->getName());
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    public function trigger($event, $target = null, $argv = [])
    {
        $event  = $this->_normalizeEvent($event, $argv, $target);
        $result = $this->_runWpHook($event->getName(), [$event]);

        return $result;
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    protected function _getWpHookDefaultPriority()
    {
        return static::DEFAULT_PRIORITY;
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    protected function _hashWpHandler($name, $handler)
    {
        $handlerHash = $this->_hashCallable($handler);
        $pair        = sprintf('%1$s|%2$s', $name, $handlerHash);

        return sha1($pair);
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    protected function _getThrowOnPropStop()
    {
        return $this->replaceWpHooks;
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    protected function _createWpHookReplacement($wpHook)
    {
        return ($wpHook instanceof WpHookReplacer)
            ? $wpHook
            : new WpHookReplacer($wpHook);
    }

    /**
     * {@inheritdoc}
     *
     * @since [*next-version*]
     */
    protected function _createReflectionMethod($className, $methodName)
    {
        return new ReflectionMethod($className, $methodName);
    }
}


<?php
namespace Cassandra\Response;

class ProgressiveStreamReader extends StreamReader{

    /**
     */
    protected $source;

    protected $dataLength = 0;

    public function __construct($data = ''){
        $this->data = $data;
        $this->dataLength = strlen($data);
    }

    public function setSource($source){
        $this->source = $source;
    }

    /**
     * Read data from stream.
     *
     * NOTICE When $this->offset == strlen($this->data), substr() will return false.  You'd better avoid call read() when $length == 0.
     *
     * @param int $length  $length should be > 0.
     * @return string
     */
    protected function read($length) {
        while($this->dataLength < $this->offset + $length){
            if ($this->source === null)
                throw new Exception('The response is incomplete, or types expectation mismatch.');

            $this->data .= $received = $this->source->readOnce($this->offset + $length - $this->dataLength);
            $this->dataLength += strlen($received);
        }

        $output = substr($this->data, $this->offset, $length);
        $this->offset += $length;
        return $output;
    }
}


<?php
namespace Cassandra\Request;
use Cassandra\Protocol\Frame;
use Cassandra\Protocol;
use Cassandra\Connection;
use Cassandra\Type;

class Batch extends Request{
    const TYPE_LOGGED = 0;
    const TYPE_UNLOGGED = 1;
    const TYPE_COUNTER = 2;

    protected $opcode = Frame::OPCODE_BATCH;

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

    /**
     * @var int
     */
    protected $_batchType;

    /**
     *
     * @var int
     */
    protected $_consistency;

    /**
     *
     * @var array
     */
    protected $_options;

    /**
     *
     * @param string $type
     * @param string $consistency
     * @param array $options
     */
    public function __construct($type = null, $consistency = null, $options = []) {
        $this->_batchType = $type === null ? Batch::TYPE_LOGGED : $type;
        $this->_consistency = $consistency === null ? Request::CONSISTENCY_ONE : $consistency;
        $this->_options = $options;
    }

    /**
     * Exec transaction
     */
    public function getBody() {
        return pack('C', $this->_batchType)
            . pack('n', count($this->_queryArray)) . implode('', $this->_queryArray)
            . self::queryParameters($this->_consistency, $this->_options);
    }

    /**
     * @param string $cql
     * @param array $values
     * @return self
     */
    public function appendQuery($cql, array $values = []) {
        $binary = pack('C', 0);

        $binary .= pack('N', strlen($cql)) . $cql;
        $binary .= Request::valuesBinary($values, !empty($this->_options['names_for_values']));

        $this->_queryArray[] = $binary;

        return $this;
    }

    /**
     *
     * @param string $queryId
     * @param array $values
     * @return self
     */
    public function appendQueryId($queryId, array $values = []) {
        $binary = pack('C', 1);

        $binary .= pack('n', strlen($queryId)) . $queryId;
        $binary .= Request::valuesBinary($values, !empty($this->_options['names_for_values']));

        $this->_queryArray[] = $binary;

        return $this;
    }

    /**
     *
     * @param int $consistency
     * @param array $options
     * @return string
     */
    public static function queryParameters($consistency, array $options = []){
        $flags = 0;
        $optional = '';

        if (isset($options['serial_consistency'])) {
            $flags |= Query::FLAG_WITH_SERIAL_CONSISTENCY;
            $optional .= pack('n', $options['serial_consistency']);
        }

        if (isset($options['default_timestamp'])) {
            $flags |= Query::FLAG_WITH_DEFAULT_TIMESTAMP;
            $optional .= Type\Bigint::binary($options['default_timestamp']);
        }

        if (!empty($options['names_for_values'])){
            /**
             * @link https://github.com/duoshuo/php-cassandra/issues/40
             */
            throw new \Cassandra\Exception('NAMES_FOR_VALUES in batch request seems never work in Cassandra 2.1.x.  Keep NAMES_FOR_VALUES flag false to avoid this bug.');

            $flags |= Query::FLAG_WITH_NAMES_FOR_VALUES;
        }

        return pack('n', $consistency) . pack('C', $flags) . $optional;
    }
}


<?php
namespace Cassandra\Response;
use Cassandra\Protocol\Frame;
use Cassandra\Type;

class Result extends Response {
    const VOID = 0x0001;
    const ROWS = 0x0002;
    const SET_KEYSPACE = 0x0003;
    const PREPARED = 0x0004;
    const SCHEMA_CHANGE = 0x0005;

    const ROWS_FLAG_GLOBAL_TABLES_SPEC = 0x0001;
    const ROWS_FLAG_HAS_MORE_PAGES = 0x0002;
    const ROWS_FLAG_NO_METADATA = 0x0004;

    /**
     *
     * @var int
     */
    protected $_kind;

    /**
     *
     * @var array
     */
    protected $_metadata;

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

    /**
     * @return \SplFixedArray|string|array|null
     */
    public function getData() {
        $this->_stream->offset(4);
        switch($this->getKind()) {
            case self::VOID:
                return null;

            case self::ROWS:
                return $this->fetchAll();

            case self::SET_KEYSPACE:
                return $this->_stream->readString();

            case self::PREPARED:
                return [
                    'id' => $this->_stream->readString(),
                    'metadata' => $this->_readMetadata(),
                    'result_metadata' => $this->_readMetadata(),
                ];

            case self::SCHEMA_CHANGE:
                return [
                    'change' => $this->_stream->readString(),
                    'keyspace' => $this->_stream->readString(),
                    'table' => $this->_stream->readString()
                ];
        }

        return null;
    }

    public function getKind(){
        if ($this->_kind === null){
            $this->_stream->offset(0);
            $this->_kind = $this->_stream->readInt();
        }

        return $this->_kind;
    }

    /**
     *
     * @param array $metadata
     * @return self
     */
    public function setMetadata(array $metadata) {
        $this->_metadata = $metadata;

        return $this;
    }

    /**
     *
     * @return array
     */
    public function getMetadata(){
        if (empty($this->_metadata)){
            $this->_stream->offset(4);
            $this->_metadata = $this->_readMetadata();
        }

        return $this->_metadata;
    }

    /**
     *
     * @param string $rowClass
     * @return self
     */
    public function setRowClass($rowClass) {
        $this->_rowClass = $rowClass;

        return $this;
    }

    /**
     * Return metadata
     * @return array
     */
    protected function _readMetadata() {
        $metadata = [
            'flags' => $this->_stream->readInt(),
            'columns_count' => $this->_stream->readInt(),
        ];
        $flags = $metadata['flags'];

        if ($flags & self::ROWS_FLAG_HAS_MORE_PAGES)
            $metadata['page_state'] = $this->_stream->readBytes();

        if (!($flags & self::ROWS_FLAG_NO_METADATA)) {
            $metadata['columns'] = [];

            if ($flags & self::ROWS_FLAG_GLOBAL_TABLES_SPEC) {
                $keyspace = $this->_stream->readString();
                $tableName = $this->_stream->readString();

                for ($i = 0; $i < $metadata['columns_count']; ++$i) {
                    $metadata['columns'][] = [
                        'keyspace' => $keyspace,
                        'tableName' => $tableName,
                        'name' => $this->_stream->readString(),
                        'type' => $this->_stream->readType()
                    ];
                }
            }
            else {
                for ($i = 0; $i < $metadata['columns_count']; ++$i) {
                    $metadata['columns'][] = [
                        'keyspace' => $this->_stream->readString(),
                        'tableName' => $this->_stream->readString(),
                        'name' => $this->_stream->readString(),
                        'type' => $this->_stream->readType()
                    ];
                }
            }
        }

        return $metadata;
    }

    /**
     *
     * @param string $rowClass
     * @throws Exception
     * @return \SplFixedArray
     */
    public function fetchAll($rowClass = null){
        if ($this->getKind() !== self::ROWS){
            throw new Exception('Unexpected Response: ' . $this->getKind());
        }
        $this->_stream->offset(4);
        $this->_metadata = $this->_metadata ? array_merge($this->_metadata, $this->_readMetadata()) : $this->_readMetadata();

        if (!isset($this->_metadata['columns']))
            throw new Exception('Missing Result Metadata');

        $rowCount = $this->_stream->readInt();
        $rows = new \SplFixedArray($rowCount);

        if ($rowClass === null)
            $rowClass = $this->_rowClass;

        for ($i = 0; $i < $rowCount; ++$i) {
            $data = [];

            foreach ($this->_metadata['columns'] as $column)
                $data[$column['name']] = $this->_stream->readValue($column['type']);

            $rows[$i] = $rowClass === null ? $data : new $rowClass($data);
        }

        return $rows;
    }

    /**
     *
     * @param int $index
     * @throws Exception
     * @return \SplFixedArray
     */
    public function fetchCol($index = 0){
        if ($this->getKind() !== self::ROWS){
            throw new Exception('Unexpected Response: ' . $this->getKind());
        }
        $this->_stream->offset(4);
        $this->_metadata = $this->_metadata ? array_merge($this->_metadata, $this->_readMetadata()) : $this->_readMetadata();

        if (!isset($this->_metadata['columns']))
            throw new Exception('Missing Result Metadata');

        $rowCount = $this->_stream->readInt();

        $array = new \SplFixedArray($rowCount);

        for($i = 0; $i < $rowCount; ++$i){
            foreach($this->_metadata['columns'] as $j => $column){
                $value = $this->_stream->readValue($column['type']);

                if ($j == $index)
                    $array[$i] = $value;
            }
        }

        return $array;
    }

    /**
     *
     * @throws Exception
     * @return array
     */
    public function fetchPairs(){
        if ($this->getKind() !== self::ROWS){
            throw new Exception('Unexpected Response: ' . $this->getKind());
        }
        $this->_stream->offset(4);
        $this->_metadata = $this->_metadata ? array_merge($this->_metadata, $this->_readMetadata()) : $this->_readMetadata();

        if (!isset($this->_metadata['columns']))
            throw new Exception('Missing Result Metadata');

        $rowCount = $this->_stream->readInt();

        $map = [];

        for($i = 0; $i < $rowCount; ++$i){
            foreach($this->_metadata['columns'] as $j => $column){
                $value = $this->_stream->readValue($column['type']);

                if ($j === 0){
                    $key = $value;
                }
                elseif($j === 1){
                    $map[$key] = $value;
                }
            }
        }

        return $map;
    }

    /**
     *
     * @param string $rowClass
     * @throws Exception
     * @return \ArrayObject
     */
    public function fetchRow($rowClass = null){
        if ($this->getKind() !== self::ROWS){
            throw new Exception('Unexpected Response: ' . $this->getKind());
        }
        $this->_stream->offset(4);
        $this->_metadata = $this->_metadata ? array_merge($this->_metadata, $this->_readMetadata()) : $this->_readMetadata();

        if (!isset($this->_metadata['columns']))
            throw new Exception('Missing Result Metadata');

        $rowCount = $this->_stream->readInt();

        if ($rowCount === 0)
            return null;

        if ($rowClass === null)
            $rowClass = $this->_rowClass;

        $data = [];
        foreach ($this->_metadata['columns'] as $column)
            $data[$column['name']] = $this->_stream->readValue($column['type']);

        return $rowClass === null ? $data : new $rowClass($data);
    }

    /**
     *
     * @throws Exception
     * @return mixed
     */
    public function fetchOne(){
        if ($this->getKind() !== self::ROWS){
            throw new Exception('Unexpected Response: ' . $this->getKind());
        }
        $this->_stream->offset(4);
        $this->_metadata = $this->_metadata ? array_merge($this->_metadata, $this->_readMetadata()) : $this->_readMetadata();

        if (!isset($this->_metadata['columns']))
            throw new Exception('Missing Result Metadata');

        $rowCount = $this->_stream->readInt();

        if ($rowCount === 0)
            return null;

        foreach ($this->_metadata['columns'] as $column)
            return $this->_stream->readValue($column['type']);

        return null;
    }
}


<?php
namespace Cassandra\Response;

use Cassandra\Type;

class StreamReader {

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

    /**
     * @var int
     */
    protected $offset = 0;

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

    /**
     * Read data from stream.
     *
     * NOTICE When $this->offset == strlen($this->data), substr() will return false.  You'd better avoid call read() when $length == 0.
     *
     * @param int $length  $length should be > 0.
     * @return string
     */
    protected function read($length) {
        $output = substr($this->data, $this->offset, $length);
        $this->offset += $length;
        return $output;
    }

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

    public function reset(){
        $this->offset = 0;
    }

    /**
     * Read single character.
     *
     * @return int
     */
    public function readChar() {
        return unpack('C', $this->read(1))[1];
    }

    /**
     * Read unsigned short.
     *
     * @return int
     */
    public function readShort() {
        return unpack('n', $this->read(2))[1];
    }

    /**
     * Read unsigned int.
     *
     * @return int
     */
    public function readInt() {
        return unpack('N', $this->read(4))[1];
    }

    /**
     * Read string.
     *
     * @return string
     */
    public function readString() {
        $length = unpack('n', $this->read(2))[1];
        return $length === 0 ? '' : $this->read($length);
    }

    /**
     * Read long string.
     *
     * @return string
     */
    public function readLongString() {
        $length = unpack('N', $this->read(4))[1];
        return $length === 0 ? '' : $this->read($length);
    }

    /**
     * Read bytes.
     *
     * @return string
     */
    public function readBytes() {
        $binaryLength = $this->read(4);
        if ($binaryLength === "\xff\xff\xff\xff")
            return null;

        $length = unpack('N', $binaryLength)[1];
        return $length === 0 ? '' : $this->read($length);
    }

    /**
     * Read uuid.
     *
     * @return string
     */
    public function readUuid() {
        $data = unpack('n8', $this->read(16));

        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', $data[1], $data[2], $data[3], $data[4], $data[5], $data[6], $data[7], $data[8]);
    }

    /**
     * Read list.
     *
     * @param array $definition [$valueType]
     * @return array
     */
    public function readList(array $definition) {
        list($valueType) = $definition;
        $list = [];
        $count = $this->readInt();
        for ($i = 0; $i < $count; ++$i) {
            $list[] = $this->readValue($valueType);
        }
        return $list;
    }

    /**
     * Read map.
     *
     * @param array $definition [$keyType, $valueType]
     * @return array
     */
    public function readMap(array $definition) {
        list($keyType, $valueType) = $definition;
        $map = [];
        $count = $this->readInt();
        for ($i = 0; $i < $count; ++$i) {
            $map[$this->readValue($keyType)] = $this->readValue($valueType);
        }
        return $map;
    }

    /**
     *
     * @param array $definition ['key1'=>$valueType1, 'key2'=>$valueType2, ...]
     * @return array
     */
    public function readTuple(array $definition) {
        $tuple = [];
        $dataLength = strlen($this->data);
        foreach ($definition as $key => $type) {
            if ($this->offset < $dataLength)
                $tuple[$key] = $this->readValue($type);
            else
                $tuple[$key] = null;
        }
        return $tuple;
    }

    /**
     * Read float.
     *
     * @return float
     */
    public function readFloat() {
        return unpack('f', strrev($this->read(4)))[1];
    }

    /**
     * Read double.
     *
     * @return double
     */
    public function readDouble() {
        return unpack('d', strrev($this->read(8)))[1];
    }

    /**
     * Read boolean.
     *
     * @return bool
     */
    public function readBoolean() {
        return (bool)$this->readChar();
    }

    /**
     * Read inet.
     *
     * @return string
     */
    public function readInet() {
        return inet_ntop($this->data);
    }

    /**
     * Read variable length integer.
     *
     * @return string
     */
    public function readVarint() {
        list($higher, $lower) = array_values(unpack('N2', $this->data));
        return $higher << 32 | $lower;
    }

    /**
     * Read variable length decimal.
     *
     * @return string
     */
    public function readDecimal() {
        $scale = $this->readInt();
        $value = $this->readVarint();
        $len = strlen($value);
        return substr($value, 0, $len - $scale) . '.' . substr($value, $len - $scale);
    }

    public function readStringMultimap(){
        $map = [];
        $count = $this->readShort();
        for($i = 0; $i < $count; $i++){
            $key = $this->readString();

            $listLength = $this->readShort();
            $list = [];
            for($j = 0; $j < $listLength; $j++)
                $list[] = $this->readString();

            $map[$key] = $list;
        }
        return $map;
    }

    /**
     * alias of readValue()
     * @deprecated
     *
     * @param int|array $type
     * @return mixed
     */
    public function readBytesAndConvertToType($type){
        return $this->readValue($type);
    }

    /**
     * read a [bytes] and read by type
     *
     * @param int|array $type
     * @return mixed
     */
    public function readValue($type){
        $binaryLength = substr($this->data, $this->offset, 4);
        $this->offset += 4;

        if ($binaryLength === "\xff\xff\xff\xff")
            return null;

        $length = unpack('N', $binaryLength)[1];

        // do not use $this->read() for performance
        // substr() returns FALSE when OFFSET is equal to the length of data
        $data = ($length == 0) ? '' : substr($this->data, $this->offset, $length);
        $this->offset += $length;
        if(!is_array($type)){
            $class = Type\Base::$typeClassMap[$type];
            return $class::parse($data);
        }
        else{
            if (!isset(Type\Base::$typeClassMap[$type['type']]))
                throw new Type\Exception('Unknown type ' . var_export($type, true));
            $class = Type\Base::$typeClassMap[$type['type']];
            return $class::parse($data, $type['definition']);
        }
    }

    /**
     * @return int|array
     */
    public function readType(){
        $type = $this->readShort();
        switch ($type) {
            case Type\Base::CUSTOM:
                return [
                    'type'    => $type,
                    'definition'=> [$this->readString()],
                ];
            case Type\Base::COLLECTION_LIST:
            case Type\Base::COLLECTION_SET:
                return [
                    'type'    => $type,
                    'definition'    => [$this->readType()],
                ];
            case Type\Base::COLLECTION_MAP:
                return [
                    'type'    => $type,
                    'definition'=> [$this->readType(), $this->readType()],
                ];
            case Type\Base::UDT:
                $data = [
                    'type'        => $type,
                    'keyspace'    => $this->readString(),
                    'name'        => $this->readString(),
                    'definition'    => [],
                ];
                $length = $this->readShort();
                for($i = 0; $i < $length; ++$i){
                    $key = $this->readString();
                    $data['definition'][$key] = $this->readType();
                }
                return $data;
            case Type\Base::TUPLE:
                $data = [
                    'type'    => $type,
                    'definition'    =>    [],
                ];
                $length = $this->readShort();
                for($i = 0; $i < $length; ++$i){
                    $data['definition'][] = $this->readType();
                }
                return $data;
            default:
                return $type;
        }
    }
}


<?php
namespace Cassandra\Response;
use Cassandra\Protocol\Frame;

class Error extends Response {
	const SERVER_ERROR = 0x0000;
	const PROTOCOL_ERROR = 0x000A;
	const BAD_CREDENTIALS = 0x0100;
	const UNAVAILABLE_EXCEPTION = 0x1000;
	const OVERLOADED = 0x1001;
	const IS_BOOTSTRAPPING = 0x1002;
	const TRUNCATE_ERROR = 0x1003;
	const WRITE_TIMEOUT = 0x1100;
	const READ_TIMEOUT = 0x1200;
	const SYNTAX_ERROR = 0x2000;
	const UNAUTHORIZED = 0x2100;
	const INVALID = 0x2200;
	const CONFIG_ERROR = 0x2300;
	const ALREADY_EXIST = 0x2400;
	const UNPREPARED = 0x2500;

	/**
	 * Indicates an error processing a request. The body of the message will be an
	 * error code ([int]) followed by a [string] error message. Then, depending on
	 * the exception, more content may follow. The error codes are defined in
	 * Section 7, along with their additional content if any.
	 *
	 * @return array
	 */
	public function getData() {
		$this->_stream->offset(0);
		$data = [];
		$data['code'] = $this->_stream->readInt();

		switch($data['code']){
			case self::SERVER_ERROR:
				$data['message'] = "Server error: " . $this->_stream->readString();
				break;

			case self::PROTOCOL_ERROR:
				$data['message'] = "Protocol error: " . $this->_stream->readString();
				break;

			case self::BAD_CREDENTIALS:
				$data['message'] = "Bad credentials: " . $this->_stream->readString();
				break;

			case self::UNAVAILABLE_EXCEPTION:
				$data['message'] = "Unavailable exception. Error data: " . var_export([
                        'error'=>$this->_stream->readString(),
                        'consistency' => $this->_stream->readShort(),
                        'node' => $this->_stream->readInt(),
						'replica' => $this->_stream->readInt()
					], true);
				break;

			case self::OVERLOADED:
				$data['message'] = "Overloaded: " . $this->_stream->readString();
				break;

			case self::IS_BOOTSTRAPPING:
				$data['message'] = "Is_bootstrapping: " . $this->_stream->readString();
				break;

			case self::TRUNCATE_ERROR:
				$data['message'] = "Truncate_error: " . $this->_stream->readString();
				break;

			case self::WRITE_TIMEOUT:
				$data['message'] = "Write_timeout. Error data: " . var_export([
                        'error'=>$this->_stream->readString(),
                        'consistency' => $this->_stream->readShort(),
						'node' => $this->_stream->readInt(),
						'replica' => $this->_stream->readInt(),
						'writeType' => $this->_stream->readString()
					], true);
				break;

			case self::READ_TIMEOUT:
				$data['message'] = "Read_timeout. Error data: " . var_export([
                        'error'=>$this->_stream->readString(),
                        'consistency' => $this->_stream->readShort(),
						'node' => $this->_stream->readInt(),
						'replica' => $this->_stream->readInt(),
						'dataPresent' => $this->_stream->readChar()
					], true);
				break;

			case self::SYNTAX_ERROR:
				$data['message'] = "Syntax_error: " . $this->_stream->readString();
				break;

			case self::UNAUTHORIZED:
				$data['message'] = "Unauthorized: " . $this->_stream->readString();
				break;

			case self::INVALID:
				$data['message'] = "Invalid: " . $this->_stream->readString();
				break;

			case self::CONFIG_ERROR:
				$data['message'] = "Config_error: " . $this->_stream->readString();
				break;

			case self::ALREADY_EXIST:
				$data['message'] = "Already_exists: " . $this->_stream->readString();
				break;

			case self::UNPREPARED:
				$data['message'] = "Unprepared: " . $this->_stream->readShort();
				break;

			default:
				$data['message'] = 'Unknown error';
		}

		return $data;
	}

	/**
	 *
	 * @return Exception
	 */
	public function getException(){
		$data = $this->getData();
		return new Exception($data['message'], $data['code']);
	}
}


<?php
namespace Cassandra;
use Cassandra\Protocol\Frame;

class Connection {

    /**
     * Connection options
     * @var array
     */
    protected $_options = [
        'CQL_VERSION' => '3.0.0'
    ];

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

    /**
     * @var array|\Traversable
     */
    protected $_nodes;

    /**
     * @var Connection\Socket|Connection\Stream
     */
    protected $_node;

    /**
     * @var int
     */
    protected $_lastStreamId = 0;

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

    /**
     *
     * @var \SplQueue
     */
    protected $_recycledStreams;

    /**
     * @var int
     */
    protected $_consistency = Request\Request::CONSISTENCY_ONE;

    /**
     * @param array|\Traversable $nodes
     * @param string $keyspace
     * @param array $options
     */
    public function __construct($nodes, $keyspace = '', array $options = []) {
        if (is_array($nodes))
            shuffle($nodes);

        $this->_nodes = $nodes;
        $this->_options = array_merge($this->_options, $options);
        $this->_keyspace = $keyspace;
        $this->_recycledStreams = new \SplQueue();
    }

    /**
     * @throws Exception
     */
    protected function _connect() {
        foreach($this->_nodes as $options){
            if (is_string($options)){
                if (!preg_match('/^(((tcp|udp|unix|ssl|tls):\/\/)?[\w\.\-]+)(\:(\d+))?/i', $options, $matches))
                    throw new Exception('Invalid host: ' . $options);

                $options = [ 'host' => $matches[1],];

                if (!empty($matches[5]))
                    $options['port'] = $matches[5];

                // Use Connection\Stream when protocol prefix is defined.
                try {
                    $this->_node = empty($matches[2]) ? new Connection\Socket($options) : new Connection\Stream($options);
                } catch (Exception $e) {
                    continue;
                }
            }
            else{
                $className = isset($options['class']) ? $options['class'] : 'Cassandra\Connection\Socket';
                try {
                    $this->_node = new $className($options);
                } catch (Exception $e) {
                    continue;
                }
            }
            return;
        }

        throw new Exception("Unable to connect to all Cassandra nodes.");
    }

    /**
     * @return bool
     */
    public function disconnect() {
        if ($this->_node === null)
            return true;

        return $this->_node->close();
    }

    /**
     * @return bool
     */
    public function isConnected() {
        return $this->_node !== null;
    }

    /**
     *
     * @param Response\Event $response
     */
    public function trigger($response){
    }

    /**
     *
     * @param int $streamId
     * @throws Response\Exception
     * @return Response\Response
     */
    public function getResponse($streamId = 0){
        do{
            $response = $this->_getResponse();
        }
        while($response->getStream() !== $streamId);

        return $response;
    }

    /**
     *
     * @throws Response\Exception
     * @return Response\Response
     */
    protected function _getResponse() {
        $version = unpack('C', $this->_node->read(1))[1];
        switch($version) {
            case 0x83:
                $header = unpack('Cflags/nstream/Copcode/Nlength', $this->_node->read(8));
                $body = $header['length'] === 0 ? '' : $this->_node->read($header['length']);

                static $responseClassMap = [
                    Frame::OPCODE_ERROR            => 'Cassandra\Response\Error',
                    Frame::OPCODE_READY            => 'Cassandra\Response\Ready',
                    Frame::OPCODE_AUTHENTICATE    => 'Cassandra\Response\Authenticate',
                    Frame::OPCODE_SUPPORTED        => 'Cassandra\Response\Supported',
                    Frame::OPCODE_RESULT        => 'Cassandra\Response\Result',
                    Frame::OPCODE_EVENT            => 'Cassandra\Response\Event',
                    Frame::OPCODE_AUTH_SUCCESS    => 'Cassandra\Response\AuthSuccess',
                ];

                if (!isset($responseClassMap[$header['opcode']]))
                    throw new Response\Exception('Unknown response');

                $responseClass = $responseClassMap[$header['opcode']];
                $response = new $responseClass($header, new Response\StreamReader($body));

                if ($header['stream'] !== 0){
                    if (isset($this->_statements[$header['stream']])){
                        $this->_statements[$header['stream']]->setResponse($response);
                        unset($this->_statements[$header['stream']]);
                        $this->_recycledStreams->enqueue($header['stream']);
                    }
                    elseif ($response instanceof Response\Event){
                        $this->trigger($response);
                    }
                }

                return $response;
            default:
                throw new Exception('php-cassandra supports CQL binary protocol v3 only, please upgrade your Cassandra to 2.1 or later.');
        }
    }

    /**
     * Wait until all statements received response.
     */
    public function flush(){
        while(!empty($this->_statements)){
            $this->_getResponse();
        }
    }

    /**
     * @return Connection\Node
     */
    public function getNode() {
        return $this->_node;
    }

    /**
     * Connect to database
     * @throws Exception
     * @return bool
     */
    public function connect() {
        if ($this->_node !== null)
            return true;

        $this->_connect();

        $response = $this->syncRequest(new Request\Startup($this->_options));

        if ($response instanceof Response\Authenticate){
            $nodeOptions = $this->_node->getOptions();

            if (empty($nodeOptions['username']) || empty($nodeOptions['password']))
                throw new Exception('Username and password are required.');

            $this->syncRequest(new Request\AuthResponse($nodeOptions['username'], $nodeOptions['password']));
        }

        if (!empty($this->_keyspace))
            $this->syncRequest(new Request\Query("USE {$this->_keyspace};"));

        return true;
    }

    /**
     * @param Request\Request $request
     * @throws Exception
     * @return Response\Response
     */
    public function syncRequest(Request\Request $request) {
        if ($this->_node === null)
            $this->connect();

        $this->_node->write($request->__toString());

        $response = $this->getResponse();

        if ($response instanceof Response\Error)
            throw $response->getException();

        return $response;
    }

    /**
     *
     * @param Request\Request $request
     * @return Statement
     */
    public function asyncRequest(Request\Request $request) {
        if ($this->_node === null)
            $this->connect();

        $streamId = $this->_getNewStreamId();
        $request->setStream($streamId);

        $this->_node->write($request->__toString());

        return $this->_statements[$streamId] = new Statement($this, $streamId);
    }

    /**
     *
     * @throws Exception
     * @return int
     */
    protected function _getNewStreamId(){
        if ($this->_lastStreamId < 32767)
            return ++$this->_lastStreamId;

        while ($this->_recycledStreams->isEmpty()){
            $this->_getResponse();
        }

        return $this->_recycledStreams->dequeue();
    }

    /***** Shorthand Methods ******/
    /**
     *
     * @param string $cql
     * @throws Exception
     * @return array
     */
    public function prepare($cql) {
        $response = $this->syncRequest(new Request\Prepare($cql));

        return $response->getData();
    }

    /**
     *
     * @param string $queryId
     * @param array $values
     * @param int $consistency
     * @param array $options
     * @throws Exception
     * @return Response\Response
     */
    public function executeSync($queryId, array $values = [], $consistency = null, array $options = []){
        $request = new Request\Execute($queryId, $values, $consistency === null ? $this->_consistency : $consistency, $options);

        return $this->syncRequest($request);
    }

    /**
     *
     * @param string $queryId
     * @param array $values
     * @param int $consistency
     * @param array $options
     * @throws Exception
     * @return Statement
     */
    public function executeAsync($queryId, array $values = [], $consistency = null, array $options = []){
        $request = new Request\Execute($queryId, $values, $consistency === null ? $this->_consistency : $consistency, $options);

        return $this->asyncRequest($request);
    }

    /**
     *
     * @param string $cql
     * @param array $values
     * @param int $consistency
     * @param array $options
     * @throws Exception
     * @return Response\Response
     */
    public function querySync($cql, array $values = [], $consistency = null, array $options = []){
        $request = new Request\Query($cql, $values, $consistency === null ? $this->_consistency : $consistency, $options);

        return $this->syncRequest($request);
    }

    /**
     *
     * @param string $cql
     * @param array $values
     * @param int $consistency
     * @param array $options
     * @throws Exception
     * @return Statement
     */
    public function queryAsync($cql, array $values = [], $consistency = null, array $options = []){
        $request = new Request\Query($cql, $values, $consistency === null ? $this->_consistency : $consistency, $options);

        return $this->asyncRequest($request);
    }

    /**
     * @param string $keyspace
     * @throws Exception
     * @return Response\Result
     */
    public function setKeyspace($keyspace) {
        $this->_keyspace = $keyspace;

        if ($this->_node === null)
            return;

        return $this->syncRequest(new Request\Query("USE {$this->_keyspace};"));
    }

    /**
     * @param int  $consistency
     */
    public function setConsistency($consistency){
        $this->_consistency = $consistency;
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms\Laravel;

use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;

/**
 * Bootstrap an instance of BulkSmsService so that it can be accessed through
 * a facade and register the config.
 */
class BulkSmsServiceProvider extends ServiceProvider
{
    /**
     * Whether the service provider should be deferred or not.
     *
     * @var boolean
     */
    protected $defer = false;

    /**
     * Whether the Laravel version is 5.x or not.
     *
     * @var boolean
     */
    protected $l5;

    /**
     * Register the service on the IoC container.
     *
     * @return void
     */
    public function register()
    {
        $l5 = $this->l5 = version_compare(Application::VERSION, '5.0', '>=');

        $factory = function ($app) use($l5) {
            $delim = $l5 ? '.' : '::';
            $config = $app['config'];
            $username  = $config->get("bulk-sms{$delim}username");
            $password  = $config->get("bulk-sms{$delim}password");
            $baseurl   = $config->get("bulk-sms{$delim}baseurl");

            $curl = isset($app['curl']) ? $app['curl'] : null;

            return new BulkSmsService($username, $password, $baseurl, $curl);
        };

        if (version_compare(Application::VERSION, '5.4', '>=')) {
            $this->app->singleton('bulksms', $factory);
        } else {
            $this->app['bulksms'] = $this->app->share($factory);
        }

        if ($l5) {
            $dir = dirname(dirname(__DIR__)).'/resources';
            $this->mergeConfigFrom($dir.'/config.php', 'bulk-sms');
        }
    }

    /**
     * Load the package config files.
     *
     * @return void
     */
    public function boot()
    {
        $dir = dirname(dirname(__DIR__)).'/resources';

        if ($this->l5) {
            $this->publishes([
                $dir.'/config.php' => config_path('bulk-sms.php')
            ], 'config');
        } else {
            $this->app['config']->package('bulk-sms', $dir, 'bulk-sms');
        }
    }

    /**
     * The services provided.
     *
     * @return array
     */
    public function provides()
    {
        return ['bulksms'];
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms\Laravel;

use Illuminate\Support\Facades\View;

/**
 * The main API class.
 */
class BulkSmsService extends \anlutro\BulkSms\BulkSmsService
{
    /**
     * Send a view with data to a recipient. Made to imitate Laravel's
     * Mail::send syntax.
     *
     * @param  string $view
     * @param  array  $data
     * @param  string $recipient Phone number
     *
     * @return mixed
     */
    public function send($view, $data, $recipient)
    {
        $message = View::make($view, $data)->render();

        return $this->sendMessage($recipient, $message);
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms;

use anlutro\cURL\cURL;
use Respect\Validation\Validator as v;

/**
 * The main API class.
 */
class BulkSmsService
{
    const TEST_ALWAYS_SUCCEED = 1;
    const TEST_ALWAYS_FAIL = 2;

    /**
     * Meaning of response status codes.
     *
     * @var array
     */
    protected static $statusMessages = array(
        0   => 'In progress',
        10 => 'Delivered upstream',
        11 => 'Delivered mobile',
        12 => 'Delivered upstream unacknowledged (presume in progress)',
        1   => 'Scheduled',
        22  => 'Internal fatal error',
        23  => 'Authentication error',
        24  => 'Data validation failed',
        25  => 'Insufficient credits',
        26  => 'Upstream credits not available',
        27  => 'Daily quota exceeded',
        28  => 'Upstream quota exceeded',
        40  => 'Temporarily unavailable',
        201 => 'Maximum batch size exceeded',
    );

    /**
     * Whether test mode is enabled.
     *
     * @var boolean
     */
    protected $testMode = false;

    /**
     * BulkSMS username
     *
     * @var string
     */
    protected $username;

    /**
     * BulkSMS password
     *
     * @var string
     */
    protected $password;

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

    /**
     * @param string $username BulkSMS username
     * @param string $password BulkSMS password
     * @param string $baseUrl  Optional - defaults to "http://bulksms.vsms.net:5567"
     * @param cURL   $curl     Optional - a new instance will be constructed if null is passed.
     */
    public function __construct($username, $password, $baseUrl = "http://bulksms.vsms.net:5567", $curl = null)
    {
        v::url()->setName("Base Bulksms URL")->check($baseUrl);
        $this->baseUrl = $baseUrl;
        $this->username = $username;
        $this->password = $password;
        $this->curl     = $curl ?: new cURL();
    }

    /**
     * Set test mode
     *
     * @param $mode
     */
    public function setTestMode($mode)
    {
        if (BulkSmsService::TEST_ALWAYS_SUCCEED == $mode) {
            $this->testMode = BulkSmsService::TEST_ALWAYS_SUCCEED;
        } elseif (BulkSmsService::TEST_ALWAYS_FAIL == $mode) {
            $this->testMode = BulkSmsService::TEST_ALWAYS_FAIL;
        } else {
            throw new \InvalidArgumentException("Invalid test mode: " . $mode);
        }
    }

    /**
     * Send a single message.
     *
     * @param  string $recipient
     * @param  string $message
     * @param  array  $params
     *
     * @return mixed
     */
    public function sendMessage($recipient, $message, array $params = null)
    {
        $sender = $this->createMessageSender();

        $msg = $this->createMessage($recipient, $message);

        $sender->setMessage($msg);
        if ($params) {
            $sender->setParams($params);
        }
        $response = $sender->send($this->testMode);
        $this->validateResponse($response);

        return $sender->extractResponse($response);
    }

    /**
     * Create a message sender instance.
     *
     * @return anlutro\BulkSms\Sender\Single
     */
    protected function createMessageSender()
    {
        return new Sender\Single($this->username, $this->password, $this->baseUrl, $this->curl);
    }

    /**
     * Create a message instance.
     *
     * @param  string $recipient
     * @param  string $message
     *
     * @return anlutro\BulkSms\Message
     */
    protected function createMessage($recipient, $message)
    {
        return new Message($recipient, $message);
    }

    /**
     * Validate a response from the API to check for errors
     *
     * @param  anlutro\cURL\Response $response
     *
     * @return bool
     * @throws BulkSmsException
     */
    public function validateResponse($response)
    {
        if ($response->code !== '200 OK') {
            throw new BulkSmsException('BulkSMS API responded with HTTP status code ' . $response->code);
        }

        $parts = explode('|', $response->body);

        if (!is_numeric($parts[ 0 ])) {
            throw new \UnexpectedValueException(
                'Unknown response code: ' . $parts[ 0 ] . ' - full response: ' . $response->body
            );
        }

        $code = (int) $parts[ 0 ];

        if ($code === 0 || $code === 1) {
            return true;
        }

        $message = array_key_exists($code, static::$statusMessages)
            ? static::$statusMessages[ $code ]
            : $parts[ 1 ];
        throw new BulkSmsException('BulkSMS API responded with code: ' . $code . ' - ' . $message);
    }

    /**
     * Send messages in bulk.
     *
     * @param  Message[] $messages
     * @param  array     $params
     *
     * @return mixed
     */
    public function sendBulkMessages(array $messages, array $params = null)
    {
        $sender = $this->createBulkSender();
        v::notEmpty()->setName("BulkSms Array")->check($messages);

        foreach ($messages as $message) {
            // make sure messages are proper objects
            v::instance('anlutro\BulkSms\Message')->check($message);
            $sender->addMessage($message);
        }

        if ($params) {
            $sender->setParams($params);
        }
        $response = $sender->send($this->testMode);
        $this->validateResponse($response);

        return $sender->extractResponse($response);
    }

    /**
     * Create a message sender instance.
     *
     * @return anlutro\BulkSms\Sender\Bulk
     */
    protected function createBulkSender()
    {
        return new Sender\Bulk($this->username, $this->password, $this->baseUrl, $this->curl);
    }

    /**
     * Check status for single id
     *
     * @param  string $bulksmsid
     *
     * @return mixed
     */
    public function getStatusForBatchId($bulksmsid)
    {
        $sender   = $this->createBulkStatusSender();
        $response = $sender->getStatusForBatchId($bulksmsid, $this->testMode);
        $this->validateResponse($response);

        return $sender->extractResponse($response);
    }

    /**
     * Create a message sender instance.
     *
     * @return anlutro\BulkSms\Sender\Status
     */
    protected function createBulkStatusSender()
    {
        return new Sender\Status($this->username, $this->password, $this->baseUrl, $this->curl);
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms\Sender;

use anlutro\BulkSms\BulkSmsException;
use anlutro\BulkSms\Laravel\BulkSmsService;
use anlutro\BulkSms\Message;
use anlutro\cURL\Response;

/**
 * Class for sending messages in bulk.
 */
class Bulk extends AbstractSender
{
    /**
     * The endpoint the call should go to.
     *
     * @var string
     */
    protected $endpoint = '/eapi/submission/send_batch/1/1.0';

    /**
     * Message container
     *
     * @var array
     */
    protected $messages;

    /**
     * Add a message to the batch.
     *
     * @param Message $message
     */
    public function addMessage(Message $message)
    {
        $this->messages[] = $message;
    }

    /**
     * Send the queued messages.
     *
     * @param bool $testmode Testmode to use
     *
     * @return mixed
     */
    public function send($testmode = false)
    {
        if (empty($this->messages)) {
            return false;
        }

        $data = array_replace($this->params, [
            'username'   => $this->username,
            'password'   => $this->password,
            'batch_data' => $this->generateCSV(),
        ]);

        // add test params if required
        if ($testmode) {
            if ($testmode == BulkSmsService::TEST_ALWAYS_SUCCEED) {
                $data['test_always_succeed'] = 1;
            } elseif ($testmode == BulkSmsService::TEST_ALWAYS_FAIL) {
                $data['test_always_fail'] = 1;
            }
        }

        return $this->curl->post($this->getUrl(), $data);
    }

    /**
     * Generate the CSV to send.
     *
     * @return string
     */
    protected function generateCSV()
    {
        $str = "msisdn,message";

        foreach ($this->messages as $message) {
            $str .= "\n";
            $recipient = $message->getRecipient();
            $message   = $message->getMessage();
            $str .= '"' . $recipient . '","' . $message . '"';
        }

        return $str;
    }

    /**
     * Extract response from Sender - depends on sender
     *
     * @param Response $response
     *
     * @return array('status_code', 'status_description', 'batch_id')
     * @throws BulkSmsException
     */
    public function extractResponse(Response $response)
    {
        $expected = array('status_code', 'status_description', 'batch_id');
        $parts    = explode('|', $response->body);
        $it       = new \ArrayIterator($parts);
        if (count($expected) != $it->count()) {
            throw new BulkSmsException(
                "Count of BulkSMS response does not match expectations!. Return: " . $response->body
            );
        }

        $toreturn = [];
        foreach ($expected as $item) {
            $toreturn[$item] = $it->current();
            $it->next();
        }

        return $toreturn;
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms\Sender;

use anlutro\BulkSms\BulkSmsException;
use anlutro\BulkSms\Laravel\BulkSmsService;
use anlutro\BulkSms\Message;
use anlutro\cURL\Response;

/**
 * Class for sending single messages.
 */
class Single extends AbstractSender
{
    /**
     * The URL the call should go to.
     *
     * @var string
     */
    protected $endpoint = '/eapi/submission/send_sms/2/2.0';

    /**
     * The message to send.
     *
     * @var anlutro\BulkSms\Message
     */
    protected $message;

    /**
     * Set the message.
     *
     * @param anlutro\BulkSms\Message $message
     */
    public function setMessage(Message $message)
    {
        $this->message = $message;
    }

    /**
     * Send the message.
     *
     * @param bool $testmode Testmode to use
     *
     * @return mixed
     */
    public function send($testmode = false)
    {
        $data = array_replace($this->params, [
            'username' => $this->username,
            'password' => $this->password,
            'message'  => $this->message->getMessage(),
            'msisdn'   => $this->message->getRecipient(),
        ]);

        $concat = $this->message->getConcatParts();

        if ($concat > 1) {
            $data['allow_concat_text_sms']     = 1;
            $data['concat_text_sms_max_parts'] = $concat;
        }

        // add test params if required
        if ($testmode) {
            if ($testmode == BulkSmsService::TEST_ALWAYS_SUCCEED) {
                $data['test_always_succeed'] = 1;
            } elseif ($testmode == BulkSmsService::TEST_ALWAYS_FAIL) {
                $data['test_always_fail'] = 1;
            }
        }

        return $this->curl->post($this->getUrl(), $data);
    }

    /**
     * Extract response from Sender - depends on sender
     *
     * @param Response $response
     *
     * @return array('status_code', 'status_description', 'batch_id')
     * @throws BulkSmsException
     */
    public function extractResponse(Response $response)
    {
        $expected = array('status_code', 'status_description', 'batch_id');
        $parts    = explode('|', $response->body);
        $it       = new \ArrayIterator($parts);
        if (count($expected) != $it->count()) {
            throw new BulkSmsException(
                "Count of BulkSMS response does not match expectations!. Return: " . $response->body
            );
        }

        $toreturn = [];
        foreach ($expected as $item) {
            $toreturn[$item] = $it->current();
            $it->next();
        }

        return $toreturn;
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms;

/**
 * Container class for a single SMS message.
 */
class Message
{
    /**
     * Phone number of the recipient.
     *
     * @var string
     */
    protected $recipient;

    /**
     * The text message to be sent.
     *
     * @var string
     */
    protected $message;

    /**
     * Whether or not the message needs to be concatenated.
     *
     * @var bool
     */
    protected $concat = false;

    /**
     * Where to start concatenating SMSes.
     *
     * @var integer
     */
    protected $concatLimit = 140;

    /**
     * @param $recipient
     * @param $text
     */
    public function __construct($recipient, $text)
    {
        $this->setRecipient($recipient);
        $this->setMessage($text);
    }

    /**
     * Get the recipient.
     *
     * @return string
     */
    public function getRecipient()
    {
        return $this->recipient;
    }

    /**
     * Set the recipient.
     *
     * @param  string|int $recipient
     *
     * @return $this
     */
    protected function setRecipient($recipient)
    {
        $this->recipient = $this->parseNumber($recipient);

        return $this;
    }

    /**
     * Get the message.
     *
     * @return string
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * Set the message.
     *
     * @param  string $message
     *
     * @return $this
     */
    protected function setMessage($message)
    {
        $this->message = $this->encodeMessage($message);

        if (strlen($this->message) > $this->concatLimit) {
            $this->concat = true;
        }

        return $this;
    }

    /**
     * Get how many SMSes the message may have to be concatenated into.
     *
     * @return int
     */
    public function getConcatParts()
    {
        if (!$this->concat) {
            return 1;
        } else {
            return $this->calculateConcat();
        }
    }

    /**
     * Calculate from the message string how many SMSes it may have to span.
     *
     * @return int
     */
    protected function calculateConcat()
    {
        $len = strlen($this->message);
        $i   = $this->concatLimit;
        $j   = 1;

        while ($i < $len) {
            $i += $this->concatLimit;
            $j++;
        }

        return $j;
    }

    /**
     * Parse a phone number.
     *
     * @param  string $number
     *
     * @return string
     */
    protected function parseNumber($number)
    {
        $number = (string) $number;

        // remove whitespaces
        $number = trim($number);
        $number = str_replace(' ', '', $number);

        // remove + in front if exists
        if (substr($number, 0, 1) == '+') {
            $number = substr($number, 1);
        }

        // remove 0s in front if exists
        while (substr($number, 0, 1) === '0') {
            $number = substr($number, 1);
        }

        // we should at this point have a normal number
        if (!is_numeric($number)) {
            throw new \InvalidArgumentException("Invalid SMS recipient: $number");
        }

        // is phone number is less than 9 characters, assume we need to append
        // a country code
        if (strlen($number) <= 8) {
            throw new \InvalidArgumentException(
                "Recipient number is too short. Is the country code missing?: " . $number
            );
        }

        return $number;
    }

    /**
     * Encode a message to the retarded GSM-charset.
     *
     * @param  string $message
     *
     * @return string
     */
    protected function encodeMessage($message)
    {
        $replaceCharacters = array(
            'Δ' => '0xD0',
            'Φ' => '0xDE',
            'Γ' => '0xAC',
            'Λ' => '0xC2',
            'Ω' => '0xDB',
            'Π' => '0xBA',
            'Ψ' => '0xDD',
            'Σ' => '0xCA',
            'Θ' => '0xD4',
            'Ξ' => '0xB1',
            '¡' => '0xA1',
            '£' => '0xA3',
            '¤' => '0xA4',
            '¥' => '0xA5',
            '§' => '0xA7',
            '¿' => '0xBF',
            'Ä' => '0xC4',
            'Å' => '0xC5',
            'Æ' => '0xC6',
            'Ç' => '0xC7',
            'É' => '0xC9',
            'Ñ' => '0xD1',
            'Ö' => '0xD6',
            'Ø' => '0xD8',
            'Ü' => '0xDC',
            'ß' => '0xDF',
            'à' => '0xE0',
            'ä' => '0xE4',
            'å' => '0xE5',
            'æ' => '0xE6',
            'è' => '0xE8',
            'é' => '0xE9',
            'ì' => '0xEC',
            'ñ' => '0xF1',
            'ò' => '0xF2',
            'ö' => '0xF6',
            'ø' => '0xF8',
            'ù' => '0xF9',
            'ü' => '0xFC',
        );

        $message = utf8_decode($message);
        $message = str_replace('"', '\"', $message);
        $message = strtr($message, $replaceCharacters);

        return $message;
    }
}


<?php
/**
 * BulkSMS PHP implementation
 *
 * @author    Andreas Lutro <anlutro@gmail.com>
 * @license   http://opensource.org/licenses/MIT
 * @package   anlutro/bulk-sms
 */

namespace anlutro\BulkSms\Sender;

use anlutro\BulkSms\BulkSmsException;
use anlutro\cURL\Response;

/**
 * Class for getting the message status
 */
class Status extends AbstractSender
{
    /**
     * The URL the call should go to.
     *
     * @var string
     */
    protected $endpoint = '/eapi/status_reports/get_report/2/2.0';

    /**
     * Get status for single batch id
     *
     * @param string $batchid
     *
     * @return \anlutro\cURL\Response|void
     */
    public function getStatusForBatchId($batchid)
    {
        return $this->send($batchid);
    }

    /**
     * Send the status query
     *
     * @param $batchid
     *
     * @return \anlutro\cURL\Response|void
     */
    public function send($batchid)
    {
        if (empty($batchid)) {
            throw new \InvalidArgumentException("Batch Id must not be empty");
        }

        $data = [
            'username' => $this->username,
            'password' => $this->password,
            'batch_id' => $batchid,
        ];

        return $this->curl->get($this->getUrl(), $data);
    }

    /**
     * Extract response from Sender - depends on sender
     *
     * @param Response $response
     *
     * @return array('msisdn', 'status_code')
     * @throws BulkSmsException
     */
    public function extractResponse(Response $response)
    {
        // dump the first 2 lines indicated by the string "0|Returns to follow\n\n"
        $cleaned     = substr($response->body, strlen("0|Returns to follow\n\n"), strlen($response->body));
        $statusitems = explode("\n", $cleaned);

        $siit     = new \ArrayIterator(array_filter($statusitems));
        $toreturn = [];
        foreach ($siit as $item) {
            $expected = array('msisdn', 'status_code');
            $parts    = explode('|', $item);
            $it       = new \ArrayIterator($parts);
            if (count($expected) != $it->count()) {
                throw new BulkSmsException(
                    "Count of BulkSMS response does not match expectations!. Return: " . $response->body
                );
            }

            $status = [];
            foreach ($expected as $statusitem) {
                $status[ $statusitem ] = $it->current();
                $it->next();

            }
            $toreturn[ ] = $status;
        }

        return $toreturn;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Sulu\Bundle\FormBundle\Entity\Form;
use Sulu\Bundle\FormBundle\Manager\FormManager;
use Sulu\Component\Rest\ListBuilder\Doctrine\DoctrineListBuilderFactory;
use Sulu\Component\Rest\ListBuilder\Doctrine\FieldDescriptor\DoctrineCaseFieldDescriptor;
use Sulu\Component\Rest\ListBuilder\Doctrine\FieldDescriptor\DoctrineDescriptor;
use Sulu\Component\Rest\ListBuilder\Doctrine\FieldDescriptor\DoctrineFieldDescriptor;
use Sulu\Component\Rest\ListBuilder\Doctrine\FieldDescriptor\DoctrineJoinDescriptor;
use Sulu\Component\Rest\ListBuilder\ListRepresentation;
use Sulu\Component\Rest\RestHelperInterface;
use Sulu\Component\Security\SecuredControllerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * Generated by https://github.com/alexander-schranz/sulu-backend-bundle.
 */
class FormController extends FOSRestController implements ClassResourceInterface, SecuredControllerInterface
{
    /**
     * {@inheritdoc}
     */
    public function getSecurityContext()
    {
        return 'sulu.form.forms';
    }

    /**
     * @return FormManager
     */
    public function getManager()
    {
        return $this->get('sulu_form.manager.form');
    }

    /**
     * @param string $locale
     * @param array $filters
     *
     * @return DoctrineFieldDescriptor[] $filters
     */
    public function getFieldDescriptors($locale, $filters)
    {
        $fieldDescriptors = [];

        $fieldDescriptors['id'] = new DoctrineFieldDescriptor(
            'id',
            'id',
            Form::class,
            'public.id',
            [],
            true,
            false
        );

        $fieldDescriptors['title'] = new DoctrineCaseFieldDescriptor(
            'title',
            new DoctrineDescriptor(
                'translation',
                'title',
                [
                    'translation' => new DoctrineJoinDescriptor(
                        'translation',
                        Form::class . '.translations',
                        sprintf('translation.locale = \'%s\'', $locale)
                    ),
                ]
            ),
            new DoctrineDescriptor(
                'defaultTranslation',
                'title',
                [
                    'defaultTranslation' => new DoctrineJoinDescriptor(
                        'defaultTranslation',
                        Form::class . '.translations',
                        sprintf('defaultTranslation.locale = %s.defaultLocale', Form::class)
                    ),
                ]
            ),
            'public.title',
            false,
            true
        );

        $fieldDescriptors['changed'] = new DoctrineCaseFieldDescriptor(
            'changed',
            new DoctrineDescriptor(
                'translation',
                'changed',
                [
                    'translation' => new DoctrineJoinDescriptor(
                        'translation',
                        Form::class . '.translations',
                        sprintf('translation.locale = \'%s\'', $locale)
                    ),
                ]
            ),
            new DoctrineDescriptor(
                'defaultTranslation',
                'changed',
                [
                    'defaultTranslation' => new DoctrineJoinDescriptor(
                        'defaultTranslation',
                        Form::class . '.translations',
                        sprintf('defaultTranslation.locale = %s.defaultLocale', Form::class)
                    ),
                ]
            ),
            'public.changed',
            false,
            false
        );

        $fieldDescriptors['created'] = new DoctrineCaseFieldDescriptor(
            'created',
            new DoctrineDescriptor(
                'translation',
                'created',
                [
                    'translation' => new DoctrineJoinDescriptor(
                        'translation',
                        Form::class . '.translations',
                        sprintf('translation.locale = \'%s\'', $locale)
                    ),
                ]
            ),
            new DoctrineDescriptor(
                'defaultTranslation',
                'created',
                [
                    'defaultTranslation' => new DoctrineJoinDescriptor(
                        'defaultTranslation',
                        Form::class . '.translations',
                        sprintf('defaultTranslation.locale = %s.defaultLocale', Form::class)
                    ),
                ]
            ),
            'public.created',
            false,
            false
        );

        $fieldDescriptors['locale'] = new DoctrineCaseFieldDescriptor(
            'locale',
            new DoctrineDescriptor(
                'translation',
                'locale',
                [
                    'translation' => new DoctrineJoinDescriptor(
                        'translation',
                        Form::class . '.translations',
                        sprintf('translation.locale = \'%s\'', $locale)
                    ),
                ]
            ),
            new DoctrineDescriptor(
                'defaultTranslation',
                'locale',
                [
                    'defaultTranslation' => new DoctrineJoinDescriptor(
                        'defaultTranslation',
                        Form::class . '.translations',
                        sprintf('defaultTranslation.locale = %s.defaultLocale', Form::class)
                    ),
                ]
            ),
            'security.permission.role.language',
            true,
            false
        );

        return $fieldDescriptors;
    }

    /**
     * @return string
     */
    public function getModelClass()
    {
        return Form::class;
    }

    /**
     * @return string
     */
    public function getListName()
    {
        return 'forms';
    }

    /**
     * @return string
     */
    public function getBundleName()
    {
        return 'SuluFormBundle';
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function cgetFieldsAction(Request $request)
    {
        $fieldDescriptors = $this->getFieldDescriptors(
            $this->getLocale($request),
            $this->getFilters($request)
        );

        return $this->handleView($this->view($fieldDescriptors));
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function cgetAction(Request $request)
    {
        $locale = $this->getLocale($request);
        $filters = $this->getFilters($request);

        // flatted entities
        if ('true' === $request->get('flat')) {
            /** @var RestHelperInterface $restHelper */
            $restHelper = $this->get('sulu_core.doctrine_rest_helper');

            /** @var DoctrineListBuilderFactory $factory */
            $factory = $this->get('sulu_core.doctrine_list_builder_factory');

            // get model class
            $listBuilder = $factory->create($this->getModelClass());

            // get fieldDescriptors
            $fieldDescriptors = $this->getFieldDescriptors($locale, $filters);
            $restHelper->initializeListBuilder($listBuilder, $fieldDescriptors);

            $listBuilder->addSelectField($fieldDescriptors['locale']);

            if ('true' !== $request->get('ghost')) {
                $listBuilder->where($fieldDescriptors['locale'], $locale);
            }

            // load entities
            $list = $listBuilder->execute();

            // get pagination
            $total = $listBuilder->count();
            $page = $listBuilder->getCurrentPage();
            $limit = $listBuilder->getLimit();
        } else {
            // load all entities by filters
            $list = $this->getManager()->findAll($locale, $filters);

            foreach ($list as $key => $entity) {
                $list[$key] = $this->getApiEntity($entity, $locale);
            }

            // get pagination
            $offset = $this->getOffset($filters);
            $limit = $this->getLimit($filters);
            $total = $offset + count($list);
            $page = $this->getPage($filters);

            // if to avoid db request with less items then the limit
            if (count($list) >= $limit) {
                $total = $this->getManager()->count($locale, $this->getCountFilters($filters));
            }
        }

        // create list representation
        $representation = new ListRepresentation(
            $list,
            $this->getListName(),
            $request->get('_route'),
            $request->query->all(),
            $page,
            $limit,
            $total
        );

        return $this->handleView($this->view($representation));
    }

    /**
     * @param Request $request
     * @param $id
     *
     * @return Response
     */
    public function getAction(Request $request, $id)
    {
        $locale = $this->getLocale($request);

        $entity = $this->getManager()->findById($id, $locale);

        if (!$entity) {
            throw $this->createNotFoundException(sprintf('No form with id "%s" was found!', $id));
        }

        return $this->handleView($this->view($this->getApiEntity($entity, $locale)));
    }

    /**
     * @param Request $request
     *
     * @return Response
     */
    public function postAction(Request $request)
    {
        $locale = $this->getLocale($request);

        // create entity
        $entity = $this->getManager()->save($this->getData($request), $locale);

        return $this->handleView($this->view($this->getApiEntity($entity, $locale), 201));
    }

    /**
     * @param Request $request
     * @param $id
     *
     * @return Response
     */
    public function putAction(Request $request, $id)
    {
        $locale = $this->getLocale($request);

        // save entity
        $entity = $this->getManager()->save($this->getData($request), $locale, $id);

        if (!$entity) {
            throw $this->createNotFoundException(sprintf('No form with id "%s" was found!', $id));
        }

        return $this->handleView($this->view($this->getApiEntity($entity, $locale)));
    }

    /**
     * @param Request $request
     * @param $id
     *
     * @return Response
     */
    public function deleteAction(Request $request, $id)
    {
        $locale = $this->getLocale($request);

        $entity = $this->getManager()->delete($id, $locale);

        if (!$entity) {
            throw $this->createNotFoundException(sprintf('No form with id "%s" was found!', $id));
        }

        return new Response('', 204);
    }

    /**
     * {@inheritdoc}
     */
    public function getLocale(Request $request)
    {
        return $request->get('locale', $request->getLocale());
    }

    /**
     * @param Request $request
     *
     * @return array
     */
    protected function getFilters(Request $request)
    {
        $filters = $request->query->all();

        $listRestHelper = $this->get('sulu_core.list_rest_helper');

        unset($filters['page']);
        unset($filters['limit']);
        unset($filters['fields']);
        unset($filters['search']);
        unset($filters['searchFields']);
        unset($filters['locale']);
        unset($filters['flat']);

        $filters['fields'] = $listRestHelper->getFields();
        $filters['limit'] = (int) $listRestHelper->getLimit();
        $filters['offset'] = (int) $listRestHelper->getOffset();
        $filters['sortColumn'] = $listRestHelper->getSortColumn();
        $filters['sortOrder'] = $listRestHelper->getSortOrder();
        $filters['searchFields'] = $listRestHelper->getSearchFields();
        $filters['searchPattern'] = $listRestHelper->getSearchPattern();

        return $filters;
    }

    /**
     * @param Request $request
     *
     * @return array
     */
    protected function getData(Request $request)
    {
        return $request->request->all();
    }

    /**
     * @param array $filters
     *
     * @return array
     */
    protected function getCountFilters($filters)
    {
        unset($filters['page']);
        unset($filters['offset']);
        unset($filters['limit']);

        return $filters;
    }

    /**
     * @param array $filters
     *
     * @return int
     */
    protected function getLimit($filters)
    {
        if (!isset($filters['limit'])) {
            return 10;
        }

        return $filters['limit'];
    }

    /**
     * @param array $filters
     *
     * @return int
     */
    protected function getOffset($filters)
    {
        if (!isset($filters['offset'])) {
            return 0;
        }

        return $filters['offset'];
    }

    /**
     * @param array $filters
     *
     * @return int
     */
    protected function getPage($filters)
    {
        if (!isset($filters['page'])) {
            if (isset($filters['limit']) && isset($filters['offset'])) {
                return floor($filters['offset'] / $filters['limit']) + 1;
            }

            return 1;
        }

        return $filters['page'];
    }

    /**
     * TODO use seralizer.
     *
     * @param Form $entity
     * @param string $locale
     *
     * @return array
     */
    private function getApiEntity(Form $entity, $locale)
    {
        // Translation
        $translation = $entity->getTranslation($locale);

        $translations = [];

        if ($translation) {
            $receivers = [];

            foreach ($translation->getReceivers() as $receiver) {
                $receivers[] = [
                    'id' => $receiver->getId(),
                    'name' => $receiver->getName(),
                    'email' => $receiver->getEmail(),
                    'type' => $receiver->getType(),
                ];
            }

            $translations = [
                'title' => $translation->getTitle(),
                'fromEmail' => $translation->getFromEmail(),
                'fromName' => $translation->getFromName(),
                'toEmail' => $translation->getToEmail(),
                'toName' => $translation->getToName(),
                'subject' => $translation->getSubject(),
                'mailText' => $translation->getMailText(),
                'submitLabel' => $translation->getSubmitLabel(),
                'successText' => $translation->getSuccessText(),
                'sendAttachments' => $translation->getSendAttachments(),
                'deactivateNotifyMails' => $translation->getDeactivateNotifyMails(),
                'deactivateCustomerMails' => $translation->getDeactivateCustomerMails(),
                'receivers' => $receivers,
                'replyTo' => $translation->getReplyTo(),
            ];
        }

        // Fields
        $fields = [];

        foreach ($entity->getFields() as $field) {
            $fieldTranslation = $field->getTranslation($locale);

            $fieldData = [
                'id' => $field->getId(),
                'type' => $field->getType(),
                'key' => $field->getKey(),
                'required' => $field->getRequired(),
                'order' => $field->getOrder(),
                'width' => $field->getWidth(),
            ];

            if ($fieldTranslation) {
                $fieldData['title'] = $fieldTranslation->getTitle();
                $fieldData['placeholder'] = $fieldTranslation->getPlaceholder();
                $fieldData['defaultValue'] = $fieldTranslation->getDefaultValue();
                $fieldData['shortTitle'] = $fieldTranslation->getShortTitle();

                foreach ($fieldTranslation->getOptions() as $key => $option) {
                    $fieldData['options[' . $key . ']'] = $option;
                }
            }

            $fields[] = $fieldData;
        }

        // Api Entity
        return array_merge(
            [
                'id' => $entity->getId(),
                'locale' => $locale,
                'fields' => $fields,
            ],
            $translations
        );
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Controller;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Mail\HelperInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Translation\Translator;

/**
 * Generated by https://github.com/alexander-schranz/sulu-backend-bundle.
 */
class TemplateController extends Controller
{
    /**
     * @param Request $request
     *
     * @return Response
     */
    public function formAction(Request $request)
    {
        $widths = [];
        foreach ($this->getParameter('sulu_form.dynamic_widths') as $id => $name) {
            $widths[] = [
                'id' => $id,
                'name' => $name,
            ];
        }

        $types = $this->get('sulu_form.dynamic.form_field_type_pool')->all();
        $receiverTypes = [
            'to' => HelperInterface::MAIL_RECEIVER_TO,
            'cc' => HelperInterface::MAIL_RECEIVER_CC,
            'bcc' => HelperInterface::MAIL_RECEIVER_BCC,
        ];

        return $this->render(
            'SuluFormBundle:forms:template.html.twig',
            [
                'types' => $this->getSortedTypes($types),
                'widths' => $widths,
                'receiverTypes' => $receiverTypes,
                'locale' => $request->get('locale', $request->getLocale()),
                'autoTitle' => $this->getParameter('sulu_form.dynamic_auto_title'),
                'fallbackEmails' => [
                    'from' => $this->getParameter('sulu_form.mail.from'),
                    'to' => $this->getParameter('sulu_form.mail.to'),
                ],
            ]
        );
    }

    /**
     * @param FormFieldTypeInterface[] $types
     *
     * @return FormFieldTypeInterface[]
     */
    private function getSortedTypes($types = [])
    {
        /** @var Translator $translator */
        $translator = $this->get('translator');
        $locale = $this->getUser()->getLocale();

        $sortedTypes = [];
        $returnTypes = [];

        $i = 0;
        foreach ($types as $alias => $type) {
            $translation = $translator->trans($type->getConfiguration()->getTitle(), [], 'backend', $locale);
            $group = $type->getConfiguration()->getGroup();
            $sortedTypes[$group . '_' . $translation . '_' . $i] = [
                'alias' => $alias,
                'type' => $type,
            ];
            ++$i;
        }

        ksort($sortedTypes);
        foreach ($sortedTypes as $sortedType) {
            $returnTypes[$sortedType['alias']] = $sortedType['type'];
        }

        return $returnTypes;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\Extension\Core\Type\EmailType as TypeEmailType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Email as EmailConstraint;

/**
 * The Email form field type.
 */
class EmailType implements FormFieldTypeInterface
{
    use SimpleTypeTrait;

    /**
     * {@inheritdoc}
     */
    public function getConfiguration()
    {
        return new FormFieldTypeConfiguration(
            'sulu_form.type.email',
            'SuluFormBundle:forms:fields/types/email.html.twig'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function build(FormBuilderInterface $builder, FormField $field, $locale, $options)
    {
        $options['constraints'][] = new EmailConstraint();
        $type = TypeEmailType::class;
        $builder->add($field->getKey(), $type, $options);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\DateType as TypeDateType;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * The Date form field type.
 */
class DateType implements FormFieldTypeInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfiguration()
    {
        return new FormFieldTypeConfiguration(
            'sulu_form.type.date',
            'SuluFormBundle:forms:fields/types/date.html.twig',
            [],
            'basic'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function build(FormBuilderInterface $builder, FormField $field, $locale, $options)
    {
        $type = TypeDateType::class;
        $translation = $field->getTranslation($locale);
        if ($translation && $translation->getOption('birthday')) {
            $type = BirthdayType::class;
        }
        $options['format'] = \IntlDateFormatter::LONG;
        $options['input'] = 'string';
        $builder->add($field->getKey(), $type, $options);
    }

    /**
     * {@inheritdoc}
     */
    public function getDefaultValue(FormField $field, $locale)
    {
        $value = $field->getTranslation($locale)->getDefaultValue();

        return $value;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * The Free text form field type.
 */
class FreeTextType implements FormFieldTypeInterface
{
    use SimpleTypeTrait;

    /**
     * {@inheritdoc}
     */
    public function getConfiguration()
    {
        return new FormFieldTypeConfiguration(
            'sulu_form.type.freetext',
            'SuluFormBundle:forms:fields/types/freetext.html.twig',
            [],
            'additional'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function build(FormBuilderInterface $builder, FormField $field, $locale, $options = [])
    {
        $options['mapped'] = false;
        $options['attr']['type'] = $field->getType();

        $type = HiddenType::class;
        $builder->add($field->getKey(), $type, $options);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;

/**
 * Repository for the dynamic entity.
 */
class DynamicRepository extends EntityRepository
{
    /**
     * Finds dynamic entries by the given parameters.
     *
     * @param array $filters
     * @param array $sort
     * @param int|null $limit
     * @param int|null $offset
     *
     * @return array
     */
    public function findByFilters($filters, $sort = [], $limit = null, $offset = null)
    {
        $queryBuilder = $this->createQueryBuilder('dynamic');

        // Add filter.
        $this->addFilter($queryBuilder, $filters);

        // Add sorting.
        if (!empty($sort)) {
            foreach ($sort as $key => $value) {
                $queryBuilder->addOrderBy('dynamic.' . $key, $value);
            }
        }

        // Add limit and offset.
        if ($limit) {
            $queryBuilder->setMaxResults($limit);
        }

        if ($offset) {
            $queryBuilder->setFirstResult($offset);
        }

        return $queryBuilder->getQuery()->getResult();
    }

    /**
     * Returns the number of found entities.
     *
     * @param array $filters
     *
     * @return int
     */
    public function countByFilters($filters)
    {
        $queryBuilder = $this->createQueryBuilder('dynamic');
        $queryBuilder->select($queryBuilder->expr()->count('dynamic.id'));
        $this->addFilter($queryBuilder, $filters);

        return (int) $queryBuilder->getQuery()->getSingleScalarResult();
    }

    /**
     * Adds the filters from the given array to the QueryBuilder.
     *
     * @param QueryBuilder $queryBuilder
     * @param array $filters
     */
    protected function addFilter($queryBuilder, $filters)
    {
        $fromDate = null;
        $toDate = null;
        $search = null;
        $searchFields = null;

        if (isset($filters['fromDate'])) {
            $fromDate = $filters['fromDate'];
            unset($filters['fromDate']);
        }

        if (isset($filters['toDate'])) {
            $toDate = $filters['toDate'];
            unset($filters['toDate']);
        }

        if (isset($filters['search'])) {
            $search = $filters['search'];
            unset($filters['search']);
        }

        if (isset($filters['searchFields'])) {
            $searchFields = $filters['searchFields'];
            unset($filters['searchFields']);
        }

        $this->addSearchFilter($queryBuilder, $search, $searchFields);
        $this->addDateRangeFilter($queryBuilder, $fromDate, $toDate);

        $counter = 0;
        foreach ($filters as $key => $value) {
            $queryBuilder->andWhere($queryBuilder->expr()->eq('dynamic.' . $key, ':value' . $counter));
            $queryBuilder->setParameter('value' . $counter, $value);

            ++$counter;
        }
    }

    /**
     * Depending on the given search term, this function filters the result based on search fields.
     *
     * @param QueryBuilder $queryBuilder
     * @param string $search
     * @param string[] $searchFields
     */
    protected function addSearchFilter($queryBuilder, $search, $searchFields)
    {
        if (empty($search) || empty($searchFields)) {
            return;
        }

        $terms = explode(' ', $search);

        // Search each term in each search field
        foreach ($terms as $counter => $term) {
            $expressions = [];

            foreach ($searchFields as $searchField) {
                $expressions[] = $queryBuilder->expr()->like(
                    'dynamic.' . $searchField,
                    ':searchTerm' . $counter
                );
            }

            $queryBuilder->andWhere(call_user_func_array([$queryBuilder->expr(), 'orX'], $expressions));
            $queryBuilder->setParameter('searchTerm' . $counter, '%' . $term . '%');
        }
    }

    /**
     * Depending on the given dates, this function filters the result based on the created date.
     *
     * @param QueryBuilder $queryBuilder
     * @param string $fromDate
     * @param string $toDate
     */
    protected function addDateRangeFilter($queryBuilder, $fromDate, $toDate)
    {
        if ($fromDate && $toDate) {
            $queryBuilder->andWhere($queryBuilder->expr()->between('dynamic.created', ':fromDate', ':toDate'));
            $queryBuilder->setParameter('fromDate', $fromDate);
            $queryBuilder->setParameter('toDate', $toDate);
        } elseif ($fromDate) {
            $queryBuilder->andWhere($queryBuilder->expr()->gte('dynamic.created', ':fromDate'));
            $queryBuilder->setParameter('fromDate', $fromDate);
        } elseif ($toDate) {
            $queryBuilder->andWhere($queryBuilder->expr()->lte('dynamic.created', ':toDate'));
            $queryBuilder->setParameter('toDate', $toDate);
        }
    }
}