<?php

namespace getjump\Vk;

use Closure;
use getjump\Vk\Response\Api;

/**
 * Class Core.
 */
class Core
{
    /**
     * Arguments array.
     *
     * @var array
     */
    private $params = [];

    /**
     * Define language that API will return responses in.
     *
     * @var bool|string
     */
    private $lang = false;
    /**
     * Define version of API interfaces.
     *
     * @var bool|string
     */
    private $version = false;
    /**
     * Define access token.
     *
     * @var bool|string
     */
    private $accessToken = false;

    /**
     * Define secret for nohttps requests.
     *
     * @var bool|string
     */
    private $noHttpsSecret = false;

    /**
     * Callable that used against every element in returning array.
     *
     * @var bool|callable
     */
    public $callback = false;

    /**
     * Callable that used against every element in returning array if VkJs was used.
     *
     * @var bool|callable
     */
    public $jsCallback = false;

    /**
     * Set one param.
     *
     * @param mixed $key
     * @param mixed $value
     * @param bool  $defaultValue
     *
     * @return $this
     */
    public function param($key, $value, $defaultValue = false)
    {
        if (!$value && $defaultValue) {
            $value = $defaultValue;
        }

        $this->params[$key] = $value;

        return $this;
    }

    /**
     * Set many params.
     *
     * @param array $data
     *
     * @return $this
     */
    public function params(array $data)
    {
        foreach ($data as $k => $v) {
            $this->param($k, $v);
        }

        return $this;
    }

    /**
     * Will set callback for element creation.
     *
     * @param Closure $callback
     *
     * @return $this
     */
    public function createAs(Closure $callback)
    {
        $this->callback = $callback;

        return $this;
    }

    /**
     * API Request, will return RequestTransaction.
     *
     * @param string     $methodName
     * @param bool|array $args
     *
     * @return Api|RequestTransaction
     */
    public function request($methodName, $args = false)
    {
        if (is_array($args)) {
            $this->params($args);
        }

        $this->params = array_merge($this->params, $this->systemArgs());

        $d = new RequestTransaction($methodName, $this->params, $this->accessToken, $this->callback, $this->noHttpsSecret);
        $this->reset();

        return $d;
    }

    /**
     * Clear current params.
     */
    public function reset()
    {
        $this->params = [];
    }

    /**
     * Set necessary arguments.
     *
     * @return array
     */
    private function systemArgs()
    {
        $array = [];

        if ($this->lang) {
            $array['lang'] = $array;
        }
        if ($this->version) {
            $array['v'] = $this->version;
        }

        return $array;
    }

    /**
     * Set's token.
     *
     * @param string $accessToken
     *
     * @return $this
     */
    public function setToken($accessToken)
    {
        $this->accessToken = $accessToken;

        return $this;
    }

    /**
     * Sets nohttps secret.
     *
     * @param string $noHttpsSecret
     *
     * @return $this
     */
    public function setNoHttpsSecret($noHttpsSecret)
    {
        $this->noHttpsSecret = $noHttpsSecret;

        return $this;
    }

    public function setLang($lang)
    {
        $this->lang = $lang;

        return $this;
    }

    /**
     * Set's api version.
     *
     * @param string $version
     *
     * @return $this
     */
    public function apiVersion($version)
    {
        $this->version = $version;

        return $this;
    }

    /**
     * @var Core
     */
    public static $instance;

    /**
     * We want same instance.
     *
     * @return Core
     */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }
}


<?php

namespace getjump\Vk\Wrapper;

use getjump\Vk\Model\Photos\UploadResponse;
use getjump\Vk\Model\Photos\UploadUrl;
use GuzzleHttp\Post\PostFile;

class Photos extends BaseWrapper
{
    /**
     * @param $album
     * @param bool $group
     *
     * @return UploadUrl
     */
    public function getUploadServer($album, $group = false)
    {
        return $this->vk
            ->param('album_id', $album)
            ->param('group_id', $group)
            ->request('photos.getUploadServer')
            ->one();
    }

    /**
     * @return UploadUrl
     */
    public function getMessagesUploadServer()
    {
        return $this->vk
            ->request('photos.getMessagesUploadServer')
            ->one();
    }

    /**
     * todo Process response.
     *
     * @param UploadResponse $data
     */
    public function save($data)
    {
        $this->vk
            ->param('album_id', $data->aid)
            ->param('server', $data->server)
            ->param('photos_list', $data->photos_list)
            ->param('hash', $data->hash)
            ->request('photos.save')->execute();
    }

    public function saveMessagesPhoto($data)
    {
        return $this->vk
            ->param('photo', $data->photo)
            ->param('hash', $data->hash)
            ->param('server', $data->server)
            ->request('photos.saveMessagesPhoto')->one();
    }

    public function uploadAlbum(array $files = [], $album = false, $group = false)
    {
        if (count($files) > 5 || count($files) == 0) {
            // todo Exception
        }

        $server = $this->getUploadServer($album, $group);

        $request = $this->guzzle->createRequest('POST', $server->upload_url);
        $postBody = $request->getBody();
        foreach ($files as $k => $file) {
            $postBody->addFile(new PostFile('file'.($k + 1), fopen($file, 'r')));
        }
        $response = $this->guzzle->send($request);
        $this->save($response->json(['object' => 1]));
    }

    public function uploadMessages($file)
    {
        $server = $this->getMessagesUploadServer();
        var_dump($server);
        $request = $this->guzzle->createRequest('POST', $server->upload_url);
        $postBody = $request->getBody();
        $postBody->addFile(new PostFile('photo', fopen($file, 'r')));
        $response = $this->guzzle->send($request);

        return $this->saveMessagesPhoto($response->json(['object' => 1]));
    }
}


<?php

namespace getjump\Vk;

/**
 * Class Auth.
 */
class Auth
{
    const URL_ACCESS_TOKEN = 'https://oauth.vk.com/access_token?client_id=%s&client_secret=%s&code=%s&redirect_uri=%s';

    /**
     * @var \GuzzleHttp\Client
     */
    public $guzzle = false;

    private $options = [];

    /**
     * @param bool $options
     */
    public function __construct($options = false)
    {
        if (is_array($options)) {
            $this->options = $options;
        }
        $this->options['response_type'] = 'code';
    }

    /**
     * @return string
     */
    public function getAppId()
    {
        return $this->options['client_id'];
    }

    /**
     * @param string $id
     *
     * @return $this
     */
    public function setAppId($id)
    {
        $this->options['client_id'] = $id;

        return $this;
    }

    /**
     * @return string
     */
    public function getSecret()
    {
        return $this->options['client_secret'];
    }

    /**
     * @param string $secret
     *
     * @return $this
     */
    public function setSecret($secret)
    {
        $this->options['client_secret'] = $secret;

        return $this;
    }

    /**
     * @return string
     */
    public function getRedirectUri()
    {
        return $this->options['redirect_uri'];
    }

    /**
     * @param string $uri
     *
     * @return $this
     */
    public function setRedirectUri($uri)
    {
        $this->options['redirect_uri'] = $uri;

        return $this;
    }

    /**
     * @param $scope
     *
     * @return $this
     */
    public function setScope($scope)
    {
        $this->options['scope'] = $scope;

        return $this;
    }

    /**
     * @param $v
     *
     * @return $this
     */
    public function setVersion($v)
    {
        $this->options['v'] = $v;

        return $this;
    }

    /**
     * @return string
     */
    public function getUrl()
    {
        return sprintf('https://oauth.vk.com/authorize?%s', http_build_query($this->options));
    }

    /**
     * Just an alias, for an array.
     *
     * @param $d
     *
     * @return mixed
     */
    public function g($d)
    {
        return $this->options[$d];
    }

    /**
     * Will return token if everything is OK.
     *
     * @return \getjump\Vk\Response\Auth|bool|string
     */
    public function startCallback()
    {
        if (isset($_GET['code'])) {
            $token = $this->getToken($_GET['code']);

            return $token;
        } elseif (isset($_GET['error'])) {
            //blah blah
        }

        return false;
    }

    /**
     * Method converts code to token.
     *
     * @param $code
     *
     * @return \getjump\Vk\Response\Auth|bool
     */
    public function getToken($code)
    {
        if (!$this->guzzle) {
            $this->guzzle = new \GuzzleHttp\Client();
        }

        $uri = sprintf(
            self::URL_ACCESS_TOKEN,
            $this->g('client_id'),
            $this->g('client_secret'),
            $code,
            urlencode($this->g('redirect_uri'))
        );

        $data = $this->guzzle->get($uri)->getBody();
        $data = json_decode($data);

        if (isset($data->access_token)) {
            return new \getjump\Vk\Response\Auth($data->access_token, $data->expires_in, $data->user_id);
        } elseif (isset($data->error)) {
            // ERROR PROCESSING
        }

        return false;
    }

    /**
     * @return Auth
     */
    public static function getInstance()
    {
        return new self();
    }
}


<?php namespace Elasticquent;

class ElasticquentResultCollection extends \Illuminate\Database\Eloquent\Collection
{
    protected $took;
    protected $timed_out;
    protected $shards;
    protected $hits;
    protected $aggregations = null;

    /**
     * _construct
     *
     * @param   $results elasticsearch results
     * @param $instance
     * @return \Elasticquent\ElasticquentResultCollection
     */
    public function __construct($results, $instance)
    {
        // Take our result data and map it
        // to some class properties.
        $this->took         = $results['took'];
        $this->timed_out    = $results['timed_out'];
        $this->shards       = $results['_shards'];
        $this->hits         = $results['hits'];
        $this->aggregations = isset($results['aggregations']) ? $results['aggregations'] : array();

        // Now we need to assign our hits to the
        // items in the collection.
        $this->items = $this->hitsToItems($instance);
    }

    /**
     * Hits To Items
     *
     * @param   Eloquent model instance $instance
     * @return  array
     */
    private function hitsToItems($instance)
    {
        $items = array();

        foreach ($this->hits['hits'] as $hit) {

            $items[] = $instance->newFromHitBuilder($hit);
        }

        return $items;
    }

    /**
     * Total Hits
     *
     * @return int
     */
    public function totalHits()
    {
        return $this->hits['total'];
    }

    /**
     * Max Score
     *
     * @return float
     */
    public function maxScore()
    {
        return $this->hits['max_score'];
    }

    /**
     * Get Shards
     *
     * @return array
     */
    public function getShards()
    {
        return $this->shards;
    }

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

    /**
     * Timed Out
     *
     * @return bool
     */
    public function timedOut()
    {
        return (bool)$this->timed_out;
    }

    /**
     * Get Hits
     *
     * Get the raw hits array from
     * Elasticsearch results.
     *
     * @return array
     */
    public function getHits()
    {
        return $this->hits;
    }

    /**
     * Get aggregations
     *
     * Get the raw hits array from
     * Elasticsearch results.
     *
     * @return array
     */
    public function getAggregations()
    {
        return $this->aggregations;
    }

}


<?php namespace Elasticquent;

use \Elasticquent\ElasticquentCollection as ElasticquentCollection;
use \Elasticquent\ElasticquentResultCollection as ResultCollection;

/**
 * Elasticquent Trait
 *
 * Functionality extensions for Elequent that
 * makes working with Elasticsearch easier.
 */
trait ElasticquentTrait
{
    /**
     * Uses Timestamps In Index
     *
     * @var bool
     */
    protected $usesTimestampsInIndex = true;

    /**
     * Is ES Document
     *
     * Set to true when our model is
     * populated by a
     *
     * @var bool
     */
    protected $isDocument = false;

    /**
     * Document Score
     *
     * Hit score when using data
     * from Elasticsearch results.
     *
     * @var null|int
     */
    protected $documentScore = null;

    /**
     * Document Version
     *
     * Elasticsearch document version.
     *
     * @var null|int
     */
    protected $documentVersion = null;

    /**
     * Get ElasticSearch Client
     *
     * @return \Elasticsearch\Client
     */
    public function getElasticSearchClient()
    {
        $config = array();

        if (\Config::has('elasticquent.config')) {
            $config = \Config::get('elasticquent.config');
        }

        return new \Elasticsearch\Client($config);
    }

    /**
     * New Collection
     *
     * @param array $models
     * @return Collection
     */
    public function newCollection(array $models = array())
    {
        return new ElasticquentCollection($models);
    }

    /**
     * Get Index Name
     *
     * @return string
     */
    public function getIndexName()
    {
        // The first thing we check is if there
        // is an elasticquery config file and if there is a
        // default index.
        if (\Config::has('elasticquent.default_index')) {
            return \Config::get('elasticquent.default_index');
        }

        // Otherwise we will just go with 'default'
        return 'default';
    }

    /**
     * Get Type Name
     *
     * @return string
     */
    public function getTypeName()
    {
        return $this->getTable();
    }

    /**
     * Uses Timestamps In Index
     *
     * @return void
     */
    public function usesTimestampsInIndex()
    {
        return $this->usesTimestampsInIndex;
    }

    /**
     * Use Timestamps In Index
     *
     * @return void
     */
    public function useTimestampsInIndex()
    {
        $this->usesTimestampsInIndex = true;
    }

    /**
     * Don't Use Timestamps In Index
     *
     * @return void
     */
    public function dontUseTimestampsInIndex()
    {
        $this->usesTimestampsInIndex = false;
    }

    /**
     * Get Mapping Properties
     *
     * @return array
     */
    public function getMappingProperties()
    {
        return $this->mappingProperties;
    }

    /**
     * Set Mapping Properties
     *
     * @param $mapping
     * @internal param array $mappingProperties
     */
    public function setMappingProperties($mapping)
    {
        $this->mappingProperties = $mapping;
    }

    /**
     * Is Elasticsearch Document
     *
     * Is the data in this module sourced
     * from an Elasticsearch document source?
     *
     * @return bool
     */
    public function isDocument()
    {
        return $this->isDocument;
    }

    /**
     * Get Document Score
     *
     * @return null|float
     */
    public function documentScore()
    {
        return $this->documentScore;
    }

    /**
     * Document Version
     *
     * @return null|int
     */
    public function documentVersion()
    {
        return $this->documentVersion;
    }

    /**
     * Get Index Document Data
     *
     * Get the data that Elasticsearch will
     * index for this particular document.
     *
     * @return  array
     */
    public function getIndexDocumentData()
    {
        return $this->toArray();
    }

    /**
     * Index Documents
     *
     * Index all documents in an Eloquent model.
     *
     * @return  array
     */
    public static function addAllToIndex()
    {
        $instance = new static;

        $all = $instance->newQuery()->get(array('*'));

        return $all->addToIndex();
    }

    /**
     * Re-Index All Content
     *
     * @return array
     */
    public static function reindex()
    {
        $instance = new static;

        $all = $instance->newQuery()->get(array('*'));

        return $all->reindex();
    }

    /**
     * Search By Query
     *
     * Search with a query array
     *
     * @param   array $query
     * @param   array $aggregations
     * @param   array $sourceFields
     * @param   int $limit
     * @param   int $offset
     * @return  ResultCollection
     */
    public static function searchByQuery($query = null, $aggregations = null, $sourceFields = null, $limit = null, $offset = null, $sort = null)
    {
        $instance = new static;

        $params = $instance->getBasicEsParams(true, true, true, $limit, $offset);

        if ($sourceFields) {
            $params['body']['_source']['include'] = $sourceFields;
        }

        if ($query) {
            $params['body']['query'] = $query;
        }

        if ($aggregations) {
            $params['body']['aggs'] = $aggregations;
        }

        if ($sort) {
            $params['body']['sort'] = $sort;
        }

        $result = $instance->getElasticSearchClient()->search($params);

        return new ResultCollection($result, $instance = new static);
    }

    /**
     * Search
     *
     * Simple search using a match _all query
     *
     * @param   string $term
     * @return  ResultCollection
     */
    public static function search($term = null)
    {
        $instance = new static;

        $params = $instance->getBasicEsParams();

        $params['body']['query']['match']['_all'] = $term;

        $result = $instance->getElasticSearchClient()->search($params);

        return new ResultCollection($result, $instance = new static);
    }

    /**
     * Add to Search Index
     *
     * @throws Exception
     * @return array
     */
    public function addToIndex()
    {
        if ( ! $this->exists) {
            throw new Exception('Document does not exist.');
        }

        $params = $this->getBasicEsParams();

        // Get our document body data.
        $params['body'] = $this->getIndexDocumentData();

        // The id for the document must always mirror the
        // key for this model, even if it is set to something
        // other than an auto-incrementing value. That way we
        // can do things like remove the document from
        // the index, or get the document from the index.
        $params['id'] = $this->getKey();

        return $this->getElasticSearchClient()->index($params);
    }

    /**
     * Remove From Search Index
     *
     * @return array
     */
    public function removeFromIndex()
    {
        return $this->getElasticSearchClient()->delete($this->getBasicEsParams());
    }

    /**
     * Get Search Document
     *
     * Retrieve an ElasticSearch document
     * for this enty.
     *
     * @return array
     */
    public function getIndexedDocument()
    {
        return $this->getElasticSearchClient()->get($this->getBasicEsParams());
    }

    /**
     * Get Basic Elasticsearch Params
     *
     * Most Elasticsearch API calls need the index and
     * type passed in a parameter array.
     *
     * @param     bool $getIdIfPossible
     * @param     bool $getSourceIfPossible
     * @param     bool $getTimestampIfPossible
     * @param     int $limit
     * @param     int $offset
     *
     * @return    array
     */
    public function getBasicEsParams($getIdIfPossible = true, $getSourceIfPossible = false, $getTimestampIfPossible = false, $limit = null, $offset = null)
    {
        $params = array(
            'index'     => $this->getIndexName(),
            'type'      => $this->getTypeName()
        );

        if ($getIdIfPossible and $this->getKey()) {
            $params['id'] = $this->getKey();
        }

        $fieldsParam = array();

        if ($getSourceIfPossible) {
            array_push($fieldsParam, '_source');
        }

        if ($getTimestampIfPossible) {
            array_push($fieldsParam, '_timestamp');
        }

        if ($fieldsParam) {
            $params['fields'] = implode(",", $fieldsParam);
        }

        if (is_numeric($limit)) {
            $params['size'] = $limit;
        }

        if (is_numeric($offset)) {
            $params['from'] = $offset;
        }

        return $params;
    }

    /**
     * Mapping Exists
     *
     * @return bool
     */
    public static function mappingExists()
    {
        $instance = new static;

        $mapping = $instance->getMapping();

        return (empty($mapping)) ? false : true;
    }

    /**
     * Get Mapping
     *
     * @return void
     */
    public static function getMapping()
    {
        $instance = new static;

        $params = $instance->getBasicEsParams();

        return $instance->getElasticSearchClient()->indices()->getMapping($params);
    }

    /**
     * Put Mapping
     *
     * @param    bool $ignoreConflicts
     * @return   array
     */
    public static function putMapping($ignoreConflicts = false)
    {
        $instance = new static;

        $mapping = $instance->getBasicEsParams();

        $params = array(
            '_source'       => array('enabled' => true),
            'properties'    => $instance->getMappingProperties()
        );

        $mapping['body'][$instance->getTypeName()] = $params;

        return $instance->getElasticSearchClient()->indices()->putMapping($mapping);
    }

    /**
     * Delete Mapping
     *
     * @return array
     */
    public static function deleteMapping()
    {
        $instance = new static;

        $params = $instance->getBasicEsParams();

        return $instance->getElasticSearchClient()->indices()->deleteMapping($params);
    }

    /**
     * Rebuild Mapping
     *
     * This will delete and then re-add
     * the mapping for this model.
     *
     * @return array
     */
    public static function rebuildMapping()
    {
        $instance = new static;

        // If the mapping exists, let's delete it.
        if ($instance->mappingExists()) {
            $instance->deleteMapping();
        }

        // Don't need ignore conflicts because if we
        // just removed the mapping there shouldn't
        // be any conflicts.
        return $instance->putMapping();
    }

    /**
     * Create Index
     *
     * @param int $shards
     * @param int $replicas
     * @return array
     */
    public static function createIndex($shards = null, $replicas = null)
    {
        $instance = new static;

        $client = $instance->getElasticSearchClient();

        $index = array(
            'index'     => $instance->getIndexName()
        );

        if ($shards) {
            $index['body']['settings']['number_of_shards'] = $shards;
        }

        if ($replicas) {
            $index['body']['settings']['number_of_replicas'] = $replicas;
        }

        return $client->indices()->create($index);
    }

    /**
     * Type Exists
     *
     * Does this type exist?
     *
     * @return bool
     */
    public static function typeExists()
    {
        $instance = new static;

        $params = $instance->getBasicEsParams();

        return $instance->getElasticSearchClient()->indices()->existsType($params);
    }

    /**
     * New From Hit Builder
     *
     * Variation on newFromBuilder. Instead, takes
     *
     * @param  array  $hit
     * @return static
     */
    public function newFromHitBuilder($hit = array())
    {
        $instance = $this->newInstance(array(), true);

        $attributes = $hit['_source'];

        // Add fields to attributes
        if (isset($hit['fields'])) {
            foreach ($hit['fields'] as $key => $value) {
                $attributes[$key] = $value;
            }
        }

        $instance->setRawAttributes((array) $attributes, true);

        // In addition to setting the attributes
        // from the index, we will set the score as well.
        $instance->documentScore = $hit['_score'];

        // This is now a model created
        // from an Elasticsearch document.
        $instance->isDocument = true;

        // Set our document version if it's
        if (isset($hit['_version'])) {
            $instance->documentVersion = $hit['_version'];
        }

        return $instance;
    }
}


<?php

namespace FL\QBJSParser\Parser\Doctrine;

abstract class SelectPartialParser
{
    const OBJECT_WORD = 'object';

    final private function __construct()
    {
    }

    /**
     * @param array $fieldPrefixesToClasses
     *
     * @return string
     */
    final public static function parse(array $fieldPrefixesToClasses = []): string
    {
        $selectString = 'SELECT '.static::OBJECT_WORD;

        foreach ($fieldPrefixesToClasses as $fieldPrefix => $associationClass) {
            $selectString .= sprintf(
                ', %s_%s',
                self::OBJECT_WORD,
                StringManipulator::replaceAllDots($fieldPrefix)
            );
        }

        return $selectString.' ';
    }
}


<?php

namespace FL\QBJSParser\Parser\Doctrine;

use FL\QBJSParser\Exception\Parser\Doctrine\InvalidFieldException;

abstract class OrderPartialParser
{
    /**
     * @var array
     */
    private static $queryBuilderFieldsToOrderAlias;

    final private function __construct()
    {
    }

    /**
     * @param array      $queryBuilderFieldsToProperties
     * @param array|null $sortColumns
     * @param array      $embeddableFieldsToProperties
     * @param array      $embeddableInsideEmbeddableFieldsToProperties
     *
     * @return string
     */
    final public static function parse(
        array $queryBuilderFieldsToProperties,
        array $sortColumns = null,
        array $embeddableFieldsToProperties,
        array $embeddableInsideEmbeddableFieldsToProperties
    ): string {
        foreach ($queryBuilderFieldsToProperties as $field => $property) {
            static::$queryBuilderFieldsToOrderAlias[$field] = StringManipulator::replaceAllDotsExceptLast(SelectPartialParser::OBJECT_WORD.'.'.$property);
        }
        foreach ($embeddableFieldsToProperties as $field => $property) {
            static::$queryBuilderFieldsToOrderAlias[$field] = SelectPartialParser::OBJECT_WORD.StringManipulator::replaceAllDotsExceptLastTwo('.'.$property);
        }
        foreach ($embeddableInsideEmbeddableFieldsToProperties as $field => $property) {
            static::$queryBuilderFieldsToOrderAlias[$field] = SelectPartialParser::OBJECT_WORD.StringManipulator::replaceAllDotsExceptLastThree('.'.$property);
        }

        if (null === $sortColumns || 0 === count($sortColumns)) {
            return '';
        }

        $orderString = ' ORDER BY ';
        foreach ($sortColumns as $field => $order) {
            $orderString .= sprintf(' %s %s, ',
                static::queryBuilderFieldToOrderAlias($field),
                static::queryBuilderOrderDirectionToSafeValue($order)
            );
        }

        return rtrim($orderString, ', ').' ';
    }

    /**
     * @param string $orderDirection
     *
     * @return string
     */
    final private static function queryBuilderOrderDirectionToSafeValue(string $orderDirection): string
    {
        $dictionary = [
            'ASC' => 'ASC',
            'DESC' => 'DESC',
        ];

        if (!array_key_exists($orderDirection, $dictionary)) {
            throw new InvalidFieldException($orderDirection);
        }

        return $dictionary[$orderDirection];
    }

    /**
     * @param string $queryBuilderField
     *
     * @return string
     */
    final private static function queryBuilderFieldToOrderAlias(string $queryBuilderField): string
    {
        $dictionary = static::$queryBuilderFieldsToOrderAlias;

        if (!array_key_exists($queryBuilderField, $dictionary)) {
            throw new InvalidFieldException($queryBuilderField);
        }

        return $dictionary[$queryBuilderField];
    }
}


<?php

namespace FL\QBJSParser\Parser\Doctrine;

use FL\QBJSParser\Exception\Parser\Doctrine\InvalidFieldException;
use FL\QBJSParser\Exception\Parser\Doctrine\InvalidOperatorException;
use FL\QBJSParser\Model\RuleGroupInterface;
use FL\QBJSParser\Model\RuleInterface;
use FL\QBJSParser\Parsed\Doctrine\ParsedRuleGroup;

abstract class WherePartialParser
{
    /**
     * @var string
     */
    private static $dqlPartialWhereString;

    /**
     * @var array
     */
    private static $parameters;

    /**
     * @var array
     */
    private static $queryBuilderFieldsToWhereAlias;

    /**
     * @codeCoverageIgnore
     */
    final private function __construct()
    {
    }

    /**
     * @param array              $queryBuilderFieldsToProperties
     * @param RuleGroupInterface $ruleGroup
     * @param array              $embeddableFieldsToProperties
     * @param array              $embeddableInsideEmbeddableFieldsToProperties
     * @param string             $className
     *
     * @return ParsedRuleGroup
     */
    final public static function parse(
        array $queryBuilderFieldsToProperties,
        RuleGroupInterface $ruleGroup,
        array $embeddableFieldsToProperties,
        array $embeddableInsideEmbeddableFieldsToProperties,
        string $className
    ): ParsedRuleGroup {
        foreach ($queryBuilderFieldsToProperties as $field => $property) {
            static::$queryBuilderFieldsToWhereAlias[$field] = StringManipulator::replaceAllDotsExceptLast(SelectPartialParser::OBJECT_WORD.'.'.$property);
        }
        foreach ($embeddableFieldsToProperties as $field => $property) {
            static::$queryBuilderFieldsToWhereAlias[$field] = SelectPartialParser::OBJECT_WORD.StringManipulator::replaceAllDotsExceptLastTwo('.'.$property);
        }
        foreach ($embeddableInsideEmbeddableFieldsToProperties as $field => $property) {
            static::$queryBuilderFieldsToWhereAlias[$field] = SelectPartialParser::OBJECT_WORD.StringManipulator::replaceAllDotsExceptLastThree('.'.$property);
        }

        static::$parameters = [];
        static::$dqlPartialWhereString = '';

        // populate static::$dqlPartialWhereString and static::$parameters
        if (!(
            0 === count($ruleGroup->getRuleGroups())
            && 0 === count($ruleGroup->getRules())
        )) {
            static::parseRuleGroup($ruleGroup, ' WHERE ( ', ' ) ');
        }

        return new ParsedRuleGroup(static::$dqlPartialWhereString, static::$parameters, $className);
    }

    /**
     * @param RuleGroupInterface $ruleGroup
     * @param string|null        $prepend
     * @param string|null        $append
     */
    final private static function parseRuleGroup(RuleGroupInterface $ruleGroup, string $prepend = null, string $append = null)
    {
        static::$dqlPartialWhereString .= $prepend ?? '';
        $iteration = 0;

        $andOr = RuleGroupInterface::MODE_AND === $ruleGroup->getMode() ? ' AND ' : ' OR ';

        if ($ruleGroup->isNot()) {
            static::$dqlPartialWhereString .= ' NOT (';
        }

        foreach ($ruleGroup->getRules() as $rule) {
            if (0 === $iteration) {
                static::parseRule($rule, ' ', ' ');
            } else {
                static::parseRule($rule, ' '.$andOr.' ', ' ');
            }
            ++$iteration;
        }

        foreach ($ruleGroup->getRuleGroups() as $ruleGroup) {
            if (0 === $iteration) {
                static::parseRuleGroup($ruleGroup, ' ( ', ' ) ');
            } else {
                static::parseRuleGroup($ruleGroup, ' '.$andOr.' ( ', ' ) ');
            }
            ++$iteration;
        }

        if ($ruleGroup->isNot()) {
            static::$dqlPartialWhereString .= ' ) ';
        }

        static::$dqlPartialWhereString .= $append ?? '';

        return;
    }

    /**
     * @param RuleInterface $rule
     * @param string|null   $prepend
     * @param string|null   $append
     */
    final private static function parseRule(RuleInterface $rule, string $prepend = null, string $append = null)
    {
        static::$dqlPartialWhereString .= $prepend ?? '';

        $queryBuilderField = $rule->getField();
        $safeField = static::queryBuilderFieldToWhereAlias($queryBuilderField);
        $queryBuilderOperator = $rule->getOperator();
        $doctrineOperator = static::queryBuilderOperatorToDoctrineOperator($queryBuilderOperator);
        $value = static::transformValueAccordingToQueryBuilderOperator($queryBuilderOperator, $rule->getValue());

        $parameterCount = count(static::$parameters);

        if (static::queryBuilderOperator_UsesValue($queryBuilderOperator)) {
            static::$dqlPartialWhereString .= $safeField.' '.$doctrineOperator.' ?'.$parameterCount.' ';
            static::$parameters[$parameterCount] = $value;
        } elseif (static::queryBuilderOperator_UsesArray($queryBuilderOperator)) {
            static::$dqlPartialWhereString .= $safeField.' '.$doctrineOperator.' (?'.$parameterCount.') ';
            static::$parameters[$parameterCount] = $value;
        } elseif (static::queryBuilderOperator_UsesArrayOfTwo($queryBuilderOperator)) {
            static::$dqlPartialWhereString .= $safeField.' '.$doctrineOperator.' ?'.$parameterCount.' AND ?'.($parameterCount + 1).' ';
            static::$parameters[$parameterCount] = $value[0];
            static::$parameters[$parameterCount + 1] = $value[1];
        } elseif (static::queryBuilderOperator_UsesNull($queryBuilderOperator)) {
            static::$dqlPartialWhereString .= $safeField.' '.$doctrineOperator.' ';
        }

        static::$dqlPartialWhereString .= $append ?? '';

        return;
    }

    /**
     * @param string $operator
     *
     * @return bool
     */
    final private static function queryBuilderOperator_UsesValue(string $operator): bool
    {
        return in_array($operator, [
            'equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal',
            'begins_with', 'not_begins_with', 'contains', 'not_contains', 'ends_with', 'not_ends_with',
        ]);
    }

    /**
     * @param string $operator
     *
     * @return bool
     */
    final private static function queryBuilderOperator_UsesArray(string $operator): bool
    {
        return in_array($operator, ['in', 'not_in']);
    }

    /**
     * @param string $operator
     *
     * @return bool
     */
    final private static function queryBuilderOperator_UsesArrayOfTwo(string $operator): bool
    {
        return in_array($operator, ['between', 'not_between']);
    }

    /**
     * @param string $operator
     *
     * @return bool
     */
    final private static function queryBuilderOperator_UsesNull(string $operator): bool
    {
        return in_array($operator, ['is_empty', 'is_not_empty', 'is_null', 'is_not_null']);
    }

    /**
     * @param string $queryBuilderOperator
     *
     * @return string
     */
    final private static function queryBuilderOperatorToDoctrineOperator(string $queryBuilderOperator): string
    {
        $dictionary = [
            'equal' => '=',
            'not_equal' => '!=',
            'in' => 'IN',
            'not_in' => 'NOT IN',
            'between' => 'BETWEEN',
            'not_between' => 'NOT BETWEEN',
            'less' => '<',
            'less_or_equal' => '<=',
            'greater' => '>',
            'greater_or_equal' => '>=',
            'begins_with' => 'LIKE',
            'not_begins_with' => 'NOT LIKE',
            'contains' => 'LIKE',
            'not_contains' => 'NOT LIKE',
            'ends_with' => 'LIKE',
            'not_ends_with' => 'NOT LIKE',
            // doctrine's 'IS EMPTY' and 'IS NOT EMPTY' is for collections, not strings
            'is_empty' => '= \'\'',
            'is_not_empty' => '!= \'\'', 'is_null' => 'IS NULL',
            'is_not_null' => 'IS NOT NULL',
        ];

        if (!array_key_exists($queryBuilderOperator, $dictionary)) {
            throw new InvalidOperatorException();
        }

        return $dictionary[$queryBuilderOperator];
    }

    /**
     * @param string $queryBuilderOperator
     * @param mixed  $value
     *
     * @return string
     *
     * @see http://doctrine.readthedocs.io/en/latest/en/manual/dql-doctrine-query-language.html#like-expressions
     */
    final private static function transformValueAccordingToQueryBuilderOperator(string $queryBuilderOperator, $value)
    {
        if (!is_string($value)) {
            return $value;
        }

        switch ($queryBuilderOperator) {
            case 'begins_with':
            case 'not_begins_with':
                return $value.'%';
            case 'contains':
            case 'not_contains':
                return '%'.$value.'%';
            case 'ends_with':
            case 'not_ends_with':
                return '%'.$value;
            default:
                return $value;
        }
    }

    /**
     * @param string $queryBuilderField
     *
     * @return string
     */
    final private static function queryBuilderFieldToWhereAlias(string $queryBuilderField): string
    {
        $dictionary = static::$queryBuilderFieldsToWhereAlias;

        if (!array_key_exists($queryBuilderField, $dictionary)) {
            throw new InvalidFieldException($queryBuilderField);
        }

        return $dictionary[$queryBuilderField];
    }
}


<?php

namespace FL\QBJSParser\Serializer;

use FL\QBJSParser\Exception\Serializer\JsonDeserializerInvalidJsonException;
use FL\QBJSParser\Exception\Serializer\JsonDeserializerConditionException;
use FL\QBJSParser\Exception\Serializer\JsonDeserializerUnexpectedTypeException;
use FL\QBJSParser\Exception\Serializer\JsonDeserializerRuleKeyException;
use FL\QBJSParser\Model\Rule;
use FL\QBJSParser\Model\RuleGroup;
use FL\QBJSParser\Model\RuleGroupInterface;
use FL\QBJSParser\Model\RuleInterface;

class JsonDeserializer implements DeserializerInterface
{
    /**
     * {@inheritdoc}
     */
    public function deserialize(string $string): RuleGroupInterface
    {
        $decodedRuleGroup = json_decode($string, true);
        if (is_null($decodedRuleGroup) || !is_array($decodedRuleGroup)) {
            throw new JsonDeserializerInvalidJsonException();
        }

        return $this->deserializeRuleGroup($decodedRuleGroup);
    }

    /**
     * @param array $decodedRuleGroup
     *
     * @return RuleGroupInterface
     */
    private function deserializeRuleGroup(array $decodedRuleGroup): RuleGroupInterface
    {
        if (!array_key_exists('condition', $decodedRuleGroup)) {
            throw new JsonDeserializerConditionException('Missing key condition in RuleGroup');
        }

        $not = false;
        if (
            array_key_exists('not', $decodedRuleGroup) &&
            true === $decodedRuleGroup['not']
        ) {
            $not = true;
        }

        $deserializedRuleGroup = new RuleGroup($decodedRuleGroup['condition'], $not);

        foreach ($decodedRuleGroup['rules'] as $ruleOrGroup) {
            if (array_key_exists('condition', $ruleOrGroup)) {
                $deserializedRuleGroup->addRuleGroup($this->deserializeRuleGroup($ruleOrGroup));
            } elseif (array_key_exists('id', $ruleOrGroup)) {
                $deserializedRuleGroup->addRule($this->deserializeRule($ruleOrGroup));
            }
        }

        return $deserializedRuleGroup;
    }

    /**
     * @param array $decodedRule
     *
     * @return RuleInterface
     *
     * @throws \Exception
     */
    private function deserializeRule(array $decodedRule): RuleInterface
    {
        $requiredKeyIsMissing = !(
            array_key_exists('id', $decodedRule)
            && array_key_exists('field', $decodedRule)
            && array_key_exists('type', $decodedRule)
            && array_key_exists('operator', $decodedRule)
            && array_key_exists('value', $decodedRule)
        );
        if ($requiredKeyIsMissing) {
            $keysGiven = implode(', ', array_keys($decodedRule));

            throw new JsonDeserializerRuleKeyException('Keys given: '.$keysGiven.'. Expected id, field, type, operator, value');
        }

        $id = $decodedRule['id'];
        $field = $decodedRule['field'];
        $type = $decodedRule['type'];
        $operator = $decodedRule['operator'];
        $value = $decodedRule['value'];

        // operators in and not_in require an array, ensure that single values are converted to an array
        if (in_array($operator, ['in', 'not_in']) && !is_array($value)) {
            $value = [$value];
        }

        // some operators require a single value, ensure that arrays are converted to a single value
        if (
            in_array($operator, ['equal', 'not_equal', 'less', 'less_or_equal', 'greater', 'greater_or_equal', 'begins_with', 'not_begins_with', 'contains', 'ends_with', 'not_ends_with'])
            && is_array($value)
        ) {
            $value = array_values($value)[0];
        }

        if (!is_array($value)) {
            $value = $this->convertValueAccordingToType($type, $value);

            return new Rule($id, $field, $type, $operator, $value);
        } else {
            $valuesArray = $value;
            foreach ($valuesArray as $key => $value) {
                $valuesArray[$key] = $this->convertValueAccordingToType($type, $value);
            }

            return new Rule($id, $field, $type, $operator, $valuesArray);
        }
    }

    /**
     * @param string $type
     * @param mixed  $value
     *
     * @return mixed
     */
    private function convertValueAccordingToType(string $type, $value)
    {
        if (is_null($value) || 'null' === $value || 'NULL' === $value) {
            return; // nulls shouldn't be converted
        }

        switch ($type) { /* @see Rule::$type */
            default:
                throw new JsonDeserializerUnexpectedTypeException(sprintf('Unexpected type %s', $type));
            case 'string':
                return strval($value);
            case 'integer':
                return intval($value);
            case 'double':
                return floatval($value);
            case 'date':
            case 'time':
            case 'datetime':
                return new \DateTimeImmutable($value);
            case 'boolean':
                return boolval($value);
        }
    }
}


<?php

namespace FL\QBJSParser\Parser\Doctrine;

abstract class StringManipulator
{
    private function __construct()
    {
    }

    /**
     * Will convert "object_something.cool.today.midday" TO "object_something.cool.today.midday".
     * Will convert "object.something.cool.today" TO "object.something.cool.today".
     * Will convert "object.something.cool" TO "object.something.cool".
     * Will convert "object.something" TO "object.something".
     * Will convert "object" TO "object".
     *
     * @param string $string
     *
     * @return string
     */
    final public static function replaceAllDotsExceptLastThree(string $string): string
    {
        $countDots = substr_count($string, '.');
        if ($countDots > 3) {
            $stringArray = explode('.', $string);
            $string = '';
            for ($i = 0; $i < $countDots - 3; ++$i) {
                $string .= $stringArray[$i].'_';
            }
            $string .= $stringArray[$countDots - 3].'.'.$stringArray[$countDots - 2].'.'.$stringArray[$countDots - 1].'.'.$stringArray[$countDots];
        }

        return $string;
    }

    /**
     * Will convert "object.something.cool.today" TO "object_something.cool.today".
     * Will convert "object.something.cool" TO "object.something.cool".
     * Will convert "object.something" TO "object.something".
     * Will convert "object" TO "object".
     *
     * @param string $string
     *
     * @return string
     */
    final public static function replaceAllDotsExceptLastTwo(string $string): string
    {
        $countDots = substr_count($string, '.');
        if ($countDots > 2) {
            $stringArray = explode('.', $string);
            $string = '';
            for ($i = 0; $i < $countDots - 2; ++$i) {
                $string .= $stringArray[$i].'_';
            }
            $string .= $stringArray[$countDots - 2].'.'.$stringArray[$countDots - 1].'.'.$stringArray[$countDots];
        }

        return $string;
    }

    /**
     * Will convert "object.something.cool.today" TO "object_something_cool.today".
     * Will convert "object.something.cool" TO "object_something.cool".
     * Will convert "object.something" TO "object.something".
     * Will convert "object" TO "object".
     *
     * @param string $string
     *
     * @return string
     */
    final public static function replaceAllDotsExceptLast(string $string): string
    {
        $countDots = substr_count($string, '.');
        if ($countDots > 1) {
            $stringArray = explode('.', $string);
            $string = '';
            for ($i = 0; $i < $countDots - 1; ++$i) {
                $string .= $stringArray[$i].'_';
            }
            $string .= $stringArray[$countDots - 1].'.'.$stringArray[$countDots];
        }

        return $string;
    }

    /**
     * Will convert "object.something.cool.today" TO "object_something_cool_today".
     * Will convert "object.something.cool" TO object_something_cool".
     * Will convert "object.something" "TO object_something".
     * Will convert "object" TO "object".
     *
     * @param string $string
     *
     * @return string
     */
    final public static function replaceAllDots(string $string): string
    {
        return str_replace('.', '_', $string);
    }
}


<?php

namespace FL\QBJSParser\Parser\Doctrine;

use FL\QBJSParser\Exception\Parser\Doctrine\DuplicatePrefixException;
use FL\QBJSParser\Exception\Parser\Doctrine\InvalidClassNameException;
use FL\QBJSParser\Exception\Parser\Doctrine\FieldMappingException;
use FL\QBJSParser\Exception\Parser\Doctrine\MissingAssociationClassException;
use FL\QBJSParser\Model\RuleGroupInterface;
use FL\QBJSParser\Parsed\Doctrine\ParsedRuleGroup;
use FL\QBJSParser\Tests\Util\Doctrine\Mock\DoctrineParser\MockEntityWithEmbeddableDoctrineParser;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;

class DoctrineParser implements DoctrineParserInterface
{
    /**
     * @var string
     */
    private $className;

    /**
     * @var array
     */
    private $fieldsToProperties;

    /**
     * @var array
     */
    private $fieldPrefixesToClasses;

    /**
     * @var array
     */
    private $embeddableFieldsToProperties;

    /**
     * @var array
     */
    private $embeddableInsideEmbeddableFieldsToProperties;

    /**
     * @var array
     */
    private $embeddableFieldPrefixesToClasses;

    /**
     * @var array
     */
    private $embeddableFieldPrefixesToEmbeddableClasses;

    /**
     * @param string $className
     * @param array  $fieldsToProperties
     * @param array  $fieldPrefixesToClasses
     * @param array  $embeddableFieldsToProperties
     * @param array  $embeddableInsideEmbeddableFieldsToProperties
     * @param array  $embeddableFieldPrefixesToClasses
     * @param array  $embeddableFieldPrefixesToEmbeddableClasses
     *
     * @see MockEntityWithEmbeddableDoctrineParser for full example
     */
    public function __construct(
        string $className,
        array $fieldsToProperties,
        array $fieldPrefixesToClasses = [],
        array $embeddableFieldsToProperties = [],
        array $embeddableInsideEmbeddableFieldsToProperties = [],
        array $embeddableFieldPrefixesToClasses = [],
        array $embeddableFieldPrefixesToEmbeddableClasses = []
    ) {
        $this->className = $className;
        $this->fieldsToProperties = $fieldsToProperties;
        $this->fieldPrefixesToClasses = $fieldPrefixesToClasses;
        $this->embeddableFieldsToProperties = $embeddableFieldsToProperties;
        $this->embeddableInsideEmbeddableFieldsToProperties = $embeddableInsideEmbeddableFieldsToProperties;
        $this->embeddableFieldPrefixesToClasses = $embeddableFieldPrefixesToClasses;
        $this->embeddableFieldPrefixesToEmbeddableClasses = $embeddableFieldPrefixesToEmbeddableClasses;
        $this->validate();
    }

    /**
     * {@inheritdoc}
     *
     * @return ParsedRuleGroup
     */
    final public function parse(RuleGroupInterface $ruleGroup, array $sortColumns = null): ParsedRuleGroup
    {
        $selectString = SelectPartialParser::parse($this->fieldPrefixesToClasses);
        $fromString = FromPartialParser::parse($this->className);
        $joinString = JoinPartialParser::parse($this->fieldPrefixesToClasses);

        $whereParsedRuleGroup = WherePartialParser::parse(
            $this->fieldsToProperties,
            $ruleGroup,
            $this->embeddableFieldsToProperties,
            $this->embeddableInsideEmbeddableFieldsToProperties,
            $this->className
        );
        $whereString = $whereParsedRuleGroup->getQueryString();
        $parameters = $whereParsedRuleGroup->getParameters();

        $orderString = OrderPartialParser::parse(
            $this->fieldsToProperties,
            $sortColumns,
            $this->embeddableFieldsToProperties,
            $this->embeddableInsideEmbeddableFieldsToProperties
        );

        $dqlString = preg_replace('/\s+/', ' ', $selectString.$fromString.$joinString.$whereString.$orderString);

        return new ParsedRuleGroup($dqlString, $parameters, $this->className); // preg_replace -> no more than one space
    }

    /**
     * @throws InvalidClassNameException|FieldMappingException|MissingAssociationClassException
     */
    final private function validate()
    {
        $this->validateClass($this->className);
        $this->validateFieldsToProperties($this->fieldsToProperties, $this->fieldPrefixesToClasses);
        $this->validateFieldPrefixesToClasses($this->fieldPrefixesToClasses);
        $allEmbeddableFields = array_merge($this->embeddableFieldsToProperties, $this->embeddableInsideEmbeddableFieldsToProperties);
        $allEmbeddablePrefixesToClasses = array_merge($this->embeddableFieldPrefixesToClasses, $this->embeddableFieldPrefixesToEmbeddableClasses);
        $this->validateFieldsToProperties($allEmbeddableFields, $allEmbeddablePrefixesToClasses);
        $this->validateFieldPrefixesToClasses($allEmbeddablePrefixesToClasses);
        $this->validateEmbeddableFieldPrefixes($this->embeddableFieldPrefixesToClasses, $this->embeddableFieldPrefixesToEmbeddableClasses);
    }

    /**
     * @param array $fieldsToProperties
     * @param array $fieldPrefixesToClasses
     *
     * @throws InvalidClassNameException|FieldMappingException|MissingAssociationClassException
     */
    final private function validateFieldsToProperties(array $fieldsToProperties, array $fieldPrefixesToClasses)
    {
        // check $fieldsToProperties
        foreach ($fieldsToProperties as $queryBuilderField => $property) {
            $suffixPattern = '/\.((?!\.).)+$/';
            $suffixMatches = [];
            $doesFieldHaveSuffix = preg_match($suffixPattern, $queryBuilderField, $suffixMatches);
            if ($doesFieldHaveSuffix) { // $queryBuilderField is for an Association
                $fieldPrefix = preg_replace($suffixPattern, '', $queryBuilderField);
                $this->validateFieldPrefixIsInAssociations($fieldPrefix, $fieldPrefixesToClasses);
                $fieldSuffix = str_replace('.', '', $suffixMatches[0]); // remove preceding dot
                $classForThisPrefix = $fieldPrefixesToClasses[$fieldPrefix];
                $this->validateClass($classForThisPrefix);
                $this->validateClassHasProperty($classForThisPrefix, $fieldSuffix);
            } else { // $queryBuilderField is for $this->className
                $this->validateClassHasProperty($this->className, $property);
            }
        }
    }

    /**
     * @param array $fieldPrefixesToClasses
     *
     * @throws InvalidClassNameException|FieldMappingException|MissingAssociationClassException
     */
    final private function validateFieldPrefixesToClasses(array $fieldPrefixesToClasses)
    {
        // validate fieldPrefixesToClasses
        foreach ($fieldPrefixesToClasses as $fieldPrefix => $associationClass) {
            $suffixPattern = '/\.((?!\.).)+$/';
            $suffixMatches = [];
            $doesFieldPrefixHaveSuffix = preg_match($suffixPattern, $fieldPrefix, $suffixMatches);
            if ($doesFieldPrefixHaveSuffix) { // $fieldPrefix is for an Association in an Association
                $fieldPrefixPrefix = preg_replace($suffixPattern, '', $fieldPrefix);
                $fieldSuffix = str_replace('.', '', $suffixMatches[0]); // remove preceding dot
                if (!array_key_exists($fieldPrefixPrefix, $fieldPrefixesToClasses)) {
                    throw new MissingAssociationClassException(sprintf(
                        'Missing association class for queryBuilderFieldPrefix %s, at class %s, for parser %s',
                        $fieldPrefixPrefix,
                        $this->className,
                        static::class
                    ));
                }
                $classForThisPrefix = $fieldPrefixesToClasses[$fieldPrefixPrefix];
                $this->validateClassHasProperty($classForThisPrefix, $fieldSuffix);
            } else { // $fieldPrefix an association for $this->className
                $this->validateClassHasProperty($this->className, $fieldPrefix);
            }
        }
    }

    /**
     * There should be no repeated prefixes (keys) between the two arrays.
     *
     * @param array $embeddableFieldPrefixesToClasses
     * @param array $embeddableFieldPrefixesToEmbeddableClasses
     */
    final private function validateEmbeddableFieldPrefixes(array $embeddableFieldPrefixesToClasses, array $embeddableFieldPrefixesToEmbeddableClasses)
    {
        $prefixes = array_keys($embeddableFieldPrefixesToClasses);

        foreach ($prefixes as $prefix) {
            if (array_key_exists($prefix, $embeddableFieldPrefixesToEmbeddableClasses)) {
                throw new DuplicatePrefixException(sprintf(
                    'Duplicate embeddable field prefix %s, at class %s, for parser %s',
                    $prefix,
                    $this->className,
                    static::class
                ));
            }
        }
    }

    /**
     * @param string $className
     *
     * @see http://symfony.com/doc/current/components/property_info.html#components-property-info-extractors
     *
     * @throws InvalidClassNameException
     */
    final private function validateClass(string $className)
    {
        if (!class_exists($className)) {
            throw new InvalidClassNameException(sprintf(
                'Expected valid class name in %s. %s was given, and it is not a valid class name.',
                static::class,
                $className
            ));
        }
    }

    /**
     * @param string $className
     * @param string $classProperty
     *
     * @see http://symfony.com/doc/current/components/property_info.html#components-property-info-extractors
     *
     * @throws FieldMappingException
     */
    final private function validateClassHasProperty(string $className, string $classProperty)
    {
        $propertyInfo = new PropertyInfoExtractor([new ReflectionExtractor()]);
        $properties = $propertyInfo->getProperties($className);

        if (!in_array($classProperty, $properties)) {
            throw new FieldMappingException(sprintf(
                'Property %s is not accessible in %s.',
                $classProperty,
                $className
            ));
        }
    }

    /**
     * @param string $fieldPrefix
     * @param array  $fieldPrefixesToClasses
     *
     * @throws MissingAssociationClassException
     */
    final private function validateFieldPrefixIsInAssociations(string $fieldPrefix, array $fieldPrefixesToClasses)
    {
        if (!array_key_exists($fieldPrefix, $fieldPrefixesToClasses)) {
            throw new MissingAssociationClassException(sprintf(
                'Missing class for fieldPrefix %s, at class %s, for parser %s',
                $fieldPrefix,
                $this->className,
                static::class
            ));
        }
    }
}


<?php

namespace FL\QBJSParser\Parsed\Doctrine;

use FL\QBJSParser\Exception\Parsed\Doctrine\ParsedRuleGroupConstructionException;
use FL\QBJSParser\Parsed\AbstractParsedRuleGroup;

class ParsedRuleGroup extends AbstractParsedRuleGroup
{
    /**
     * @var string
     */
    private $queryString;

    /**
     * @var array
     */
    private $parameters;

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

    /**
     * @param string $queryString
     * @param array  $parameters
     * @param string $className
     */
    public function __construct(string $queryString, array $parameters, string $className)
    {
        $this->queryString = trim($queryString);
        $this->parameters = $parameters;
        $this->className = $className;
        $this->validateParametersCountInDql();
        $this->validateClassName();
    }

    /**
     * @throws ParsedRuleGroupConstructionException
     */
    private function validateParametersCountInDql()
    {
        $queryString = $this->queryString;

        $dqlParametersCount = preg_match_all('/\?\d+/', $queryString);
        $boundParametersCount = count($this->parameters);

        if ($dqlParametersCount !== $boundParametersCount) {
            throw new ParsedRuleGroupConstructionException(sprintf(
                '%s has %u parameters. Expected %u.',
                $queryString,
                $boundParametersCount,
                $dqlParametersCount
            ));
        }
    }

    /**
     * @throws ParsedRuleGroupConstructionException
     */
    private function validateClassName()
    {
        if (!class_exists($this->className)) {
            throw new ParsedRuleGroupConstructionException(sprintf(
                'The class %s does not exist',
                $this->className
            ));
        }
    }

    /**
     * This is a dql string.
     *
     * {@inheritdoc}
     */
    public function getQueryString(): string
    {
        return $this->queryString;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameters(): array
    {
        return $this->parameters;
    }

    /**
     * {@inheritdoc}
     */
    public function getClassName(): string
    {
        return $this->className;
    }

    /**
     * For example, $parsedRuleGroup->copyWithReplacedString('ORDER BY', 'GROUP BY object.id ORDER BY', 'GROUP BY object.id').
     *
     * "SELECT object FROM App\Entity\Class object ORDER BY object.id" would change to
     * "SELECT object FROM App\Entity\Class object GROUP BY object.id ORDER BY object.id".
     *
     * "SELECT object FROM App\Entity\Class object" would change to
     * "SELECT object FROM App\Entity\Class object GROUP BY object.id"
     *
     * {@inheritdoc}
     */
    public function copyWithReplacedString(string $search, string $replace, string $appendToEndIfNotFound): AbstractParsedRuleGroup
    {
        $count = 0;
        $newDql = str_replace($search, $replace, $this->getQueryString(), $count);

        if (0 === $count) {
            $newDql = $this->getQueryString().$appendToEndIfNotFound;
        }

        return new self($newDql, $this->parameters, $this->className);
    }

    /**
     * For example $parsedRuleGroup->copyWithReplacedString('/SELECT.+object.+FROM/', 'SELECT object FROM', '').
     *
     * "SELECT object, association FROM App\Entity\Class object LEFT JOIN object.association association ORDER BY object.id" would change to
     * "SELECT object FROM App\Entity\Class object LEFT JOIN object.association association ORDER BY object.id".
     *
     * {@inheritdoc}
     */
    public function copyWithReplacedStringRegex(
        string $pattern,
        string $replace,
        string $appendToEndIfNotFound
    ): AbstractParsedRuleGroup {
        $count = 0;
        $newDql = preg_replace($pattern, $replace, $this->getQueryString(), -1, $count);

        if (0 === $count) {
            $newDql = $this->getQueryString().$appendToEndIfNotFound;
        }

        return new self($newDql, $this->parameters, $this->className);
    }
}


<?php

namespace FL\QBJSParser\Parser\Doctrine;

abstract class JoinPartialParser
{
    final private function __construct()
    {
    }

    /**
     * @param array $queryBuilderFieldPrefixesToAssociationClasses
     *
     * @return string
     */
    final public static function parse(array $queryBuilderFieldPrefixesToAssociationClasses): string
    {
        $joinString = '';
        foreach ($queryBuilderFieldPrefixesToAssociationClasses as $queryBuilderPrefix => $associationClass) {
            $joinPart = sprintf(
                ' %s.%s ',
                SelectPartialParser::OBJECT_WORD,
                $queryBuilderPrefix
            );
            $joinString .= sprintf(
                ' LEFT JOIN %s %s ',
                StringManipulator::replaceAllDotsExceptLast($joinPart),
                StringManipulator::replaceAllDots($joinPart)
            );
        }

        return $joinString;
    }
}


<?php

/*
* This file is part of the SCDatetimepickerBundle package.
*
* (c) Stephane Collot
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace SC\DatetimepickerBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* DatetimeType
*
*/
class DatetimeType extends AbstractType
{
    /**
     *
     * @var array
     */
    private $options;

    /**
     *
     * @var array
     */
    private static $malotFormater = array("yyyy", "yyyy", "ss", "ii", "hh", "HH", "dd", "mm", "MM",   "yy", "p", "P", "s", "i", "h", "H", "d", "m", "M");

    /**
     *
     * @var array
     */
    private static $intlFormater  = array("y", "yyyy", "ss", "mm", "HH", "hh", "dd", "MM", "MMMM", "yy", "a", "a", "s", "m", "H", "h", "d", "M", "MMM");

    /**
    * Constructs
    *
    * @param array $options
    */
    public function __construct(array $options = array())
    {
        $this->options = $options;

    }

    /**
    * {@inheritdoc}
    */
    public function buildView(FormView $view, FormInterface $form, array $options)
    {
        $pickerOptions = array_merge($this->options, $options['pickerOptions']);

        //Set automatically the language
        if(!isset($pickerOptions['language']))
            $pickerOptions['language'] = \Locale::getDefault();
        if($pickerOptions['language'] == 'en')
            unset($pickerOptions['language']);

        //Set the defaut format of malot.fr/bootstrap-datetimepicker
        if(!isset($pickerOptions['format']))
            $pickerOptions['format'] = 'mm/dd/yyyy HH:ii';

        if ($pickerOptions['formatter'] == 'php'){
            $pickerOptions['format'] = DatetimeType::convertIntlFormaterToMalot( $pickerOptions['format'] );
        }

        $view->vars = array_replace($view->vars, array(
            'pickerOptions' => $pickerOptions,
        ));
    }

    /**
    * {@inheritdoc}
    */
    public function configureOptions(OptionsResolver $resolver)
    {
        $configs = $this->options;

        $resolver
            ->setDefaults(array(
                'widget' => 'single_text',
                'format' => function (Options $options, $value) use ($configs) {
                    $pickerOptions = array_merge($configs, $options['pickerOptions']);

                    if ($pickerOptions['formatter'] == 'php'){
                        if (isset($pickerOptions['format'])){
                            return $pickerOptions['format'];
                        } else {
                            return 'mm/dd/yyyy HH:ii';
                        }
                    } elseif ($pickerOptions['formatter'] == 'js'){
                        if (isset($pickerOptions['format'])){
                            return DatetimeType::convertMalotToIntlFormater( $pickerOptions['format'] );
                        } else {
                            return DatetimeType::convertMalotToIntlFormater( 'mm/dd/yyyy HH:ii' );
                        }
                    }
                },
                'pickerOptions' => array(),
            ));
    }

    /**
     * Convert the PHP date format to Bootstrap Datetimepicker date format
     */
    public static function convertIntlFormaterToMalot($formatter)
    {
        $intlToMalot = array_combine(self::$intlFormater, self::$malotFormater);

        $patterns = preg_split('([\\\/.:_;,\s-\ ]{1})', $formatter);
        $exits = array();

        foreach ($patterns as $val) {
            if (isset($intlToMalot[$val])){
                $exits[$val] = $intlToMalot[$val];
            } else {
                // it can throw an Exception
                $exits[$val] = $val;
            }
        }

        return str_replace(array_keys($exits), array_values($exits), $formatter);
    }

    /**
     * Convert the Bootstrap Datetimepicker date format to PHP date format
     */
    public static function convertMalotToIntlFormater($formatter)
    {
        $malotToIntl = array_combine(self::$malotFormater, self::$intlFormater);

        $patterns = preg_split('([\\\/.:_;,\s-\ ]{1})', $formatter);
        $exits = array();

        foreach ($patterns as $val) {
            if (isset($malotToIntl[$val])){
                $exits[$val] = $malotToIntl[$val];
            } else {
                // it can throw an Exception
                $exits[$val] = $val;
            }
        }

        return str_replace(array_keys($exits), array_values($exits), $formatter);
    }

    /**
     *
     * @see \Symfony\Component\Form\AbstractType::getParent()
     */
    public function getParent()
    {
        return \Symfony\Component\Form\Extension\Core\Type\DateTimeType::class;
    }

    public function getName()
    {
        return $this->getBlockPrefix();
    }

    public function getBlockPrefix()
    {
        return 'collot_datetime';
    }
}


<?php

namespace SC\DatetimepickerBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;

/**
 * This is the class that loads and manages bundle configuration
 */
class SCDatetimepickerExtension extends Extension
{
    /**
     * {@inheritDoc}
     */
    public function load(array $configs, ContainerBuilder $container)
    {
        $configs = $this->processConfiguration(new Configuration(), $configs);

        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
		$loader->load('twig.xml');
        $loader->load('datetime.xml');

        if (isset($configs["picker"]) && !empty($configs["picker"]['enabled'])) {
            $method = 'register' . ucfirst("picker") . 'Configuration';

            $this->$method($configs["picker"], $container);
        }

    }

    /**
     * Loads Picker configuration
     *
     * @param array            $config    A configuration array
     * @param ContainerBuilder $container A ContainerBuilder instance
     */
    private function registerPickerConfiguration(array $configs, ContainerBuilder $container)
    {
        $container->setParameter('sc_datetimepicker.form.options', $configs['configs']);
    }
}


<?php

/*
 * This file is part of the GenemuFormBundle package.
 *
 * (c) Olivier Chauvel <olivier@generation-multiple.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace SC\DatetimepickerBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Add a new twig.form.resources
 *
 * @author Olivier Chauvel <olivier@generation-multiple.com>
 */
class FormPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        $resources = $container->getParameter('twig.form.resources');

        foreach (array('div', 'jquery', 'stylesheet') as $template) {
            $resources[] = 'SCDatetimepickerBundle:Form:' . $template . '_layout.html.twig';
        }

        $container->setParameter('twig.form.resources', $resources);
    }
}


<?php

namespace SC\DatetimepickerBundle\DependencyInjection;

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

/**
 * This is the class that validates and merges configuration from app/config files
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritDoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('stephane_collot_datetimepicker');

        $this->addPicker($rootNode);

        return $treeBuilder;
    }

    /**
     * Add configuration Picker
     *
     * @param ArrayNodeDefinition $rootNode
     */
    private function addPicker(ArrayNodeDefinition $rootNode)
    {
        $rootNode
            ->children()
                ->arrayNode('picker')
                    ->canBeUnset()
                    ->addDefaultsIfNotSet()
                    ->treatNullLike(array('enabled' => true))
                    ->treatTrueLike(array('enabled' => true))
                    ->children()
                        ->booleanNode('enabled')->defaultTrue()->end()
                        ->arrayNode('configs')
                            ->addDefaultsIfNotSet()
                            ->children()
                                ->enumNode('formatter')->defaultValue('js')
                                    ->values(array('js', 'php'))
                                ->end()
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end()
        ;
    }
}


<?php

namespace Develpr\AlexaApp\Provider;

use Develpr\AlexaApp\Alexa;
use Develpr\AlexaApp\Certificate\DatabaseCertificateProvider;
use Develpr\AlexaApp\Certificate\EloquentCertificateProvider;
use Develpr\AlexaApp\Certificate\FileCertificateProvider;
use Develpr\AlexaApp\Certificate\RedisCertificateProvider;
use Develpr\AlexaApp\Device\DatabaseDeviceProvider;
use Develpr\AlexaApp\Device\EloquentDeviceProvider;
use Develpr\AlexaApp\Request\AlexaRequest;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
use Exception;

class AlexaServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $request = $this->app->make('request');

        $this->registerRouter();
        $this->bindAlexaRequest($request);
        $this->bindCertificateProvider();
        $this->bindAlexa();
        $this->registerMiddleware();

    }

    protected function registerRouter()
    {
        $this->app->singleton('alexa.router', function ($app) {
            return $app->make(\Develpr\AlexaApp\Http\Routing\AlexaRouter::class);
        });
    }

    /**
     * Bind the appropriate AlexaResponse type to the IoC container
     */
    private function bindAlexaRequest(Request $request)
    {
        $this->app->singleton('alexa.request', function($app) use ($request) {
            /** @var AlexaRequest $alexaRequest */
            $alexaRequest = AlexaRequest::capture();

            if (!$app['config']['alexa.prompt.enable'] || $alexaRequest->getIntent() !== $app['config']['alexa.prompt.response_intent'] || is_null($alexaRequest->getPromptResponseIntent())) {
                return $alexaRequest;
            } else{
                $alexaRequest->setPromptResponse(true);

                return $alexaRequest;
            }
        });
    }

    private function bindAlexa()
    {
        $this->app->singleton('alexa', function($app) {
            $providerType = $app['config']['alexa.device.provider'];
            $provider = null;

            if ($providerType == "eloquent") {
                $provider = new EloquentDeviceProvider($app['config']['alexa.device.model']);
            } elseif ($providerType == "database") {
                $connection = $app['db']->connection();
                $provider = new DatabaseDeviceProvider($connection, $app['config']['alexa.device.table']);
            } else {
                throw new Exception("Unsupported Alexa Device Provider specified - currently only 'database' and 'eloquent' are supported");
            }

            $alexaRequest = $this->app['alexa.request'];

            return new Alexa($alexaRequest, $provider, $app['config']['alexa']);
        });
    }

    /**
     * Register the middleware.
     */
    protected function registerMiddleware()
    {
        $this->app->singleton(\Develpr\AlexaApp\Http\Middleware\Request::class, function ($app) {
            return new \Develpr\AlexaApp\Http\Middleware\Request($app, $app['alexa.router'], $app['alexa.request'], $app['alexa.router.middleware']);
        });

        $this->app->singleton(\Develpr\AlexaApp\Http\Middleware\Certificate::class, function ($app) {
            return new \Develpr\AlexaApp\Http\Middleware\Certificate($app['alexa.request'], $app['alexa.certificateProvider'], $app['config']['alexa']);
        });
    }

    private function bindCertificateProvider()
    {
        $this->app->bind('alexa.certificateProvider', function($app) {
            $providerType = $this->app['config']['alexa.certificate.provider'];

            switch ($providerType) {
                case 'file':
                    return new FileCertificateProvider(new Filesystem, $this->app['config']['alexa.certificate.filePath']);
                case 'redis':
                    return new RedisCertificateProvider($app->make('redis'));
                case 'eloquent':
                    return new EloquentCertificateProvider($app['config']['alexa.certificate.model']);
                case 'database':
                    return new DatabaseCertificateProvider($app['db']->connection(), $app['config']['alexa.device.table']);
            }

            throw new Exception("Unsupported Alexa Certificate Provider specified - currently only 'file', 'database', and 'eloquent' are supported");
        });
    }
}


<?php

namespace Develpr\AlexaApp;

use Develpr\AlexaApp\Contracts\AmazonEchoDevice;
use Develpr\AlexaApp\Contracts\DeviceProvider;
use Develpr\AlexaApp\Request\AlexaRequest;
use Develpr\AlexaApp\Response\AlexaResponse;
use Develpr\AlexaApp\Response\AudioFile;
use Develpr\AlexaApp\Response\Card;
use Develpr\AlexaApp\Response\Directives\AudioPlayer\Play;
use Develpr\AlexaApp\Response\Directives\AudioPlayer\Stop;
use Develpr\AlexaApp\Response\Reprompt;
use Develpr\AlexaApp\Response\Speech;
use Develpr\AlexaApp\Response\SSML;

class Alexa
{
    /**
     * @var \Develpr\AlexaApp\Contracts\AlexaRequest
     */
    private $alexaRequest;

    /**
     * @var array
     */
    private $session;

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

    /**
     * @var Contracts\DeviceProvider
     */
    private $deviceProvider;

    /**
     * @var array
     */
    private $alexaConfig;

    /**
     * @var AmazonEchoDevice | null
     */
    private $device;

    /**
     * @var bool
     */
    private $isAlexaRequest;

    /**
     * Alexa constructor.
     *
     * @param AlexaRequest   $alexaRequest
     * @param DeviceProvider $deviceProvider
     * @param array          $alexaConfig
     */
    public function __construct(AlexaRequest $alexaRequest, DeviceProvider $deviceProvider, array $alexaConfig)
    {
        $this->alexaRequest = $alexaRequest;
        $this->deviceProvider = $deviceProvider;

        $this->setupSession();
        $this->setupContext();

        $this->alexaConfig = $alexaConfig;

        $this->isAlexaRequest = $this->alexaRequest->isAlexaRequest();
    }

    /**
     * @return bool
     */
    public function isAlexaRequest()
    {
        return $this->isAlexaRequest;
    }

    /**
     * @return mixed
     */
    public function requestType()
    {
        return $this->alexaRequest->getRequestType();
    }

    /**
     * @return \Develpr\AlexaApp\Contracts\AlexaRequest
     */
    public function request()
    {
        return $this->alexaRequest;
    }

    /**
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function response()
    {
        return new AlexaResponse();
    }

    /**
     * @param string $statementWords
     * @param string $speechType
     *
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function say($statementWords, $speechType = Speech::DEFAULT_TYPE)
    {
        $response = new AlexaResponse(new Speech($statementWords, $speechType));

        return $response;
    }

    /**
     * @param string $statementWords
     * @param string $repromptWords
     * @param string $speechType
     *
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function reprompt($statementWords, $repromptWords = ' ', $speechType = Speech::DEFAULT_TYPE)
    {
        $response = $this->say($statementWords, $speechType);
        $response->setReprompt(new Reprompt($repromptWords, $speechType))->endSession(false);
        return $response;
    }

    /**
     * @param string $audioURI
     *
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function playAudio($audioURI)
    {
        $audio = new AudioFile();
        $audio->addAudioFile($audioURI);

        $response = new AlexaResponse($audio);

        return $response;
    }

    /**
     * @param $url
     * @param null $token
     * @param null $playBehavior
     * @param null $offsetInMilliseconds
     * @param null $expectedPreviousToken
     *
     * @return AlexaResponse
     */
    public function play($url, $token = null, $offsetInMilliseconds = null, $playBehavior = null, $expectedPreviousToken = null)
    {
        if(config('alexa.audio.proxy.enabled'))
        {
            $url = url(config('alexa.audio.proxy.route') . '/' . base64_encode($url));
        }

        $audio = new Play($url, $token, $offsetInMilliseconds, $playBehavior, $expectedPreviousToken);

        $response = new AlexaResponse();
        $response->setAudio($audio);

        // Cache the URL that belongs to the token so that we can resume later.
        cache([$audio->getToken() => $audio->getUrl()], 48*60);

        return $response;
    }

    /**
     * Returns a stop response
     *
     * @return AlexaResponse
     */
    public function pause()
    {
        $response = new AlexaResponse();
        $response->withDirective(new Stop());

        return $response;
    }

    /**
     * Resumes from the previous position
     *
     * @return AlexaResponse
     */
    public function resume()
    {
        $token = $this->context('AudioPlayer.token');
        $url = cache($token);
        $offset = $this->context('AudioPlayer.offsetInMilliseconds');
        return $this->play($url, $token, $offset);
    }

    /**
     * @param string $ssmlValue
     *
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function ssml($ssmlValue)
    {
        $ssml = new SSML();
        $ssml->setValue($ssmlValue);

        $response = new AlexaResponse($ssml);

        return $response;
    }

    /**
     * @param string $question
     *
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function ask($question)
    {
        $response = new AlexaResponse(new Speech($question));

        $response->setIsPrompt(true);

        return $response;
    }

    /**
     * @param string $title
     * @param string $subtitle
     * @param string $content
     *
     * @return \Develpr\AlexaApp\Response\AlexaResponse
     */
    public function card($title = '', $subtitle = '', $content = '')
    {
        $response = new AlexaResponse();

        $response->setCard(new Card($title, $subtitle, $content));

        return $response;
    }

    /**
     * @param array $attributes
     *
     * @return \Develpr\AlexaApp\Contracts\AmazonEchoDevice|null
     */
    public function device($attributes = [])
    {
        if (!$this->isAlexaRequest()) {
            return;
        }

        if (!is_null($this->device)) {
            return $this->device;
        }

        if (!array_key_exists($this->alexaConfig['device']['device_identifier'], $attributes)) {
            $attributes[$this->alexaConfig['device']['device_identifier']] = $this->alexaRequest->getUserId();
        }

        $result = $this->deviceProvider->retrieveByCredentials($attributes);

        if ($result instanceof AmazonEchoDevice) {
            $this->device = $result;
        }

        return $result;
    }

    /**
     * @param string $requestedSlot
     *
     * @return mixed|null
     */
    public function slot($requestedSlot = '')
    {
        return $this->alexaRequest->slot($requestedSlot);
    }

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

    public function updateSlot($slotName, $value, $confirmed = false) {
        return $this->alexaRequest->updateSlot($slotName, $value, $confirmed);
    }

    /**
     * @param string|null $key
     * @param mixed|null  $value
     *
     * @return array|mixed|null
     */
    public function session($key = null, $value = null)
    {
        if (!is_null($value)) {
            $this->setSession($key, $value);
        } elseif (is_null($key)) {
            return $this->session;
        } else {
            return array_key_exists($key, $this->session) ? $this->session[$key] : null;
        }
    }

    /**
     * @param string|null $key
     *
     * @return array|mixed|null
     */
    public function context($key = null)
    {
        if (is_null($key)) {
            return $this->context;
        } else {
            return array_get($this->context, $key);
        }
    }

    /**
     * @param string|array $key
     * @param mixed|null   $value
     */
    public function setSession($key, $value = null)
    {
        if (is_array($key)) {
            foreach ($key as $aKey => $aValue) {
                $this->session[$aKey] = $aValue;
            }
        } elseif (!is_null($key)) {
            $this->session[$key] = $value;
        }
    }

    /**
     * @param string $key
     */
    public function unsetSession($key)
    {
        unset($this->session[$key]);
    }

    private function setupSession()
    {
        $this->session = $this->alexaRequest->getSession();
    }

    private function setupContext()
    {
        $this->context = $this->alexaRequest->getContext();
    }
}


<?php

namespace Develpr\AlexaApp\Response\Directives\Dialog;

use Develpr\AlexaApp\Response\Directives\Directive;
use Develpr\AlexaApp\Request\AlexaRequest;

abstract class DialogDirective extends Directive
{
    /** @var AlexaRequest */
    private $alexaRequest;

    /**
     * Get the dialog directive type
     *
     * @return mixed
     */
    abstract public function getType();

    /**
     * Get the alexa request
     *
     * @return AlexaRequest
     */
    public function request()
    {
        if (!isset($this->alexaRequest)) {
            $this->alexaRequest = function_exists('app') ?
                app(AlexaRequest::class) : new AlexaRequest();
        }

        return $this->alexaRequest;
    }

    public function toArray()
    {
        return [
            'type' => $this->getType(),
            'updatedIntent' => [
                'name' => $this->request()->getIntent(),
                'confirmationStatus' => $this->request()->getConfirmationStatus(),
                'slots' => $this->request()->slots(),
            ],
        ];
    }
}


<?php

namespace Develpr\AlexaApp\Provider;

use Illuminate\Support\ServiceProvider;
use ReflectionClass;

class LumenServiceProvider extends ServiceProvider
{
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->setupConfig();
        $reflection = new ReflectionClass($this->app);
        $this->app->instance('app.middleware', $this->gatherAppMiddleware($reflection));

        $this->app->register('Develpr\AlexaApp\Provider\AlexaServiceProvider');

        $this->addRequestMiddlewareToBeginning($reflection);
    }

    protected function setupConfig()
    {
        $this->mergeConfigFrom(realpath(__DIR__.'/../../config/alexa.php'), 'alexa');
    }

    /**
     * Add the request middleware to the beginning of the middleware stack on the
     * Lumen application instance.
     *
     * @param \ReflectionClass $reflection
     */
    protected function addRequestMiddlewareToBeginning(ReflectionClass $reflection)
    {
        $property = $reflection->getProperty('middleware');
        $property->setAccessible(true);

        $middleware = $property->getValue($this->app);

        array_unshift($middleware, 'Develpr\AlexaApp\Http\Middleware\Request');

        $property->setValue($this->app, $middleware);
        $property->setAccessible(false);
    }

    /**
     * Gather the application middleware besides this one so that we can send
     * our request through them, exactly how the developer wanted.
     *
     * @param \ReflectionClass $reflection
     *
     * @return array
     */
    protected function gatherAppMiddleware(ReflectionClass $reflection)
    {
        $property = $reflection->getProperty('middleware');
        $property->setAccessible(true);

        $middleware = $property->getValue($this->app);

        return $middleware;
    }
}


<?php

namespace Develpr\AlexaApp\Http\Routing\Matching;

use Develpr\AlexaApp\Http\Routing\AlexaRoute;
use Illuminate\Http\Request;
use Illuminate\Routing\Matching\ValidatorInterface;
use Illuminate\Routing\Route;

class AlexaValidator implements ValidatorInterface
{
    /**
     * Validate a given rule against a route and request.
     *
     * @param  \Develpr\AlexaApp\Http\Routing\AlexaRoute $route
     * @param  \Develpr\AlexaApp\Request\AlexaRequest    $request
     *
     * @return bool
     */
    public function matches(Route $route, Request $request)
    {
        // If this isn't an Alexa Route then it doesn't match!
        if (!$route instanceof AlexaRoute) {
            return false;
        }

        if (!$request->isPromptResponse()) {
            return ($request->getRequestType() . $request->getIntent() == $route->getRouteIntent());
        } else {
            return ($request->getRequestType() . $request->getPromptResponseIntent() == $route->getRouteIntent());
        }
    }
}


<?php

namespace Mdb\PayPal\Ipn;

class Message
{
    /**
     * @var array
     */
    private $data;

    /**
     * @param array|string $data
     */
    public function __construct($data = [])
    {
        if (!is_array($data)) {
            $data = $this->extractDataFromRawPostDataString($data);
        }

        $this->data = $data;
    }

    /**
     * @param string $key
     *
     * @return string
     */
    public function get($key)
    {
        $value = '';

        if (isset($this->data[$key])) {
            $value = $this->data[$key];
        }

        return $value;
    }

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

    /**
     * @return string
     */
    public function __toString()
    {
        return http_build_query($this->getAll(), null, '&');
    }

    /**
     * @param string $rawPostDataString
     *
     * @return array
     */
    private function extractDataFromRawPostDataString($rawPostDataString)
    {
        parse_str($rawPostDataString, $data);

        return $data;
    }
}


<?php

namespace Develpr\AlexaApp\Response\Directives\AudioPlayer;

use Develpr\AlexaApp\Response\Directives\Directive;

class ClearQueue extends Directive
{
    const TYPE = 'AudioPlayer.ClearQueue';

    const DEFAULT_CLEAR_BEHAVIOR = 'CLEAR_ALL';

    private $validClearBehaviors = ['CLEAR_ENQUEUED', 'CLEAR_ALL'];

    /**
     * Get the instance as an array.
     *
     * @return array
     */
    public function toArray()
    {
        $clearBehaviorAsArray['type'] = self::TYPE;
        $this->addAttributeToArray('clearBehavior', $clearBehaviorAsArray);

        return $clearBehaviorAsArray;
    }

    /**
     * @param string $type
     *
     * @throws \Exception
     *
     * @return $this
     */
    public function setClearBehavior($clearBehavior)
    {
        if (!in_array($clearBehavior, $this->validClearBehaviors)) {
            throw new \Exception('Invalid clear behavior supplied');
        }

        $this->clearBehavior = $clearBehavior;

        return $this;
    }

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


<?php

namespace Develpr\AlexaApp\Http\Routing;

use Develpr\AlexaApp\Response\AlexaResponse;
use Illuminate\Routing\Router as IlluminateRouter;
use Alexa;

class AlexaRouter extends IlluminateRouter
{
    private $intentRoutes = [];

    private $audioIntents = [
        'AMAZON.PauseIntent',
        'AMAZON.ResumeIntent',
        'AMAZON.CancelIntent',
        'AMAZON.RepeatIntent',
        'AMAZON.NextIntent',
        'AMAZON.PreviousIntent',
        'AMAZON.LoopOnIntent',
        'AMAZON.LoopOffIntent',
        'AMAZON.ShuffleOnIntent',
        'AMAZON.ShuffleOffIntent',
        'AMAZON.StartOverIntent'
    ];

    private $audioEvents = [
        'PlaybackStarted',
        'PlaybackStopped',
        'PlaybackFinished',
        'PlaybackNearlyFinished',
        'PlaybackFailed'
    ];

    /**
     * @param string                     $uri
     * @param string                     $intent
     * @param \Closure|array|string|null $action
     *
     * @return $this
     */
    public function intent($uri, $intent, $action)
    {
        $this->intentRoutes[] = $uri;
        $this->addAlexaRoute('POST', $this->prefix($uri), 'IntentRequest' . $intent, $action);

        return $this;
    }

    /**
     * @param string                     $uri
     * @param \Closure|array|string|null $action
     *
     * @return $this
     */
    public function launch($uri, $action)
    {
        $this->intentRoutes[] = $uri;
        $this->addAlexaRoute('POST', $this->prefix($uri), 'LaunchRequest', $action);

        return $this;
    }

    /**
     * @param string                     $uri
     * @param \Closure|array|string|null $action
     *
     * @return $this
     */
    public function sessionEnded($uri, $action)
    {
        $this->intentRoutes[] = $uri;
        $this->addAlexaRoute('POST', $this->prefix($uri), 'SessionEndedRequest', $action);

        return $this;
    }

    /**
     * @param string                     $uri
     * @param string                     $event
     * @param \Closure|array|string|null $action
     *
     * @return $this
     */
    public function audioPlayer($uri, $event, $action)
    {
        $this->intentRoutes[] = $uri;
        $this->addAlexaRoute('POST', $this->prefix($uri), 'AudioPlayer.' . $event, $action);

        return $this;
    }

    /**
     * Adds the routes needed (or optional) for audio playing with empty responses
     * Can/should be overridden by detailled routes
     *
     * This just enables you to quickly start audio playback
     *
     * @param $uri
     */
    public function audioPlayerRoutes($uri)
    {
        foreach($this->audioEvents as $audioEvent)
        {
            $this->audioPlayer($uri, $audioEvent, function(){});
        }

        foreach($this->audioIntents as $audioIntent)
        {
            switch($audioIntent)
            {
                case 'AMAZON.PauseIntent':
                    $this->intent($uri, $audioIntent, function(){return Alexa::pause();});
                    break;
                case 'AMAZON.ResumeIntent':
                    $this->intent($uri, $audioIntent, function(){return Alexa::resume();});
                    break;
                default:
                    $this->intent($uri, $audioIntent, function(){return new AlexaResponse();});

            }
        }

        $this->sessionEnded($uri, function(){return new AlexaResponse();});
    }

    /**
     * Add a route to the underlying route collection.
     *
     * @param array|string               $methods
     * @param string                     $uri
     * @param string                     $intent
     * @param \Closure|array|string|null $action
     *
     * @return \Illuminate\Routing\Route
     */
    protected function addAlexaRoute($methods, $uri, $intent, $action)
    {
        return $this->routes->add($this->createAlexaRoute($methods, $uri, $intent, $action));
    }

    /**
     * Create a new Route object.
     *
     * @param array|string               $methods
     * @param string                     $uri
     * @param string                     $intent
     * @param \Closure|array|string|null $action
     *
     * @return \Illuminate\Routing\Route
     */
    protected function newAlexaRoute($methods, $uri, $intent, $action)
    {
        return (new AlexaRoute($methods, $uri, $intent, $action))->setContainer($this->container)->setRouter($this);
    }

    /**
     * Create a new route instance.
     *
     * @param array|string               $methods
     * @param string                     $uri
     * @param string                     $intent
     * @param \Closure|array|string|null $action
     *
     * @return \Illuminate\Routing\Route
     */
    protected function createAlexaRoute($methods, $uri, $intent, $action)
    {
        // If the route is routing to a controller we will parse the route action into
        // an acceptable array format before registering it and creating this route
        // instance itself. We need to build the Closure that will call this out.
        if ($this->actionReferencesController($action)) {
            $action = $this->convertToControllerAction($action);
        }

        $route = $this->newAlexaRoute(
            $methods, $this->prefix($uri), $intent, $action
        );

        // If we have groups that need to be merged, we will merge them now after this
        // route has already been created and is ready to go. After we're done with
        // the merge we will be ready to return the route back out to the caller.
        if ($this->hasGroupStack()) {
            $this->mergeGroupAttributesIntoRoute($route);
        }

        $this->addWhereClausesToRoute($route);

        return $route;
    }
}