Phalcon Framework 3.4.1

Elasticsearch\Common\Exceptions\Forbidden403Exception: {"Message":"User: anonymous is not authorized to perform: es:ESHttpGet"}

/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php (605)
#0Elasticsearch\Connections\Connection->process4xxError(Array([http_method] => GET, [scheme] => http, [uri] => /drooble_users/_search?from=0&size=11, [body] => {"query":{"bool":{"should":[{"match":{"tags.instruments_primary.en":"Keyboard"}},{"match":{"tags.instruments_secondary.en":"Keyboard"}}],"minimum_should_match":1}},"sort":[{"_score":"desc"}]}, [headers] => Array([Host] => Array([0] => search-drooble1-irvhqoehqpblnqmd5va6n5pmsy.us-west-2.es.amazonaws.com:80), [Content-Type] => Array([0] => application/json), [Accept] => Array([0] => application/json))), Array([transfer_stats] => Array(28), [curl] => Array([error] => (empty string), [errno] => 0), [effective_url] => http://search-drooble1-irvhqoehqpblnqmd5va6n5pmsy.us-west-2.es.amazonaws.com:80/drooble_users/_search?from=0&size=11, [headers] => Array([Date] => Array([0] => Mon, 24 Jan 2022 22:32:35 GMT), [Content-Type] => Array([0] => application/json), [Content-Length] => Array([0] => 72), [Connection] => Array([0] => close), [x-amzn-RequestId] => Array([0] => 9d814693-46a8-4cc5-80bf-a3c4627b46a5), [Access-Control-Allow-Origin] => Array([0] => *)), [version] => 1.1, [status] => 403, [reason] => Forbidden, [body] => {"Message":"User: anonymous is not authorized to perform: es:ESHttpGet"}), Array())
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php (279)
<?php
 
namespace Elasticsearch\Connections;
 
use Elasticsearch\Common\Exceptions\AlreadyExpiredException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Conflict409Exception;
use Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost;
use Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException;
use Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException;
use Elasticsearch\Common\Exceptions\Forbidden403Exception;
use Elasticsearch\Common\Exceptions\MaxRetriesException;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\NoDocumentsToGetException;
use Elasticsearch\Common\Exceptions\NoShardAvailableException;
use Elasticsearch\Common\Exceptions\RequestTimeout408Exception;
use Elasticsearch\Common\Exceptions\RoutingMissingException;
use Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException;
use Elasticsearch\Common\Exceptions\ServerErrorResponseException;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use Psr\Log\LoggerInterface;
 
/**
 * Class AbstractConnection
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <[email protected]>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Connection implements ConnectionInterface
{
    /** @var  callable */
    protected $handler;
 
    /** @var SerializerInterface */
    protected $serializer;
 
    /**
     * @var string
     */
    protected $transportSchema = 'http';    // TODO depreciate this default
 
    /**
     * @var string
     */
    protected $host;
 
    /**
     * @var string || null
     */
    protected $path;
 
    /**
     * @var LoggerInterface
     */
    protected $log;
 
    /**
     * @var LoggerInterface
     */
    protected $trace;
 
    /**
     * @var array
     */
    protected $connectionParams;
 
    /** @var  array */
    protected $headers = [];
 
    /** @var bool  */
    protected $isAlive = false;
 
    /** @var float  */
    private $pingTimeout = 1;    //TODO expose this
 
    /** @var int  */
    private $lastPing = 0;
 
    /** @var int  */
    private $failedPings = 0;
 
    private $lastRequest = array();
 
    /**
     * Constructor
     *
     * @param $handler
     * @param array $hostDetails
     * @param array $connectionParams Array of connection-specific parameters
     * @param \Elasticsearch\Serializers\SerializerInterface $serializer
     * @param \Psr\Log\LoggerInterface $log              Logger object
     * @param \Psr\Log\LoggerInterface $trace
     */
    public function __construct(
        $handler,
        $hostDetails,
        $connectionParams,
        SerializerInterface $serializer,
        LoggerInterface $log,
        LoggerInterface $trace
    ) {
    
        if (isset($hostDetails['port']) !== true) {
            $hostDetails['port'] = 9200;
        }
 
        if (isset($hostDetails['scheme'])) {
            $this->transportSchema = $hostDetails['scheme'];
        }
 
        if (isset($hostDetails['user']) && isset($hostDetails['pass'])) {
            $connectionParams['client']['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
            $connectionParams['client']['curl'][CURLOPT_USERPWD] = $hostDetails['user'].':'.$hostDetails['pass'];
        }
 
        if (isset($connectionParams['client']['headers']) === true) {
            $this->headers = $connectionParams['client']['headers'];
            unset($connectionParams['client']['headers']);
        }
 
        $host = $hostDetails['host'].':'.$hostDetails['port'];
        $path = null;
        if (isset($hostDetails['path']) === true) {
            $path = $hostDetails['path'];
        }
        $this->host             = $host;
        $this->path             = $path;
        $this->log              = $log;
        $this->trace            = $trace;
        $this->connectionParams = $connectionParams;
        $this->serializer       = $serializer;
 
        $this->handler = $this->wrapHandler($handler, $log, $trace);
    }
 
    /**
     * @param $method
     * @param $uri
     * @param null $params
     * @param null $body
     * @param array $options
     * @param \Elasticsearch\Transport $transport
     * @return mixed
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport = null)
    {
        if (isset($body) === true) {
            $body = $this->serializer->serialize($body);
        }
 
        $request = [
            'http_method' => $method,
            'scheme'      => $this->transportSchema,
            'uri'         => $this->getURI($uri, $params),
            'body'        => $body,
            'headers'     => array_merge([
                'Host'  => [$this->host]
            ], $this->headers)
        ];
 
        $request = array_replace_recursive($request, $this->connectionParams, $options);
 
        // RingPHP does not like if client is empty
        if (empty($request['client'])) {
            unset($request['client']);
        }
 
        $handler = $this->handler;
        $future = $handler($request, $this, $transport, $options);
 
        return $future;
    }
 
    /** @return string */
    public function getTransportSchema()
    {
        return $this->transportSchema;
    }
 
    /** @return array */
    public function getLastRequestInfo()
    {
        return $this->lastRequest;
    }
 
    private function wrapHandler(callable $handler, LoggerInterface $logger, LoggerInterface $tracer)
    {
        return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler, $logger, $tracer) {
 
            $this->lastRequest = [];
            $this->lastRequest['request'] = $request;
 
            // Send the request using the wrapped handler.
            $response =  Core::proxy($handler($request), function ($response) use ($connection, $transport, $logger, $tracer, $request, $options) {
 
                $this->lastRequest['response'] = $response;
 
                if (isset($response['error']) === true) {
                    if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) {
                        $this->log->warning("Curl exception encountered.");
 
                        $exception = $this->getCurlRetryException($request, $response);
 
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
 
                        $node = $connection->getHost();
                        $this->log->warning("Marking node $node dead.");
                        $connection->markDead();
 
                        // If the transport has not been set, we are inside a Ping or Sniff,
                        // so we don't want to retrigger retries anyway.
                        //
                        // TODO this could be handled better, but we are limited because connectionpools do not
                        // have access to Transport.  Architecturally, all of this needs to be refactored
                        if (isset($transport) === true) {
                            $transport->connectionPool->scheduleCheck();
 
                            $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false;
                            $shouldRetry = $transport->shouldRetry($request);
                            $shouldRetryText = ($shouldRetry) ? 'true' : 'false';
 
                            $this->log->warning("Retries left? $shouldRetryText");
                            if ($shouldRetry && !$neverRetry) {
                                return $transport->performRequest(
                                    $request['http_method'],
                                    $request['uri'],
                                    [],
                                    $request['body'],
                                    $options
                                );
                            }
                        }
 
                        $this->log->warning("Out of retries, throwing exception from $node");
                        // Only throw if we run out of retries
                        throw $exception;
                    } else {
                        // Something went seriously wrong, bail
                        $exception = new TransportException($response['error']->getMessage());
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
                        throw $exception;
                    }
                } else {
                    $connection->markAlive();
 
                    if (isset($response['body']) === true) {
                        $response['body'] = stream_get_contents($response['body']);
                        $this->lastRequest['response']['body'] = $response['body'];
                    }
 
                    if ($response['status'] >= 400 && $response['status'] < 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process4xxError($request, $response, $ignore);
                    } elseif ($response['status'] >= 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process5xxError($request, $response, $ignore);
                    }
 
                    // No error, deserialize
                    $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
                }
                $this->logRequestSuccess(
                    $request['http_method'],
                    $response['effective_url'],
                    $request['body'],
                    $request['headers'],
                    $response['status'],
                    $response['body'],
                    $response['transfer_stats']['total_time']
                );
 
                return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body'];
            });
 
            return $response;
        };
    }
 
    /**
     * @param string $uri
     * @param array $params
     *
     * @return string
     */
    private function getURI($uri, $params)
    {
        if (isset($params) === true && !empty($params)) {
            array_walk($params, function (&$value, &$key) {
                if ($value === true) {
                    $value = 'true';
                } elseif ($value === false) {
                    $value = 'false';
                }
            });
 
            $uri .= '?' . http_build_query($params);
        }
 
        if ($this->path !== null) {
            $uri = $this->path . $uri;
        }
 
        return $uri;
    }
 
    /**
     * Log a successful request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array  $headers
     * @param string $statusCode
     * @param string $response
     * @param string $duration
     *
     * @return void
     */
    public function logRequestSuccess($method, $fullURI, $body, $headers, $statusCode, $response, $duration)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->info(
            'Request Success:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
        $this->log->debug('Response', array($response));
 
        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }
 
    /**
     * Log a a failed request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array $headers
     * @param null|string $statusCode
     * @param null|string $response
     * @param string $duration
     * @param \Exception|null $exception
     *
     * @return void
     */
    public function logRequestFail($method, $fullURI, $body, $headers, $statusCode, $response, $duration, \Exception $exception)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->warning(
            'Request Failure:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
                'error'     => $exception->getMessage(),
            )
        );
        $this->log->warning('Response', array($response));
 
        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }
 
    /**
     * @return bool
     */
    public function ping()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true,
                'verbose' => true
            ]
        ];
        try {
            $response = $this->performRequest('HEAD', '/', null, null, $options);
            $response = $response->wait();
        } catch (TransportException $exception) {
            $this->markDead();
 
            return false;
        }
 
        if ($response['status'] === 200) {
            $this->markAlive();
 
            return true;
        } else {
            $this->markDead();
 
            return false;
        }
    }
 
    /**
     * @return array
     */
    public function sniff()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true
            ]
        ];
 
        return $this->performRequest('GET', '/_nodes/', null, null, $options);
    }
 
    /**
     * @return bool
     */
    public function isAlive()
    {
        return $this->isAlive;
    }
 
    public function markAlive()
    {
        $this->failedPings = 0;
        $this->isAlive = true;
        $this->lastPing = time();
    }
 
    public function markDead()
    {
        $this->isAlive = false;
        $this->failedPings += 1;
        $this->lastPing = time();
    }
 
    /**
     * @return int
     */
    public function getLastPing()
    {
        return $this->lastPing;
    }
 
    /**
     * @return int
     */
    public function getPingFailures()
    {
        return $this->failedPings;
    }
 
    /**
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }
 
    /**
     * @return null|string
     */
    public function getUserPass()
    {
        if (isset($this->connectionParams['client']['curl'][CURLOPT_USERPWD]) === true) {
            return $this->connectionParams['client']['curl'][CURLOPT_USERPWD];
        }
        return null;
    }
 
    /**
     * @return null|string
     */
    public function getPath()
    {
        return $this->path;
    }
 
    /**
     * @param $request
     * @param $response
     * @return \Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost|\Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException|\Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException|\Elasticsearch\Common\Exceptions\MaxRetriesException
     */
    protected function getCurlRetryException($request, $response)
    {
        $exception = null;
        $message = $response['error']->getMessage();
        $exception = new MaxRetriesException($message);
        switch ($response['curl']['errno']) {
            case 6:
                $exception = new CouldNotResolveHostException($message, null, $exception);
                break;
            case 7:
                $exception = new CouldNotConnectToHost($message, null, $exception);
                break;
            case 28:
                $exception = new OperationTimeoutException($message, null, $exception);
                break;
        }
 
        return $exception;
    }
 
    /**
     * Construct a string cURL command
     *
     * @param string $method HTTP method
     * @param string $uri    Full URI of request
     * @param string $body   Request body
     *
     * @return string
     */
    private function buildCurlCommand($method, $uri, $body)
    {
        if (strpos($uri, '?') === false) {
            $uri .= '?pretty=true';
        } else {
            str_replace('?', '?pretty=true', $uri);
        }
 
        $curlCommand = 'curl -X' . strtoupper($method);
        $curlCommand .= " '" . $uri . "'";
 
        if (isset($body) === true && $body !== '') {
            $curlCommand .= " -d '" . $body . "'";
        }
 
        return $curlCommand;
    }
 
    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\AlreadyExpiredException|\Elasticsearch\Common\Exceptions\BadRequest400Exception|\Elasticsearch\Common\Exceptions\Conflict409Exception|\Elasticsearch\Common\Exceptions\Forbidden403Exception|\Elasticsearch\Common\Exceptions\Missing404Exception|\Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException|null
     */
    private function process4xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];
 
        /** @var \Exception $exception */
        $exception = $this->tryDeserialize400Error($response);
 
        if (array_search($response['status'], $ignore) !== false) {
            return;
        }
 
        if ($statusCode === 400 && strpos($responseBody, "AlreadyExpiredException") !== false) {
            $exception = new AlreadyExpiredException($responseBody, $statusCode);
        } elseif ($statusCode === 403) {
            $exception = new Forbidden403Exception($responseBody, $statusCode);
        } elseif ($statusCode === 404) {
            $exception = new Missing404Exception($responseBody, $statusCode);
        } elseif ($statusCode === 409) {
            $exception = new Conflict409Exception($responseBody, $statusCode);
        } elseif ($statusCode === 400 && strpos($responseBody, 'script_lang not supported') !== false) {
            $exception = new ScriptLangNotSupportedException($responseBody. $statusCode);
        } elseif ($statusCode === 408) {
            $exception = new RequestTimeout408Exception($responseBody, $statusCode);
        } else {
            $exception = new BadRequest400Exception($responseBody, $statusCode);
        }
 
        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );
 
        throw $exception;
    }
 
    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\NoDocumentsToGetException|\Elasticsearch\Common\Exceptions\NoShardAvailableException|\Elasticsearch\Common\Exceptions\RoutingMissingException|\Elasticsearch\Common\Exceptions\ServerErrorResponseException
     */
    private function process5xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];
 
        /** @var \Exception $exception */
        $exception = $this->tryDeserialize500Error($response);
 
        $exceptionText = "[$statusCode Server Exception] ".$exception->getMessage();
        $this->log->error($exceptionText);
        $this->log->error($exception->getTraceAsString());
 
        if (array_search($statusCode, $ignore) !== false) {
            return;
        }
 
        if ($statusCode === 500 && strpos($responseBody, "RoutingMissingException") !== false) {
            $exception = new RoutingMissingException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && preg_match('/ActionRequestValidationException.+ no documents to get/', $responseBody) === 1) {
            $exception = new NoDocumentsToGetException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && strpos($responseBody, 'NoShardAvailableActionException') !== false) {
            $exception = new NoShardAvailableException($exception->getMessage(), $statusCode, $exception);
        } else {
            $exception = new ServerErrorResponseException($responseBody, $statusCode);
        }
 
        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );
 
        throw $exception;
    }
 
    private function tryDeserialize400Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\BadRequest400Exception');
    }
 
    private function tryDeserialize500Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\ServerErrorResponseException');
    }
 
    private function tryDeserializeError($response, $errorClass)
    {
        $error = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
        if (is_array($error) === true) {
            // 2.0 structured exceptions
            if (isset($error['error']['reason']) === true) {
                // Try to use root cause first (only grabs the first root cause)
                $root = $error['error']['root_cause'];
                if (isset($root) && isset($root[0])) {
                    $cause = $root[0]['reason'];
                    $type = $root[0]['type'];
                } else {
                    $cause = $error['error']['reason'];
                    $type = $error['error']['type'];
                }
 
                $original = new $errorClass($response['body'], $response['status']);
 
                return new $errorClass("$type: $cause", $response['status'], $original);
            } elseif (isset($error['error']) === true) {
                // <2.0 semi-structured exceptions
                $original = new $errorClass($response['body'], $response['status']);
 
                return new $errorClass($error['error'], $response['status'], $original);
            }
 
            // <2.0 "i just blew up" nonstructured exception
            // $error is an array but we don't know the format, reuse the response body instead
            return new $errorClass($response['body'], $response['status']);
        }
 
        // <2.0 "i just blew up" nonstructured exception
        return new $errorClass($response['body']);
    }
}
#1Elasticsearch\Connections\Connection->Elasticsearch\Connections\{closure}(Array([transfer_stats] => Array(28), [curl] => Array([error] => (empty string), [errno] => 0), [effective_url] => http://search-drooble1-irvhqoehqpblnqmd5va6n5pmsy.us-west-2.es.amazonaws.com:80/drooble_users/_search?from=0&size=11, [headers] => Array([Date] => Array([0] => Mon, 24 Jan 2022 22:32:35 GMT), [Content-Type] => Array([0] => application/json), [Content-Length] => Array([0] => 72), [Connection] => Array([0] => close), [x-amzn-RequestId] => Array([0] => 9d814693-46a8-4cc5-80bf-a3c4627b46a5), [Access-Control-Allow-Origin] => Array([0] => *)), [version] => 1.1, [status] => 403, [reason] => Forbidden, [body] => {"Message":"User: anonymous is not authorized to perform: es:ESHttpGet"}))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/FulfilledPromise.php (25)
<?php
 
namespace React\Promise;
 
class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $value;
 
    public function __construct($value = null)
    {
        if ($value instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
        }
 
        $this->value = $value;
    }
 
    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onFulfilled) {
            return $this;
        }
 
        try {
            return resolve($onFulfilled($this->value));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        } catch (\Exception $exception) {
            return new RejectedPromise($exception);
        }
    }
 
    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onFulfilled) {
            return;
        }
 
        $result = $onFulfilled($this->value);
 
        if ($result instanceof ExtendedPromiseInterface) {
            $result->done();
        }
    }
 
    public function otherwise(callable $onRejected)
    {
        return $this;
    }
 
    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(function ($value) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        });
    }
 
    public function progress(callable $onProgress)
    {
        return $this;
    }
 
    public function cancel()
    {
    }
}
#2React\Promise\FulfilledPromise->then(Object(Closure), null, null)
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php (55)
<?php
namespace GuzzleHttp\Ring\Future;
 
use React\Promise\FulfilledPromise;
use React\Promise\RejectedPromise;
 
/**
 * Represents a future value that has been resolved or rejected.
 */
class CompletedFutureValue implements FutureInterface
{
    protected $result;
    protected $error;
 
    private $cachedPromise;
 
    /**
     * @param mixed      $result Resolved result
     * @param \Exception $e      Error. Pass a GuzzleHttp\Ring\Exception\CancelledFutureAccessException
     *                           to mark the future as cancelled.
     */
    public function __construct($result, \Exception $e = null)
    {
        $this->result = $result;
        $this->error = $e;
    }
 
    public function wait()
    {
        if ($this->error) {
            throw $this->error;
        }
 
        return $this->result;
    }
 
    public function cancel() {}
 
    public function promise()
    {
        if (!$this->cachedPromise) {
            $this->cachedPromise = $this->error
                ? new RejectedPromise($this->error)
                : new FulfilledPromise($this->result);
        }
 
        return $this->cachedPromise;
    }
 
    public function then(
        callable $onFulfilled = null,
        callable $onRejected = null,
        callable $onProgress = null
    ) {
        return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
    }
}
#3GuzzleHttp\Ring\Future\CompletedFutureValue->then(Object(Closure), null, null)
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Core.php (341)
<?php
namespace GuzzleHttp\Ring;
 
use GuzzleHttp\Stream\StreamInterface;
use GuzzleHttp\Ring\Future\FutureArrayInterface;
use GuzzleHttp\Ring\Future\FutureArray;
 
/**
 * Provides core functionality of Ring handlers and middleware.
 */
class Core
{
    /**
     * Returns a function that calls all of the provided functions, in order,
     * passing the arguments provided to the composed function to each function.
     *
     * @param callable[] $functions Array of functions to proxy to.
     *
     * @return callable
     */
    public static function callArray(array $functions)
    {
        return function () use ($functions) {
            $args = func_get_args();
            foreach ($functions as $fn) {
                call_user_func_array($fn, $args);
            }
        };
    }
 
    /**
     * Gets an array of header line values from a message for a specific header
     *
     * This method searches through the "headers" key of a message for a header
     * using a case-insensitive search.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to retrieve
     *
     * @return array
     */
    public static function headerLines($message, $header)
    {
        $result = [];
 
        if (!empty($message['headers'])) {
            foreach ($message['headers'] as $name => $value) {
                if (!strcasecmp($name, $header)) {
                    $result = array_merge($result, $value);
                }
            }
        }
 
        return $result;
    }
 
    /**
     * Gets a header value from a message as a string or null
     *
     * This method searches through the "headers" key of a message for a header
     * using a case-insensitive search. The lines of the header are imploded
     * using commas into a single string return value.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to retrieve
     *
     * @return string|null Returns the header string if found, or null if not.
     */
    public static function header($message, $header)
    {
        $match = self::headerLines($message, $header);
        return $match ? implode(', ', $match) : null;
    }
 
    /**
     * Returns the first header value from a message as a string or null. If
     * a header line contains multiple values separated by a comma, then this
     * function will return the first value in the list.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to retrieve
     *
     * @return string|null Returns the value as a string if found.
     */
    public static function firstHeader($message, $header)
    {
        if (!empty($message['headers'])) {
            foreach ($message['headers'] as $name => $value) {
                if (!strcasecmp($name, $header)) {
                    // Return the match itself if it is a single value.
                    $pos = strpos($value[0], ',');
                    return $pos ? substr($value[0], 0, $pos) : $value[0];
                }
            }
        }
 
        return null;
    }
 
    /**
     * Returns true if a message has the provided case-insensitive header.
     *
     * @param array  $message Request or response hash.
     * @param string $header  Header to check
     *
     * @return bool
     */
    public static function hasHeader($message, $header)
    {
        if (!empty($message['headers'])) {
            foreach ($message['headers'] as $name => $value) {
                if (!strcasecmp($name, $header)) {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    /**
     * Parses an array of header lines into an associative array of headers.
     *
     * @param array $lines Header lines array of strings in the following
     *                     format: "Name: Value"
     * @return array
     */
    public static function headersFromLines($lines)
    {
        $headers = [];
 
        foreach ($lines as $line) {
            $parts = explode(':', $line, 2);
            $headers[trim($parts[0])][] = isset($parts[1])
                ? trim($parts[1])
                : null;
        }
 
        return $headers;
    }
 
    /**
     * Removes a header from a message using a case-insensitive comparison.
     *
     * @param array  $message Message that contains 'headers'
     * @param string $header  Header to remove
     *
     * @return array
     */
    public static function removeHeader(array $message, $header)
    {
        if (isset($message['headers'])) {
            foreach (array_keys($message['headers']) as $key) {
                if (!strcasecmp($header, $key)) {
                    unset($message['headers'][$key]);
                }
            }
        }
 
        return $message;
    }
 
    /**
     * Replaces any existing case insensitive headers with the given value.
     *
     * @param array  $message Message that contains 'headers'
     * @param string $header  Header to set.
     * @param array  $value   Value to set.
     *
     * @return array
     */
    public static function setHeader(array $message, $header, array $value)
    {
        $message = self::removeHeader($message, $header);
        $message['headers'][$header] = $value;
 
        return $message;
    }
 
    /**
     * Creates a URL string from a request.
     *
     * If the "url" key is present on the request, it is returned, otherwise
     * the url is built up based on the scheme, host, uri, and query_string
     * request values.
     *
     * @param array $request Request to get the URL from
     *
     * @return string Returns the request URL as a string.
     * @throws \InvalidArgumentException if no Host header is present.
     */
    public static function url(array $request)
    {
        if (isset($request['url'])) {
            return $request['url'];
        }
 
        $uri = (isset($request['scheme'])
                ? $request['scheme'] : 'http') . '://';
 
        if ($host = self::header($request, 'host')) {
            $uri .= $host;
        } else {
            throw new \InvalidArgumentException('No Host header was provided');
        }
 
        if (isset($request['uri'])) {
            $uri .= $request['uri'];
        }
 
        if (isset($request['query_string'])) {
            $uri .= '?' . $request['query_string'];
        }
 
        return $uri;
    }
 
    /**
     * Reads the body of a message into a string.
     *
     * @param array|FutureArrayInterface $message Array containing a "body" key
     *
     * @return null|string Returns the body as a string or null if not set.
     * @throws \InvalidArgumentException if a request body is invalid.
     */
    public static function body($message)
    {
        if (!isset($message['body'])) {
            return null;
        }
 
        if ($message['body'] instanceof StreamInterface) {
            return (string) $message['body'];
        }
 
        switch (gettype($message['body'])) {
            case 'string':
                return $message['body'];
            case 'resource':
                return stream_get_contents($message['body']);
            case 'object':
                if ($message['body'] instanceof \Iterator) {
                    return implode('', iterator_to_array($message['body']));
                } elseif (method_exists($message['body'], '__toString')) {
                    return (string) $message['body'];
                }
            default:
                throw new \InvalidArgumentException('Invalid request body: '
                    . self::describeType($message['body']));
        }
    }
 
    /**
     * Rewind the body of the provided message if possible.
     *
     * @param array $message Message that contains a 'body' field.
     *
     * @return bool Returns true on success, false on failure
     */
    public static function rewindBody($message)
    {
        if ($message['body'] instanceof StreamInterface) {
            return $message['body']->seek(0);
        }
 
        if ($message['body'] instanceof \Generator) {
            return false;
        }
 
        if ($message['body'] instanceof \Iterator) {
            $message['body']->rewind();
            return true;
        }
 
        if (is_resource($message['body'])) {
            return rewind($message['body']);
        }
 
        return is_string($message['body'])
            || (is_object($message['body'])
                && method_exists($message['body'], '__toString'));
    }
 
    /**
     * Debug function used to describe the provided value type and class.
     *
     * @param mixed $input
     *
     * @return string Returns a string containing the type of the variable and
     *                if a class is provided, the class name.
     */
    public static function describeType($input)
    {
        switch (gettype($input)) {
            case 'object':
                return 'object(' . get_class($input) . ')';
            case 'array':
                return 'array(' . count($input) . ')';
            default:
                ob_start();
                var_dump($input);
                // normalize float vs double
                return str_replace('double(', 'float(', rtrim(ob_get_clean()));
        }
    }
 
    /**
     * Sleep for the specified amount of time specified in the request's
     * ['client']['delay'] option if present.
     *
     * This function should only be used when a non-blocking sleep is not
     * possible.
     *
     * @param array $request Request to sleep
     */
    public static function doSleep(array $request)
    {
        if (isset($request['client']['delay'])) {
            usleep($request['client']['delay'] * 1000);
        }
    }
 
    /**
     * Returns a proxied future that modifies the dereferenced value of another
     * future using a promise.
     *
     * @param FutureArrayInterface $future      Future to wrap with a new future
     * @param callable    $onFulfilled Invoked when the future fulfilled
     * @param callable    $onRejected  Invoked when the future rejected
     * @param callable    $onProgress  Invoked when the future progresses
     *
     * @return FutureArray
     */
    public static function proxy(
        FutureArrayInterface $future,
        callable $onFulfilled = null,
        callable $onRejected = null,
        callable $onProgress = null
    ) {
        return new FutureArray(
            $future->then($onFulfilled, $onRejected, $onProgress),
            [$future, 'wait'],
            [$future, 'cancel']
        );
    }
 
    /**
     * Returns a debug stream based on the provided variable.
     *
     * @param mixed $value Optional value
     *
     * @return resource
     */
    public static function getDebugResource($value = null)
    {
        if (is_resource($value)) {
            return $value;
        } elseif (defined('STDOUT')) {
            return STDOUT;
        } else {
            return fopen('php://output', 'w');
        }
    }
}
#4GuzzleHttp\Ring\Core::proxy(Object(GuzzleHttp\Ring\Future\CompletedFutureArray), Object(Closure))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php (299)
<?php
 
namespace Elasticsearch\Connections;
 
use Elasticsearch\Common\Exceptions\AlreadyExpiredException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Conflict409Exception;
use Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost;
use Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException;
use Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException;
use Elasticsearch\Common\Exceptions\Forbidden403Exception;
use Elasticsearch\Common\Exceptions\MaxRetriesException;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\NoDocumentsToGetException;
use Elasticsearch\Common\Exceptions\NoShardAvailableException;
use Elasticsearch\Common\Exceptions\RequestTimeout408Exception;
use Elasticsearch\Common\Exceptions\RoutingMissingException;
use Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException;
use Elasticsearch\Common\Exceptions\ServerErrorResponseException;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use Psr\Log\LoggerInterface;
 
/**
 * Class AbstractConnection
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <[email protected]>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Connection implements ConnectionInterface
{
    /** @var  callable */
    protected $handler;
 
    /** @var SerializerInterface */
    protected $serializer;
 
    /**
     * @var string
     */
    protected $transportSchema = 'http';    // TODO depreciate this default
 
    /**
     * @var string
     */
    protected $host;
 
    /**
     * @var string || null
     */
    protected $path;
 
    /**
     * @var LoggerInterface
     */
    protected $log;
 
    /**
     * @var LoggerInterface
     */
    protected $trace;
 
    /**
     * @var array
     */
    protected $connectionParams;
 
    /** @var  array */
    protected $headers = [];
 
    /** @var bool  */
    protected $isAlive = false;
 
    /** @var float  */
    private $pingTimeout = 1;    //TODO expose this
 
    /** @var int  */
    private $lastPing = 0;
 
    /** @var int  */
    private $failedPings = 0;
 
    private $lastRequest = array();
 
    /**
     * Constructor
     *
     * @param $handler
     * @param array $hostDetails
     * @param array $connectionParams Array of connection-specific parameters
     * @param \Elasticsearch\Serializers\SerializerInterface $serializer
     * @param \Psr\Log\LoggerInterface $log              Logger object
     * @param \Psr\Log\LoggerInterface $trace
     */
    public function __construct(
        $handler,
        $hostDetails,
        $connectionParams,
        SerializerInterface $serializer,
        LoggerInterface $log,
        LoggerInterface $trace
    ) {
    
        if (isset($hostDetails['port']) !== true) {
            $hostDetails['port'] = 9200;
        }
 
        if (isset($hostDetails['scheme'])) {
            $this->transportSchema = $hostDetails['scheme'];
        }
 
        if (isset($hostDetails['user']) && isset($hostDetails['pass'])) {
            $connectionParams['client']['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
            $connectionParams['client']['curl'][CURLOPT_USERPWD] = $hostDetails['user'].':'.$hostDetails['pass'];
        }
 
        if (isset($connectionParams['client']['headers']) === true) {
            $this->headers = $connectionParams['client']['headers'];
            unset($connectionParams['client']['headers']);
        }
 
        $host = $hostDetails['host'].':'.$hostDetails['port'];
        $path = null;
        if (isset($hostDetails['path']) === true) {
            $path = $hostDetails['path'];
        }
        $this->host             = $host;
        $this->path             = $path;
        $this->log              = $log;
        $this->trace            = $trace;
        $this->connectionParams = $connectionParams;
        $this->serializer       = $serializer;
 
        $this->handler = $this->wrapHandler($handler, $log, $trace);
    }
 
    /**
     * @param $method
     * @param $uri
     * @param null $params
     * @param null $body
     * @param array $options
     * @param \Elasticsearch\Transport $transport
     * @return mixed
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport = null)
    {
        if (isset($body) === true) {
            $body = $this->serializer->serialize($body);
        }
 
        $request = [
            'http_method' => $method,
            'scheme'      => $this->transportSchema,
            'uri'         => $this->getURI($uri, $params),
            'body'        => $body,
            'headers'     => array_merge([
                'Host'  => [$this->host]
            ], $this->headers)
        ];
 
        $request = array_replace_recursive($request, $this->connectionParams, $options);
 
        // RingPHP does not like if client is empty
        if (empty($request['client'])) {
            unset($request['client']);
        }
 
        $handler = $this->handler;
        $future = $handler($request, $this, $transport, $options);
 
        return $future;
    }
 
    /** @return string */
    public function getTransportSchema()
    {
        return $this->transportSchema;
    }
 
    /** @return array */
    public function getLastRequestInfo()
    {
        return $this->lastRequest;
    }
 
    private function wrapHandler(callable $handler, LoggerInterface $logger, LoggerInterface $tracer)
    {
        return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler, $logger, $tracer) {
 
            $this->lastRequest = [];
            $this->lastRequest['request'] = $request;
 
            // Send the request using the wrapped handler.
            $response =  Core::proxy($handler($request), function ($response) use ($connection, $transport, $logger, $tracer, $request, $options) {
 
                $this->lastRequest['response'] = $response;
 
                if (isset($response['error']) === true) {
                    if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) {
                        $this->log->warning("Curl exception encountered.");
 
                        $exception = $this->getCurlRetryException($request, $response);
 
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
 
                        $node = $connection->getHost();
                        $this->log->warning("Marking node $node dead.");
                        $connection->markDead();
 
                        // If the transport has not been set, we are inside a Ping or Sniff,
                        // so we don't want to retrigger retries anyway.
                        //
                        // TODO this could be handled better, but we are limited because connectionpools do not
                        // have access to Transport.  Architecturally, all of this needs to be refactored
                        if (isset($transport) === true) {
                            $transport->connectionPool->scheduleCheck();
 
                            $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false;
                            $shouldRetry = $transport->shouldRetry($request);
                            $shouldRetryText = ($shouldRetry) ? 'true' : 'false';
 
                            $this->log->warning("Retries left? $shouldRetryText");
                            if ($shouldRetry && !$neverRetry) {
                                return $transport->performRequest(
                                    $request['http_method'],
                                    $request['uri'],
                                    [],
                                    $request['body'],
                                    $options
                                );
                            }
                        }
 
                        $this->log->warning("Out of retries, throwing exception from $node");
                        // Only throw if we run out of retries
                        throw $exception;
                    } else {
                        // Something went seriously wrong, bail
                        $exception = new TransportException($response['error']->getMessage());
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
                        throw $exception;
                    }
                } else {
                    $connection->markAlive();
 
                    if (isset($response['body']) === true) {
                        $response['body'] = stream_get_contents($response['body']);
                        $this->lastRequest['response']['body'] = $response['body'];
                    }
 
                    if ($response['status'] >= 400 && $response['status'] < 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process4xxError($request, $response, $ignore);
                    } elseif ($response['status'] >= 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process5xxError($request, $response, $ignore);
                    }
 
                    // No error, deserialize
                    $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
                }
                $this->logRequestSuccess(
                    $request['http_method'],
                    $response['effective_url'],
                    $request['body'],
                    $request['headers'],
                    $response['status'],
                    $response['body'],
                    $response['transfer_stats']['total_time']
                );
 
                return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body'];
            });
 
            return $response;
        };
    }
 
    /**
     * @param string $uri
     * @param array $params
     *
     * @return string
     */
    private function getURI($uri, $params)
    {
        if (isset($params) === true && !empty($params)) {
            array_walk($params, function (&$value, &$key) {
                if ($value === true) {
                    $value = 'true';
                } elseif ($value === false) {
                    $value = 'false';
                }
            });
 
            $uri .= '?' . http_build_query($params);
        }
 
        if ($this->path !== null) {
            $uri = $this->path . $uri;
        }
 
        return $uri;
    }
 
    /**
     * Log a successful request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array  $headers
     * @param string $statusCode
     * @param string $response
     * @param string $duration
     *
     * @return void
     */
    public function logRequestSuccess($method, $fullURI, $body, $headers, $statusCode, $response, $duration)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->info(
            'Request Success:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
        $this->log->debug('Response', array($response));
 
        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }
 
    /**
     * Log a a failed request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array $headers
     * @param null|string $statusCode
     * @param null|string $response
     * @param string $duration
     * @param \Exception|null $exception
     *
     * @return void
     */
    public function logRequestFail($method, $fullURI, $body, $headers, $statusCode, $response, $duration, \Exception $exception)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->warning(
            'Request Failure:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
                'error'     => $exception->getMessage(),
            )
        );
        $this->log->warning('Response', array($response));
 
        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }
 
    /**
     * @return bool
     */
    public function ping()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true,
                'verbose' => true
            ]
        ];
        try {
            $response = $this->performRequest('HEAD', '/', null, null, $options);
            $response = $response->wait();
        } catch (TransportException $exception) {
            $this->markDead();
 
            return false;
        }
 
        if ($response['status'] === 200) {
            $this->markAlive();
 
            return true;
        } else {
            $this->markDead();
 
            return false;
        }
    }
 
    /**
     * @return array
     */
    public function sniff()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true
            ]
        ];
 
        return $this->performRequest('GET', '/_nodes/', null, null, $options);
    }
 
    /**
     * @return bool
     */
    public function isAlive()
    {
        return $this->isAlive;
    }
 
    public function markAlive()
    {
        $this->failedPings = 0;
        $this->isAlive = true;
        $this->lastPing = time();
    }
 
    public function markDead()
    {
        $this->isAlive = false;
        $this->failedPings += 1;
        $this->lastPing = time();
    }
 
    /**
     * @return int
     */
    public function getLastPing()
    {
        return $this->lastPing;
    }
 
    /**
     * @return int
     */
    public function getPingFailures()
    {
        return $this->failedPings;
    }
 
    /**
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }
 
    /**
     * @return null|string
     */
    public function getUserPass()
    {
        if (isset($this->connectionParams['client']['curl'][CURLOPT_USERPWD]) === true) {
            return $this->connectionParams['client']['curl'][CURLOPT_USERPWD];
        }
        return null;
    }
 
    /**
     * @return null|string
     */
    public function getPath()
    {
        return $this->path;
    }
 
    /**
     * @param $request
     * @param $response
     * @return \Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost|\Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException|\Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException|\Elasticsearch\Common\Exceptions\MaxRetriesException
     */
    protected function getCurlRetryException($request, $response)
    {
        $exception = null;
        $message = $response['error']->getMessage();
        $exception = new MaxRetriesException($message);
        switch ($response['curl']['errno']) {
            case 6:
                $exception = new CouldNotResolveHostException($message, null, $exception);
                break;
            case 7:
                $exception = new CouldNotConnectToHost($message, null, $exception);
                break;
            case 28:
                $exception = new OperationTimeoutException($message, null, $exception);
                break;
        }
 
        return $exception;
    }
 
    /**
     * Construct a string cURL command
     *
     * @param string $method HTTP method
     * @param string $uri    Full URI of request
     * @param string $body   Request body
     *
     * @return string
     */
    private function buildCurlCommand($method, $uri, $body)
    {
        if (strpos($uri, '?') === false) {
            $uri .= '?pretty=true';
        } else {
            str_replace('?', '?pretty=true', $uri);
        }
 
        $curlCommand = 'curl -X' . strtoupper($method);
        $curlCommand .= " '" . $uri . "'";
 
        if (isset($body) === true && $body !== '') {
            $curlCommand .= " -d '" . $body . "'";
        }
 
        return $curlCommand;
    }
 
    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\AlreadyExpiredException|\Elasticsearch\Common\Exceptions\BadRequest400Exception|\Elasticsearch\Common\Exceptions\Conflict409Exception|\Elasticsearch\Common\Exceptions\Forbidden403Exception|\Elasticsearch\Common\Exceptions\Missing404Exception|\Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException|null
     */
    private function process4xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];
 
        /** @var \Exception $exception */
        $exception = $this->tryDeserialize400Error($response);
 
        if (array_search($response['status'], $ignore) !== false) {
            return;
        }
 
        if ($statusCode === 400 && strpos($responseBody, "AlreadyExpiredException") !== false) {
            $exception = new AlreadyExpiredException($responseBody, $statusCode);
        } elseif ($statusCode === 403) {
            $exception = new Forbidden403Exception($responseBody, $statusCode);
        } elseif ($statusCode === 404) {
            $exception = new Missing404Exception($responseBody, $statusCode);
        } elseif ($statusCode === 409) {
            $exception = new Conflict409Exception($responseBody, $statusCode);
        } elseif ($statusCode === 400 && strpos($responseBody, 'script_lang not supported') !== false) {
            $exception = new ScriptLangNotSupportedException($responseBody. $statusCode);
        } elseif ($statusCode === 408) {
            $exception = new RequestTimeout408Exception($responseBody, $statusCode);
        } else {
            $exception = new BadRequest400Exception($responseBody, $statusCode);
        }
 
        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );
 
        throw $exception;
    }
 
    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\NoDocumentsToGetException|\Elasticsearch\Common\Exceptions\NoShardAvailableException|\Elasticsearch\Common\Exceptions\RoutingMissingException|\Elasticsearch\Common\Exceptions\ServerErrorResponseException
     */
    private function process5xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];
 
        /** @var \Exception $exception */
        $exception = $this->tryDeserialize500Error($response);
 
        $exceptionText = "[$statusCode Server Exception] ".$exception->getMessage();
        $this->log->error($exceptionText);
        $this->log->error($exception->getTraceAsString());
 
        if (array_search($statusCode, $ignore) !== false) {
            return;
        }
 
        if ($statusCode === 500 && strpos($responseBody, "RoutingMissingException") !== false) {
            $exception = new RoutingMissingException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && preg_match('/ActionRequestValidationException.+ no documents to get/', $responseBody) === 1) {
            $exception = new NoDocumentsToGetException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && strpos($responseBody, 'NoShardAvailableActionException') !== false) {
            $exception = new NoShardAvailableException($exception->getMessage(), $statusCode, $exception);
        } else {
            $exception = new ServerErrorResponseException($responseBody, $statusCode);
        }
 
        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );
 
        throw $exception;
    }
 
    private function tryDeserialize400Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\BadRequest400Exception');
    }
 
    private function tryDeserialize500Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\ServerErrorResponseException');
    }
 
    private function tryDeserializeError($response, $errorClass)
    {
        $error = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
        if (is_array($error) === true) {
            // 2.0 structured exceptions
            if (isset($error['error']['reason']) === true) {
                // Try to use root cause first (only grabs the first root cause)
                $root = $error['error']['root_cause'];
                if (isset($root) && isset($root[0])) {
                    $cause = $root[0]['reason'];
                    $type = $root[0]['type'];
                } else {
                    $cause = $error['error']['reason'];
                    $type = $error['error']['type'];
                }
 
                $original = new $errorClass($response['body'], $response['status']);
 
                return new $errorClass("$type: $cause", $response['status'], $original);
            } elseif (isset($error['error']) === true) {
                // <2.0 semi-structured exceptions
                $original = new $errorClass($response['body'], $response['status']);
 
                return new $errorClass($error['error'], $response['status'], $original);
            }
 
            // <2.0 "i just blew up" nonstructured exception
            // $error is an array but we don't know the format, reuse the response body instead
            return new $errorClass($response['body'], $response['status']);
        }
 
        // <2.0 "i just blew up" nonstructured exception
        return new $errorClass($response['body']);
    }
}
#5Elasticsearch\Connections\Connection->Elasticsearch\Connections\{closure}(Array([http_method] => GET, [scheme] => http, [uri] => /drooble_users/_search?from=0&size=11, [body] => {"query":{"bool":{"should":[{"match":{"tags.instruments_primary.en":"Keyboard"}},{"match":{"tags.instruments_secondary.en":"Keyboard"}}],"minimum_should_match":1}},"sort":[{"_score":"desc"}]}, [headers] => Array([Host] => Array([0] => search-drooble1-irvhqoehqpblnqmd5va6n5pmsy.us-west-2.es.amazonaws.com:80), [Content-Type] => Array([0] => application/json), [Accept] => Array([0] => application/json))), Object(Elasticsearch\Connections\Connection), Object(Elasticsearch\Transport), Array())
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php (177)
<?php
 
namespace Elasticsearch\Connections;
 
use Elasticsearch\Common\Exceptions\AlreadyExpiredException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Conflict409Exception;
use Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost;
use Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException;
use Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException;
use Elasticsearch\Common\Exceptions\Forbidden403Exception;
use Elasticsearch\Common\Exceptions\MaxRetriesException;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\NoDocumentsToGetException;
use Elasticsearch\Common\Exceptions\NoShardAvailableException;
use Elasticsearch\Common\Exceptions\RequestTimeout408Exception;
use Elasticsearch\Common\Exceptions\RoutingMissingException;
use Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException;
use Elasticsearch\Common\Exceptions\ServerErrorResponseException;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Serializers\SerializerInterface;
use Elasticsearch\Transport;
use GuzzleHttp\Ring\Core;
use GuzzleHttp\Ring\Exception\ConnectException;
use GuzzleHttp\Ring\Exception\RingException;
use Psr\Log\LoggerInterface;
 
/**
 * Class AbstractConnection
 *
 * @category Elasticsearch
 * @package  Elasticsearch\Connections
 * @author   Zachary Tong <[email protected]>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Connection implements ConnectionInterface
{
    /** @var  callable */
    protected $handler;
 
    /** @var SerializerInterface */
    protected $serializer;
 
    /**
     * @var string
     */
    protected $transportSchema = 'http';    // TODO depreciate this default
 
    /**
     * @var string
     */
    protected $host;
 
    /**
     * @var string || null
     */
    protected $path;
 
    /**
     * @var LoggerInterface
     */
    protected $log;
 
    /**
     * @var LoggerInterface
     */
    protected $trace;
 
    /**
     * @var array
     */
    protected $connectionParams;
 
    /** @var  array */
    protected $headers = [];
 
    /** @var bool  */
    protected $isAlive = false;
 
    /** @var float  */
    private $pingTimeout = 1;    //TODO expose this
 
    /** @var int  */
    private $lastPing = 0;
 
    /** @var int  */
    private $failedPings = 0;
 
    private $lastRequest = array();
 
    /**
     * Constructor
     *
     * @param $handler
     * @param array $hostDetails
     * @param array $connectionParams Array of connection-specific parameters
     * @param \Elasticsearch\Serializers\SerializerInterface $serializer
     * @param \Psr\Log\LoggerInterface $log              Logger object
     * @param \Psr\Log\LoggerInterface $trace
     */
    public function __construct(
        $handler,
        $hostDetails,
        $connectionParams,
        SerializerInterface $serializer,
        LoggerInterface $log,
        LoggerInterface $trace
    ) {
    
        if (isset($hostDetails['port']) !== true) {
            $hostDetails['port'] = 9200;
        }
 
        if (isset($hostDetails['scheme'])) {
            $this->transportSchema = $hostDetails['scheme'];
        }
 
        if (isset($hostDetails['user']) && isset($hostDetails['pass'])) {
            $connectionParams['client']['curl'][CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
            $connectionParams['client']['curl'][CURLOPT_USERPWD] = $hostDetails['user'].':'.$hostDetails['pass'];
        }
 
        if (isset($connectionParams['client']['headers']) === true) {
            $this->headers = $connectionParams['client']['headers'];
            unset($connectionParams['client']['headers']);
        }
 
        $host = $hostDetails['host'].':'.$hostDetails['port'];
        $path = null;
        if (isset($hostDetails['path']) === true) {
            $path = $hostDetails['path'];
        }
        $this->host             = $host;
        $this->path             = $path;
        $this->log              = $log;
        $this->trace            = $trace;
        $this->connectionParams = $connectionParams;
        $this->serializer       = $serializer;
 
        $this->handler = $this->wrapHandler($handler, $log, $trace);
    }
 
    /**
     * @param $method
     * @param $uri
     * @param null $params
     * @param null $body
     * @param array $options
     * @param \Elasticsearch\Transport $transport
     * @return mixed
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [], Transport $transport = null)
    {
        if (isset($body) === true) {
            $body = $this->serializer->serialize($body);
        }
 
        $request = [
            'http_method' => $method,
            'scheme'      => $this->transportSchema,
            'uri'         => $this->getURI($uri, $params),
            'body'        => $body,
            'headers'     => array_merge([
                'Host'  => [$this->host]
            ], $this->headers)
        ];
 
        $request = array_replace_recursive($request, $this->connectionParams, $options);
 
        // RingPHP does not like if client is empty
        if (empty($request['client'])) {
            unset($request['client']);
        }
 
        $handler = $this->handler;
        $future = $handler($request, $this, $transport, $options);
 
        return $future;
    }
 
    /** @return string */
    public function getTransportSchema()
    {
        return $this->transportSchema;
    }
 
    /** @return array */
    public function getLastRequestInfo()
    {
        return $this->lastRequest;
    }
 
    private function wrapHandler(callable $handler, LoggerInterface $logger, LoggerInterface $tracer)
    {
        return function (array $request, Connection $connection, Transport $transport = null, $options) use ($handler, $logger, $tracer) {
 
            $this->lastRequest = [];
            $this->lastRequest['request'] = $request;
 
            // Send the request using the wrapped handler.
            $response =  Core::proxy($handler($request), function ($response) use ($connection, $transport, $logger, $tracer, $request, $options) {
 
                $this->lastRequest['response'] = $response;
 
                if (isset($response['error']) === true) {
                    if ($response['error'] instanceof ConnectException || $response['error'] instanceof RingException) {
                        $this->log->warning("Curl exception encountered.");
 
                        $exception = $this->getCurlRetryException($request, $response);
 
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
 
                        $node = $connection->getHost();
                        $this->log->warning("Marking node $node dead.");
                        $connection->markDead();
 
                        // If the transport has not been set, we are inside a Ping or Sniff,
                        // so we don't want to retrigger retries anyway.
                        //
                        // TODO this could be handled better, but we are limited because connectionpools do not
                        // have access to Transport.  Architecturally, all of this needs to be refactored
                        if (isset($transport) === true) {
                            $transport->connectionPool->scheduleCheck();
 
                            $neverRetry = isset($request['client']['never_retry']) ? $request['client']['never_retry'] : false;
                            $shouldRetry = $transport->shouldRetry($request);
                            $shouldRetryText = ($shouldRetry) ? 'true' : 'false';
 
                            $this->log->warning("Retries left? $shouldRetryText");
                            if ($shouldRetry && !$neverRetry) {
                                return $transport->performRequest(
                                    $request['http_method'],
                                    $request['uri'],
                                    [],
                                    $request['body'],
                                    $options
                                );
                            }
                        }
 
                        $this->log->warning("Out of retries, throwing exception from $node");
                        // Only throw if we run out of retries
                        throw $exception;
                    } else {
                        // Something went seriously wrong, bail
                        $exception = new TransportException($response['error']->getMessage());
                        $this->logRequestFail(
                            $request['http_method'],
                            $response['effective_url'],
                            $request['body'],
                            $request['headers'],
                            $response['status'],
                            $response['body'],
                            $response['transfer_stats']['total_time'],
                            $exception
                        );
                        throw $exception;
                    }
                } else {
                    $connection->markAlive();
 
                    if (isset($response['body']) === true) {
                        $response['body'] = stream_get_contents($response['body']);
                        $this->lastRequest['response']['body'] = $response['body'];
                    }
 
                    if ($response['status'] >= 400 && $response['status'] < 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process4xxError($request, $response, $ignore);
                    } elseif ($response['status'] >= 500) {
                        $ignore = isset($request['client']['ignore']) ? $request['client']['ignore'] : [];
                        $this->process5xxError($request, $response, $ignore);
                    }
 
                    // No error, deserialize
                    $response['body'] = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
                }
                $this->logRequestSuccess(
                    $request['http_method'],
                    $response['effective_url'],
                    $request['body'],
                    $request['headers'],
                    $response['status'],
                    $response['body'],
                    $response['transfer_stats']['total_time']
                );
 
                return isset($request['client']['verbose']) && $request['client']['verbose'] === true ? $response : $response['body'];
            });
 
            return $response;
        };
    }
 
    /**
     * @param string $uri
     * @param array $params
     *
     * @return string
     */
    private function getURI($uri, $params)
    {
        if (isset($params) === true && !empty($params)) {
            array_walk($params, function (&$value, &$key) {
                if ($value === true) {
                    $value = 'true';
                } elseif ($value === false) {
                    $value = 'false';
                }
            });
 
            $uri .= '?' . http_build_query($params);
        }
 
        if ($this->path !== null) {
            $uri = $this->path . $uri;
        }
 
        return $uri;
    }
 
    /**
     * Log a successful request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array  $headers
     * @param string $statusCode
     * @param string $response
     * @param string $duration
     *
     * @return void
     */
    public function logRequestSuccess($method, $fullURI, $body, $headers, $statusCode, $response, $duration)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->info(
            'Request Success:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
        $this->log->debug('Response', array($response));
 
        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }
 
    /**
     * Log a a failed request
     *
     * @param string $method
     * @param string $fullURI
     * @param string $body
     * @param array $headers
     * @param null|string $statusCode
     * @param null|string $response
     * @param string $duration
     * @param \Exception|null $exception
     *
     * @return void
     */
    public function logRequestFail($method, $fullURI, $body, $headers, $statusCode, $response, $duration, \Exception $exception)
    {
        $this->log->debug('Request Body', array($body));
        $this->log->warning(
            'Request Failure:',
            array(
                'method'    => $method,
                'uri'       => $fullURI,
                'headers'   => $headers,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
                'error'     => $exception->getMessage(),
            )
        );
        $this->log->warning('Response', array($response));
 
        // Build the curl command for Trace.
        $curlCommand = $this->buildCurlCommand($method, $fullURI, $body);
        $this->trace->info($curlCommand);
        $this->trace->debug(
            'Response:',
            array(
                'response'  => $response,
                'method'    => $method,
                'uri'       => $fullURI,
                'HTTP code' => $statusCode,
                'duration'  => $duration,
            )
        );
    }
 
    /**
     * @return bool
     */
    public function ping()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true,
                'verbose' => true
            ]
        ];
        try {
            $response = $this->performRequest('HEAD', '/', null, null, $options);
            $response = $response->wait();
        } catch (TransportException $exception) {
            $this->markDead();
 
            return false;
        }
 
        if ($response['status'] === 200) {
            $this->markAlive();
 
            return true;
        } else {
            $this->markDead();
 
            return false;
        }
    }
 
    /**
     * @return array
     */
    public function sniff()
    {
        $options = [
            'client' => [
                'timeout' => $this->pingTimeout,
                'never_retry' => true
            ]
        ];
 
        return $this->performRequest('GET', '/_nodes/', null, null, $options);
    }
 
    /**
     * @return bool
     */
    public function isAlive()
    {
        return $this->isAlive;
    }
 
    public function markAlive()
    {
        $this->failedPings = 0;
        $this->isAlive = true;
        $this->lastPing = time();
    }
 
    public function markDead()
    {
        $this->isAlive = false;
        $this->failedPings += 1;
        $this->lastPing = time();
    }
 
    /**
     * @return int
     */
    public function getLastPing()
    {
        return $this->lastPing;
    }
 
    /**
     * @return int
     */
    public function getPingFailures()
    {
        return $this->failedPings;
    }
 
    /**
     * @return string
     */
    public function getHost()
    {
        return $this->host;
    }
 
    /**
     * @return null|string
     */
    public function getUserPass()
    {
        if (isset($this->connectionParams['client']['curl'][CURLOPT_USERPWD]) === true) {
            return $this->connectionParams['client']['curl'][CURLOPT_USERPWD];
        }
        return null;
    }
 
    /**
     * @return null|string
     */
    public function getPath()
    {
        return $this->path;
    }
 
    /**
     * @param $request
     * @param $response
     * @return \Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost|\Elasticsearch\Common\Exceptions\Curl\CouldNotResolveHostException|\Elasticsearch\Common\Exceptions\Curl\OperationTimeoutException|\Elasticsearch\Common\Exceptions\MaxRetriesException
     */
    protected function getCurlRetryException($request, $response)
    {
        $exception = null;
        $message = $response['error']->getMessage();
        $exception = new MaxRetriesException($message);
        switch ($response['curl']['errno']) {
            case 6:
                $exception = new CouldNotResolveHostException($message, null, $exception);
                break;
            case 7:
                $exception = new CouldNotConnectToHost($message, null, $exception);
                break;
            case 28:
                $exception = new OperationTimeoutException($message, null, $exception);
                break;
        }
 
        return $exception;
    }
 
    /**
     * Construct a string cURL command
     *
     * @param string $method HTTP method
     * @param string $uri    Full URI of request
     * @param string $body   Request body
     *
     * @return string
     */
    private function buildCurlCommand($method, $uri, $body)
    {
        if (strpos($uri, '?') === false) {
            $uri .= '?pretty=true';
        } else {
            str_replace('?', '?pretty=true', $uri);
        }
 
        $curlCommand = 'curl -X' . strtoupper($method);
        $curlCommand .= " '" . $uri . "'";
 
        if (isset($body) === true && $body !== '') {
            $curlCommand .= " -d '" . $body . "'";
        }
 
        return $curlCommand;
    }
 
    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\AlreadyExpiredException|\Elasticsearch\Common\Exceptions\BadRequest400Exception|\Elasticsearch\Common\Exceptions\Conflict409Exception|\Elasticsearch\Common\Exceptions\Forbidden403Exception|\Elasticsearch\Common\Exceptions\Missing404Exception|\Elasticsearch\Common\Exceptions\ScriptLangNotSupportedException|null
     */
    private function process4xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];
 
        /** @var \Exception $exception */
        $exception = $this->tryDeserialize400Error($response);
 
        if (array_search($response['status'], $ignore) !== false) {
            return;
        }
 
        if ($statusCode === 400 && strpos($responseBody, "AlreadyExpiredException") !== false) {
            $exception = new AlreadyExpiredException($responseBody, $statusCode);
        } elseif ($statusCode === 403) {
            $exception = new Forbidden403Exception($responseBody, $statusCode);
        } elseif ($statusCode === 404) {
            $exception = new Missing404Exception($responseBody, $statusCode);
        } elseif ($statusCode === 409) {
            $exception = new Conflict409Exception($responseBody, $statusCode);
        } elseif ($statusCode === 400 && strpos($responseBody, 'script_lang not supported') !== false) {
            $exception = new ScriptLangNotSupportedException($responseBody. $statusCode);
        } elseif ($statusCode === 408) {
            $exception = new RequestTimeout408Exception($responseBody, $statusCode);
        } else {
            $exception = new BadRequest400Exception($responseBody, $statusCode);
        }
 
        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );
 
        throw $exception;
    }
 
    /**
     * @param $request
     * @param $response
     * @param $ignore
     * @throws \Elasticsearch\Common\Exceptions\NoDocumentsToGetException|\Elasticsearch\Common\Exceptions\NoShardAvailableException|\Elasticsearch\Common\Exceptions\RoutingMissingException|\Elasticsearch\Common\Exceptions\ServerErrorResponseException
     */
    private function process5xxError($request, $response, $ignore)
    {
        $statusCode = $response['status'];
        $responseBody = $response['body'];
 
        /** @var \Exception $exception */
        $exception = $this->tryDeserialize500Error($response);
 
        $exceptionText = "[$statusCode Server Exception] ".$exception->getMessage();
        $this->log->error($exceptionText);
        $this->log->error($exception->getTraceAsString());
 
        if (array_search($statusCode, $ignore) !== false) {
            return;
        }
 
        if ($statusCode === 500 && strpos($responseBody, "RoutingMissingException") !== false) {
            $exception = new RoutingMissingException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && preg_match('/ActionRequestValidationException.+ no documents to get/', $responseBody) === 1) {
            $exception = new NoDocumentsToGetException($exception->getMessage(), $statusCode, $exception);
        } elseif ($statusCode === 500 && strpos($responseBody, 'NoShardAvailableActionException') !== false) {
            $exception = new NoShardAvailableException($exception->getMessage(), $statusCode, $exception);
        } else {
            $exception = new ServerErrorResponseException($responseBody, $statusCode);
        }
 
        $this->logRequestFail(
            $request['http_method'],
            $response['effective_url'],
            $request['body'],
            $request['headers'],
            $response['status'],
            $response['body'],
            $response['transfer_stats']['total_time'],
            $exception
        );
 
        throw $exception;
    }
 
    private function tryDeserialize400Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\BadRequest400Exception');
    }
 
    private function tryDeserialize500Error($response)
    {
        return $this->tryDeserializeError($response, 'Elasticsearch\Common\Exceptions\ServerErrorResponseException');
    }
 
    private function tryDeserializeError($response, $errorClass)
    {
        $error = $this->serializer->deserialize($response['body'], $response['transfer_stats']);
        if (is_array($error) === true) {
            // 2.0 structured exceptions
            if (isset($error['error']['reason']) === true) {
                // Try to use root cause first (only grabs the first root cause)
                $root = $error['error']['root_cause'];
                if (isset($root) && isset($root[0])) {
                    $cause = $root[0]['reason'];
                    $type = $root[0]['type'];
                } else {
                    $cause = $error['error']['reason'];
                    $type = $error['error']['type'];
                }
 
                $original = new $errorClass($response['body'], $response['status']);
 
                return new $errorClass("$type: $cause", $response['status'], $original);
            } elseif (isset($error['error']) === true) {
                // <2.0 semi-structured exceptions
                $original = new $errorClass($response['body'], $response['status']);
 
                return new $errorClass($error['error'], $response['status'], $original);
            }
 
            // <2.0 "i just blew up" nonstructured exception
            // $error is an array but we don't know the format, reuse the response body instead
            return new $errorClass($response['body'], $response['status']);
        }
 
        // <2.0 "i just blew up" nonstructured exception
        return new $errorClass($response['body']);
    }
}
#6Elasticsearch\Connections\Connection->performRequest(GET, /drooble_users/_search, Array([from] => 0, [size] => 11), {"query":{"bool":{"should":[{"match":{"tags.instruments_primary.en":"Keyboard"}},{"match":{"tags.instruments_secondary.en":"Keyboard"}}],"minimum_should_match":1}},"sort":[{"_score":"desc"}]}, Array(), Object(Elasticsearch\Transport))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php (110)
<?php
 
namespace Elasticsearch;
 
use Elasticsearch\Common\Exceptions;
use Elasticsearch\ConnectionPool\AbstractConnectionPool;
use Elasticsearch\Connections\Connection;
use Elasticsearch\Connections\ConnectionInterface;
use GuzzleHttp\Ring\Future\FutureArrayInterface;
use Psr\Log\LoggerInterface;
 
/**
 * Class Transport
 *
 * @category Elasticsearch
 * @package  Elasticsearch
 * @author   Zachary Tong <[email protected]>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Transport
{
    /**
     * @var AbstractConnectionPool
     */
    public $connectionPool;
 
    /**
     * @var LoggerInterface
     */
    private $log;
 
    /** @var  int */
    public $retryAttempts = 0;
 
    /** @var  Connection */
    public $lastConnection;
 
    /** @var int  */
    public $retries;
 
    /**
     * Transport class is responsible for dispatching requests to the
     * underlying cluster connections
     *
     * @param $retries
     * @param bool $sniffOnStart
     * @param ConnectionPool\AbstractConnectionPool $connectionPool
     * @param \Psr\Log\LoggerInterface $log    Monolog logger object
     */
  // @codingStandardsIgnoreStart
  // "Arguments with default values must be at the end of the argument list" - cannot change the interface
    public function __construct($retries, $sniffOnStart = false, AbstractConnectionPool $connectionPool, LoggerInterface $log)
    {
      // @codingStandardsIgnoreEnd
 
        $this->log            = $log;
        $this->connectionPool = $connectionPool;
        $this->retries        = $retries;
 
        if ($sniffOnStart === true) {
            $this->log->notice('Sniff on Start.');
            $this->connectionPool->scheduleCheck();
        }
    }
 
    /**
     * Returns a single connection from the connection pool
     * Potentially performs a sniffing step before returning
     *
     * @return ConnectionInterface Connection
     */
 
    public function getConnection()
    {
        return $this->connectionPool->nextConnection();
    }
 
    /**
     * Perform a request to the Cluster
     *
     * @param string $method     HTTP method to use
     * @param string $uri        HTTP URI to send request to
     * @param null $params     Optional query parameters
     * @param null $body       Optional query body
     * @param array $options
     *
     * @throws Common\Exceptions\NoNodesAvailableException|\Exception
     * @return FutureArrayInterface
     */
    public function performRequest($method, $uri, $params = null, $body = null, $options = [])
    {
        try {
            $connection  = $this->getConnection();
        } catch (Exceptions\NoNodesAvailableException $exception) {
            $this->log->critical('No alive nodes found in cluster');
            throw $exception;
        }
 
        $response             = array();
        $caughtException      = null;
        $this->lastConnection = $connection;
 
        $future = $connection->performRequest(
            $method,
            $uri,
            $params,
            $body,
            $options,
            $this
        );
 
        $future->promise()->then(
            //onSuccess
            function ($response) {
                $this->retryAttempts = 0;
                // Note, this could be a 4xx or 5xx error
            },
            //onFailure
            function ($response) {
                // Ignore 400 level errors, as that means the server responded just fine
                if (!(isset($response['code']) && $response['code'] >=400 && $response['code'] < 500)) {
                    // Otherwise schedule a check
                    $this->connectionPool->scheduleCheck();
                }
            }
        );
 
        return $future;
    }
 
    /**
     * @param FutureArrayInterface $result  Response of a request (promise)
     * @param array                $options Options for transport
     *
     * @return callable|array
     */
    public function resultOrFuture($result, $options = [])
    {
        $response = null;
        $async = isset($options['client']['future']) ? $options['client']['future'] : null;
        if (is_null($async) || $async === false) {
            do {
                $result = $result->wait();
            } while ($result instanceof FutureArrayInterface);
 
            return $result;
        } elseif ($async === true || $async === 'lazy') {
            return $result;
        }
    }
 
    /**
     * @param $request
     *
     * @return bool
     */
    public function shouldRetry($request)
    {
        if ($this->retryAttempts < $this->retries) {
            $this->retryAttempts += 1;
 
            return true;
        }
 
        return false;
    }
 
    /**
     * Returns the last used connection so that it may be inspected.  Mainly
     * for debugging/testing purposes.
     *
     * @return Connection
     */
    public function getLastConnection()
    {
        return $this->lastConnection;
    }
}
#7Elasticsearch\Transport->performRequest(GET, /drooble_users/_search, Array([from] => 0, [size] => 11), Array([query] => Array([bool] => Array([should] => Array(), [minimum_should_match] => 1)), [sort] => Array([0] => Array([_score] => desc))), Array())
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Client.php (1553)
<?php
 
namespace Elasticsearch;
 
use Elasticsearch\Common\Exceptions\BadMethodCallException;
use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Namespaces\AbstractNamespace;
use Elasticsearch\Namespaces\CatNamespace;
use Elasticsearch\Namespaces\ClusterNamespace;
use Elasticsearch\Namespaces\IndicesNamespace;
use Elasticsearch\Namespaces\IngestNamespace;
use Elasticsearch\Namespaces\NamespaceBuilderInterface;
use Elasticsearch\Namespaces\NodesNamespace;
use Elasticsearch\Namespaces\RemoteNamespace;
use Elasticsearch\Namespaces\SnapshotNamespace;
use Elasticsearch\Namespaces\BooleanRequestWrapper;
use Elasticsearch\Namespaces\TasksNamespace;
 
/**
 * Class Client
 *
 * @category Elasticsearch
 * @package  Elasticsearch
 * @author   Zachary Tong <[email protected]>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Client
{
    /**
     * @var Transport
     */
    public $transport;
 
    /**
     * @var array
     */
    protected $params;
 
    /**
     * @var IndicesNamespace
     */
    protected $indices;
 
    /**
     * @var ClusterNamespace
     */
    protected $cluster;
 
    /**
     * @var NodesNamespace
     */
    protected $nodes;
 
    /**
     * @var SnapshotNamespace
     */
    protected $snapshot;
 
    /**
     * @var CatNamespace
     */
    protected $cat;
 
    /**
     * @var IngestNamespace
     */
    protected $ingest;
 
    /**
     * @var TasksNamespace
     */
    protected $tasks;
 
    /**
     * @var RemoteNamespace
     */
    protected $remote;
 
    /** @var  callback */
    protected $endpoints;
 
    /** @var  NamespaceBuilderInterface[] */
    protected $registeredNamespaces = [];
 
    /**
     * Client constructor
     *
     * @param Transport $transport
     * @param callable $endpoint
     * @param AbstractNamespace[] $registeredNamespaces
     */
    public function __construct(Transport $transport, callable $endpoint, array $registeredNamespaces)
    {
        $this->transport = $transport;
        $this->endpoints = $endpoint;
        $this->indices   = new IndicesNamespace($transport, $endpoint);
        $this->cluster   = new ClusterNamespace($transport, $endpoint);
        $this->nodes     = new NodesNamespace($transport, $endpoint);
        $this->snapshot  = new SnapshotNamespace($transport, $endpoint);
        $this->cat       = new CatNamespace($transport, $endpoint);
        $this->ingest    = new IngestNamespace($transport, $endpoint);
        $this->tasks     = new TasksNamespace($transport, $endpoint);
        $this->remote    = new RemoteNamespace($transport, $endpoint);
        $this->registeredNamespaces = $registeredNamespaces;
    }
 
    /**
     * @param $params
     * @return array
     */
    public function info($params = [])
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Info $endpoint */
        $endpoint = $endpointBuilder('Info');
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * @param $params array Associative array of parameters
     *
     * @return bool
     */
    public function ping($params = [])
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Ping $endpoint */
        $endpoint = $endpointBuilder('Ping');
        $endpoint->setParams($params);
 
        try {
            $this->performRequest($endpoint);
        } catch (Missing404Exception $exception) {
            return false;
        } catch (TransportException $exception) {
            return false;
        } catch (NoNodesAvailableException $exception) {
            return false;
        }
 
        return true;
    }
 
    /**
     * $params['id']              = (string) The document ID (Required)
     *        ['index']           = (string) The name of the index (Required)
     *        ['type']            = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['ignore_missing']  = ??
     *        ['fields']          = (list) A comma-separated list of fields to return in the response
     *        ['parent']          = (string) The ID of the parent document
     *        ['preference']      = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']        = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']         = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']         = (string) Specific routing value
     *        ['_source']         = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (list) A list of fields to extract and return from the _source field
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function get($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Get $endpoint */
        $endpoint = $endpointBuilder('Get');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']             = (string) The document ID (Required)
     *        ['index']          = (string) The name of the index (Required)
     *        ['type']           = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['ignore_missing'] = ??
     *        ['parent']         = (string) The ID of the parent document
     *        ['preference']     = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']       = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']        = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']        = (string) Specific routing value
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getSource($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Get $endpoint */
        $endpoint = $endpointBuilder('Get');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->returnOnlySource();
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']           = (string) The document ID (Required)
     *        ['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['consistency']  = (enum) Specific write consistency setting for the operation
     *        ['parent']       = (string) ID of parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['version_type'] = (enum) Specific version type
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function delete($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        $this->verifyNotNullOrEmpty("id", $id);
        $this->verifyNotNullOrEmpty("type", $type);
        $this->verifyNotNullOrEmpty("index", $index);
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Delete $endpoint */
        $endpoint = $endpointBuilder('Delete');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     *
     * $params['_source'] = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (array) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (array) A list of fields to extract and return from the _source field
     *        ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['analyze_wildcard'] = (bool) Specify whether wildcard and prefix queries should be analyzed (default: false)
     *        ['analyzer'] = (string) The analyzer to use for the query string
     *        ['conflicts'] = (enum) What to do when the delete-by-query hits version conflicts?
     *        ['default_operator'] = (enum) The default operator for query string query (AND or OR)
     *        ['df'] = (string) The field to use as default where no field prefix is given in the query string
     *        ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['from'] = (number) Starting offset (default: 0)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['lenient'] = (bool) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q'] = (string) Query in the Lucene query string syntax
     *        ['refresh'] = (bool) Should the effected indexes be refreshed?
     *        ['request_cache'] = (bool) Specify if request cache should be used for this request or not, defaults to index level setting
     *        ['requests_per_second'] = (number) The throttle for this request in sub-requests per second. -1 means no throttle.
     *        ['routing'] = (array) A comma-separated list of specific routing values
     *        ['scroll'] = (number) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['scroll_size'] = (number) Size on the scroll request powering the update_by_query
     *        ['search_timeout'] = (number) Explicit timeout for each search request. Defaults to no timeout.
     *        ['search_type'] = (enum) Search operation type
     *        ['size'] = (number) Number of hits to return (default: 10)
     *        ['slices'] = (integer) The number of slices this task should be divided into. Defaults to 1 meaning the task isn't sliced into subtasks.
     *        ['sort'] = (array) A comma-separated list of <field>:<direction> pairs
     *        ['stats'] = (array) Specific 'tag' of the request for logging and statistical purposes
     *        ['terminate_after'] = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early.
     *        ['timeout'] = (number) Time each individual bulk request should wait for shards that are unavailable.
     *        ['version'] = (bool) Specify whether to return document version as part of a hit
     *        ['wait_for_active_shards'] = (string) Sets the number of shard copies that must be active before proceeding with the delete by query operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)
     *        ['wait_for_completion'] = (bool) Should the request should block until the delete-by-query is complete.
     *
     * @param array $params
     *
     * @return array
     */
    public function deleteByQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');
 
        $type = $this->extractArgument($params, 'type');
 
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\DeleteByQuery $endpoint */
        $endpoint = $endpointBuilder('DeleteByQuery');
        $endpoint->setIndex($index)
                ->setType($type)
                ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['type']               = (list) A comma-separated list of types to restrict the results
     *        ['min_score']          = (number) Include only documents with a specific `_score` value in the result
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['source']             = (string) The URL-encoded query definition (instead of using the request body)
     *        ['body']               = (array) A query to restrict the results (optional)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function count($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Count $endpoint */
        $endpoint = $endpointBuilder('Count');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['type']               = (list) A comma-separated list of types to restrict the results
     *        ['id']                 = (string) ID of document
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['allow_no_indices']   = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['body']               = (array) A query to restrict the results (optional)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['percolate_index']    = (string) The index to count percolate the document into. Defaults to index.
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['version']            = (number) Explicit version number for concurrency control
     *        ['version_type']       = (enum) Specific version type
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function countPercolate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\CountPercolate $endpoint */
        $endpoint = $endpointBuilder('CountPercolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']        = (string) The name of the index with a registered percolator query (Required)
     *        ['type']         = (string) The document type (Required)
     *        ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true)
     *        ['body']         = (array) The document (`doc`) to percolate against registered queries; optionally also a `query` to limit the percolation to specific registered queries
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function percolate($params)
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Percolate $endpoint */
        $endpoint = $endpointBuilder('Percolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (string) Default index for items which don't provide one
     *        ['type']               = (string) Default document type for items which don't provide one
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function mpercolate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\MPercolate $endpoint */
        $endpoint = $endpointBuilder('MPercolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']            = (string) Default index for items which don't provide one
     *        ['type']             = (string) Default document type for items which don't provide one
     *        ['term_statistics']  = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['fields']           = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['offsets']          = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['positions']        = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['payloads']         = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['preference']       = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['routing']          = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['parent']           = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['realtime']         = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true).
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function termvectors($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\TermVectors $endpoint */
        $endpoint = $endpointBuilder('TermVectors');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']            = (string) Default index for items which don't provide one
     *        ['type']             = (string) Default document type for items which don't provide one
     *        ['ids']              = (list) A comma-separated list of documents ids. You must define ids as parameter or set \"ids\" or \"docs\" in the request body
     *        ['term_statistics']  = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['fields']           = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['offsets']          = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['positions']        = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['payloads']         = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['preference']       = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['routing']          = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['parent']           = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['realtime']         = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true).
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function mtermvectors($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\MTermVectors $endpoint */
        $endpoint = $endpointBuilder('MTermVectors');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']         = (string) The document ID (Required)
     *        ['index']      = (string) The name of the index (Required)
     *        ['type']       = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['parent']     = (string) The ID of the parent document
     *        ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']   = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']    = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']    = (string) Specific routing value
     *
     * @param $params array Associative array of parameters
     *
     * @return array | boolean
     */
    public function exists($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Exists $endpoint */
        $endpoint = $endpointBuilder('Exists');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }
 
    /**
     * $params['index']           = (string) The name of the index
     *        ['type']            = (string) The type of the document
     *        ['fields']          = (list) A comma-separated list of fields to return in the response
     *        ['parent']          = (string) The ID of the parent document
     *        ['preference']      = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']        = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']         = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']         = (string) Specific routing value
     *        ['body']            = (array) Document identifiers; can be either `docs` (containing full document information) or `ids` (when index and type is provided in the URL.
     *        ['_source']         = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (list) A list of fields to extract and return from the _source field
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function mget($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Mget $endpoint */
        $endpoint = $endpointBuilder('Mget');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']       = (list) A comma-separated list of index names to use as default
     *        ['type']        = (list) A comma-separated list of document types to use as default
     *        ['search_type'] = (enum) Search operation type
     *        ['body']        = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function msearch($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Msearch $endpoint */
        $endpoint = $endpointBuilder('Msearch');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']       = (list) A comma-separated list of index names to use as default
     *        ['type']        = (list) A comma-separated list of document types to use as default
     *        ['search_type'] = (enum) Search operation type
     *        ['body']        = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines
     *        ['max_concurrent_searches'] = (number) Controls the maximum number of concurrent searches the multi search api will execute
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function msearchTemplate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\MsearchTemplate $endpoint */
        $endpoint = $endpointBuilder('MsearchTemplate');
        $endpoint->setIndex($index)
            ->setType($type)
            ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['id']           = (string) Specific document ID (when the POST method is used)
     *        ['consistency']  = (enum) Explicit write consistency setting for the operation
     *        ['parent']       = (string) ID of the parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['timestamp']    = (time) Explicit timestamp for the document
     *        ['ttl']          = (duration) Expiration time for the document
     *        ['version']      = (number) Explicit version number for concurrency control
     *        ['version_type'] = (enum) Specific version type
     *        ['body']         = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function create($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Create $endpoint */
        $endpoint = $endpointBuilder('Create');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']       = (string) Default index for items which don't provide one
     *        ['type']        = (string) Default document type for items which don't provide one
     *        ['consistency'] = (enum) Explicit write consistency setting for the operation
     *        ['refresh']     = (boolean) Refresh the index after performing the operation
     *        ['replication'] = (enum) Explicitly set the replication type
     *        ['fields']      = (list) Default comma-separated list of fields to return in the response for updates
     *        ['body']        = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function bulk($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Bulk $endpoint */
        $endpoint = $endpointBuilder('Bulk');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['id']           = (string) Specific document ID (when the POST method is used)
     *        ['consistency']  = (enum) Explicit write consistency setting for the operation
     *        ['op_type']      = (enum) Explicit operation type
     *        ['parent']       = (string) ID of the parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['timestamp']    = (time) Explicit timestamp for the document
     *        ['ttl']          = (duration) Expiration time for the document
     *        ['version']      = (number) Explicit version number for concurrency control
     *        ['version_type'] = (enum) Specific version type
     *        ['body']         = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function index($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Index $endpoint */
        $endpoint = $endpointBuilder('Index');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['refresh']             = (boolean) Should the effected indexes be refreshed?
     *        ['timeout']             = (time) Time each individual bulk request should wait for shards that are unavailable
     *        ['consistency']         = (enum) Explicit write consistency setting for the operation
     *        ['wait_for_completion'] = (boolean) Should the request should block until the reindex is complete
     *        ['requests_per_second'] = (float) The throttle for this request in sub-requests per second. 0 means set no throttle
     *        ['body']                = (array) The search definition using the Query DSL and the prototype for the index request (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function reindex($params)
    {
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
        /** @var \Elasticsearch\Endpoints\Reindex $endpoint */
        $endpoint = $endpointBuilder('Reindex');
        $endpoint->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']          = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices
     *        ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['preference']     = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']        = (string) Specific routing value
     *        ['source']         = (string) The URL-encoded request definition (instead of using request body)
     *        ['body']           = (array) The request definition
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function suggest($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Suggest $endpoint */
        $endpoint = $endpointBuilder('Suggest');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']                       = (string) The document ID (Required)
     *        ['index']                    = (string) The name of the index (Required)
     *        ['type']                     = (string) The type of the document (Required)
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcards and prefix queries in the query string query should be analyzed (default: false)
     *        ['analyzer']                 = (string) The analyzer for the query string query
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR)
     *        ['df']                       = (string) The default field for query string query (default: _all)
     *        ['fields']                   = (list) A comma-separated list of fields to return in the response
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['parent']                   = (string) The ID of the parent document
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['routing']                  = (string) Specific routing value
     *        ['source']                   = (string) The URL-encoded query definition (instead of using the request body)
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['body']                     = (string) The URL-encoded query definition (instead of using the request body)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function explain($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Explain $endpoint */
        $endpoint = $endpointBuilder('Explain');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *        ['analyzer']                 = (string) The analyzer to use for the query string
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcard and prefix queries should be analyzed (default: false)
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR)
     *        ['df']                       = (string) The field to use as default where no field prefix is given in the query string
     *        ['explain']                  = (boolean) Specify whether to return detailed information about score computation as part of a hit
     *        ['fields']                   = (list) A comma-separated list of fields to return as part of a hit
     *        ['from']                     = (number) Starting offset (default: 0)
     *        ['ignore_indices']           = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['indices_boost']            = (list) Comma-separated list of index boosts
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['query_cache']              = (boolean) Enable query cache for this request
     *        ['request_cache']            = (boolean) Enable request cache for this request
     *        ['routing']                  = (list) A comma-separated list of specific routing values
     *        ['scroll']                   = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['search_type']              = (enum) Search operation type
     *        ['size']                     = (number) Number of hits to return (default: 10)
     *        ['sort']                     = (list) A comma-separated list of <field>:<direction> pairs
     *        ['source']                   = (string) The URL-encoded request definition using the Query DSL (instead of using request body)
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['stats']                    = (list) Specific 'tag' of the request for logging and statistical purposes
     *        ['suggest_field']            = (string) Specify which field to use for suggestions
     *        ['suggest_mode']             = (enum) Specify suggest mode
     *        ['suggest_size']             = (number) How many suggestions to return in response
     *        ['suggest_text']             = (text) The source text for which the suggestions should be returned
     *        ['timeout']                  = (time) Explicit operation timeout
     *        ['terminate_after']          = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early.
     *        ['version']                  = (boolean) Specify whether to return document version as part of a hit
     *        ['body']                     = (array|string) The search definition using the Query DSL
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function search($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Search $endpoint */
        $endpoint = $endpointBuilder('Search');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']               = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['local']              = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function searchShards($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\SearchShards $endpoint */
        $endpoint = $endpointBuilder('SearchShards');
        $endpoint->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function searchTemplate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Search $endpoint */
        $endpoint = $endpointBuilder('SearchTemplate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['scroll_id'] = (string) The scroll ID for scrolled search
     *        ['scroll']    = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['body']      = (string) The scroll ID for scrolled search
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function scroll($params = array())
    {
        $scrollID = $this->extractArgument($params, 'scroll_id');
        $body = $this->extractArgument($params, 'body');
        $scroll = $this->extractArgument($params, 'scroll');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Scroll $endpoint */
        $endpoint = $endpointBuilder('Scroll');
        $endpoint->setScrollID($scrollID)
                 ->setScroll($scroll)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['scroll_id'] = (string) The scroll ID for scrolled search
     *        ['scroll']    = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['body']      = (string) The scroll ID for scrolled search
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function clearScroll($params = array())
    {
        $scrollID = $this->extractArgument($params, 'scroll_id');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\ClearScroll $endpoint */
        $endpoint = $endpointBuilder('ClearScroll');
        $endpoint->setScrollID($scrollID)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']                = (string) Document ID (Required)
     *        ['index']             = (string) The name of the index (Required)
     *        ['type']              = (string) The type of the document (Required)
     *        ['consistency']       = (enum) Explicit write consistency setting for the operation
     *        ['fields']            = (list) A comma-separated list of fields to return in the response
     *        ['lang']              = (string) The script language (default: mvel)
     *        ['parent']            = (string) ID of the parent document
     *        ['refresh']           = (boolean) Refresh the index after performing the operation
     *        ['replication']       = (enum) Specific replication type
     *        ['retry_on_conflict'] = (number) Specify how many times should the operation be retried when a conflict occurs (default: 0)
     *        ['routing']           = (string) Specific routing value
     *        ['script']            = () The URL-encoded script definition (instead of using request body)
     *        ['timeout']           = (time) Explicit operation timeout
     *        ['timestamp']         = (time) Explicit timestamp for the document
     *        ['ttl']               = (duration) Expiration time for the document
     *        ['version_type']      = (number) Explicit version number for concurrency control
     *        ['body']              = (array) The request definition using either `script` or partial `doc`
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function update($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Update $endpoint */
        $endpoint = $endpointBuilder('Update');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or
     * empty string to perform the operation on all indices (Required)
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to
     * perform the operation on all types
     *        ['analyzer']                 = (string) The analyzer to use for the query string
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcard and prefix queries should be analyzed
     * (default: false)
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR) (AND,OR)
     * (default: OR)
     *        ['df']                       = (string) The field to use as default where no field prefix is given in the
     * query string
     *        ['explain']                  = (boolean) Specify whether to return detailed information about score
     * computation as part of a hit
     *        ['fields']                   = (list) A comma-separated list of fields to return as part of a hit
     *        ['fielddata_fields']         = (list) A comma-separated list of fields to return as the field data
     * representation of a field for each hit
     *        ['from']                     = (number) Starting offset (default: 0)
     *        ['ignore_unavailable']       = (boolean) Whether specified concrete indices should be ignored when
     * unavailable (missing or closed)
     *        ['allow_no_indices']         = (boolean) Whether to ignore if a wildcard indices expression resolves into
     * no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['conflicts']                = (enum) What to do when the reindex hits version conflicts? (abort,proceed)
     * (default: abort)
     *        ['expand_wildcards']         = (enum) Whether to expand wildcard expression to concrete indices that are
     * open, closed or both. (open,closed,none,all) (default: open)
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing
     * text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on
     * (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['routing']                  = (list) A comma-separated list of specific routing values
     *        ['scroll']                   = (duration) Specify how long a consistent view of the index should be
     * maintained for scrolled search
     *        ['search_type']              = (enum) Search operation type (query_then_fetch,dfs_query_then_fetch)
     *        ['search_timeout']           = (time) Explicit timeout for each search request. Defaults to no timeout.
     *        ['size']                     = (number) Number of hits to return (default: 10)
     *        ['sort']                     = (list) A comma-separated list of <field>:<direction> pairs
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of
     * fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['terminate_after']          = (number) The maximum number of documents to collect for each shard, upon
     * reaching which the query execution will terminate early.
     *        ['stats']                    = (list) Specific 'tag' of the request for logging and statistical purposes
     *        ['suggest_field']            = (string) Specify which field to use for suggestions
     *        ['suggest_mode']             = (enum) Specify suggest mode (missing,popular,always) (default: missing)
     *        ['suggest_size']             = (number) How many suggestions to return in response
     *        ['suggest_text']             = (text) The source text for which the suggestions should be returned
     *        ['timeout']                  = (time) Time each individual bulk request should wait for shards that are
     * unavailable. (default: 1m)
     *        ['track_scores']             = (boolean) Whether to calculate and return scores even if they are not used
     * for sorting
     *        ['version']                  = (boolean) Specify whether to return document version as part of a hit
     *        ['version_type']             = (boolean) Should the document increment the version number (internal) on
     * hit or not (reindex)
     *        ['request_cache']            = (boolean) Specify if request cache should be used for this request or not,
     * defaults to index level setting
     *        ['refresh']                  = (boolean) Should the effected indexes be refreshed?
     *        ['consistency']              = (enum) Explicit write consistency setting for the operation
     * (one,quorum,all)
     *        ['scroll_size']              = (integer) Size on the scroll request powering the update_by_query
     *        ['wait_for_completion']      = (boolean) Should the request should block until the reindex is complete.
     * (default: false)
     *        ['body']                     = The search definition using the Query DSL
     *
     * @param array $params
     *
     * @return array
     */
    public function updateByQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');
 
        $body = $this->extractArgument($params, 'body');
 
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\UpdateByQuery $endpoint */
        $endpoint = $endpointBuilder('UpdateByQuery');
        $endpoint->setIndex($index)
            ->setType($type)
            ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getScript($params)
    {
        $id = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Script\Get $endpoint */
        $endpoint = $endpointBuilder('Script\Get');
        $endpoint->setID($id)
                 ->setLang($lang);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteScript($params)
    {
        $id = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Script\Delete $endpoint */
        $endpoint = $endpointBuilder('Script\Delete');
        $endpoint->setID($id)
                 ->setLang($lang);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putScript($params)
    {
        $id   = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Script\Put $endpoint */
        $endpoint = $endpointBuilder('Script\Put');
        $endpoint->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getTemplate($params)
    {
        $id = $this->extractArgument($params, 'id');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Template\Get $endpoint */
        $endpoint = $endpointBuilder('Template\Get');
        $endpoint->setID($id);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteTemplate($params)
    {
        $id = $this->extractArgument($params, 'id');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Template\Delete $endpoint */
        $endpoint = $endpointBuilder('Template\Delete');
        $endpoint->setID($id);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['fields']             = (list) A comma-separated list of fields for to get field statistics for (min value, max value, and more)
     *        ['level']              = (enum) Defines if field stats should be returned on a per index level or on a cluster wide level
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fieldStats($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\FieldStats $endpoint */
        $endpoint = $endpointBuilder('FieldStats');
        $endpoint->setIndex($index)
            ->setBody($body)
            ->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fieldCaps($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\FieldCaps $endpoint */
        $endpoint = $endpointBuilder('FieldCaps');
        $endpoint->setIndex($index)
            ->setBody($body)
            ->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']                 = (string) ID of the template to render
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function renderSearchTemplate($params = array())
    {
        $body = $this->extractArgument($params, 'body');
        $id   = $this->extractArgument($params, 'id');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\RenderSearchTemplate $endpoint */
        $endpoint = $endpointBuilder('RenderSearchTemplate');
        $endpoint->setBody($body)
            ->setID($id);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * Operate on the Indices Namespace of commands
     *
     * @return IndicesNamespace
     */
    public function indices()
    {
        return $this->indices;
    }
 
    /**
     * Operate on the Cluster namespace of commands
     *
     * @return ClusterNamespace
     */
    public function cluster()
    {
        return $this->cluster;
    }
 
    /**
     * Operate on the Nodes namespace of commands
     *
     * @return NodesNamespace
     */
    public function nodes()
    {
        return $this->nodes;
    }
 
    /**
     * Operate on the Snapshot namespace of commands
     *
     * @return SnapshotNamespace
     */
    public function snapshot()
    {
        return $this->snapshot;
    }
 
    /**
     * Operate on the Cat namespace of commands
     *
     * @return CatNamespace
     */
    public function cat()
    {
        return $this->cat;
    }
 
    /**
     * Operate on the Ingest namespace of commands
     *
     * @return IngestNamespace
     */
    public function ingest()
    {
        return $this->ingest;
    }
 
    /**
     * Operate on the Tasks namespace of commands
     *
     * @return TasksNamespace
     */
    public function tasks()
    {
        return $this->tasks;
    }
 
    /**
     * Operate on the Remote namespace of commands
     *
     * @return RemoteNamespace
     */
    public function remote()
    {
        return $this->remote;
    }
 
    /**
     * Catchall for registered namespaces
     *
     * @param $name
     * @param $arguments
     * @return Object
     * @throws BadMethodCallException if the namespace cannot be found
     */
    public function __call($name, $arguments)
    {
        if (isset($this->registeredNamespaces[$name])) {
            return $this->registeredNamespaces[$name];
        }
        throw new BadMethodCallException("Namespace [$name] not found");
    }
 
    /**
     * @param array $params
     * @param string $arg
     *
     * @return null|mixed
     */
    public function extractArgument(&$params, $arg)
    {
        if (is_object($params) === true) {
            $params = (array) $params;
        }
 
        if (array_key_exists($arg, $params) === true) {
            $val = $params[$arg];
            unset($params[$arg]);
 
            return $val;
        } else {
            return null;
        }
    }
 
    private function verifyNotNullOrEmpty($name, $var)
    {
        if ($var === null) {
            throw new InvalidArgumentException("$name cannot be null.");
        }
 
        if (is_string($var)) {
            if (strlen($var) === 0) {
                throw new InvalidArgumentException("$name cannot be an empty string");
            }
        }
 
        if (is_array($var)) {
            if (strlen(implode("", $var)) === 0) {
                throw new InvalidArgumentException("$name cannot be an array of empty strings");
            }
        }
    }
 
    /**
     * @param $endpoint AbstractEndpoint
     *
     * @throws \Exception
     * @return array
     */
    private function performRequest(AbstractEndpoint $endpoint)
    {
        $promise =  $this->transport->performRequest(
            $endpoint->getMethod(),
            $endpoint->getURI(),
            $endpoint->getParams(),
            $endpoint->getBody(),
            $endpoint->getOptions()
        );
 
        return $this->transport->resultOrFuture($promise, $endpoint->getOptions());
    }
}
#8Elasticsearch\Client->performRequest(Object(Elasticsearch\Endpoints\Search))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Client.php (952)
<?php
 
namespace Elasticsearch;
 
use Elasticsearch\Common\Exceptions\BadMethodCallException;
use Elasticsearch\Common\Exceptions\InvalidArgumentException;
use Elasticsearch\Common\Exceptions\NoNodesAvailableException;
use Elasticsearch\Common\Exceptions\BadRequest400Exception;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Elasticsearch\Common\Exceptions\TransportException;
use Elasticsearch\Endpoints\AbstractEndpoint;
use Elasticsearch\Namespaces\AbstractNamespace;
use Elasticsearch\Namespaces\CatNamespace;
use Elasticsearch\Namespaces\ClusterNamespace;
use Elasticsearch\Namespaces\IndicesNamespace;
use Elasticsearch\Namespaces\IngestNamespace;
use Elasticsearch\Namespaces\NamespaceBuilderInterface;
use Elasticsearch\Namespaces\NodesNamespace;
use Elasticsearch\Namespaces\RemoteNamespace;
use Elasticsearch\Namespaces\SnapshotNamespace;
use Elasticsearch\Namespaces\BooleanRequestWrapper;
use Elasticsearch\Namespaces\TasksNamespace;
 
/**
 * Class Client
 *
 * @category Elasticsearch
 * @package  Elasticsearch
 * @author   Zachary Tong <[email protected]>
 * @license  http://www.apache.org/licenses/LICENSE-2.0 Apache2
 * @link     http://elastic.co
 */
class Client
{
    /**
     * @var Transport
     */
    public $transport;
 
    /**
     * @var array
     */
    protected $params;
 
    /**
     * @var IndicesNamespace
     */
    protected $indices;
 
    /**
     * @var ClusterNamespace
     */
    protected $cluster;
 
    /**
     * @var NodesNamespace
     */
    protected $nodes;
 
    /**
     * @var SnapshotNamespace
     */
    protected $snapshot;
 
    /**
     * @var CatNamespace
     */
    protected $cat;
 
    /**
     * @var IngestNamespace
     */
    protected $ingest;
 
    /**
     * @var TasksNamespace
     */
    protected $tasks;
 
    /**
     * @var RemoteNamespace
     */
    protected $remote;
 
    /** @var  callback */
    protected $endpoints;
 
    /** @var  NamespaceBuilderInterface[] */
    protected $registeredNamespaces = [];
 
    /**
     * Client constructor
     *
     * @param Transport $transport
     * @param callable $endpoint
     * @param AbstractNamespace[] $registeredNamespaces
     */
    public function __construct(Transport $transport, callable $endpoint, array $registeredNamespaces)
    {
        $this->transport = $transport;
        $this->endpoints = $endpoint;
        $this->indices   = new IndicesNamespace($transport, $endpoint);
        $this->cluster   = new ClusterNamespace($transport, $endpoint);
        $this->nodes     = new NodesNamespace($transport, $endpoint);
        $this->snapshot  = new SnapshotNamespace($transport, $endpoint);
        $this->cat       = new CatNamespace($transport, $endpoint);
        $this->ingest    = new IngestNamespace($transport, $endpoint);
        $this->tasks     = new TasksNamespace($transport, $endpoint);
        $this->remote    = new RemoteNamespace($transport, $endpoint);
        $this->registeredNamespaces = $registeredNamespaces;
    }
 
    /**
     * @param $params
     * @return array
     */
    public function info($params = [])
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Info $endpoint */
        $endpoint = $endpointBuilder('Info');
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * @param $params array Associative array of parameters
     *
     * @return bool
     */
    public function ping($params = [])
    {
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Ping $endpoint */
        $endpoint = $endpointBuilder('Ping');
        $endpoint->setParams($params);
 
        try {
            $this->performRequest($endpoint);
        } catch (Missing404Exception $exception) {
            return false;
        } catch (TransportException $exception) {
            return false;
        } catch (NoNodesAvailableException $exception) {
            return false;
        }
 
        return true;
    }
 
    /**
     * $params['id']              = (string) The document ID (Required)
     *        ['index']           = (string) The name of the index (Required)
     *        ['type']            = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['ignore_missing']  = ??
     *        ['fields']          = (list) A comma-separated list of fields to return in the response
     *        ['parent']          = (string) The ID of the parent document
     *        ['preference']      = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']        = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']         = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']         = (string) Specific routing value
     *        ['_source']         = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (list) A list of fields to extract and return from the _source field
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function get($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Get $endpoint */
        $endpoint = $endpointBuilder('Get');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']             = (string) The document ID (Required)
     *        ['index']          = (string) The name of the index (Required)
     *        ['type']           = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['ignore_missing'] = ??
     *        ['parent']         = (string) The ID of the parent document
     *        ['preference']     = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']       = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']        = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']        = (string) Specific routing value
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getSource($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Get $endpoint */
        $endpoint = $endpointBuilder('Get');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->returnOnlySource();
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']           = (string) The document ID (Required)
     *        ['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['consistency']  = (enum) Specific write consistency setting for the operation
     *        ['parent']       = (string) ID of parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['version_type'] = (enum) Specific version type
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function delete($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        $this->verifyNotNullOrEmpty("id", $id);
        $this->verifyNotNullOrEmpty("type", $type);
        $this->verifyNotNullOrEmpty("index", $index);
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Delete $endpoint */
        $endpoint = $endpointBuilder('Delete');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     *
     * $params['_source'] = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (array) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (array) A list of fields to extract and return from the _source field
     *        ['allow_no_indices'] = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['analyze_wildcard'] = (bool) Specify whether wildcard and prefix queries should be analyzed (default: false)
     *        ['analyzer'] = (string) The analyzer to use for the query string
     *        ['conflicts'] = (enum) What to do when the delete-by-query hits version conflicts?
     *        ['default_operator'] = (enum) The default operator for query string query (AND or OR)
     *        ['df'] = (string) The field to use as default where no field prefix is given in the query string
     *        ['expand_wildcards'] = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['from'] = (number) Starting offset (default: 0)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['lenient'] = (bool) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q'] = (string) Query in the Lucene query string syntax
     *        ['refresh'] = (bool) Should the effected indexes be refreshed?
     *        ['request_cache'] = (bool) Specify if request cache should be used for this request or not, defaults to index level setting
     *        ['requests_per_second'] = (number) The throttle for this request in sub-requests per second. -1 means no throttle.
     *        ['routing'] = (array) A comma-separated list of specific routing values
     *        ['scroll'] = (number) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['scroll_size'] = (number) Size on the scroll request powering the update_by_query
     *        ['search_timeout'] = (number) Explicit timeout for each search request. Defaults to no timeout.
     *        ['search_type'] = (enum) Search operation type
     *        ['size'] = (number) Number of hits to return (default: 10)
     *        ['slices'] = (integer) The number of slices this task should be divided into. Defaults to 1 meaning the task isn't sliced into subtasks.
     *        ['sort'] = (array) A comma-separated list of <field>:<direction> pairs
     *        ['stats'] = (array) Specific 'tag' of the request for logging and statistical purposes
     *        ['terminate_after'] = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early.
     *        ['timeout'] = (number) Time each individual bulk request should wait for shards that are unavailable.
     *        ['version'] = (bool) Specify whether to return document version as part of a hit
     *        ['wait_for_active_shards'] = (string) Sets the number of shard copies that must be active before proceeding with the delete by query operation. Defaults to 1, meaning the primary shard only. Set to `all` for all shard copies, otherwise set to any non-negative value less than or equal to the total number of copies for the shard (number of replicas + 1)
     *        ['wait_for_completion'] = (bool) Should the request should block until the delete-by-query is complete.
     *
     * @param array $params
     *
     * @return array
     */
    public function deleteByQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');
 
        $type = $this->extractArgument($params, 'type');
 
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\DeleteByQuery $endpoint */
        $endpoint = $endpointBuilder('DeleteByQuery');
        $endpoint->setIndex($index)
                ->setType($type)
                ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['type']               = (list) A comma-separated list of types to restrict the results
     *        ['min_score']          = (number) Include only documents with a specific `_score` value in the result
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['source']             = (string) The URL-encoded query definition (instead of using the request body)
     *        ['body']               = (array) A query to restrict the results (optional)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function count($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Count $endpoint */
        $endpoint = $endpointBuilder('Count');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['type']               = (list) A comma-separated list of types to restrict the results
     *        ['id']                 = (string) ID of document
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['allow_no_indices']   = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['body']               = (array) A query to restrict the results (optional)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['percolate_index']    = (string) The index to count percolate the document into. Defaults to index.
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *        ['version']            = (number) Explicit version number for concurrency control
     *        ['version_type']       = (enum) Specific version type
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function countPercolate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\CountPercolate $endpoint */
        $endpoint = $endpointBuilder('CountPercolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']        = (string) The name of the index with a registered percolator query (Required)
     *        ['type']         = (string) The document type (Required)
     *        ['prefer_local'] = (boolean) With `true`, specify that a local shard should be used if available, with `false`, use a random shard (default: true)
     *        ['body']         = (array) The document (`doc`) to percolate against registered queries; optionally also a `query` to limit the percolation to specific registered queries
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function percolate($params)
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Percolate $endpoint */
        $endpoint = $endpointBuilder('Percolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (string) Default index for items which don't provide one
     *        ['type']               = (string) Default document type for items which don't provide one
     *        ['ignore_unavailable'] = (boolean) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (boolean) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     *
     * @deprecated
     */
    public function mpercolate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\MPercolate $endpoint */
        $endpoint = $endpointBuilder('MPercolate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']            = (string) Default index for items which don't provide one
     *        ['type']             = (string) Default document type for items which don't provide one
     *        ['term_statistics']  = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['fields']           = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['offsets']          = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['positions']        = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['payloads']         = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['preference']       = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['routing']          = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['parent']           = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['realtime']         = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true).
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function termvectors($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $id    = $this->extractArgument($params, 'id');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\TermVectors $endpoint */
        $endpoint = $endpointBuilder('TermVectors');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']            = (string) Default index for items which don't provide one
     *        ['type']             = (string) Default document type for items which don't provide one
     *        ['ids']              = (list) A comma-separated list of documents ids. You must define ids as parameter or set \"ids\" or \"docs\" in the request body
     *        ['term_statistics']  = (boolean) Specifies if total term frequency and document frequency should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['field_statistics'] = (boolean) Specifies if document count, sum of document frequencies and sum of total term frequencies should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['fields']           = (list) A comma-separated list of fields to return. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['offsets']          = (boolean) Specifies if term offsets should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['positions']        = (boolean) Specifies if term positions should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\"."
     *        ['payloads']         = (boolean) Specifies if term payloads should be returned. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['preference']       = (string) Specify the node or shard the operation should be performed on (default: random) .Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['routing']          = (string) Specific routing value. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['parent']           = (string) Parent id of documents. Applies to all returned documents unless otherwise specified in body \"params\" or \"docs\".
     *        ['realtime']         = (boolean) Specifies if request is real-time as opposed to near-real-time (default: true).
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function mtermvectors($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type  = $this->extractArgument($params, 'type');
        $body  = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\MTermVectors $endpoint */
        $endpoint = $endpointBuilder('MTermVectors');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']         = (string) The document ID (Required)
     *        ['index']      = (string) The name of the index (Required)
     *        ['type']       = (string) The type of the document (use `_all` to fetch the first document matching the ID across all types) (Required)
     *        ['parent']     = (string) The ID of the parent document
     *        ['preference'] = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']   = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']    = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']    = (string) Specific routing value
     *
     * @param $params array Associative array of parameters
     *
     * @return array | boolean
     */
    public function exists($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        //manually make this verbose so we can check status code
        $params['client']['verbose'] = true;
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Exists $endpoint */
        $endpoint = $endpointBuilder('Exists');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return BooleanRequestWrapper::performRequest($endpoint, $this->transport);
    }
 
    /**
     * $params['index']           = (string) The name of the index
     *        ['type']            = (string) The type of the document
     *        ['fields']          = (list) A comma-separated list of fields to return in the response
     *        ['parent']          = (string) The ID of the parent document
     *        ['preference']      = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['realtime']        = (boolean) Specify whether to perform the operation in realtime or search mode
     *        ['refresh']         = (boolean) Refresh the shard containing the document before performing the operation
     *        ['routing']         = (string) Specific routing value
     *        ['body']            = (array) Document identifiers; can be either `docs` (containing full document information) or `ids` (when index and type is provided in the URL.
     *        ['_source']         = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude'] = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include'] = (list) A list of fields to extract and return from the _source field
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function mget($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Mget $endpoint */
        $endpoint = $endpointBuilder('Mget');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']       = (list) A comma-separated list of index names to use as default
     *        ['type']        = (list) A comma-separated list of document types to use as default
     *        ['search_type'] = (enum) Search operation type
     *        ['body']        = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function msearch($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Msearch $endpoint */
        $endpoint = $endpointBuilder('Msearch');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']       = (list) A comma-separated list of index names to use as default
     *        ['type']        = (list) A comma-separated list of document types to use as default
     *        ['search_type'] = (enum) Search operation type
     *        ['body']        = (array|string) The request definitions (metadata-search request definition pairs), separated by newlines
     *        ['max_concurrent_searches'] = (number) Controls the maximum number of concurrent searches the multi search api will execute
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function msearchTemplate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\MsearchTemplate $endpoint */
        $endpoint = $endpointBuilder('MsearchTemplate');
        $endpoint->setIndex($index)
            ->setType($type)
            ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['id']           = (string) Specific document ID (when the POST method is used)
     *        ['consistency']  = (enum) Explicit write consistency setting for the operation
     *        ['parent']       = (string) ID of the parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['timestamp']    = (time) Explicit timestamp for the document
     *        ['ttl']          = (duration) Expiration time for the document
     *        ['version']      = (number) Explicit version number for concurrency control
     *        ['version_type'] = (enum) Specific version type
     *        ['body']         = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function create($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Create $endpoint */
        $endpoint = $endpointBuilder('Create');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']       = (string) Default index for items which don't provide one
     *        ['type']        = (string) Default document type for items which don't provide one
     *        ['consistency'] = (enum) Explicit write consistency setting for the operation
     *        ['refresh']     = (boolean) Refresh the index after performing the operation
     *        ['replication'] = (enum) Explicitly set the replication type
     *        ['fields']      = (list) Default comma-separated list of fields to return in the response for updates
     *        ['body']        = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function bulk($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Bulk $endpoint */
        $endpoint = $endpointBuilder('Bulk');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']        = (string) The name of the index (Required)
     *        ['type']         = (string) The type of the document (Required)
     *        ['id']           = (string) Specific document ID (when the POST method is used)
     *        ['consistency']  = (enum) Explicit write consistency setting for the operation
     *        ['op_type']      = (enum) Explicit operation type
     *        ['parent']       = (string) ID of the parent document
     *        ['refresh']      = (boolean) Refresh the index after performing the operation
     *        ['replication']  = (enum) Specific replication type
     *        ['routing']      = (string) Specific routing value
     *        ['timeout']      = (time) Explicit operation timeout
     *        ['timestamp']    = (time) Explicit timestamp for the document
     *        ['ttl']          = (duration) Expiration time for the document
     *        ['version']      = (number) Explicit version number for concurrency control
     *        ['version_type'] = (enum) Specific version type
     *        ['body']         = (array) The document
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function index($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Index $endpoint */
        $endpoint = $endpointBuilder('Index');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['refresh']             = (boolean) Should the effected indexes be refreshed?
     *        ['timeout']             = (time) Time each individual bulk request should wait for shards that are unavailable
     *        ['consistency']         = (enum) Explicit write consistency setting for the operation
     *        ['wait_for_completion'] = (boolean) Should the request should block until the reindex is complete
     *        ['requests_per_second'] = (float) The throttle for this request in sub-requests per second. 0 means set no throttle
     *        ['body']                = (array) The search definition using the Query DSL and the prototype for the index request (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function reindex($params)
    {
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
        /** @var \Elasticsearch\Endpoints\Reindex $endpoint */
        $endpoint = $endpointBuilder('Reindex');
        $endpoint->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']          = (list) A comma-separated list of index names to restrict the operation; use `_all` or empty string to perform the operation on all indices
     *        ['ignore_indices'] = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['preference']     = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']        = (string) Specific routing value
     *        ['source']         = (string) The URL-encoded request definition (instead of using request body)
     *        ['body']           = (array) The request definition
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function suggest($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Suggest $endpoint */
        $endpoint = $endpointBuilder('Suggest');
        $endpoint->setIndex($index)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']                       = (string) The document ID (Required)
     *        ['index']                    = (string) The name of the index (Required)
     *        ['type']                     = (string) The type of the document (Required)
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcards and prefix queries in the query string query should be analyzed (default: false)
     *        ['analyzer']                 = (string) The analyzer for the query string query
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR)
     *        ['df']                       = (string) The default field for query string query (default: _all)
     *        ['fields']                   = (list) A comma-separated list of fields to return in the response
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['parent']                   = (string) The ID of the parent document
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['routing']                  = (string) Specific routing value
     *        ['source']                   = (string) The URL-encoded query definition (instead of using the request body)
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['body']                     = (string) The URL-encoded query definition (instead of using the request body)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function explain($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Explain $endpoint */
        $endpoint = $endpointBuilder('Explain');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *        ['analyzer']                 = (string) The analyzer to use for the query string
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcard and prefix queries should be analyzed (default: false)
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR)
     *        ['df']                       = (string) The field to use as default where no field prefix is given in the query string
     *        ['explain']                  = (boolean) Specify whether to return detailed information about score computation as part of a hit
     *        ['fields']                   = (list) A comma-separated list of fields to return as part of a hit
     *        ['from']                     = (number) Starting offset (default: 0)
     *        ['ignore_indices']           = (enum) When performed on multiple indices, allows to ignore `missing` ones
     *        ['indices_boost']            = (list) Comma-separated list of index boosts
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['query_cache']              = (boolean) Enable query cache for this request
     *        ['request_cache']            = (boolean) Enable request cache for this request
     *        ['routing']                  = (list) A comma-separated list of specific routing values
     *        ['scroll']                   = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['search_type']              = (enum) Search operation type
     *        ['size']                     = (number) Number of hits to return (default: 10)
     *        ['sort']                     = (list) A comma-separated list of <field>:<direction> pairs
     *        ['source']                   = (string) The URL-encoded request definition using the Query DSL (instead of using request body)
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['stats']                    = (list) Specific 'tag' of the request for logging and statistical purposes
     *        ['suggest_field']            = (string) Specify which field to use for suggestions
     *        ['suggest_mode']             = (enum) Specify suggest mode
     *        ['suggest_size']             = (number) How many suggestions to return in response
     *        ['suggest_text']             = (text) The source text for which the suggestions should be returned
     *        ['timeout']                  = (time) Explicit operation timeout
     *        ['terminate_after']          = (number) The maximum number of documents to collect for each shard, upon reaching which the query execution will terminate early.
     *        ['version']                  = (boolean) Specify whether to return document version as part of a hit
     *        ['body']                     = (array|string) The search definition using the Query DSL
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function search($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Search $endpoint */
        $endpoint = $endpointBuilder('Search');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']               = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *        ['preference']         = (string) Specify the node or shard the operation should be performed on (default: random)
     *        ['routing']            = (string) Specific routing value
     *        ['local']              = (bool) Return local information, do not retrieve the state from master node (default: false)
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function searchShards($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\SearchShards $endpoint */
        $endpoint = $endpointBuilder('SearchShards');
        $endpoint->setIndex($index)
                 ->setType($type);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or empty string to perform the operation on all indices
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to perform the operation on all types
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function searchTemplate($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Search $endpoint */
        $endpoint = $endpointBuilder('SearchTemplate');
        $endpoint->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['scroll_id'] = (string) The scroll ID for scrolled search
     *        ['scroll']    = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['body']      = (string) The scroll ID for scrolled search
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function scroll($params = array())
    {
        $scrollID = $this->extractArgument($params, 'scroll_id');
        $body = $this->extractArgument($params, 'body');
        $scroll = $this->extractArgument($params, 'scroll');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Scroll $endpoint */
        $endpoint = $endpointBuilder('Scroll');
        $endpoint->setScrollID($scrollID)
                 ->setScroll($scroll)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['scroll_id'] = (string) The scroll ID for scrolled search
     *        ['scroll']    = (duration) Specify how long a consistent view of the index should be maintained for scrolled search
     *        ['body']      = (string) The scroll ID for scrolled search
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function clearScroll($params = array())
    {
        $scrollID = $this->extractArgument($params, 'scroll_id');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\ClearScroll $endpoint */
        $endpoint = $endpointBuilder('ClearScroll');
        $endpoint->setScrollID($scrollID)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']                = (string) Document ID (Required)
     *        ['index']             = (string) The name of the index (Required)
     *        ['type']              = (string) The type of the document (Required)
     *        ['consistency']       = (enum) Explicit write consistency setting for the operation
     *        ['fields']            = (list) A comma-separated list of fields to return in the response
     *        ['lang']              = (string) The script language (default: mvel)
     *        ['parent']            = (string) ID of the parent document
     *        ['refresh']           = (boolean) Refresh the index after performing the operation
     *        ['replication']       = (enum) Specific replication type
     *        ['retry_on_conflict'] = (number) Specify how many times should the operation be retried when a conflict occurs (default: 0)
     *        ['routing']           = (string) Specific routing value
     *        ['script']            = () The URL-encoded script definition (instead of using request body)
     *        ['timeout']           = (time) Explicit operation timeout
     *        ['timestamp']         = (time) Explicit timestamp for the document
     *        ['ttl']               = (duration) Expiration time for the document
     *        ['version_type']      = (number) Explicit version number for concurrency control
     *        ['body']              = (array) The request definition using either `script` or partial `doc`
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function update($params)
    {
        $id = $this->extractArgument($params, 'id');
        $index = $this->extractArgument($params, 'index');
        $type = $this->extractArgument($params, 'type');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Update $endpoint */
        $endpoint = $endpointBuilder('Update');
        $endpoint->setID($id)
                 ->setIndex($index)
                 ->setType($type)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']                    = (list) A comma-separated list of index names to search; use `_all` or
     * empty string to perform the operation on all indices (Required)
     *        ['type']                     = (list) A comma-separated list of document types to search; leave empty to
     * perform the operation on all types
     *        ['analyzer']                 = (string) The analyzer to use for the query string
     *        ['analyze_wildcard']         = (boolean) Specify whether wildcard and prefix queries should be analyzed
     * (default: false)
     *        ['default_operator']         = (enum) The default operator for query string query (AND or OR) (AND,OR)
     * (default: OR)
     *        ['df']                       = (string) The field to use as default where no field prefix is given in the
     * query string
     *        ['explain']                  = (boolean) Specify whether to return detailed information about score
     * computation as part of a hit
     *        ['fields']                   = (list) A comma-separated list of fields to return as part of a hit
     *        ['fielddata_fields']         = (list) A comma-separated list of fields to return as the field data
     * representation of a field for each hit
     *        ['from']                     = (number) Starting offset (default: 0)
     *        ['ignore_unavailable']       = (boolean) Whether specified concrete indices should be ignored when
     * unavailable (missing or closed)
     *        ['allow_no_indices']         = (boolean) Whether to ignore if a wildcard indices expression resolves into
     * no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['conflicts']                = (enum) What to do when the reindex hits version conflicts? (abort,proceed)
     * (default: abort)
     *        ['expand_wildcards']         = (enum) Whether to expand wildcard expression to concrete indices that are
     * open, closed or both. (open,closed,none,all) (default: open)
     *        ['lenient']                  = (boolean) Specify whether format-based query failures (such as providing
     * text to a numeric field) should be ignored
     *        ['lowercase_expanded_terms'] = (boolean) Specify whether query terms should be lowercased
     *        ['preference']               = (string) Specify the node or shard the operation should be performed on
     * (default: random)
     *        ['q']                        = (string) Query in the Lucene query string syntax
     *        ['routing']                  = (list) A comma-separated list of specific routing values
     *        ['scroll']                   = (duration) Specify how long a consistent view of the index should be
     * maintained for scrolled search
     *        ['search_type']              = (enum) Search operation type (query_then_fetch,dfs_query_then_fetch)
     *        ['search_timeout']           = (time) Explicit timeout for each search request. Defaults to no timeout.
     *        ['size']                     = (number) Number of hits to return (default: 10)
     *        ['sort']                     = (list) A comma-separated list of <field>:<direction> pairs
     *        ['_source']                  = (list) True or false to return the _source field or not, or a list of
     * fields to return
     *        ['_source_exclude']          = (list) A list of fields to exclude from the returned _source field
     *        ['_source_include']          = (list) A list of fields to extract and return from the _source field
     *        ['terminate_after']          = (number) The maximum number of documents to collect for each shard, upon
     * reaching which the query execution will terminate early.
     *        ['stats']                    = (list) Specific 'tag' of the request for logging and statistical purposes
     *        ['suggest_field']            = (string) Specify which field to use for suggestions
     *        ['suggest_mode']             = (enum) Specify suggest mode (missing,popular,always) (default: missing)
     *        ['suggest_size']             = (number) How many suggestions to return in response
     *        ['suggest_text']             = (text) The source text for which the suggestions should be returned
     *        ['timeout']                  = (time) Time each individual bulk request should wait for shards that are
     * unavailable. (default: 1m)
     *        ['track_scores']             = (boolean) Whether to calculate and return scores even if they are not used
     * for sorting
     *        ['version']                  = (boolean) Specify whether to return document version as part of a hit
     *        ['version_type']             = (boolean) Should the document increment the version number (internal) on
     * hit or not (reindex)
     *        ['request_cache']            = (boolean) Specify if request cache should be used for this request or not,
     * defaults to index level setting
     *        ['refresh']                  = (boolean) Should the effected indexes be refreshed?
     *        ['consistency']              = (enum) Explicit write consistency setting for the operation
     * (one,quorum,all)
     *        ['scroll_size']              = (integer) Size on the scroll request powering the update_by_query
     *        ['wait_for_completion']      = (boolean) Should the request should block until the reindex is complete.
     * (default: false)
     *        ['body']                     = The search definition using the Query DSL
     *
     * @param array $params
     *
     * @return array
     */
    public function updateByQuery($params = array())
    {
        $index = $this->extractArgument($params, 'index');
 
        $body = $this->extractArgument($params, 'body');
 
        $type = $this->extractArgument($params, 'type');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\UpdateByQuery $endpoint */
        $endpoint = $endpointBuilder('UpdateByQuery');
        $endpoint->setIndex($index)
            ->setType($type)
            ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getScript($params)
    {
        $id = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Script\Get $endpoint */
        $endpoint = $endpointBuilder('Script\Get');
        $endpoint->setID($id)
                 ->setLang($lang);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteScript($params)
    {
        $id = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Script\Delete $endpoint */
        $endpoint = $endpointBuilder('Script\Delete');
        $endpoint->setID($id)
                 ->setLang($lang);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The script ID (Required)
     *        ['lang'] = (string) The script language (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function putScript($params)
    {
        $id   = $this->extractArgument($params, 'id');
        $lang = $this->extractArgument($params, 'lang');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Script\Put $endpoint */
        $endpoint = $endpointBuilder('Script\Put');
        $endpoint->setID($id)
                 ->setBody($body);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function getTemplate($params)
    {
        $id = $this->extractArgument($params, 'id');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Template\Get $endpoint */
        $endpoint = $endpointBuilder('Template\Get');
        $endpoint->setID($id);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']   = (string) The search template ID (Required)
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function deleteTemplate($params)
    {
        $id = $this->extractArgument($params, 'id');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\Template\Delete $endpoint */
        $endpoint = $endpointBuilder('Template\Delete');
        $endpoint->setID($id);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['fields']             = (list) A comma-separated list of fields for to get field statistics for (min value, max value, and more)
     *        ['level']              = (enum) Defines if field stats should be returned on a per index level or on a cluster wide level
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fieldStats($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\FieldStats $endpoint */
        $endpoint = $endpointBuilder('FieldStats');
        $endpoint->setIndex($index)
            ->setBody($body)
            ->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['index']              = (list) A comma-separated list of indices to restrict the results
     *        ['ignore_unavailable'] = (bool) Whether specified concrete indices should be ignored when unavailable (missing or closed)
     *        ['allow_no_indices']   = (bool) Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified)
     *        ['expand_wildcards']   = (enum) Whether to expand wildcard expression to concrete indices that are open, closed or both.
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function fieldCaps($params = array())
    {
        $index = $this->extractArgument($params, 'index');
        $body = $this->extractArgument($params, 'body');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\FieldCaps $endpoint */
        $endpoint = $endpointBuilder('FieldCaps');
        $endpoint->setIndex($index)
            ->setBody($body)
            ->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * $params['id']                 = (string) ID of the template to render
     *
     * @param $params array Associative array of parameters
     *
     * @return array
     */
    public function renderSearchTemplate($params = array())
    {
        $body = $this->extractArgument($params, 'body');
        $id   = $this->extractArgument($params, 'id');
 
        /** @var callback $endpointBuilder */
        $endpointBuilder = $this->endpoints;
 
        /** @var \Elasticsearch\Endpoints\RenderSearchTemplate $endpoint */
        $endpoint = $endpointBuilder('RenderSearchTemplate');
        $endpoint->setBody($body)
            ->setID($id);
        $endpoint->setParams($params);
 
        return $this->performRequest($endpoint);
    }
 
    /**
     * Operate on the Indices Namespace of commands
     *
     * @return IndicesNamespace
     */
    public function indices()
    {
        return $this->indices;
    }
 
    /**
     * Operate on the Cluster namespace of commands
     *
     * @return ClusterNamespace
     */
    public function cluster()
    {
        return $this->cluster;
    }
 
    /**
     * Operate on the Nodes namespace of commands
     *
     * @return NodesNamespace
     */
    public function nodes()
    {
        return $this->nodes;
    }
 
    /**
     * Operate on the Snapshot namespace of commands
     *
     * @return SnapshotNamespace
     */
    public function snapshot()
    {
        return $this->snapshot;
    }
 
    /**
     * Operate on the Cat namespace of commands
     *
     * @return CatNamespace
     */
    public function cat()
    {
        return $this->cat;
    }
 
    /**
     * Operate on the Ingest namespace of commands
     *
     * @return IngestNamespace
     */
    public function ingest()
    {
        return $this->ingest;
    }
 
    /**
     * Operate on the Tasks namespace of commands
     *
     * @return TasksNamespace
     */
    public function tasks()
    {
        return $this->tasks;
    }
 
    /**
     * Operate on the Remote namespace of commands
     *
     * @return RemoteNamespace
     */
    public function remote()
    {
        return $this->remote;
    }
 
    /**
     * Catchall for registered namespaces
     *
     * @param $name
     * @param $arguments
     * @return Object
     * @throws BadMethodCallException if the namespace cannot be found
     */
    public function __call($name, $arguments)
    {
        if (isset($this->registeredNamespaces[$name])) {
            return $this->registeredNamespaces[$name];
        }
        throw new BadMethodCallException("Namespace [$name] not found");
    }
 
    /**
     * @param array $params
     * @param string $arg
     *
     * @return null|mixed
     */
    public function extractArgument(&$params, $arg)
    {
        if (is_object($params) === true) {
            $params = (array) $params;
        }
 
        if (array_key_exists($arg, $params) === true) {
            $val = $params[$arg];
            unset($params[$arg]);
 
            return $val;
        } else {
            return null;
        }
    }
 
    private function verifyNotNullOrEmpty($name, $var)
    {
        if ($var === null) {
            throw new InvalidArgumentException("$name cannot be null.");
        }
 
        if (is_string($var)) {
            if (strlen($var) === 0) {
                throw new InvalidArgumentException("$name cannot be an empty string");
            }
        }
 
        if (is_array($var)) {
            if (strlen(implode("", $var)) === 0) {
                throw new InvalidArgumentException("$name cannot be an array of empty strings");
            }
        }
    }
 
    /**
     * @param $endpoint AbstractEndpoint
     *
     * @throws \Exception
     * @return array
     */
    private function performRequest(AbstractEndpoint $endpoint)
    {
        $promise =  $this->transport->performRequest(
            $endpoint->getMethod(),
            $endpoint->getURI(),
            $endpoint->getParams(),
            $endpoint->getBody(),
            $endpoint->getOptions()
        );
 
        return $this->transport->resultOrFuture($promise, $endpoint->getOptions());
    }
}
#9Elasticsearch\Client->search(Array([from] => 0, [size] => 11))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/Search/DroobleSearchClient.php (91)
<?php
 
namespace Drooble\Search;
 
class DroobleSearchClient //implements \Lib\Drooble\Interfaces\DroobleSearchClient
{
  private $di;
    
    private $pageTypes = []; // page tyeps in every language
    
  private $user_schema = array(
                
                "username"=>"",
                "email"=>"",
                "first_name"=>"",
                "last_name"=>"",
                "gender"=>"",
                "country"=>"",
                "city"=>"",
                "jam"=>"",
                "teach"=>"",
                "is_looking_for_a_band"=>"",
                "record_label"=>"",
//                "tags"=>[
//                  "equipment"=>[],        
//                  "genres"=>[],
//                  "influences"=>[],
//                  "instruments"=>[],
//                  "languages"=>[],
//                  "profile"=>[]
//                ],
                                /*todo
                "audios"=>array(
                  "songs"=>[],
                  "samples"=>[]
                ),
                "videos"=>array(
                  "videos"=>[]
                )*/
              
                /*TODO 
                "engagements"=>[]*/
 
              );
 
  private $page_schema = array(
                
                "username"=>"",
                "email"=>"",
                "name"=>"",
                "type"=>[],
                "country"=>"",
                "city"=>"",
                "tags"=>array(
                  "equipment"=>[],        
                  "genres"=>[],
                  "influences"=>[],
                  "instruments"=>[],
                  "languages"=>[],
                  "profile"=>[]
                ),/*todo
                "audios"=>array(
                  "songs"=>[],
                  "samples"=>[]
                ),
                "videos"=>array(
                  "videos"=>[]
                )*/
              
                /*TODO 
                "engagements"=>[]*/
 
              );
 
 
 
 
  private $client;
 
 
  
  public function __construct($elastic_client)
  {
    //$this->di = $di;
    //\Helpers\Debug::log("getDi",$di->get('elastic'));
    //$this->client = $di->get('elastic');
    $this->client = $elastic_client;
  }
    
    public function search($params) {
        $response = $this->client->search($params);
        return $response;
    }
 
  public function query($index,$type,$q_json){
 
 
    
 
    $params = [
        'index' => $index,
        'type' => $type,
        'body' => $q_json
    ];
 
    try{
      $response = $this->client->search($params);
    }catch(\Exception $e){
      $response = false;
    }
 
    /*\Helpers\Debug::log("elastic indeces");
 
    $response = $this->client->indices()->getSettings(['index' => [ 'profiles' ]]);
 
    \Helpers\Debug::log("indeces" ,$response);
    */
    return $response;
 
  }
    // TODO after merge: use query function (if made generic)
    public function queryTest($q_json) {
        $params = [
        'index' => \Config::elasticsearch()->INDICES->tags,//'tags',
        'type' => 'tags',
        'body' => $q_json
    ];
 
    try{
      $response = $this->client->search($params);
    }catch(\Exception $e){
      $response = false;
    }
 
    return $response;
    }
 
  public function getUserById($userId){
    $params = [
        'index' => \Config::elasticsearch()->INDICES->users,//'users',
        'type' => 'users',
        'id' => $userId
    ];
    
    try{
      $response = $this->client->get($params);
    }catch(\Exception $e){
      $response = false;
    }
 
    /*\Helpers\Debug::log("elastic indeces");
 
    $response = $this->client->indices()->getSettings(['index' => [ 'profiles' ]]);
 
    \Helpers\Debug::log("indeces" ,$response);
    */
    return $response;
  }
    
  public function getTagById($tagId){
    $params = [
        'index' => \Config::elasticsearch()->INDICES->tags,//'tags',
        'type' => 'tags',
        'id' => $tagId
    ];
    
    try{
      $response = $this->client->get($params);
    }catch(\Exception $e){
      $response = false;
    }
 
    $response = $this->client->indices()->getSettings(['index' => [ \Config::elasticsearch()->INDICES->tags, ]]);
 
    \Helpers\Debug::log("indeces" ,$response);
    return $response;
  }
    
    
 
    public function addTag(\Collections\Tags $tag) {
        $body = [];
        
        $rating = 0;
        $types = [];
    
        foreach (unserialize(TAGS_TYPES) as $tagType) {
            if ( (bool) $tag->type->{$tagType}->status && !(bool) $tag->type->{$tagType}->banned && !(bool) $tag->type->{$tagType}->is_delete ) {
                $rating += (int) $tag->type->{$tagType}->user_rating + (int) $tag->type->{$tagType}->moderator_rating;
                $types[] = $tagType;
            }
        }
        $body['types'] = $types;
        $body['rating'] = $rating;
        
        $indices = \Config::elasticsearch()->INDICES->tags;
        
        foreach ($indices as $lang => $idx) {
            
            $body['text'] = $tag->translation->{$lang}->text;
            if ( strlen($body['text']) == 0 ) {
                $body['text'] = $tag->translation->en->text;    
            }
            if ( strlen($body['text']) ) {
                $body['text_suggest'] = $body['text'];
                $params = [
                    'index' => $idx,
                    'type' => 'tag',
                    'id' => (string) $tag->_id,
                    'body' => $body
                ];
                $this->client->index($params);
            }
        }
    return true;
    }
    
    public function removeTag(\Collections\Tags $tag){
        $indices = \Config::elasticsearch()->INDICES->tags;
        foreach ($indices as $lang => $idx) {
            $params = [
                'index' => $idx,
                'type' => 'tag',
                'id' => (string) $tag->_id
            ];
 
            try{
                $this->client->delete($params);
            }catch(\Elasticsearch\Common\Exceptions\Missing404Exception $e){
                return false;
            }
            
        }
    return true;
  }
 
 
  public function addUser(\Models\Users $user){
    $u = $this->user_schema;
    $u['username'] = $user->username;
    $u['email'] = $user->email;
    $u['name'] = $user->first_name . " " . $user->last_name; // it's better to have the same field for Users and Pages
    $u['first_name'] = $user->first_name;
    $u['last_name'] = $user->last_name;
    $u['gender'] = $user->gender;
//    $u['country'] = $user->country;
    $u['city'] = $user->city;
    $u['jam'] = $user->is_jam;
    $u['teach'] = $user->is_teach;
    $u['lastseen'] = (int) $user->lastseen;
    $u['is_looking_for_a_band'] = $user->is_looking_for_a_band;
    $u['record_label'] = $user->record_label;
        $text = \Models\UserTexts::findFirst(array(
            "conditions" => "scene = 'about' AND user_id = :uid: AND is_deleted = '0'",
            "bind" => array("uid" => $user->id)
        ));
        $u['about'] = [];
        if ($text) {
            $u['about']['title'] = $text->title;
            $u['about']['text'] = $text->text;
        }
        
        $cachedInfo = \Models\UserCache::findFirst("user_id = {$user->id}");
    $u['price'] = $cachedInfo->price;
        
        $avatars=\Helpers\Pictures::getDefault($user->id);
        if ( strpos($avatars['avatar_icon'], $user->id . "/avatars_icon" ) ) {
            $u['has_avatar'] = true; // many searches will be sorted by "has avatar that is not default"
        } else {
            $u['has_avatar'] = false;
        }
        $u['avatar'] = $avatars['avatar'];
        $u['avatar_icon'] = $avatars['avatar_icon'];
        
        if ($user->city) {
            $selected_place = \Models\GooglePlaces::findFirst([
                "conditions" => "google_place_id = :google_place_id:",
                "bind" => ["google_place_id" => $user->city]
            ]);
            $u['place'] = $selected_place->google_place_details_en;
            $u['country'] = $selected_place->country_code;
            $u['location'] = (object) ['lat' => floatval($user->lat), 'lon' => floatval($user->lng)];
        } else {
            $u['place'] = "";
            $u['location'] = (object) ['lat' => floatval($user->lat), 'lon' => floatval($user->lng)];
        }
 
        $pin = new \stdClass();
        $pin->location = $u['location'];
        $u['pin']= $pin;
 
    $ucommunities = [];
    $utags = [];
    $utags['equipment']=[];
    $utags['genres_primary']=[];
    $utags['genres_secondary']=[];
    $utags['influences']=[];
    $utags['instruments_primary']=[];
    $utags['instruments_secondary']=[];
    $utags['languages']=[];
    $utags['profile']=[];
 
//    foreach($user->Tags as $tag){
//      if( in_array($tag->type, array_keys($utags)) ){
//        $utags[$tag->type][] = $tag->name;
//      }
//    }
        
        $allCommunities = \Helpers\Tags::getCommunities($user->id); // from apc cache (is_member is NOT cached)
        foreach ($allCommunities as $community) {
            if ($community['is_member']) {
                $data = new \stdClass();
                // NOTE! getCommunities returns translations even for missing languages! 
                // e.g. (tags that miss 'es' translation is filled with 'en' text
                foreach ($community['translation'] as $code => $info) {
                    $data->{$code} = $info->text;
                }
                $ucommunities[] = $data;
            }
        }
        
        $userTags = \Helpers\Users::getUserTags($user->id, 3, true, null, false);
        foreach ($userTags as $type => $tags) {
            foreach ($tags as $tag) {
                $data = new \stdClass();
                foreach ($tag->translation as $code => $info) {
                    $data->{$code} = $info->text;
                }
                $utags[$type][] = $data;
            }
        }
        
        //\Helpers\Debug::log("test");
    //\Helpers\Debug::log($utags);
    
    
    $u['tags'] = $utags;
    $u['communities'] = $ucommunities;
        
        $forSearch = \Helpers\UserValues::getComputed('profile-completed-for-search', null, $user->id);
        if ($forSearch['value']) {
            // It seems that 'visible' is a reserved word in Elasticsearch!!! but not documented ... calculate .. smqtaj
            $u['visibility'] = [
                'profile_completed'
            ];
        }
    
    $params = [
        'index' => \Config::elasticsearch()->INDICES->users,//'users',
        'type' => 'users',
        'id' => $user->id,
        'body' => $u
    ];
 
    // Document will be indexed to my_index/my_type/my_id
 
    $response = $this->client->index($params);
 
//        var_dump($response);
    //\Helpers\Debug::log("resp ", $response);
    return $response;
 
  }
 
 
  public function removeUser(\Models\Users $user){
    $params = [
        'index' => \Config::elasticsearch()->INDICES->users,//'users',
        'type' => 'users',
        'id' => $user->id
    ];
 
    // Delete doc at /my_index/my_type/my_id
    try{
      $response = $this->client->delete($params);
    }catch(\Elasticsearch\Common\Exceptions\Missing404Exception $e){
      //\Helpers\Debug::log("ne mogia da iztria s parametri", $params);
 
      $response = false;
    }
    return $response;
  }
 
 
    private function loadPageTypesTranslations() {
        if (!$this->types_loaded) {
            $di = \Phalcon\DI\FactoryDefault::getDefault();
            $this->db = $di->getDb();
            $sql = "SELECT language_id, `key`, `value` FROM `text_translations` tt
                    JOIN text_keys tk ON tk.id = tt.text_key_id
                    WHERE description = 'users_page_types.title'";
            
            $res = $this->db->query($sql);
            $res->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $trRes = $res->fetchAll();
            foreach ($trRes as $data) {
                if (is_array($this->pageTypes[$data['key']])) {
                    $this->pageTypes[$data['key']][$data['language_id']] = $data['value'];
                } else {
                    $this->pageTypes[$data['key']] = [];
                    $this->pageTypes[$data['key']][$data['language_id']] = $data['value'];
                }
            }
            $this->types_loaded = true;
        }
    }
 
 
  public function addPage(\Models\Users $page){
        $this->loadPageTypesTranslations();
    $p = $this->page_schema;
    $p['username'] = $page->username;
    $p['email'] = $page->email;
    $p['name'] = trim($page->first_name);
        $p['type'] = [];
        $languages_config = json_decode(json_encode(\Config::languages()->language), true);
        $typeKey = strtolower(str_replace([" ", "/"], ["_", "_"], $page->PageType->title));
        if ($typeKey == "band") {
            $typeKey = "page_band"; 
        } 
        foreach ($languages_config as $code => $info) {
            $p['type'][$code] = $this->pageTypes[$typeKey][$info['id']];
        }
        
        $text = \Models\UserTexts::findFirst(array(
                    "conditions" => "scene = 'about' AND user_id = :uid: AND is_deleted = '0'",
                    "bind" => array("uid" => $page->id)
        ));
        $p['about'] = [];
        if ($text) {
            $p['about']['title'] = $text->title;
            $p['about']['text'] = $text->text;
        }
        
        // all visible for now
        $p['visibility'] = [
            'profile_completed'
        ];
 
        $p['country'] = $page->country;
    $p['city'] = $page->city;
    $p['page_type_id'] = (int) $page->page_type_id;
        $p['lastseen'] = (int) $page->lastseen; // set by last interaction from Page (via DroobleHooksManager)
 
    $utags = [];
    $utags['equipment']=[];
    $utags['genres_primary']=[];
    $utags['influences']=[];
    $utags['instruments_primary']=[];
    $utags['languages']=[];
    $utags['profile']=[];
        
        $avatars=\Helpers\Pictures::getDefault($page->id);
        if ( strpos($avatars['avatar_icon'], $page->id . "/avatars_icon" ) ) {
            $p['has_avatar'] = true; // many searches will be sorted by "has avatar that is not default"
        } else {
            $p['has_avatar'] = false;
        }
        
        $p['avatar'] = $avatars['avatar'];
        $p['avatar_icon'] = $avatars['avatar_icon'];
        
        if ($page->city) {
            $selected_place = \Models\GooglePlaces::findFirst([
                "conditions" => "google_place_id = :google_place_id:",
                "bind" => ["google_place_id" => $page->city]
            ]);
            $p['place'] = $selected_place->google_place_details_en;
            $p['location'] = (object) ['lat' => floatval($page->lat), 'lon' => floatval($page->lng)];
        } else {
            $p['place'] = "";
            $p['location'] = null;
        }
        $pin = new \stdClass();
        $pin->location = $p['location'];
        $p['pin']= $pin;
        
        $pageTags = \Helpers\Users::getUserTags($page->id, 3, true, null, false);
        foreach ($pageTags as $type => $tags) {
            foreach ($tags as $tag) {
                $data = new \stdClass();
                foreach ($tag->translation as $code => $info) {
                    $data->{$code} = $info->text;
                }
                $utags[$type][] = $data;
            }
        }
 
        //\Helpers\Debug::log("test");
    //\Helpers\Debug::log($utags);
    
    
    $p['tags'] = $utags;
    
    
    $params = [
        'index' => \Config::elasticsearch()->INDICES->pages,//'pages',
        'type' => 'pages',
        'id' => $page->id,
        'body' => $p
    ];
 
    // Document will be indexed to my_index/my_type/my_id
 
    $response = $this->client->index($params);
 
    return $response;
 
  }
 
 
  public function removePage(\Models\Users $page){
    $params = [
        'index' => \Config::elasticsearch()->INDICES->pages,//'pages',
        'type' => 'pages',
        'id' => $page->id
    ];
 
    // Delete doc at /my_index/my_type/my_id
    try{
      $response = $this->client->delete($params);
    }catch(\Elasticsearch\Common\Exceptions\Missing404Exception $e){
      //\Helpers\Debug::log("ne mogia da iztria s parametri", $params);
 
      $response = false;
        } catch (\Elasticsearch\Common\Exceptions\BadRequest400Exception $e) {
            \Helpers\Debug::error( $e->getTraceAsString() );
            $response = false;
        }
    return $response;
  }
 
 
  public function getPageById($pageId){
    $params = [
        'index' => \Config::elasticsearch()->INDICES->pages,//'pages',
        'type' => 'pages',
        'id' => $pageId
    ];
    
    try{
      $response = $this->client->get($params);
    }catch(Exception $e){
      $response = false;
    }
 
    /*\Helpers\Debug::log("elastic indeces");
 
    $response = $this->client->indices()->getSettings(['index' => [ 'profiles' ]]);
 
    \Helpers\Debug::log("indeces" ,$response);
    */
    return $response;
  }
 
 
  
}
#10Drooble\Search\DroobleSearchClient->search(Array([from] => 0, [size] => 11, [index] => drooble_users, [body] => Array([query] => Array([bool] => Array()), [sort] => Array([0] => Array()))))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/Search/DroobleSearchManager.php (20)
<?php
namespace Drooble\Search;
 
 
 
class DroobleSearchManager extends \Phalcon\Mvc\User\Component
{
  private $client;
  
  public function __construct()
  {
    $elastic = \Phalcon\Di::getDefault()->getElastic();
    //$elastic = null;
    //\Helpers\Debug::log($elastic);  
    $this->client = new \Drooble\Search\DroobleSearchClient($elastic);    
    //$this->client = new \StdClass();
  }
  
    public function search($params) {
        $response = $this->client->search($params);
        return $response;
    }
 
    // DEPRECATED
  public function usersSearch($query){
/*
  $json = '{
    "from" : 0, "size" : 5,
    "_source": [ "profile.username", "profile.first_name","profile.last_name","profile.email"],
    "query": {
      "multi_match": {
          "fields":  [ "profile.username", "profile.first_name","profile.last_name","profile.email" ],
          "query":     "'.$query.'",
          "fuzziness": "AUTO"
      }
    }
  }';
 
 
  $json = '{
        "from" : 0, "size" : 5,
        "_source": [ "profile.username", "profile.first_name","profile.last_name","profile.email"],
        "query": {
          "dis_max": {
              "tie_breaker": 0.1,
              "boost": 1.2,
              "queries": [
              
              {
                    "multi_match": {
                            "fields":  [ "profile.username", "profile.first_name","profile.last_name","profile.email" ],
                        "query": "'.$query.'",
                        "type": "phrase",
                        "boost": 5
                    }
                  },
                  {
                    "multi_match": {
                            "fields":  [ "profile.username", "profile.first_name","profile.last_name","profile.email" ],
                        "query": "'.$query.'",
                        "type": "phrase",
                        "fuzziness": "AUTO",
                        "boost": 3
                    }
                  },
                  {
                    "query_string": {
                      "default_field": "profile.username",
                      "query": "'.$query.'"
                    }
                  }
            ]
          }
        }
      }';
 
 
 
  $json = '{
    "from" : 0, "size" : 5,
    "_source": [ "profile.username", "profile.first_name","profile.last_name","profile.email"],
    "query": {
      "multi_match": {
          "fields":  [ "profile.username", "profile.first_name","profile.last_name","profile.email" ],
          "query":     "'.$query.'",
          "fuzziness": "AUTO",
              "type":     "cross_fields",
              "operator":   "and"
      }
    }
  }';
 
  $json = '{
    "from" : 0, "size" : 5,
    "_source": [ "profile.first_name", "profile.last_name"],
    "query": {
      "multi_match": {
          "fields":  [ "profile.username", "profile.first_name","profile.last_name","profile.email" ],
          "query":     "'.$query.'",
          "fuzziness": "AUTO",
              "type":     "cross_fields",
              "operator":   "and"
      }
    }
  }';
*/
    $json = '{
      "from" : 0, "size" : 5,
      "_source": [ "first_name", "last_name"],
      "query": {
        "bool": {
          "should":[
            {
              "match_phrase_prefix" : {
                "full_name" : {
                      "query": "'.$query.'",
                      "slop":"100",
                         "fuzziness": "AUTO"
                }
              }
                },
                {
                  "multi_match": {
                  "fields":  [ "username", "first_name","last_name","email" ],
                  "query":     "'.$query.'",
                  "fuzziness": "AUTO"
              }
            }
              ]
          }
      }
    }';
    \Helpers\Debug::log($json);
 
    
    $searched_users = $this->client->query("users","users",$json);
 
    //\Helpers\Debug::log($searched_users['hits']['hits']);
 
 
    $users = [];
    foreach($searched_users['hits']['hits'] as $user){
      //$u = \Models\Users::findFirst($user["_id"]);
      //$users[] = \Patterns\UserSearchInfo::doAll($u);
      $users[] = \Helpers\Users::getUserShortInfoFromId($user["_id"]);
    }
 
    return ["users"=>$users,"raw_result"=>$searched_users];
    //return [];
  }
    
    // DEPRECATED
  public function pagesSearch($query){
    $json = '{
      "from" : 0, "size" : 5,
      "_source": [ "first_name", "last_name"],
      "query": {
        "bool": {
          "should":[
            {
              "match_phrase_prefix" : {
                "name" : {
                      "query": "'.$query.'",
                      "slop":"100",
                         "fuzziness": "AUTO"
                }
              }
                },
                {
                  "multi_match": {
                  "fields":  [ "username", "name", "type" ],
                  "query":     "'.$query.'",
                  "fuzziness": "AUTO"
              }
            }
              ]
          }
      }
    }';
 
    \Helpers\Debug::log($json);
 
    
    $searched_pages = $this->client->query("pages","pages",$json);
 
    //\Helpers\Debug::log($searched_users['hits']['hits']);
 
 
    $pages = [];
    foreach($searched_pages['hits']['hits'] as $page){
      //$u = \Models\Users::findFirst($user["_id"]);
      //$users[] = \Patterns\UserSearchInfo::doAll($u);
      $pages[] = \Helpers\Users::getUserShortInfoFromId($page["_id"]);
    }
 
    return ["pages"=>$pages,"raw_result"=>$searched_pages];
  }
 
  public function addTag(\Collections\Tags $tag) {
        return $this->client->addTag($tag);
  }
 
    
    public function removeTag(\Collections\Tags $tag) {
    return $this->client->removeTag($tag);
 
  }
    
    public function addUser(\Models\Users $user) {
 
    return $this->client->addUser($user);
 
  }
 
 
  public function removeUser(\Models\Users $user){
    
    return $this->client->removeUser($user);
 
  }
 
  public function getUserById($userId){
    
    return $this->client->getUserById($userId);
 
  }
 
 
 
  public function addPage(\Models\Users $page) {
 
    return $this->client->addPage($page);
 
  }
 
 
  public function removePage(\Models\Users $page){
    
    return $this->client->removePage($page);
 
  }
 
  public function getPageById($pageId){
    
    return $this->client->getPageById($pageId);
 
  }
 
}
#11Drooble\Search\DroobleSearchManager->search(Array([from] => 0, [size] => 11, [index] => drooble_users, [body] => Array([query] => Array([bool] => Array()), [sort] => Array([0] => Array()))))
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/logic/search.class.php (1587)
<?php
class search{
 
  private $db;
  private $c;
    private $app;
 
  public function __construct($db,&$c, $app) {
    $this->db = $db;
    $this->c = $c;
        $this->app = $app;
  }
/*
  public function doReindex($data){
    $ret=array();
    foreach($this->db->q("SELECT `id` FROM `users_profiles` WHERE 1") as $el){
      $ret[]=$this->lib->profileSearchIndexing($el['id']);
    }
 
    return array(true,$ret);
  }
*/
    public function searchCity($data) {
        $return = new \stdClass();
 
        $return->items = \Helpers\Search::getInstance()->searchCity($data['name']);
 
        return array(true, $return);
 
 
        /*
        // SELECT *, CASE WHEN name LIKE 'Sofia%' THEN 1 ELSE 0 END AS keymatch FROM `cities` WHERE MATCH (name) AGAINST ('+Sofia' IN BOOLEAN MODE) ORDER BY keymatch DESC
        $cities = $this->app->db->fetchAll("
            SELECT *, CASE
                WHEN search_by_me LIKE ".$this->app->db->escapeString($keywords)." THEN 4
                WHEN search_by_me LIKE ".$this->app->db->escapeString($keywords." %")." THEN 3
                WHEN search_by_me LIKE ".$this->app->db->escapeString("% ".$keywords." %")." THEN 2
                WHEN search_by_me LIKE ".$this->app->db->escapeString("% ".$keywords)." THEN 1
                ELSE 0 END AS keymatch
            FROM `cities`
            WHERE
            MATCH (search_by_me) AGAINST (".$this->app->db->escapeString($keywords).")
            ORDER BY keymatch DESC
            LIMIT 22
        ");
 
        $items = array();
 
        if ($cities) {
            foreach($cities as $city)
            {
                $title_suffix = $city['country'];
                if ($city['region']) {
                    $title_suffix = $city['region'].", ".$city['country'];
                }
 
                $items[] = array(
                    'id' => $city['id'],
                    'title' => $city['name'].", ".$title_suffix,
                    'name' => $city['name'],
                    'country' => $city['country'],
                    'region' => $city['region']
                );
            }
        }
 
        $return->items = $items;
 
        return array(true, $return);
        */
    }
 
  public function searchByMail($data){
    $email=$data['email'];
 
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
      return array(false,"not valid");
    }
 
        /*
    $search=$email;
    $search=str_replace("*","",$search);
    $search=str_replace(" ","",$search);
    $search=str_replace(")","",$search);
    $search=str_replace("(","",$search);
 
 
    $sql="SELECT `short_info` FROM `search_cache` WHERE MATCH (`email`) AGAINST ('".'"'.$search.'"'."')";
    $r=$this->db->one($sql);
         *
         */
 
        $person = \Models\UserCache::findFirst([
            'conditions' => 'email LIKE {email:str}',
            'bind' => ['email' => $email]
        ]);
 
        if ($person) {
            return array(true, json_decode($person->short_info_json, true));
        } else {
            return array(true,'notexists');
        }
  }
 
  public function otherPersonContactsSuggester($data){
    $user_id=\Helpers\Users::getIdFromUsername($data['profile']);
    if(isset($data['circle_id']) && $data['profile'] != $_SESSION['profile']['username']){
      unset($data['circle_id']);
    }
    if(!$data['results']){
      $data['results']=9;
    }
    if(isset($data['mix_order'])){
      $data['mix_order']=true;
    }
 
    // \Helpers\Debug::alert($data['profile']);
    if($user_id){
      return $this->ContactsSuggester($user_id,$data);
    }else{
      return array(false);
    }
  }
 
  public function myContactsSuggester($data){
 
    return $this->ContactsSuggester($_SESSION['profile']['id'],$data,true, isset($data['online_first']) ? $data['online_first'] : true);
  }
 
  public function otherPersonFollowSuggester($data) {
    $data["filterUsername"] = $data["profile"];
    unset($data["profile"]);
 
    $data["follow_type"] = "followings";
 
    return $this->ContactsSuggester($_SESSION['profile']['id'],$data,true, false);
  }
 
    public function searchInMyMessages($data){
        // Used data
        $user_id = (int)$_SESSION['profile']['id'];
        if ($data['band_id'] > 0) {
            if (!\Helpers\Basic::checkUserRights((int) $data['band_id'], $user_id)) {
                return array(false,false);
            } else {
                $user_id = (int) $data['band_id'];
            }
        }
        
        $offset = $data['offset'];
        $limit = $data['limit'];
 
        $search = trim(strip_tags($data['search']));
        $search = str_replace(array("%", "'", '"'), "", $search);
 
        $length_to_get = 50; // How much characters to get
        $symbols_before = 10; // How much symbols before match to get
 
        $return = array();
 
        // Search query
        $query = "
            SELECT * FROM
            (
                SELECT
                    um.text, um.conversations_id, c.is_group, c.members,
                    IF (um.reciever_id = '".$user_id."', scsender.short_info_json, screciever.short_info_json) as partner,
                    IF (um.reciever_id = '".$user_id."', scsender.user_id, screciever.user_id) as partner_id
                FROM users_messages as um
                LEFT JOIN conversations AS c ON c.id = um.conversations_id
                LEFT JOIN search_cache AS scsender ON scsender.user_id = um.sender_id
                LEFT JOIN search_cache AS screciever ON screciever.user_id = um.reciever_id
                WHERE
                    um.sender_id != um.reciever_id
                    AND (um.sender_id = '".$user_id."' OR um.reciever_id = '".$user_id."')
                    AND um.is_deleted=0
                    AND (
                        um.text LIKE :keywords
                        OR (scsender.cache LIKE :keywords AND scsender.user_id != '".$user_id."')
                        OR (screciever.cache LIKE :keywords AND screciever.user_id != '".$user_id."')
                    )
                ORDER BY um.send_time DESC
            ) as results
            GROUP BY results.conversations_id, results.text, results.partner, results.partner_id
        ";
 
        $prepare = array(
            'keywords' => "%".$search."%"
        );
        
        // Here we store user information - reason - when we have a user, does not look for it again
        $cache_profiles = array();
 
        // So... lets begin... just in case string was more than 3 symbols
        if (mb_strlen($search) >= 3)
        {
            $cids = [];
            // Put information
            foreach($this->db->prepare($query, $prepare)->execute()->fetchAllAssoc()->cache( 1 )->result as $el) {
                $cids[] = $el['conversations_id'];
                continue; // !!!! NOTE we'll be using getMail at end of func to parse the response
                
                //this below -- NOT executed !!! 
                
                
                $text = trim(strip_tags($el['text']));
 
                $find_position = mb_strpos($search, $text);
                $text_length = mb_strlen($text);
                $string_length = mb_strlen($search);
 
                // Stupid way but... i cant regex :/
                if ($text_length > $length_to_get)
                {
                    if (($find_position - $symbols_before) <= 0)
                    {
                        $text = mb_substr($text, 0, $length_to_get)."...";
                    }
                    else if (($find_position - $symbols_before) > 0 AND ($find_position - $symbols_before + $length_to_get) <= $text_length)
                    {
                        $text = "...".mb_substr($text, ($find_position - $symbols_before), $length_to_get)."...";
                    }
                    else
                    {
                        $text = "...".mb_substr($text, ($length_to_get*-1));
                    }
                }
 
                $line = array(
                    'quote' => $text,
                    'conversation' => "#".$el['conversations_id']
                );
 
                // Put information about users in converstation
                if ((int)$el['is_group'] == 0)
                {
                    if (!isset($cache_profiles[$el['partner_id']]))
                    {
                        $user = unserialize($el['partner']);
 
                        $cache_profiles[$el['partner_id']] = array(
                            'names' => $user['display_name']
                        );
 
                        if (isset($user['avatar_icon']) AND $user['avatar_icon'] != "")
                        {
                            $cache_profiles[$el['partner_id']]['avatar_icon'] = $user['avatar_icon'];
                        }
                    }
 
                    $line['names'] = $cache_profiles[$el['partner_id']]['names'];
                    $line['avatar_icon'] = $cache_profiles[$el['partner_id']]['avatar_icon'];
                }
                else
                {
                    // Check collection
                    $collection = array();
                    $user_ids = explode(",", $el['members']);
                    foreach($user_ids as $key => $val)
                    {
                        if ((int)$val != 0 AND (int)$val != $user_id)
                        {
                            $collection[(int)$val] = false;
                        }
                    }
 
                    // Filter which users we have
                    $search_user_ids = array();
                    foreach($collection as $key => $val)
                    {
                        if (isset($cache_profiles[$key]))
                        {
                            // Put it in our collection
                            $collection[$key] = $cache_profiles[$key]['names'];
                        }
                        else
                        {
                            $search_user_ids[] = $key;
                        }
                    }
 
                    // Get missed users
                    if (!empty($search_user_ids))
                    {
                        $query = "SELECT user_id, short_info_json FROM search_cache WHERE user_id IN (".implode(",", $search_user_ids).")";
 
                        foreach($this->db->prepare($query, array())->execute()->fetchAllAssoc()->cache( 1 )->result as $usr) {
                            $user = json_decode($usr['short_info_json'], true);
                            $cache_profiles[(int)$usr['user_id']] = array(
                                'names' => $user['display_name']
                            );
 
                            if (isset($user['avatar_icon']) AND $user['avatar_icon'] != "")
                            {
                                $cache_profiles[(int)$usr['user_id']]['avatar_icon'] = $user['avatar_icon'];
                            }
 
                            // Put it in our collection
                            $collection[(int)$usr['user_id']] = $cache_profiles[(int)$usr['user_id']]['names'];
                        }
                    }
 
                    // Now merge names
                    $line['names'] = implode(", ", $collection);
                    $line['avatar_icon'] = DEF_GROUP_CHAT_ICON;
                }
 
                // Some modifications
                // $text = str_replace($search, "<em>".$search."</em>", $text);
                $seek_and_destroy = '#\b'.$search.'\b#iu';
                $line['quote'] = preg_replace($seek_and_destroy, '<em>$0</em>', $line['quote']);
                $line['names'] = preg_replace($seek_and_destroy, '<em>$0</em>', $line['names']);
 
                $return[] = $line;
            }
        }
        
        // NOTE!!  overwrite response here
        $mc = new \Controllers\API\MessengerController();
        $cids = array_reverse($cids);
        $cids = array_slice($cids, $offset, $limit);
        $getMailData = [
            'folder' => 'All',
            'mode' => 'search',
            'results' => $limit,
            'cids' => $cids,
            'pid' => $data['band_id']
        ];
        
        $getMailRe = $mc->getMail($getMailData);
        $return = is_array($getMailRe) ? $getMailRe : ['conversations' => []];
        
        // Return
        return array(true, array("length" => count($return['conversations']) , "conversations" => $return['conversations']));
    }
 
  private function ContactsSuggester($user_id,$data,$myContactsOnly=false, $onlineFirst = false){
    $uid=$user_id;
    $prepare=array();
        
        $excludeMothership = array_key_exists('exclude_mothership', $data) ? $data['exclude_mothership'] : true;
 
    // $prepare['uid']=$uid;
    $search=$data['search'];
 
    /*if (empty($search)) {
      return array(false, 'empty search query');
    }*/
 
    $select   = array();
    $leftJoin   = array();
    $order     = array();
 
    if($myContactsOnly){
      $data['follow_type']='followings';
    }
    $search=trim($search);
    $search=str_replace("*","",$search);
    $search=str_replace(")","",$search);
    $search=str_replace("(","",$search);
    $search=str_replace(" ","* +*",$search);
 
    if($search){
      $search=$this->db->quote("*".$search."*");
    }else{
      $search=NULL;
    }
    $circle_id=$data['circle_id'];
    // $prepare['circle_id']=$circle_id;
 
        $show_status=$onlineFirst ? true : $data['show_status'];
    $latest=isset($data['latest']) ? true : false;
    $is_online=$data['is_online'];
 
    $mix_order=$data['mix_order'];
 
        $where = [
            '`up`.`is_banned` IS NULL AND `up`.`is_disabled` = 0'
        ];
        
    if($is_online){
      $show_status=1;
      $where[]="(`up`.`user_status`>0 AND `up`.`lastseen`> " . (time() - \Config::defaults()->DEF_LASTSEEN_OFFLINE_TIME).") ";
    }
 
    if (isset($data["user_type"])) {
//      $LeftJoinUserStatus="LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
 
//      if ($data["user_type"] == "page") {
//        $where[] = "`up`.`is_page` = 1";
//      } else if ($data["user_type"] == "user") {
//                $where[] = "`up`.`is_page` IS NULL";
//            }
    }
 
    if(isset($show_status)){
            $SelectUserStatus=" fn_calculateProfilePublicStatus(`up`.`user_status`,`up`.`system_status`,`up`.`lastseen`) as `publicStatus`, `up`.`id` as `user_suggest_id`,
                                if(`chat_status` > 0,fn_calculateProfilePublicStatus(`user_status`, `system_status`, `lastseen`),0) as `cstatus`,";
      //$SelectUserStatus="`up`.`user_status`, `up`.`lastseen`, `up`.`system_status`, ";
//      $LeftJoinUserStatus="LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
      $where[] = "`up`.`is_page` IS NULL";
    }
 
    if ( $data['gender'] ) {
      $select[] = "`up`.`gender`";
 
//      if ( !$LeftJoinUserStatus ) {
//        $leftJoin[] = "LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
//      }
    }
 
        if ( $data['is_page'] ) {
            //\Helpers\Debug::log('is_page passed');
      $select[] = "`up`.`is_page`";
//      if ( !$LeftJoinUserStatus ) {
//        $leftJoin[] = "LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
//      }
    }
 
    if ( $data['pictures_first'] ) {
      $select[]   = 'IF(`upi`.`id`, 1, 0) AS `has_picture`'; // moje da stane greshka, zashtoto ne znaeme koi e profile album-a
      //$leftJoin[] = "LEFT JOIN `users_pictures` AS `upi` ON ( `upi`.`user_id` = `sc`.`user_id` AND `upi`.`profilepic` = 1 ) ";
      $leftJoin[] = "LEFT JOIN `users_elements` AS `upi` ON `sc`.`id` = `upi`.`creator_id` AND `upi`.`type`='picture' AND `upi`.`is_default` = '1' AND `upi`.`deleted_time` IS NULL AND `upi`.`published_time` IS NOT NULL AND `upi`.`deployed_time` IS NOT NULL ";
      $order[]   = '`has_picture` DESC';
    }
 
    if ( $data['compare_followers'] && in_array($data['follow_type'], array('followers', 'followings')) && isset($_SESSION['profile']['id']) ) {
      $select[]   = 'IF(`my_ucm`.`id`, 1, 0) AS `same_follower`';
      $select[]   = 'IF(`my_ucm`.`id`, `my_ucm`.`circle_id`, -1) AS `circle_id`';
      $leftJoin[] = "LEFT JOIN `users_circles_members` AS `my_ucm` ON ( `my_ucm`.`user_id` = " . $_SESSION['profile']['id'] . " AND `my_ucm`.`target_user_id` = " . ( $data['follow_type'] == 'followers' ? "`ucm`.`user_id`" : "`ucm`.`target_user_id`" ) . " ) ";
      $order[]   = '`same_follower` DESC';
    }
 
    if ($data['filterUsername']) {
      $otherUserId = \Helpers\Users::getIdFromUsername($data['filterUsername']);
      if ($otherUserId) {
        $leftJoin[] = "LEFT JOIN `users_circles_members` AS `other_ucm` ON ( `other_ucm`.`user_id` = " . $otherUserId . " AND `other_ucm`.`target_user_id` = `ucm`.`target_user_id` ) ";
        $where[] = "`other_ucm`.`id` IS NULL";
        $where[] = "`up`.`id` != {$otherUserId}";
      }
    }
 
    switch($data['follow_type']){
      case "followers":
        $where[]="`ucm`.`target_user_id` = '{$uid}'";
        $leftJoinCS="LEFT JOIN `search_cache` AS `sc` ON `sc`.`user_id` = `ucm`.`user_id`";
      break;
      case "followings":
        $where[]="`ucm`.`user_id` = '{$uid}'";
        $leftJoinCS="LEFT JOIN `search_cache` AS `sc` ON `sc`.`user_id` = `ucm`.`target_user_id`";
      break;
      default:
        $leftJoinCS="LEFT JOIN `search_cache` AS `sc` ON (`sc`.`user_id` = `ucm`.`target_user_id` OR `sc`.`user_id` =`ucm`.`user_id`)";
    }
 
    $limit = " ";
    if(isset($data["limit"])){
      $prepare['limit']=$data['limit'];
      $limit = " LIMIT :limit ";
 
      if (isset($data["offset"])) {
        $prepare['offset']=$data['offset'];
        $limit .= "OFFSET :offset ";
      }
    }
 
    if($search){
      $where[]="MATCH (`cache`) AGAINST ($search IN BOOLEAN MODE)";
    }
 
    if($circle_id and $circle_id!='All'){
      $where[]="`uc`.`id` = '{$circle_id}'";
    }
 
    $where[] ="`sc`.`user_id`!= '{$uid}'";
 
    if ( $uid != 1 && $excludeMothership) {
            $where[]="`ucm`.`target_user_id` != 1";
        }
 
        if (isset($data['exclude_ids'])) {
            $exclude = is_array($data['exclude_ids']) ? implode(',', $data['exclude_ids']) : $data['exclude_ids'];
 
            if ( strlen($exclude) > 0 ) {
           $where[]="`ucm`.`target_user_id` NOT IN ($exclude)";
            }
        }
       
    $where = implode(" AND ",$where);
    if($where=='') $where = '1';
 
        if ($latest) {
            $order[] = "`ucm`.`create_time` DESC";
        }
 
    if ($onlineFirst) {
      $order[]='FIELD(`publicStatus`, 2) DESC';
      $order[]='`up`.`first_name`';
      $order[]='`up`.`last_name`';
    }
 
    if(isset($mix_order)) {
      $order[]='RAND()';
    }
 
    if ( count($select) > 0 ) {
      $select = implode(', ', $select) . ',';
    } else {
      $select = '';
    }
    $leftJoin = implode(' ', $leftJoin);
 
    if ( count($order) > 0 ) {
      $order = 'ORDER BY ' . implode(', ', $order);
    } else {
      $order = '';
    }
 
    $sql="SELECT
           {$select}
           {$SelectUserStatus}
           `sc`.`short_info_json`,
           `uc`.`name` as `circle_name`,
           `ucm`.`target_user_id`
        FROM `users_circles_members` AS `ucm`
        {$leftJoinCS}
        {$LeftJoinUserStatus}
        {$leftJoinProfiles}
        {$leftJoin}
       LEFT JOIN `users_circles` AS `uc` ON `uc`.`id` = `ucm`.`circle_id`
           LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id`
         WHERE  ( {$where} ) AND `sc`.`is_disabled` = 0 /*AND `ucm`.`target_user_id`!='1'*/
         GROUP BY `sc`.`id`
         {$order}
         {$limit}
    ";
               
    // \Helpers\Debug::log($sql);
        // echo $sql;
    $ret=array();
 
    //foreach($this->db->q($sql,$prepare) as $el){
    $cache = isset($data['cache']) ? $data['cache'] : 1;
    // echo print_r(array($sql, $prepare), 1);
    // \Helpers\Debug::log($sql);
    if (isset($prepare["limit"])) $prepare["limit"] = intval($prepare["limit"]);
    if (isset($prepare["offset"])) $prepare["offset"] = intval($prepare["offset"]);
 
    $mem_before = memory_get_usage();
        $res = $this->db->prepare($sql,$prepare)->execute()->fetchAllAssoc()->result;
  //  \Helpers\Debug::log("Memory used by the contacts array: ", memory_get_usage() - $mem_before);
        // $res = $this->db->prepare($sql,$prepare)->execute()->fetchAllAssoc()->cache( $cache )->result;
        // echo "$sql - " . print_r($prepare,1);
    foreach($res as $el){
      $a=json_decode($el['short_info_json'], true);
 
      if($show_status){
                $status=array();
                $statuses=unserialize(USER_STATUS_STATEMENTS);
                $status['status_id']=$el['publicStatus'];
                $status['status_name']=$statuses[$status['status_id']];
        $chatStatus=array();
                $chatStatus['status_id']=$el['cstatus'];
        $chatStatus['status_name']=$statuses[$chatStatus['status_id']];
                $a['status']=$status;
        $a['chat_status']=$chatStatus;
      }
 
      if($a!=false){
        $a['gender']     = $el['gender'];
        $a['has_picture']   = $el['has_picture'];
        $a['same_follower'] = $el['same_follower'];
        $a['circle_id']   = $el['circle_id'];
        //Compatibility
        $a['profile_tags']  = $a['tags']['profile'];
 
        if($el['user_id'] == $_SESSION['profile']['id'] and $uid == $_SESSION['profile']['id']){
          $a['circle']=$el['circle_name'];
        }
        $ret[]=$a;
      }
    }
 
    return array(true,array("profiles"=>$ret));
    //return array(true,array("profiles"=>$ret),str_replace("\t","",str_replace("\n", "", $sql)),$prepare);
    //return array(true,$ret);
  }
 
    public function chatBarSuggester($data) {
        $uid = (int)$_SESSION['profile']['id'];
        $limit = isset($data['limit']) ? $data['limit'] : 15;
        $return = array();
 
        $query = "
                    SELECT
                            DISTINCT
                            `sender_id`,
                            `reciever_id`,
                            /*fn_calculateProfilePublicStatus(`up`.`user_status`,`up`.`system_status`,`up`.`lastseen`) as `publicStatus`,*/
                            if(`chat_status` > 0,fn_calculateProfilePublicStatus(`user_status`, `system_status`, `lastseen`),0) as `cstatus`
                    FROM
                            `users_messages` AS `um`
                        JOIN
                            `conversations` AS `c`
                        ON
                            (`um`.`conversations_id` = `c`.`id`)
                        LEFT JOIN
                            `users_profiles` AS `up`
                        ON
                            `up`.`id` = IF(`sender_id` = :uid, `reciever_id`, `sender_id`)
                    WHERE
                            (`sender_id` = :sid OR `reciever_id` = :rid)
                        AND
                            `sender_id` <> `reciever_id`
                        AND
                            `up`.`is_page` IS NULL
                    GROUP BY
                            `um`.`conversations_id`
                    ORDER BY
                            `send_time` DESC
                    LIMIT
                            0,$limit";
 
        $prep = array("uid" => $uid, "sid" => $uid, "rid" => $uid);
        
        $result = $this->db->prepare($query, $prep)->execute()->fetchAllAssoc()->result;
        $exclude = array();
        $statuses=unserialize(USER_STATUS_STATEMENTS);
        $chatstatuses=unserialize(CHAT_STATUS_STATEMENTS);
 
        foreach ( $result as $r ) {
            $id = $r['sender_id'] == $uid ? $r['reciever_id'] : $r['sender_id'];
            $user = \Helpers\Users::getUserShortInfoFromId($id);
 
            $status=array();
            $status['status_id']=\Helpers\Users::getInstance()->getProfilePublicStatus($id);//$r['publicStatus'];
            $status['status_name']=$statuses[$status['status_id']];
            $chatStatus=array();
            $chatStatus['status_id']=\Helpers\Users::getInstance()->getProfilePublicChatStatus($id);
            $chatStatus['status_name']=$chatstatuses[$chatStatus['status_id']];
            $user['status']=$status;
            $user['chat_status']=$chatStatus;
 
            if (!in_array($id, $exclude)) {
                $return[] = $user;
                $exclude[] = $id;
                //\Helpers\Debug::log($id,$status);
            }
        }
 
 
        if ( count($return) < $limit ) {
            $data["limit"] = $limit - count($return);
            $data["exclude_ids"] = $exclude;
            $additional = $this->myContactsSuggester($data);
 
            if ($additional[0] && count($additional[1]['profiles']) > 0) {
                $return = array_merge($return, $additional[1]['profiles']);
            }
        }
 
        foreach ( $return as &$r ) {
            $r['profile_tags']  = $r['tags']['profile'];
        }
 
        return array(true, array("profiles" => $return));
    }
    /* DEPRECATED
  public function followSuggester($data) {
    return array(true);
    $users   = array();
    $offset = $data['offset'] || 0;
    $lat = $this->app->request->ipInfo["latitude"];
        $long = $this->app->request->ipInfo["longitude"];
    $userTags   = array();
    //$results = $this->db->q( "SELECT `name`, `type` FROM `users_tags` WHERE `user_id` = :uid", array("uid" => $_SESSION['profile']['id']));
    $results = $this->db->prepare( "SELECT `name`, `type` FROM `users_tags` WHERE `user_id` = :uid", array("uid" => $_SESSION['profile']['id']))->execute()->fetchAllAssoc()->cache()->result;
 
    foreach ( $results as $el ) {
      $userTags[ $el[ 'type' ] ][]   = ucwords(strtolower($el['name']));
    }
 
    // foreach ( $userTags as $k => $e ) {
    //   $tagsCache[$k] = implode( " ", str_replace( " ", "_", $e ) );
    // }
 
    $query   = "
      SELECT
        `upr`.`id` as `user_id`,
        `sc`.`short_info`,
        " . ( ( $lat != 0 && $long != 0 ) ? "IF ( " . ($lat - 10) . " < `upr`.`lat` AND `upr`.`lat` < " . ($lat + 10) . " AND " . ($long - 10) . " < `upr`.`long` AND `upr`.`long` < " . ($long + 10) . ", 1, 0 )" : "0" ) . " as `is_near`,
        MATCH (instruments) AGAINST (:instruments IN NATURAL LANGUAGE MODE) AS score_instruments,
        MATCH (genres) AGAINST (:genres IN NATURAL LANGUAGE MODE) AS score_genres,
        MATCH (influences) AGAINST (:influences IN NATURAL LANGUAGE MODE) AS score_influences,
        IF ( `ucm`.`id` IS NOT NULL, 1, 0 ) as `followed`,
        IF ( `uc`.`id` IS NOT NULL, 1, 0 ) as `has_posts`
      FROM
        `users_profiles` `upr`
      JOIN
        `users_elements` `upi`
          ON
            (`upr`.`id` = `upi`.`creator_id` AND `upr`.`profile_album_id` = `upi`.`album_id` AND `upi`.`type`='picture' AND `upi`.`is_default` = '1' AND `upi`.`deleted_time` IS NULL AND `upi`.`published_time` IS NOT NULL AND `upi`.`deployed_time` IS NOT NULL)
      JOIN
        `search_cache` `sc`
          ON
            (`sc`.`user_id` = `upr`.`id`)
      LEFT JOIN
        `users_comments` `uc`
          ON
            (`uc`.`owner_element_id` = `upr`.`id` AND `uc`.`model` = 'post')
      LEFT JOIN
        `users_circles_members` `ucm`
          ON
            (`ucm`.`user_id` = :uid AND `ucm`.`target_user_id` = `upr`.`id`)
      WHERE
        `upr`.`id` != :uuid
      GROUP BY
        `uc`.`owner_element_id`
      HAVING
        `followed` = 0
      ORDER BY
        `score_genres` DESC,
        `score_influences` DESC,
        `score_instruments` DESC,
        `is_near` DESC,
        `has_posts` DESC
      LIMIT
        :offset, 9
    ";
 
    $results = $this->db->prepare($query, array(
      'offset'     => $offset,
      'uid'       => $_SESSION['profile']['id'],
      'uuid'       => $_SESSION['profile']['id'],
      'genres'     => ( isset( $userTags['genres'] )     ? implode( " ", str_replace( " ", "_", $userTags['genres'] ) ) : '' ),
      'instruments'   => ( isset( $userTags['instruments'] )   ? implode( " ", str_replace( " ", "_", $userTags['instruments'] ) ) : '' ),
      'influences'   => ( isset( $userTags['influences'] )   ? implode( " ", str_replace( " ", "_", $userTags['influences'] ) ) : '' )
    ))->execute()->fetchAllAssoc()->cache()->result;
 
 
    foreach ( $results as $result ) {
      $user = unserialize( $result['short_info'] );
 
      if ( $user != false ) {
        $users[] = $user;
      }
    }
 
    return array(true, $users);
  }*/
 
  public function searchSuggester($data) {
        $results = isset($data['limit']) ? $data['limit'] : \Config::defaults()->RESULTS_TO_SHOW->search;
        if ($results > \Config::defaults()->MAX_RESULTS_TO_SHOW->search) {
            $results = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
        }
 
        $reminder = $result % 2;
 
        $search = $data['autocomplete'];
        $msc = microtime(true);
        if (!isset($data['skipTags']) || !$data['skipTags']) {
            $tags = $this->tagsSuggester(array('autocomplete' => $search, 'limit' => (int) ($results / 2) + $reminder));
        }
 
        $profiles = [];
        $pages = [];
        if (isset($data['skipPages']) || $data['skipPages'] == 'true') { // legacy string true
            $profilesRes = $this->search(array('autocomplete' => $search, 'show_profiles' => true, 'visible' => $data['visible']));
            if ($profilesRes[0]) {
                $profiles = $profilesRes[1];
            }
        } else { // search both users and pages and separate them here
 
            $searchRes = $this->search(['autocomplete' => $search, 'visible' => $data['visible']]); 
 
            if ($searchRes[0]) {
                foreach ($searchRes[1] as $i => $sr) {
                    if ($i == 0) {
                        $firstType = $sr['is_page'] ? "page" : "profile";
                    }
                    if ($sr['is_page']) {
                        $pages[] = $sr;
                    } else {
                        $profiles[] = $sr;
                    }
                }
            }
        }
 
        $communities = \Helpers\Tags::getCommunities(null, false, $search, $results);
 
 
        $sCtrl = new \Controllers\API\SearchController();
        $posts = $sCtrl->searchPosts($data);
        
        $msc=microtime(true)-$msc;
        $res=array();
        $res['profiles'] = $profiles;
        $res['pages'] = $pages;
        $res['posts'] = $posts;
        $res['communities'] = $communities;
        $res['tags'] = $tags[0] ? $tags[1]['tags'] : array();
        $res['first_type'] = isset($firstType) ? $firstType : false;
        return array(true, $res);
    }
 
    public function getFacebookFriends ()
    {
        $uid = (int)$this->app->session->profile['id'];
        $friends = \Helpers\Users::getUnfollowedFacebookFriends($uid);
        return [true, $friends];
    }
 
    //polzva se samo za chat-a, no i tam e spriano i vrushta []
    public function generatePeopleSuggestions ($data) {
        return array(true,[]);
        if (isset($data['chat'])) {
            $uid = $_SESSION["profile"]["id"];
            $usedUids = [];
            if (isset($data['used_ids']) AND is_array($data['used_ids'])) {
                $usedUids = $data['used_ids'];
            }
 
            $result = [
                'explore' =>  \Helpers\Suggestions::generateExploreSuggestions($uid, $usedUids)
            ];
 
            return array(true, $result);
        }
 
        if (!isset($data["sideMenu"]) && !isset($data["explore"])) {
            return array(
                "sideMenu"   => array(),
                "explore"  => array()
            );
        }
 
        $result = \Helpers\Suggestions::generateSuggestions($_SESSION['profile']['id'], $data["sideMenu"], $data["explore"]);
 
        if (!$result) {
            return array(false, "insert/update failed");
        }
 
        return array(true, $result);
    }
 
  public function tagsSuggesterDEPRECATED($data){
    // DEPRECATED !!!!!! ! !! !
// DONE elasticsearch!
        
    $search=trim($data['search']);
    $type = $data['type'];
 
    if(!$search) return array(true,array());
        if ( strlen($search) < 3 ) {
            return [true, []];
        }
 
    $limit = isset($data['limit']) ? intval($data['limit']) : \Config::defaults()->RESULTS_TO_SHOW->search;
    if($limit > \Config::defaults()->MAX_RESULTS_TO_SHOW->search){
      $limit = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
    }
 
    $offset = isset($data['offset']) ? intval($data['offset']) : 0;
        $lang = \Helpers\Basic::lang();
        
        $regex = new \MongoDB\BSON\Regex('^'.$search, 'i');
        
        if ($type == "" || $type == "all") {
            $conditions = [
                "translation.{$lang}.text" => $regex,
            ];
 
            $project = [
                "type" => 1,
                'translation' => 1,
                'posts_rating' => ['$sum' => ['$type.posts.moderator_rating', '$type.posts.user_rating']],
                'profile_rating' => ['$sum' => ['$type.profile.moderator_rating', '$type.profile.user_rating']],
                'instruments_rating' => ['$sum' => ['$type.instruments.moderator_rating', '$type.instruments.user_rating']],
                'influences_rating' => ['$sum' => ['$type.influences.moderator_rating', '$type.influences.user_rating']],
                'equipment_rating' => ['$sum' => ['$type.equipment.moderator_rating', '$type.equipment.user_rating']],
                'genres_rating' => ['$sum' => ['$type.genres.moderator_rating', '$type.genres.user_rating']],
                'rating' => ['$max' => ['$posts_rating', '$profile_rating', '$instruments_rating', '$influences_rating', '$equipment_rating', '$genres_rating']],
            ];
        } else {
            $conditions = [
                "translation.{$lang}.text" => $regex,
            ];
                
            $project = [
                "type.{$type}" => 1,
                'translation' => 1,
                'rating' => ['$sum' => ['$' . "type.{$type}." . 'moderator_rating', '$' . "type.{$type}." . 'user_rating']]
            ];
        }
 
        $res = \Collections\Tags::aggregate([
            ['$match' => $conditions],
            ['$project' => $project],
            ['$sort' => ['rating' => -1]],
            ['$skip' => $offset],
            ['$limit' => $limit],
        ]);
        
        $tags = [];
        foreach ($res as $tag) {
            $tagName = mb_convert_case(stripslashes($tag->translation->{$lang}->text), MB_CASE_TITLE, 'UTF-8');
            foreach ($tag->type as $type => $notneed) {
                $tags[$type][] = ['index' => \Helpers\Tags::transformIndex($tagName), "url" => \Helpers\Basic::getUrl($tagName, $type), "name"=> $tagName ];
            }
        }
        
    return array(true,array("tags"=>$tags));
  }
    
    public function tagsSuggester($data) {
        $search = trim($data['search']);
        $type = $data['type'];
 
        if (!$search) {
            return [true, []];
        }
        if (strlen($search) < 3) {
            return [true, []];
        }
 
        $limit = isset($data['limit']) ? intval($data['limit']) : \Config::defaults()->RESULTS_TO_SHOW->search;
        if ($limit > \Config::defaults()->MAX_RESULTS_TO_SHOW->search) {
            $limit = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
        }
 
        $offset = isset($data['offset']) ? intval($data['offset']) : 0;
        $lang = \Helpers\Basic::lang();
        
        $sort = [["rating" => "desc"]];
        
        $bool = [];
        
        if (strlen($type) && $type != 'all') {
            $tagTypes = unserialize(TAGS_TYPES);
            if (!in_array($type, $tagTypes)) {
                return [true, []];    
            }
            $bool['filter'] = [
                'term' => [
                    'types' => $type
                ]
            ];
        }
        
        $bool["must"] = [
            [
                "match_phrase_prefix" => [
                    "text" => $search
                ]
            ]
        ];
        $query = ["bool" => $bool];
        
        $params = [
            "from" => $offset,
            "size" => $limit,
            "index" => \Config::elasticsearch()->INDICES->tags->{$lang},
//            "explain" => 1,
            "body" => [
                "query" => $query,
                "sort" => $sort,
            ]
        ];
            
        $dsm = $this->app->droobleSearchManager;
        $result = $dsm->search($params);
        
        $tags = [];
        
        foreach ($result['hits']['hits'] as $item){
            $tagName = mb_convert_case(stripslashes($item['_source']['text']), MB_CASE_TITLE, 'UTF-8');
            $tags[$type][] = ["name"=> $tagName ];
        }
 
        return [true, ['tags' => $tags]];
    }
    
    // looks like I did this to track stuff for Segment, without making too many requests (due to the $data['count'] that is used for counting search results,
    // but now it just wraps the search() func
    public function advancedSearchPage($data){
        
        $searchStr = strlen(trim($data['autocomplete'])) ? $data['autocomplete'] : trim($data['search']);
        $properties =array(
                "category" => 'Home page',
                "search_query" => "'".$searchStr."'"
            );
 
        if (isset($data['profile']) && is_array($data['profile'])) {
            // create Segment properties from search tags
            foreach ($data['profile'] as $tag) {
                if (preg_match('/^[0-9a-z\s]+$/i', $tag)) {
                    $properties[ preg_replace('/\s+/', '_', strtolower($tag)) ] = true;
                }
            }
        }
 
        // why not just 0/1 ? ... strings 'true' | 'false'
        if(isset($data['bands']) && ($data['bands'] == 'true' or $data['bands'] == true)){
            $properties['bands'] = true;
        }
 
        if(isset($data['venues']) && ($data['venues'] == 'true' or $data['venues'] == true)){
            $properties['venues'] = true;
        }
 
        if(isset($data['clubs']) && ($data['clubs'] == 'true' or $data['clubs'] == true)){
            $properties['clubs'] = true;
        }
 
        if(isset($data['recording_studios']) && ($data['recording_studios'] == 'true' or $data['recording_studios'] == true)){
            $properties['recording_studios'] = true;
        }
 
        if(isset($data['rehearsal_space']) && ($data['rehearsal_space'] == 'true' or $data['rehearsal_space'] == true)){
            $properties['rehearsal_space'] = true;
        }
 
        if(isset($data['music_school']) && ($data['music_school'] == 'true' or $data['music_school'] == true)){
            $properties['music_school'] = true;
        }
 
        if(isset($data['jam']) && ($data['jam'] == 'true' or $data['jam'] == true)) {
            $properties['jammers'] = true;
        }
 
        if(isset($data['teach']) && ($data['teach'] == 'true' or $data['teach'] == true)){
            $properties['teachers'] = true;
 
            if (isset($data['price_min'])) {
                $properties['price_min'] = $data['price_min'];
            }
            if (isset($data['price_max'])) {
                $properties['price_max'] = $data['price_max'];
                
                // When its picked maximum of 150+... 
                if ((int)$data['price_max'] == 150) {
                    $properties['price_max'] = 999;
                }
            }
        }
 
        if(isset($data['available']) && ($data['available'] == 'true' or $data['available'] == true)){
            $properties['available'] = true;
        }
 
        if(isset($data['looking_bandmates']) && ($data['looking_bandmates'] == 'true' or $data['looking_bandmates'] == true)){
            $properties['looking_bandmates'] = true;
        }
 
        if(isset($data['instruments']) && $data['instruments']){
            $properties['instruments'] = $data['instruments'];
        }
 
        if(isset($data['instruments_primary_to_join']) && $data['instruments_primary_to_join']){
            $properties['instruments_primary_to_join'] = $data['instruments_primary_to_join'];
        }
 
        if(isset($data['instruments_primary_for_bandmates']) && $data['instruments_primary_for_bandmates']){
            $properties['instruments_primary_for_bandmates'] = $data['instruments_primary_for_bandmates'];
        }
 
        if(isset($data['genres']) && $data['genres']){
            $properties['genres'] = $data['genres'];
        }
 
        if(isset($data['influences']) && $data['influences']){
            $properties['influences'] = $data['influences'];
        }
 
        if(isset($data['equipment']) && $data['equipment']){
            $properties['equipment'] = $data['equipment'];
        }
 
        if(isset($data['languages']) && $data['languages']){
            $properties['languages'] = $data['languages'];
        }
 
        if(isset($data['location']) && $data['location']){
            $properties['location'] = $data['location'];
        }
 
        if(isset($data['place']) && $data['place']){
            $properties['place'] = $data['place'];
        }
 
        $track = array();
 
        $track['event'] = "Advanced Search";
 
        if($this->app->session->has('profile')){
            $track['userId'] = $this->app->session->get('profile')['id'];
        }else{
//            $track['anonymousId'] = rand(100000,999999);
        }
 
        $track['properties'] = $properties;
 
        if( $this->app->session->has('profile') && (!isset($data['count']) or $data['count'] == 2) ){
            if(!$_COOKIE["druast"]) {
                $secureConnection = strtolower(\Helpers\Basic::getInstance()->get_php_http_scheme()) == 'https' ? true : false;
                setcookie("druast", date("Y-m-d"), time() + 60 * 60, '/', NULL, $secureConnection, true); // 60 minutes
                \Drooble\Activities\Activities::add( "advancedSearch", $track['userId'], (object) ['type' => 'user', 'id' => $track['userId']], ['segment' => ['track' => $track]] );
            }
        }
 
        return $this->search($data);
 
    }
    
    // $data['autocomplete'] string - the search works as autocomplete e.g. Joh -> John
    // $data['search'] string - the search works as search (bad at autocomplete, but with more accurate results)
    // TODO - think if possible to use instead of getContacts - you'll need your 'followings/folloers' on top of results
    public function search($data) {
        $go_search=false;
    foreach(array_keys($data) as $e){
      if(!in_array($e, array_keys( get_object_vars( \Config::api()->MAP->search->data ) ))){
        return array(false,"input error1 '".$e."'");
      }
      if($data[$e]){
        $go_search=true;
      }
    }
    if(!$go_search){
      return array(false,"empty search");
    }
        
        $dsm = $this->app->droobleSearchManager;
        $lang = \Helpers\Basic::lang();
        $analyzer = strtolower(\Config::languages()->language->{$lang}->name_in_english);
        $offset = $data['offset'] ? $data['offset'] : 0;
        $results = isset($data['limit']) ? $data['limit'] : \Config::defaults()->RESULTS_TO_SHOW->search;
    if($results > 100){ // way too many
      $results = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
    }
        
        $count = false;
       
       $pageTypeIds = [];
       $matchTags = [
           'profile' => [],
           'instruments' => [],
           'genres' => [],
           'equipment' => [],
           'influences' => [],
           'languages' => [],
           'instruments_primary_to_join' => [],
           'instruments_primary_for_bandmates' => [],
       ];
       
       if ((int) $data['venues'] == 1) {
            $pageTypeIds[] = 106;
        }
 
        if ((int) $data['clubs'] == 1) {
            $pageTypeIds[] = 107;
        }
 
        if ((int) $data['music_school'] == 1) {
            $pageTypeIds[] = 120;
        }
 
        if ((int) $data['recording_studios'] == 1) {
            $pageTypeIds[] = 115;
        }
 
        if ((int) $data['rehearsal_space'] == 1) {
            $pageTypeIds[] = 128;
        }
 
        if (is_array($data['instruments']) AND ! empty($data['instruments'])) {
           foreach ($data['instruments'] as $item) {
               $matchTags['instruments']['should'][] = $item;
           }
       }
       if (is_array($data['genres']) AND ! empty($data['genres'])) {
           foreach ($data['genres'] as $genre) {
               $matchTags['genres']['should'][] = $genre;
           }
       }
       if (is_array($data['equipment']) AND ! empty($data['equipment'])) {
           foreach ($data['equipment'] as $item) {
               $matchTags['equipment']['should'][] = $item;
           }
       }
       if (is_array($data['influences']) AND ! empty($data['influences'])) {
           foreach ($data['influences'] as $item) {
               $matchTags['influences']['should'][] = $item;
           }
       }
       if (is_array($data['languages']) AND ! empty($data['languages'])) {
           foreach ($data['languages'] as $item) {
               $matchTags['languages']['should'][] = $item;
           }
       }
       // these are reversed ... to match users who want to play an instrument and bands that are looking for the same
       if (is_array($data['instruments_primary_to_join']) AND ! empty($data['instruments_primary_to_join'])) {
           foreach ($data['instruments_primary_to_join'] as $item) {
               $matchTags['instruments_primary_for_bandmates']['should'][] = $item;
           }
       }
       if (is_array($data['instruments_primary_for_bandmates']) AND ! empty($data['instruments_primary_for_bandmates'])) {
           foreach ($data['instruments_primary_for_bandmates'] as $item) {
               $matchTags['instruments_primary_to_join']['should'][] = $item;//
           }
       }
       
        if (is_array($data['profile']) AND !empty($data['profile'])) {
            $tmp = array();
            foreach ($data['profile'] as $el) {
                if (!trim($el))
                    continue;
                
                // Some predefined
                // TODO config with translations in all languages VS cache ...
                if ($el == $this->app->translate->get('drummers')) {
                    $tag = \Helpers\Tags::findByText('drums', null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText('drummer', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('singers') || $el == $this->app->translate->get('vocalists')) {
                    $tag = \Helpers\Tags::findByText('vocals', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText('singer', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText('vocalist', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('guitarists')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('guitar'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('guitarist'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('acoustic_guitar'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('electric_guitar'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('bass_players') || $el == $this->app->translate->get('bassists')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('bass'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('bass_player'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('bassist'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('producers')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('producer'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('musicians')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('musician'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
            }
        }
        
        $bool = [];
        $must = [];
        $should = [];
        
        if (isset($data['autocomplete']) && trim($data['autocomplete'])) {
            $searchStr = trim($data['autocomplete']);
            // phrase_prefix is the "poorman's autocomplete" of Elastic. Works great
            // In future we can create more complex autocomplete, but it's totally unnecessary for now
            $combineType = "phrase_prefix";
            $minLength = 2;
        } else {
            $searchStr = trim($data['search']);
            $combineType = "cross_fields";
            $minLength = 3;
        }
        
        if (strtolower($searchStr) == "search" || strtolower($searchStr) == "%%tag%%") {
            try {
                // we got weird "search" requests .. log em
//                \Helpers\Debug::log("weird search ", $_SERVER['HTTP_REFERER']);
//                \Helpers\Debug::log($_SERVER['REQUEST_URI']);
//                \Helpers\Debug::log($_SERVER['QUERY_STRING']);
//                \Helpers\Debug::log($_SERVER['HTTP_USER_AGENT']);
            } catch (\Exception $e) {
                
            }
        }
        
        if (strlen($searchStr) >= $minLength) {
            // keep search string for 'tag' cloud of frequent searches
            \Helpers\SearchTags::add($searchStr);
            $combine = [];
            $combine[] = [
                "multi_match" => [
                    "query" => $searchStr,
                    "analyzer" => $analyzer,
                    "type" => $combineType,
                    "fields" => [
                        "name^5", // full name of User / Page
                        "username^1",
//                        "about.title^4",
//                        "about.text^2",
//                        "type.{$lang}^2", // Pages only
//                        "tags.profile.{$lang}^3",
//                        "tags.equipment.{$lang}^2",
//                        "tags.genres.{$lang}^2", // Pages only
//                        "tags.genres_primary.{$lang}^2",
//                        "tags.genres_secondary.{$lang}",
//                        "tags.influences.{$lang}^2",
//                        "tags.instruments_primary.{$lang}^2",
//                        "tags.instruments_secondary.{$lang}",
//                        "tags.languages.{$lang}",
                    ]
                ]
            ];
            $combine[] = [
                "multi_match" => [
                    "query" => $searchStr,
                    "analyzer" => $analyzer,
                    "fields" => [
                        "name^3", // full name of User / Page
//                        "username^1",
//                        "about.title^3",
//                        "about.text^2",
//                        "type.{$lang}^2", // Pages only
//                        "tags.profile.{$lang}^3",
//                        "tags.equipment.{$lang}^2",
//                        "tags.genres.{$lang}^2", // Pages only
//                        "tags.genres_primary.{$lang}^2",
//                        "tags.genres_secondary.{$lang}",
//                        "tags.influences.{$lang}^2",
//                        "tags.instruments_primary.{$lang}^2",
//                        "tags.instruments_secondary.{$lang}",
//                        "tags.languages.{$lang}",
                    ],
                    "fuzziness" => "AUTO"
                ]
            ];
            $must[] = ['bool' => ['should' => $combine, "boost" => 5]];
        }
 
        /*        
        if ($data['teach'] == 1) {
            $must[] = ['match' => ['teach' => 1]];
        }
        
        if (intval($data['price_min']) > 0 || intval($data['price_max']) > 0) {
            $price = [];
            if (intval($data['price_min']) > 0) {
                $this_drooble_fee = (intval($data['price_min']) / (\Config::defaults()->DROOBLE_FEE_IN_PERCENTS + 100) * \Config::defaults()->DROOBLE_FEE_IN_PERCENTS);
                $price_from = intval($data['price_min']) - $this_drooble_fee;
                if ($price_from < 0) {
                    $price_from = 0;
                }
                $price['gte'] = intval($price_from);
            }
 
            if (intval($data['price_max']) > 0) {
                $this_drooble_fee = (intval($data['price_max']) / (\Config::defaults()->DROOBLE_FEE_IN_PERCENTS + 100) * \Config::defaults()->DROOBLE_FEE_IN_PERCENTS);
                $price_to = intval($data['price_max']) - $this_drooble_fee;
                if ($price_to < 0) {
                    $price_to = 0;
                }
                $price['lte'] = intval($price_to);
            }
            $must[] = ['range' => ['price' => $price] ];
        }
         */
        
 
        if ($data['place']) {
            $data['place'] = str_replace("'", "", $data['place']);
            $data['place'] = str_replace("`", "", $data['place']);
            $data['place'] = str_replace("\"", "", $data['place']);
            
            $place = \Helpers\Basic::getGooglePlace($data['place']);
            if ($place && $place->is_country) {
                $must[] = ['match' => ['country' => $place->country_code]];
            } else {
                $must[] = ['match' => ['city' => $data['place']]];
            }
            
        }elseif($data['location']){
            $google_places = new \Lib\GooglePlaces(\Config::googleplaces()->API_KEY);
 
            $cor = array($this->app->request->ipInfo['latitude'], $this->app->request->ipInfo['longitude']);
 
            $google_places->location = $cor;
            $google_places->radius   = 100;
            $google_places->rankby   = 'distance';
            $google_places->types    = '(cities)';
            $google_places->input    = $data['location'];
            $google_places_result = $google_places->autocomplete();
            $searched_places_ids = array();
 
            if($google_places_result['status'] == 'OK' AND count($google_places_result['predictions'])>0){
                foreach($google_places_result['predictions'] as $__prediction){
                    $searched_places_ids[] = $__prediction['place_id'];
                }
            }
            if (count($searched_places_ids)) {
                $tmp = [];
                foreach ($searched_places_ids as $placeid) {
                    $tmp[] = ['match' => ["city" => $placeid]];
                }
                $must[] = ['bool' => ['should' => $tmp]];
            }
        }
        
        if (count($data['tags'])) {
            foreach ($data['tags'] as $tag) {
                foreach ($matchTags as $type => $info) {
                    $matchTags[$type]['should'][] = $tag;
                }
            }
        }
        foreach ($matchTags as $type => $info) {
            foreach ($info as $boolOperator => $tags) { // $boolOperator fills $must or $should arrays
                if (count($tags)) {
                    foreach ($tags as $tag) {
                        if ($type == "genres") {
                            ${$boolOperator}[] = ['match' => ["tags.genres.{$lang}" => $tag]]; // this is for Pages
                            ${$boolOperator}[] = ['match' => ["tags.genres_primary.{$lang}" => $tag]];
                            ${$boolOperator}[] = ['match' => ["tags.genres_secondary.{$lang}" => $tag]];
 
                        } elseif ($type == "instruments") {
                            ${$boolOperator}[] = ['match' => ["tags.instruments_primary.{$lang}" => $tag]];
                            ${$boolOperator}[] = ['match' => ["tags.instruments_secondary.{$lang}" => $tag]];
                        } else {
                            ${$boolOperator}[] = ['match' => ["tags.{$type}.{$lang}" => $tag]];
                        }
                        
                        /* this here was again because someone doesn't follow YAGNI
                        if ($type == "genres") {
                            $tmp[] = ['match_phrase' => ["tags.genres.{$lang}" => $tag]]; // this is for Pages
                            $tmp[] = ['match_phrase' => ["tags.genres_primary.{$lang}" => $tag]];
                            $tmp[] = ['match_phrase' => ["tags.genres_secondary.{$lang}" => $tag]];
 
                        } elseif ($type == "instruments") {
                            $tmp[] = ['match_phrase' => ["tags.instruments_primary.{$lang}" => $tag]];
                            $tmp[] = ['match_phrase' => ["tags.instruments_secondary.{$lang}" => $tag]];
                        } else {
                            $tmp[] = ['match_phrase' => ["tags.{$type}.{$lang}" => $tag]];
                        }
                        if ($tmp) {
                            ${$boolOperator}[] = ['bool' => ['should' => $tmp]];
                        }*/
                        
                    }
                }
            }
        }
        
//        if ($data['visible'] == 'profile_completed') {
//            $must[] = [
//                'term' => [
//                    'visibility' => 'profile_completed'
//                ]
//            ];
//        }
 
        if (count($pageTypeIds)) {
            $must[] = ['terms' => ['page_type_id' => $pageTypeIds]];
        }
        
        if (count($must)) {
            $bool['must'] = $must;
        }
        
        if (count($should)) {
            $bool['should'] = $should;
            $bool['minimum_should_match'] = 1;
            // setting minimum_should_match would mess up the has_avatar boost
        }
        
//        if ($data['autocomplete']) {
//            $indexStr = \Config::elasticsearch()->INDICES->users . ',' . \Config::elasticsearch()->INDICES->pages;
            $indexStr = \Config::elasticsearch()->INDICES->users;
//        } else {
//            $indexStr = \Config::elasticsearch()->INDICES->users;
//        }
//        if ((int) $data['bands'] == 1 || count($pageTypeIds) || $data['show_pages'] || $data['type'] == "pages") {
//            $indexStr = \Config::elasticsearch()->INDICES->pages;
//        }
        if ($data['show_profiles'] /*|| $data['type'] == "people" */) {
            $indexStr = \Config::elasticsearch()->INDICES->users;
        }
        if (count($bool)) {
            // boost has_avatar. I personally argued that we should not boost, but rather filter by has_avatar, but it was pointless .. so enjoy
//            if (!isset($tagBoost)) { // NO SOLUTION for now ...
//                $bool['should'][] = ['term' => ['has_avatar' => ["value" => true, "boost" => 100 ]]];
//            }
//            // enough BS
//            if ($indexStr == \Config::elasticsearch()->INDICES->users) {
//                $bool['should'][] = ['exists' => ['field' => 'tags.profile', 'boost' => 80]];
//                $bool['should'][] = ['exists' => ['field' => 'tags.instruments_primary', 'boost' => 70]];
//                $bool['should'][] = ['exists' => ['field' => 'tags.genres_primary', 'boost' => 60]];
//            } else {
//                $bool['should'][] = ['exists' => ['field' => 'tags.genres', 'boost' => 80]];
//                $bool['should'][] = ['exists' => ['field' => 'tags.influences', 'boost' => 70]];
//            }
            $query = ["bool" => $bool];
        } else {
            // empty search
            $bool = [
                'must' => [
                    ['match' => ['has_avatar' => true]]
                ]
            ];
            // enough BS
//            if ($indexStr == \Config::elasticsearch()->INDICES->users) {
//                $boostTags = [
//                    ['exists' => ['field' => 'tags.profile', 'boost' => 80]],
//                    ['exists' => ['field' => 'tags.instruments_primary', 'boost' => 70]],
//                    ['exists' => ['field' => 'tags.genres_primary', 'boost' => 60]],
//                ];
//            } else {
//                $boostTags = [
//                    ['exists' => ['field' => 'tags.genres', 'boost' => 80]],
//                    ['exists' => ['field' => 'tags.influences', 'boost' => 70]],
//                ];
//            }
//            if (isset($boostTags)) {
//                $bool['should'] = $boostTags;
//            }
            $query = [
                'function_score' => [
                    'query' => ['bool' => $bool],
                    'random_score' => new \stdClass()
                ]
            ];
        }
        
        $sort = [["_score" => "desc"]]; // second and third sort params were ["has_avatar" => "desc"], ["lastseen" => "desc"] they are insignificant
        
        $params = [
            "from" => $offset,
            "size" => $results,
            "index" => $indexStr,
//            "explain" => 1,
            "body" => [
                "query" => $query,
                "sort" => $sort,
            ]
        ];
 
        //\Helpers\Debug::log('dht', json_encode($params));
 
        $result = $dsm->search($params);
        
        if ((int) $data['count'] === 1) { //OMG all the legacy .. humanity
            $count = $result['hits']['total'];
            return array(true, $count);
        } else {
            return $this->viewSearchUsersAndPages($result, $searchStr);
        }
    }
    
    /*
     * @result DroobleSearchManager search result (Elastic client search result)
     * @searchStr - string from search (needed to 'inject' the search string as a Tag to all Users in the View)
     */
    public function viewSearchUsersAndPages($result, $searchStr) {
        $lang = \Helpers\Basic::lang();
        $hitIds = [];
        foreach ($result['hits']['hits'] as $item){
            $hitIds[] = $item['_id'];
        }
        $hitIdsImploded = implode(",", $hitIds);
        $count = $result['hits']['total'];
        $ret = [];
        // get data for hits
        if ($count) {
            
            if (strlen($searchStr)) {
                $foundTag = \Helpers\Tags::findByText($searchStr, null);
            }
            
            $elsCtrl = new \Controllers\API\ElementsController();
            
            $sql="
                SELECT
                    `search_cache`.`short_info_json` ,
                    `up`.`id`,
                    `up`.`user_status`,
                    `up`.`system_status`,
                    `up`.`lastseen`,
                    `up`.`is_page`,
                    IF ( `ucm`.`id` IS NOT NULL, 1, 0 ) as `isInCircle`
                FROM
                    `search_cache`
                JOIN
                    `users_profiles` `up`
                        ON
                            `up`.`id` = `search_cache`.`user_id`
                LEFT JOIN
                    `users_circles_members` `ucm`
                        ON
                            (`ucm`.`user_id` = :fuid AND `ucm`.`target_user_id` = `up`.`id`)
                WHERE `up`.`id` IN(" . $hitIdsImploded . ")
                    ORDER BY FIELD(up.id, " . $hitIdsImploded . ")
                    LIMIT " . $count . "
                   ;";
 
            $prepare = [ 'fuid' => $_SESSION['profile']['id'] ];
            $qres = $this->app->db->query($sql, $prepare);
            $qres->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $qelements = $qres->fetchAll();
            foreach ($qelements as $el) {
                $a = json_decode($el['short_info_json'], true);
                if ($a != false) {
                    $a['uid'] = $el['id'];
                    $a['isInCircle'] = $el['isInCircle'];
//                    $a['status'] = $el['publicStatus'];
                    $a['is_page'] = $el['is_page'];
//                    if ($a['rates']['session_price'] and $el['id'] != $_SESSION['profile']['id']) {
//                        $a['rates']['session_price'] = floatval($a['rates']['session_price']) + floatval($a['rates']['session_price']) * ( \Config::defaults()->DROOBLE_FEE_IN_PERCENTS / 100 );
//                    }
                    if ((int) $count >= 0) {
                        $a['counter'] = $count;
                    }
                    $tags = \Helpers\Users::getUserTags($a['uid'], 3); // get with 3 - categorized with remapped type, cause otherwise you'll get random instruments (they have 4 subtypes)
                    
                    if ($a['uid'] == 1) {
                        $a['open_to_string'] = "All music";
                    } else {
                        $open_to_string = "";
                        if ( count($tags['profile']) > 1 ) {
                            $open_to_string = $tags['profile'][0] . " " . $this->app->translate->get('and') . " " . $tags['profile'][1];
                        } elseif (count($tags['profile']) == 1) {
                            $open_to_string = $tags['profile'][0];
                        }
                        $a['open_to_string'] = $open_to_string;
                    }
                    
                    $flatList = [];
                    foreach ($tags as $arr) {
                        foreach ($arr as $tag) {
                            $flatList[] = $tag;
                        }
                    }
                    if ($foundTag) {
                        $searchAsTag = mb_convert_case(stripslashes($foundTag->translation->{$lang}->text), MB_CASE_TITLE, 'UTF-8');
                        if (!in_array($searchAsTag, array_slice($flatList, 0, 2))) {
                            // 'inject' the search string as a Tag @ first position if it's not in the top 2 tags
                            foreach ($tags as $key => $arr) {
                                array_unshift($arr, $searchAsTag);
                                $tags[$key] = $arr;
                                break;
                            }
                        }
                    }
                    // remove duplicates
                    $firstParts = array_slice($tags, 0, 3);
                    $first3keys = array_keys($firstParts);
 
                    $countTypes = count($first3keys);
                    if ($countTypes > 1) {
                        $tags[$first3keys[1]] = array_diff($tags[$first3keys[1]], $tags[$first3keys[0]]);
                    } 
                    if ($countTypes > 2) {
                        $tags[$first3keys[2]] = array_diff($tags[$first3keys[2]], $tags[$first3keys[1]]);
                        $tags[$first3keys[2]] = array_diff($tags[$first3keys[2]], $tags[$first3keys[0]]);
                    }
                        
                    $a['tags'] = $tags;
                    $ret[] = $a;
                }
            }
        }
        return [true, $ret];
 
    }
 
    public function getContacts($data) {
        return array(true, \Helpers\Search::getContactsAndTheRest($data['uid'], $data));
    }
 
}
 
?>
#12search->search(Array(10))
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/logic/search.class.php (1113)
<?php
class search{
 
  private $db;
  private $c;
    private $app;
 
  public function __construct($db,&$c, $app) {
    $this->db = $db;
    $this->c = $c;
        $this->app = $app;
  }
/*
  public function doReindex($data){
    $ret=array();
    foreach($this->db->q("SELECT `id` FROM `users_profiles` WHERE 1") as $el){
      $ret[]=$this->lib->profileSearchIndexing($el['id']);
    }
 
    return array(true,$ret);
  }
*/
    public function searchCity($data) {
        $return = new \stdClass();
 
        $return->items = \Helpers\Search::getInstance()->searchCity($data['name']);
 
        return array(true, $return);
 
 
        /*
        // SELECT *, CASE WHEN name LIKE 'Sofia%' THEN 1 ELSE 0 END AS keymatch FROM `cities` WHERE MATCH (name) AGAINST ('+Sofia' IN BOOLEAN MODE) ORDER BY keymatch DESC
        $cities = $this->app->db->fetchAll("
            SELECT *, CASE
                WHEN search_by_me LIKE ".$this->app->db->escapeString($keywords)." THEN 4
                WHEN search_by_me LIKE ".$this->app->db->escapeString($keywords." %")." THEN 3
                WHEN search_by_me LIKE ".$this->app->db->escapeString("% ".$keywords." %")." THEN 2
                WHEN search_by_me LIKE ".$this->app->db->escapeString("% ".$keywords)." THEN 1
                ELSE 0 END AS keymatch
            FROM `cities`
            WHERE
            MATCH (search_by_me) AGAINST (".$this->app->db->escapeString($keywords).")
            ORDER BY keymatch DESC
            LIMIT 22
        ");
 
        $items = array();
 
        if ($cities) {
            foreach($cities as $city)
            {
                $title_suffix = $city['country'];
                if ($city['region']) {
                    $title_suffix = $city['region'].", ".$city['country'];
                }
 
                $items[] = array(
                    'id' => $city['id'],
                    'title' => $city['name'].", ".$title_suffix,
                    'name' => $city['name'],
                    'country' => $city['country'],
                    'region' => $city['region']
                );
            }
        }
 
        $return->items = $items;
 
        return array(true, $return);
        */
    }
 
  public function searchByMail($data){
    $email=$data['email'];
 
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
      return array(false,"not valid");
    }
 
        /*
    $search=$email;
    $search=str_replace("*","",$search);
    $search=str_replace(" ","",$search);
    $search=str_replace(")","",$search);
    $search=str_replace("(","",$search);
 
 
    $sql="SELECT `short_info` FROM `search_cache` WHERE MATCH (`email`) AGAINST ('".'"'.$search.'"'."')";
    $r=$this->db->one($sql);
         *
         */
 
        $person = \Models\UserCache::findFirst([
            'conditions' => 'email LIKE {email:str}',
            'bind' => ['email' => $email]
        ]);
 
        if ($person) {
            return array(true, json_decode($person->short_info_json, true));
        } else {
            return array(true,'notexists');
        }
  }
 
  public function otherPersonContactsSuggester($data){
    $user_id=\Helpers\Users::getIdFromUsername($data['profile']);
    if(isset($data['circle_id']) && $data['profile'] != $_SESSION['profile']['username']){
      unset($data['circle_id']);
    }
    if(!$data['results']){
      $data['results']=9;
    }
    if(isset($data['mix_order'])){
      $data['mix_order']=true;
    }
 
    // \Helpers\Debug::alert($data['profile']);
    if($user_id){
      return $this->ContactsSuggester($user_id,$data);
    }else{
      return array(false);
    }
  }
 
  public function myContactsSuggester($data){
 
    return $this->ContactsSuggester($_SESSION['profile']['id'],$data,true, isset($data['online_first']) ? $data['online_first'] : true);
  }
 
  public function otherPersonFollowSuggester($data) {
    $data["filterUsername"] = $data["profile"];
    unset($data["profile"]);
 
    $data["follow_type"] = "followings";
 
    return $this->ContactsSuggester($_SESSION['profile']['id'],$data,true, false);
  }
 
    public function searchInMyMessages($data){
        // Used data
        $user_id = (int)$_SESSION['profile']['id'];
        if ($data['band_id'] > 0) {
            if (!\Helpers\Basic::checkUserRights((int) $data['band_id'], $user_id)) {
                return array(false,false);
            } else {
                $user_id = (int) $data['band_id'];
            }
        }
        
        $offset = $data['offset'];
        $limit = $data['limit'];
 
        $search = trim(strip_tags($data['search']));
        $search = str_replace(array("%", "'", '"'), "", $search);
 
        $length_to_get = 50; // How much characters to get
        $symbols_before = 10; // How much symbols before match to get
 
        $return = array();
 
        // Search query
        $query = "
            SELECT * FROM
            (
                SELECT
                    um.text, um.conversations_id, c.is_group, c.members,
                    IF (um.reciever_id = '".$user_id."', scsender.short_info_json, screciever.short_info_json) as partner,
                    IF (um.reciever_id = '".$user_id."', scsender.user_id, screciever.user_id) as partner_id
                FROM users_messages as um
                LEFT JOIN conversations AS c ON c.id = um.conversations_id
                LEFT JOIN search_cache AS scsender ON scsender.user_id = um.sender_id
                LEFT JOIN search_cache AS screciever ON screciever.user_id = um.reciever_id
                WHERE
                    um.sender_id != um.reciever_id
                    AND (um.sender_id = '".$user_id."' OR um.reciever_id = '".$user_id."')
                    AND um.is_deleted=0
                    AND (
                        um.text LIKE :keywords
                        OR (scsender.cache LIKE :keywords AND scsender.user_id != '".$user_id."')
                        OR (screciever.cache LIKE :keywords AND screciever.user_id != '".$user_id."')
                    )
                ORDER BY um.send_time DESC
            ) as results
            GROUP BY results.conversations_id, results.text, results.partner, results.partner_id
        ";
 
        $prepare = array(
            'keywords' => "%".$search."%"
        );
        
        // Here we store user information - reason - when we have a user, does not look for it again
        $cache_profiles = array();
 
        // So... lets begin... just in case string was more than 3 symbols
        if (mb_strlen($search) >= 3)
        {
            $cids = [];
            // Put information
            foreach($this->db->prepare($query, $prepare)->execute()->fetchAllAssoc()->cache( 1 )->result as $el) {
                $cids[] = $el['conversations_id'];
                continue; // !!!! NOTE we'll be using getMail at end of func to parse the response
                
                //this below -- NOT executed !!! 
                
                
                $text = trim(strip_tags($el['text']));
 
                $find_position = mb_strpos($search, $text);
                $text_length = mb_strlen($text);
                $string_length = mb_strlen($search);
 
                // Stupid way but... i cant regex :/
                if ($text_length > $length_to_get)
                {
                    if (($find_position - $symbols_before) <= 0)
                    {
                        $text = mb_substr($text, 0, $length_to_get)."...";
                    }
                    else if (($find_position - $symbols_before) > 0 AND ($find_position - $symbols_before + $length_to_get) <= $text_length)
                    {
                        $text = "...".mb_substr($text, ($find_position - $symbols_before), $length_to_get)."...";
                    }
                    else
                    {
                        $text = "...".mb_substr($text, ($length_to_get*-1));
                    }
                }
 
                $line = array(
                    'quote' => $text,
                    'conversation' => "#".$el['conversations_id']
                );
 
                // Put information about users in converstation
                if ((int)$el['is_group'] == 0)
                {
                    if (!isset($cache_profiles[$el['partner_id']]))
                    {
                        $user = unserialize($el['partner']);
 
                        $cache_profiles[$el['partner_id']] = array(
                            'names' => $user['display_name']
                        );
 
                        if (isset($user['avatar_icon']) AND $user['avatar_icon'] != "")
                        {
                            $cache_profiles[$el['partner_id']]['avatar_icon'] = $user['avatar_icon'];
                        }
                    }
 
                    $line['names'] = $cache_profiles[$el['partner_id']]['names'];
                    $line['avatar_icon'] = $cache_profiles[$el['partner_id']]['avatar_icon'];
                }
                else
                {
                    // Check collection
                    $collection = array();
                    $user_ids = explode(",", $el['members']);
                    foreach($user_ids as $key => $val)
                    {
                        if ((int)$val != 0 AND (int)$val != $user_id)
                        {
                            $collection[(int)$val] = false;
                        }
                    }
 
                    // Filter which users we have
                    $search_user_ids = array();
                    foreach($collection as $key => $val)
                    {
                        if (isset($cache_profiles[$key]))
                        {
                            // Put it in our collection
                            $collection[$key] = $cache_profiles[$key]['names'];
                        }
                        else
                        {
                            $search_user_ids[] = $key;
                        }
                    }
 
                    // Get missed users
                    if (!empty($search_user_ids))
                    {
                        $query = "SELECT user_id, short_info_json FROM search_cache WHERE user_id IN (".implode(",", $search_user_ids).")";
 
                        foreach($this->db->prepare($query, array())->execute()->fetchAllAssoc()->cache( 1 )->result as $usr) {
                            $user = json_decode($usr['short_info_json'], true);
                            $cache_profiles[(int)$usr['user_id']] = array(
                                'names' => $user['display_name']
                            );
 
                            if (isset($user['avatar_icon']) AND $user['avatar_icon'] != "")
                            {
                                $cache_profiles[(int)$usr['user_id']]['avatar_icon'] = $user['avatar_icon'];
                            }
 
                            // Put it in our collection
                            $collection[(int)$usr['user_id']] = $cache_profiles[(int)$usr['user_id']]['names'];
                        }
                    }
 
                    // Now merge names
                    $line['names'] = implode(", ", $collection);
                    $line['avatar_icon'] = DEF_GROUP_CHAT_ICON;
                }
 
                // Some modifications
                // $text = str_replace($search, "<em>".$search."</em>", $text);
                $seek_and_destroy = '#\b'.$search.'\b#iu';
                $line['quote'] = preg_replace($seek_and_destroy, '<em>$0</em>', $line['quote']);
                $line['names'] = preg_replace($seek_and_destroy, '<em>$0</em>', $line['names']);
 
                $return[] = $line;
            }
        }
        
        // NOTE!!  overwrite response here
        $mc = new \Controllers\API\MessengerController();
        $cids = array_reverse($cids);
        $cids = array_slice($cids, $offset, $limit);
        $getMailData = [
            'folder' => 'All',
            'mode' => 'search',
            'results' => $limit,
            'cids' => $cids,
            'pid' => $data['band_id']
        ];
        
        $getMailRe = $mc->getMail($getMailData);
        $return = is_array($getMailRe) ? $getMailRe : ['conversations' => []];
        
        // Return
        return array(true, array("length" => count($return['conversations']) , "conversations" => $return['conversations']));
    }
 
  private function ContactsSuggester($user_id,$data,$myContactsOnly=false, $onlineFirst = false){
    $uid=$user_id;
    $prepare=array();
        
        $excludeMothership = array_key_exists('exclude_mothership', $data) ? $data['exclude_mothership'] : true;
 
    // $prepare['uid']=$uid;
    $search=$data['search'];
 
    /*if (empty($search)) {
      return array(false, 'empty search query');
    }*/
 
    $select   = array();
    $leftJoin   = array();
    $order     = array();
 
    if($myContactsOnly){
      $data['follow_type']='followings';
    }
    $search=trim($search);
    $search=str_replace("*","",$search);
    $search=str_replace(")","",$search);
    $search=str_replace("(","",$search);
    $search=str_replace(" ","* +*",$search);
 
    if($search){
      $search=$this->db->quote("*".$search."*");
    }else{
      $search=NULL;
    }
    $circle_id=$data['circle_id'];
    // $prepare['circle_id']=$circle_id;
 
        $show_status=$onlineFirst ? true : $data['show_status'];
    $latest=isset($data['latest']) ? true : false;
    $is_online=$data['is_online'];
 
    $mix_order=$data['mix_order'];
 
        $where = [
            '`up`.`is_banned` IS NULL AND `up`.`is_disabled` = 0'
        ];
        
    if($is_online){
      $show_status=1;
      $where[]="(`up`.`user_status`>0 AND `up`.`lastseen`> " . (time() - \Config::defaults()->DEF_LASTSEEN_OFFLINE_TIME).") ";
    }
 
    if (isset($data["user_type"])) {
//      $LeftJoinUserStatus="LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
 
//      if ($data["user_type"] == "page") {
//        $where[] = "`up`.`is_page` = 1";
//      } else if ($data["user_type"] == "user") {
//                $where[] = "`up`.`is_page` IS NULL";
//            }
    }
 
    if(isset($show_status)){
            $SelectUserStatus=" fn_calculateProfilePublicStatus(`up`.`user_status`,`up`.`system_status`,`up`.`lastseen`) as `publicStatus`, `up`.`id` as `user_suggest_id`,
                                if(`chat_status` > 0,fn_calculateProfilePublicStatus(`user_status`, `system_status`, `lastseen`),0) as `cstatus`,";
      //$SelectUserStatus="`up`.`user_status`, `up`.`lastseen`, `up`.`system_status`, ";
//      $LeftJoinUserStatus="LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
      $where[] = "`up`.`is_page` IS NULL";
    }
 
    if ( $data['gender'] ) {
      $select[] = "`up`.`gender`";
 
//      if ( !$LeftJoinUserStatus ) {
//        $leftJoin[] = "LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
//      }
    }
 
        if ( $data['is_page'] ) {
            //\Helpers\Debug::log('is_page passed');
      $select[] = "`up`.`is_page`";
//      if ( !$LeftJoinUserStatus ) {
//        $leftJoin[] = "LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id` ";
//      }
    }
 
    if ( $data['pictures_first'] ) {
      $select[]   = 'IF(`upi`.`id`, 1, 0) AS `has_picture`'; // moje da stane greshka, zashtoto ne znaeme koi e profile album-a
      //$leftJoin[] = "LEFT JOIN `users_pictures` AS `upi` ON ( `upi`.`user_id` = `sc`.`user_id` AND `upi`.`profilepic` = 1 ) ";
      $leftJoin[] = "LEFT JOIN `users_elements` AS `upi` ON `sc`.`id` = `upi`.`creator_id` AND `upi`.`type`='picture' AND `upi`.`is_default` = '1' AND `upi`.`deleted_time` IS NULL AND `upi`.`published_time` IS NOT NULL AND `upi`.`deployed_time` IS NOT NULL ";
      $order[]   = '`has_picture` DESC';
    }
 
    if ( $data['compare_followers'] && in_array($data['follow_type'], array('followers', 'followings')) && isset($_SESSION['profile']['id']) ) {
      $select[]   = 'IF(`my_ucm`.`id`, 1, 0) AS `same_follower`';
      $select[]   = 'IF(`my_ucm`.`id`, `my_ucm`.`circle_id`, -1) AS `circle_id`';
      $leftJoin[] = "LEFT JOIN `users_circles_members` AS `my_ucm` ON ( `my_ucm`.`user_id` = " . $_SESSION['profile']['id'] . " AND `my_ucm`.`target_user_id` = " . ( $data['follow_type'] == 'followers' ? "`ucm`.`user_id`" : "`ucm`.`target_user_id`" ) . " ) ";
      $order[]   = '`same_follower` DESC';
    }
 
    if ($data['filterUsername']) {
      $otherUserId = \Helpers\Users::getIdFromUsername($data['filterUsername']);
      if ($otherUserId) {
        $leftJoin[] = "LEFT JOIN `users_circles_members` AS `other_ucm` ON ( `other_ucm`.`user_id` = " . $otherUserId . " AND `other_ucm`.`target_user_id` = `ucm`.`target_user_id` ) ";
        $where[] = "`other_ucm`.`id` IS NULL";
        $where[] = "`up`.`id` != {$otherUserId}";
      }
    }
 
    switch($data['follow_type']){
      case "followers":
        $where[]="`ucm`.`target_user_id` = '{$uid}'";
        $leftJoinCS="LEFT JOIN `search_cache` AS `sc` ON `sc`.`user_id` = `ucm`.`user_id`";
      break;
      case "followings":
        $where[]="`ucm`.`user_id` = '{$uid}'";
        $leftJoinCS="LEFT JOIN `search_cache` AS `sc` ON `sc`.`user_id` = `ucm`.`target_user_id`";
      break;
      default:
        $leftJoinCS="LEFT JOIN `search_cache` AS `sc` ON (`sc`.`user_id` = `ucm`.`target_user_id` OR `sc`.`user_id` =`ucm`.`user_id`)";
    }
 
    $limit = " ";
    if(isset($data["limit"])){
      $prepare['limit']=$data['limit'];
      $limit = " LIMIT :limit ";
 
      if (isset($data["offset"])) {
        $prepare['offset']=$data['offset'];
        $limit .= "OFFSET :offset ";
      }
    }
 
    if($search){
      $where[]="MATCH (`cache`) AGAINST ($search IN BOOLEAN MODE)";
    }
 
    if($circle_id and $circle_id!='All'){
      $where[]="`uc`.`id` = '{$circle_id}'";
    }
 
    $where[] ="`sc`.`user_id`!= '{$uid}'";
 
    if ( $uid != 1 && $excludeMothership) {
            $where[]="`ucm`.`target_user_id` != 1";
        }
 
        if (isset($data['exclude_ids'])) {
            $exclude = is_array($data['exclude_ids']) ? implode(',', $data['exclude_ids']) : $data['exclude_ids'];
 
            if ( strlen($exclude) > 0 ) {
           $where[]="`ucm`.`target_user_id` NOT IN ($exclude)";
            }
        }
       
    $where = implode(" AND ",$where);
    if($where=='') $where = '1';
 
        if ($latest) {
            $order[] = "`ucm`.`create_time` DESC";
        }
 
    if ($onlineFirst) {
      $order[]='FIELD(`publicStatus`, 2) DESC';
      $order[]='`up`.`first_name`';
      $order[]='`up`.`last_name`';
    }
 
    if(isset($mix_order)) {
      $order[]='RAND()';
    }
 
    if ( count($select) > 0 ) {
      $select = implode(', ', $select) . ',';
    } else {
      $select = '';
    }
    $leftJoin = implode(' ', $leftJoin);
 
    if ( count($order) > 0 ) {
      $order = 'ORDER BY ' . implode(', ', $order);
    } else {
      $order = '';
    }
 
    $sql="SELECT
           {$select}
           {$SelectUserStatus}
           `sc`.`short_info_json`,
           `uc`.`name` as `circle_name`,
           `ucm`.`target_user_id`
        FROM `users_circles_members` AS `ucm`
        {$leftJoinCS}
        {$LeftJoinUserStatus}
        {$leftJoinProfiles}
        {$leftJoin}
       LEFT JOIN `users_circles` AS `uc` ON `uc`.`id` = `ucm`.`circle_id`
           LEFT JOIN `users_profiles` AS `up` ON `up`.`id` = `sc`.`user_id`
         WHERE  ( {$where} ) AND `sc`.`is_disabled` = 0 /*AND `ucm`.`target_user_id`!='1'*/
         GROUP BY `sc`.`id`
         {$order}
         {$limit}
    ";
               
    // \Helpers\Debug::log($sql);
        // echo $sql;
    $ret=array();
 
    //foreach($this->db->q($sql,$prepare) as $el){
    $cache = isset($data['cache']) ? $data['cache'] : 1;
    // echo print_r(array($sql, $prepare), 1);
    // \Helpers\Debug::log($sql);
    if (isset($prepare["limit"])) $prepare["limit"] = intval($prepare["limit"]);
    if (isset($prepare["offset"])) $prepare["offset"] = intval($prepare["offset"]);
 
    $mem_before = memory_get_usage();
        $res = $this->db->prepare($sql,$prepare)->execute()->fetchAllAssoc()->result;
  //  \Helpers\Debug::log("Memory used by the contacts array: ", memory_get_usage() - $mem_before);
        // $res = $this->db->prepare($sql,$prepare)->execute()->fetchAllAssoc()->cache( $cache )->result;
        // echo "$sql - " . print_r($prepare,1);
    foreach($res as $el){
      $a=json_decode($el['short_info_json'], true);
 
      if($show_status){
                $status=array();
                $statuses=unserialize(USER_STATUS_STATEMENTS);
                $status['status_id']=$el['publicStatus'];
                $status['status_name']=$statuses[$status['status_id']];
        $chatStatus=array();
                $chatStatus['status_id']=$el['cstatus'];
        $chatStatus['status_name']=$statuses[$chatStatus['status_id']];
                $a['status']=$status;
        $a['chat_status']=$chatStatus;
      }
 
      if($a!=false){
        $a['gender']     = $el['gender'];
        $a['has_picture']   = $el['has_picture'];
        $a['same_follower'] = $el['same_follower'];
        $a['circle_id']   = $el['circle_id'];
        //Compatibility
        $a['profile_tags']  = $a['tags']['profile'];
 
        if($el['user_id'] == $_SESSION['profile']['id'] and $uid == $_SESSION['profile']['id']){
          $a['circle']=$el['circle_name'];
        }
        $ret[]=$a;
      }
    }
 
    return array(true,array("profiles"=>$ret));
    //return array(true,array("profiles"=>$ret),str_replace("\t","",str_replace("\n", "", $sql)),$prepare);
    //return array(true,$ret);
  }
 
    public function chatBarSuggester($data) {
        $uid = (int)$_SESSION['profile']['id'];
        $limit = isset($data['limit']) ? $data['limit'] : 15;
        $return = array();
 
        $query = "
                    SELECT
                            DISTINCT
                            `sender_id`,
                            `reciever_id`,
                            /*fn_calculateProfilePublicStatus(`up`.`user_status`,`up`.`system_status`,`up`.`lastseen`) as `publicStatus`,*/
                            if(`chat_status` > 0,fn_calculateProfilePublicStatus(`user_status`, `system_status`, `lastseen`),0) as `cstatus`
                    FROM
                            `users_messages` AS `um`
                        JOIN
                            `conversations` AS `c`
                        ON
                            (`um`.`conversations_id` = `c`.`id`)
                        LEFT JOIN
                            `users_profiles` AS `up`
                        ON
                            `up`.`id` = IF(`sender_id` = :uid, `reciever_id`, `sender_id`)
                    WHERE
                            (`sender_id` = :sid OR `reciever_id` = :rid)
                        AND
                            `sender_id` <> `reciever_id`
                        AND
                            `up`.`is_page` IS NULL
                    GROUP BY
                            `um`.`conversations_id`
                    ORDER BY
                            `send_time` DESC
                    LIMIT
                            0,$limit";
 
        $prep = array("uid" => $uid, "sid" => $uid, "rid" => $uid);
        
        $result = $this->db->prepare($query, $prep)->execute()->fetchAllAssoc()->result;
        $exclude = array();
        $statuses=unserialize(USER_STATUS_STATEMENTS);
        $chatstatuses=unserialize(CHAT_STATUS_STATEMENTS);
 
        foreach ( $result as $r ) {
            $id = $r['sender_id'] == $uid ? $r['reciever_id'] : $r['sender_id'];
            $user = \Helpers\Users::getUserShortInfoFromId($id);
 
            $status=array();
            $status['status_id']=\Helpers\Users::getInstance()->getProfilePublicStatus($id);//$r['publicStatus'];
            $status['status_name']=$statuses[$status['status_id']];
            $chatStatus=array();
            $chatStatus['status_id']=\Helpers\Users::getInstance()->getProfilePublicChatStatus($id);
            $chatStatus['status_name']=$chatstatuses[$chatStatus['status_id']];
            $user['status']=$status;
            $user['chat_status']=$chatStatus;
 
            if (!in_array($id, $exclude)) {
                $return[] = $user;
                $exclude[] = $id;
                //\Helpers\Debug::log($id,$status);
            }
        }
 
 
        if ( count($return) < $limit ) {
            $data["limit"] = $limit - count($return);
            $data["exclude_ids"] = $exclude;
            $additional = $this->myContactsSuggester($data);
 
            if ($additional[0] && count($additional[1]['profiles']) > 0) {
                $return = array_merge($return, $additional[1]['profiles']);
            }
        }
 
        foreach ( $return as &$r ) {
            $r['profile_tags']  = $r['tags']['profile'];
        }
 
        return array(true, array("profiles" => $return));
    }
    /* DEPRECATED
  public function followSuggester($data) {
    return array(true);
    $users   = array();
    $offset = $data['offset'] || 0;
    $lat = $this->app->request->ipInfo["latitude"];
        $long = $this->app->request->ipInfo["longitude"];
    $userTags   = array();
    //$results = $this->db->q( "SELECT `name`, `type` FROM `users_tags` WHERE `user_id` = :uid", array("uid" => $_SESSION['profile']['id']));
    $results = $this->db->prepare( "SELECT `name`, `type` FROM `users_tags` WHERE `user_id` = :uid", array("uid" => $_SESSION['profile']['id']))->execute()->fetchAllAssoc()->cache()->result;
 
    foreach ( $results as $el ) {
      $userTags[ $el[ 'type' ] ][]   = ucwords(strtolower($el['name']));
    }
 
    // foreach ( $userTags as $k => $e ) {
    //   $tagsCache[$k] = implode( " ", str_replace( " ", "_", $e ) );
    // }
 
    $query   = "
      SELECT
        `upr`.`id` as `user_id`,
        `sc`.`short_info`,
        " . ( ( $lat != 0 && $long != 0 ) ? "IF ( " . ($lat - 10) . " < `upr`.`lat` AND `upr`.`lat` < " . ($lat + 10) . " AND " . ($long - 10) . " < `upr`.`long` AND `upr`.`long` < " . ($long + 10) . ", 1, 0 )" : "0" ) . " as `is_near`,
        MATCH (instruments) AGAINST (:instruments IN NATURAL LANGUAGE MODE) AS score_instruments,
        MATCH (genres) AGAINST (:genres IN NATURAL LANGUAGE MODE) AS score_genres,
        MATCH (influences) AGAINST (:influences IN NATURAL LANGUAGE MODE) AS score_influences,
        IF ( `ucm`.`id` IS NOT NULL, 1, 0 ) as `followed`,
        IF ( `uc`.`id` IS NOT NULL, 1, 0 ) as `has_posts`
      FROM
        `users_profiles` `upr`
      JOIN
        `users_elements` `upi`
          ON
            (`upr`.`id` = `upi`.`creator_id` AND `upr`.`profile_album_id` = `upi`.`album_id` AND `upi`.`type`='picture' AND `upi`.`is_default` = '1' AND `upi`.`deleted_time` IS NULL AND `upi`.`published_time` IS NOT NULL AND `upi`.`deployed_time` IS NOT NULL)
      JOIN
        `search_cache` `sc`
          ON
            (`sc`.`user_id` = `upr`.`id`)
      LEFT JOIN
        `users_comments` `uc`
          ON
            (`uc`.`owner_element_id` = `upr`.`id` AND `uc`.`model` = 'post')
      LEFT JOIN
        `users_circles_members` `ucm`
          ON
            (`ucm`.`user_id` = :uid AND `ucm`.`target_user_id` = `upr`.`id`)
      WHERE
        `upr`.`id` != :uuid
      GROUP BY
        `uc`.`owner_element_id`
      HAVING
        `followed` = 0
      ORDER BY
        `score_genres` DESC,
        `score_influences` DESC,
        `score_instruments` DESC,
        `is_near` DESC,
        `has_posts` DESC
      LIMIT
        :offset, 9
    ";
 
    $results = $this->db->prepare($query, array(
      'offset'     => $offset,
      'uid'       => $_SESSION['profile']['id'],
      'uuid'       => $_SESSION['profile']['id'],
      'genres'     => ( isset( $userTags['genres'] )     ? implode( " ", str_replace( " ", "_", $userTags['genres'] ) ) : '' ),
      'instruments'   => ( isset( $userTags['instruments'] )   ? implode( " ", str_replace( " ", "_", $userTags['instruments'] ) ) : '' ),
      'influences'   => ( isset( $userTags['influences'] )   ? implode( " ", str_replace( " ", "_", $userTags['influences'] ) ) : '' )
    ))->execute()->fetchAllAssoc()->cache()->result;
 
 
    foreach ( $results as $result ) {
      $user = unserialize( $result['short_info'] );
 
      if ( $user != false ) {
        $users[] = $user;
      }
    }
 
    return array(true, $users);
  }*/
 
  public function searchSuggester($data) {
        $results = isset($data['limit']) ? $data['limit'] : \Config::defaults()->RESULTS_TO_SHOW->search;
        if ($results > \Config::defaults()->MAX_RESULTS_TO_SHOW->search) {
            $results = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
        }
 
        $reminder = $result % 2;
 
        $search = $data['autocomplete'];
        $msc = microtime(true);
        if (!isset($data['skipTags']) || !$data['skipTags']) {
            $tags = $this->tagsSuggester(array('autocomplete' => $search, 'limit' => (int) ($results / 2) + $reminder));
        }
 
        $profiles = [];
        $pages = [];
        if (isset($data['skipPages']) || $data['skipPages'] == 'true') { // legacy string true
            $profilesRes = $this->search(array('autocomplete' => $search, 'show_profiles' => true, 'visible' => $data['visible']));
            if ($profilesRes[0]) {
                $profiles = $profilesRes[1];
            }
        } else { // search both users and pages and separate them here
 
            $searchRes = $this->search(['autocomplete' => $search, 'visible' => $data['visible']]); 
 
            if ($searchRes[0]) {
                foreach ($searchRes[1] as $i => $sr) {
                    if ($i == 0) {
                        $firstType = $sr['is_page'] ? "page" : "profile";
                    }
                    if ($sr['is_page']) {
                        $pages[] = $sr;
                    } else {
                        $profiles[] = $sr;
                    }
                }
            }
        }
 
        $communities = \Helpers\Tags::getCommunities(null, false, $search, $results);
 
 
        $sCtrl = new \Controllers\API\SearchController();
        $posts = $sCtrl->searchPosts($data);
        
        $msc=microtime(true)-$msc;
        $res=array();
        $res['profiles'] = $profiles;
        $res['pages'] = $pages;
        $res['posts'] = $posts;
        $res['communities'] = $communities;
        $res['tags'] = $tags[0] ? $tags[1]['tags'] : array();
        $res['first_type'] = isset($firstType) ? $firstType : false;
        return array(true, $res);
    }
 
    public function getFacebookFriends ()
    {
        $uid = (int)$this->app->session->profile['id'];
        $friends = \Helpers\Users::getUnfollowedFacebookFriends($uid);
        return [true, $friends];
    }
 
    //polzva se samo za chat-a, no i tam e spriano i vrushta []
    public function generatePeopleSuggestions ($data) {
        return array(true,[]);
        if (isset($data['chat'])) {
            $uid = $_SESSION["profile"]["id"];
            $usedUids = [];
            if (isset($data['used_ids']) AND is_array($data['used_ids'])) {
                $usedUids = $data['used_ids'];
            }
 
            $result = [
                'explore' =>  \Helpers\Suggestions::generateExploreSuggestions($uid, $usedUids)
            ];
 
            return array(true, $result);
        }
 
        if (!isset($data["sideMenu"]) && !isset($data["explore"])) {
            return array(
                "sideMenu"   => array(),
                "explore"  => array()
            );
        }
 
        $result = \Helpers\Suggestions::generateSuggestions($_SESSION['profile']['id'], $data["sideMenu"], $data["explore"]);
 
        if (!$result) {
            return array(false, "insert/update failed");
        }
 
        return array(true, $result);
    }
 
  public function tagsSuggesterDEPRECATED($data){
    // DEPRECATED !!!!!! ! !! !
// DONE elasticsearch!
        
    $search=trim($data['search']);
    $type = $data['type'];
 
    if(!$search) return array(true,array());
        if ( strlen($search) < 3 ) {
            return [true, []];
        }
 
    $limit = isset($data['limit']) ? intval($data['limit']) : \Config::defaults()->RESULTS_TO_SHOW->search;
    if($limit > \Config::defaults()->MAX_RESULTS_TO_SHOW->search){
      $limit = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
    }
 
    $offset = isset($data['offset']) ? intval($data['offset']) : 0;
        $lang = \Helpers\Basic::lang();
        
        $regex = new \MongoDB\BSON\Regex('^'.$search, 'i');
        
        if ($type == "" || $type == "all") {
            $conditions = [
                "translation.{$lang}.text" => $regex,
            ];
 
            $project = [
                "type" => 1,
                'translation' => 1,
                'posts_rating' => ['$sum' => ['$type.posts.moderator_rating', '$type.posts.user_rating']],
                'profile_rating' => ['$sum' => ['$type.profile.moderator_rating', '$type.profile.user_rating']],
                'instruments_rating' => ['$sum' => ['$type.instruments.moderator_rating', '$type.instruments.user_rating']],
                'influences_rating' => ['$sum' => ['$type.influences.moderator_rating', '$type.influences.user_rating']],
                'equipment_rating' => ['$sum' => ['$type.equipment.moderator_rating', '$type.equipment.user_rating']],
                'genres_rating' => ['$sum' => ['$type.genres.moderator_rating', '$type.genres.user_rating']],
                'rating' => ['$max' => ['$posts_rating', '$profile_rating', '$instruments_rating', '$influences_rating', '$equipment_rating', '$genres_rating']],
            ];
        } else {
            $conditions = [
                "translation.{$lang}.text" => $regex,
            ];
                
            $project = [
                "type.{$type}" => 1,
                'translation' => 1,
                'rating' => ['$sum' => ['$' . "type.{$type}." . 'moderator_rating', '$' . "type.{$type}." . 'user_rating']]
            ];
        }
 
        $res = \Collections\Tags::aggregate([
            ['$match' => $conditions],
            ['$project' => $project],
            ['$sort' => ['rating' => -1]],
            ['$skip' => $offset],
            ['$limit' => $limit],
        ]);
        
        $tags = [];
        foreach ($res as $tag) {
            $tagName = mb_convert_case(stripslashes($tag->translation->{$lang}->text), MB_CASE_TITLE, 'UTF-8');
            foreach ($tag->type as $type => $notneed) {
                $tags[$type][] = ['index' => \Helpers\Tags::transformIndex($tagName), "url" => \Helpers\Basic::getUrl($tagName, $type), "name"=> $tagName ];
            }
        }
        
    return array(true,array("tags"=>$tags));
  }
    
    public function tagsSuggester($data) {
        $search = trim($data['search']);
        $type = $data['type'];
 
        if (!$search) {
            return [true, []];
        }
        if (strlen($search) < 3) {
            return [true, []];
        }
 
        $limit = isset($data['limit']) ? intval($data['limit']) : \Config::defaults()->RESULTS_TO_SHOW->search;
        if ($limit > \Config::defaults()->MAX_RESULTS_TO_SHOW->search) {
            $limit = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
        }
 
        $offset = isset($data['offset']) ? intval($data['offset']) : 0;
        $lang = \Helpers\Basic::lang();
        
        $sort = [["rating" => "desc"]];
        
        $bool = [];
        
        if (strlen($type) && $type != 'all') {
            $tagTypes = unserialize(TAGS_TYPES);
            if (!in_array($type, $tagTypes)) {
                return [true, []];    
            }
            $bool['filter'] = [
                'term' => [
                    'types' => $type
                ]
            ];
        }
        
        $bool["must"] = [
            [
                "match_phrase_prefix" => [
                    "text" => $search
                ]
            ]
        ];
        $query = ["bool" => $bool];
        
        $params = [
            "from" => $offset,
            "size" => $limit,
            "index" => \Config::elasticsearch()->INDICES->tags->{$lang},
//            "explain" => 1,
            "body" => [
                "query" => $query,
                "sort" => $sort,
            ]
        ];
            
        $dsm = $this->app->droobleSearchManager;
        $result = $dsm->search($params);
        
        $tags = [];
        
        foreach ($result['hits']['hits'] as $item){
            $tagName = mb_convert_case(stripslashes($item['_source']['text']), MB_CASE_TITLE, 'UTF-8');
            $tags[$type][] = ["name"=> $tagName ];
        }
 
        return [true, ['tags' => $tags]];
    }
    
    // looks like I did this to track stuff for Segment, without making too many requests (due to the $data['count'] that is used for counting search results,
    // but now it just wraps the search() func
    public function advancedSearchPage($data){
        
        $searchStr = strlen(trim($data['autocomplete'])) ? $data['autocomplete'] : trim($data['search']);
        $properties =array(
                "category" => 'Home page',
                "search_query" => "'".$searchStr."'"
            );
 
        if (isset($data['profile']) && is_array($data['profile'])) {
            // create Segment properties from search tags
            foreach ($data['profile'] as $tag) {
                if (preg_match('/^[0-9a-z\s]+$/i', $tag)) {
                    $properties[ preg_replace('/\s+/', '_', strtolower($tag)) ] = true;
                }
            }
        }
 
        // why not just 0/1 ? ... strings 'true' | 'false'
        if(isset($data['bands']) && ($data['bands'] == 'true' or $data['bands'] == true)){
            $properties['bands'] = true;
        }
 
        if(isset($data['venues']) && ($data['venues'] == 'true' or $data['venues'] == true)){
            $properties['venues'] = true;
        }
 
        if(isset($data['clubs']) && ($data['clubs'] == 'true' or $data['clubs'] == true)){
            $properties['clubs'] = true;
        }
 
        if(isset($data['recording_studios']) && ($data['recording_studios'] == 'true' or $data['recording_studios'] == true)){
            $properties['recording_studios'] = true;
        }
 
        if(isset($data['rehearsal_space']) && ($data['rehearsal_space'] == 'true' or $data['rehearsal_space'] == true)){
            $properties['rehearsal_space'] = true;
        }
 
        if(isset($data['music_school']) && ($data['music_school'] == 'true' or $data['music_school'] == true)){
            $properties['music_school'] = true;
        }
 
        if(isset($data['jam']) && ($data['jam'] == 'true' or $data['jam'] == true)) {
            $properties['jammers'] = true;
        }
 
        if(isset($data['teach']) && ($data['teach'] == 'true' or $data['teach'] == true)){
            $properties['teachers'] = true;
 
            if (isset($data['price_min'])) {
                $properties['price_min'] = $data['price_min'];
            }
            if (isset($data['price_max'])) {
                $properties['price_max'] = $data['price_max'];
                
                // When its picked maximum of 150+... 
                if ((int)$data['price_max'] == 150) {
                    $properties['price_max'] = 999;
                }
            }
        }
 
        if(isset($data['available']) && ($data['available'] == 'true' or $data['available'] == true)){
            $properties['available'] = true;
        }
 
        if(isset($data['looking_bandmates']) && ($data['looking_bandmates'] == 'true' or $data['looking_bandmates'] == true)){
            $properties['looking_bandmates'] = true;
        }
 
        if(isset($data['instruments']) && $data['instruments']){
            $properties['instruments'] = $data['instruments'];
        }
 
        if(isset($data['instruments_primary_to_join']) && $data['instruments_primary_to_join']){
            $properties['instruments_primary_to_join'] = $data['instruments_primary_to_join'];
        }
 
        if(isset($data['instruments_primary_for_bandmates']) && $data['instruments_primary_for_bandmates']){
            $properties['instruments_primary_for_bandmates'] = $data['instruments_primary_for_bandmates'];
        }
 
        if(isset($data['genres']) && $data['genres']){
            $properties['genres'] = $data['genres'];
        }
 
        if(isset($data['influences']) && $data['influences']){
            $properties['influences'] = $data['influences'];
        }
 
        if(isset($data['equipment']) && $data['equipment']){
            $properties['equipment'] = $data['equipment'];
        }
 
        if(isset($data['languages']) && $data['languages']){
            $properties['languages'] = $data['languages'];
        }
 
        if(isset($data['location']) && $data['location']){
            $properties['location'] = $data['location'];
        }
 
        if(isset($data['place']) && $data['place']){
            $properties['place'] = $data['place'];
        }
 
        $track = array();
 
        $track['event'] = "Advanced Search";
 
        if($this->app->session->has('profile')){
            $track['userId'] = $this->app->session->get('profile')['id'];
        }else{
//            $track['anonymousId'] = rand(100000,999999);
        }
 
        $track['properties'] = $properties;
 
        if( $this->app->session->has('profile') && (!isset($data['count']) or $data['count'] == 2) ){
            if(!$_COOKIE["druast"]) {
                $secureConnection = strtolower(\Helpers\Basic::getInstance()->get_php_http_scheme()) == 'https' ? true : false;
                setcookie("druast", date("Y-m-d"), time() + 60 * 60, '/', NULL, $secureConnection, true); // 60 minutes
                \Drooble\Activities\Activities::add( "advancedSearch", $track['userId'], (object) ['type' => 'user', 'id' => $track['userId']], ['segment' => ['track' => $track]] );
            }
        }
 
        return $this->search($data);
 
    }
    
    // $data['autocomplete'] string - the search works as autocomplete e.g. Joh -> John
    // $data['search'] string - the search works as search (bad at autocomplete, but with more accurate results)
    // TODO - think if possible to use instead of getContacts - you'll need your 'followings/folloers' on top of results
    public function search($data) {
        $go_search=false;
    foreach(array_keys($data) as $e){
      if(!in_array($e, array_keys( get_object_vars( \Config::api()->MAP->search->data ) ))){
        return array(false,"input error1 '".$e."'");
      }
      if($data[$e]){
        $go_search=true;
      }
    }
    if(!$go_search){
      return array(false,"empty search");
    }
        
        $dsm = $this->app->droobleSearchManager;
        $lang = \Helpers\Basic::lang();
        $analyzer = strtolower(\Config::languages()->language->{$lang}->name_in_english);
        $offset = $data['offset'] ? $data['offset'] : 0;
        $results = isset($data['limit']) ? $data['limit'] : \Config::defaults()->RESULTS_TO_SHOW->search;
    if($results > 100){ // way too many
      $results = \Config::defaults()->MAX_RESULTS_TO_SHOW->search;
    }
        
        $count = false;
       
       $pageTypeIds = [];
       $matchTags = [
           'profile' => [],
           'instruments' => [],
           'genres' => [],
           'equipment' => [],
           'influences' => [],
           'languages' => [],
           'instruments_primary_to_join' => [],
           'instruments_primary_for_bandmates' => [],
       ];
       
       if ((int) $data['venues'] == 1) {
            $pageTypeIds[] = 106;
        }
 
        if ((int) $data['clubs'] == 1) {
            $pageTypeIds[] = 107;
        }
 
        if ((int) $data['music_school'] == 1) {
            $pageTypeIds[] = 120;
        }
 
        if ((int) $data['recording_studios'] == 1) {
            $pageTypeIds[] = 115;
        }
 
        if ((int) $data['rehearsal_space'] == 1) {
            $pageTypeIds[] = 128;
        }
 
        if (is_array($data['instruments']) AND ! empty($data['instruments'])) {
           foreach ($data['instruments'] as $item) {
               $matchTags['instruments']['should'][] = $item;
           }
       }
       if (is_array($data['genres']) AND ! empty($data['genres'])) {
           foreach ($data['genres'] as $genre) {
               $matchTags['genres']['should'][] = $genre;
           }
       }
       if (is_array($data['equipment']) AND ! empty($data['equipment'])) {
           foreach ($data['equipment'] as $item) {
               $matchTags['equipment']['should'][] = $item;
           }
       }
       if (is_array($data['influences']) AND ! empty($data['influences'])) {
           foreach ($data['influences'] as $item) {
               $matchTags['influences']['should'][] = $item;
           }
       }
       if (is_array($data['languages']) AND ! empty($data['languages'])) {
           foreach ($data['languages'] as $item) {
               $matchTags['languages']['should'][] = $item;
           }
       }
       // these are reversed ... to match users who want to play an instrument and bands that are looking for the same
       if (is_array($data['instruments_primary_to_join']) AND ! empty($data['instruments_primary_to_join'])) {
           foreach ($data['instruments_primary_to_join'] as $item) {
               $matchTags['instruments_primary_for_bandmates']['should'][] = $item;
           }
       }
       if (is_array($data['instruments_primary_for_bandmates']) AND ! empty($data['instruments_primary_for_bandmates'])) {
           foreach ($data['instruments_primary_for_bandmates'] as $item) {
               $matchTags['instruments_primary_to_join']['should'][] = $item;//
           }
       }
       
        if (is_array($data['profile']) AND !empty($data['profile'])) {
            $tmp = array();
            foreach ($data['profile'] as $el) {
                if (!trim($el))
                    continue;
                
                // Some predefined
                // TODO config with translations in all languages VS cache ...
                if ($el == $this->app->translate->get('drummers')) {
                    $tag = \Helpers\Tags::findByText('drums', null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText('drummer', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('singers') || $el == $this->app->translate->get('vocalists')) {
                    $tag = \Helpers\Tags::findByText('vocals', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText('singer', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText('vocalist', null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('guitarists')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('guitar'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('guitarist'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('acoustic_guitar'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('electric_guitar'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('bass_players') || $el == $this->app->translate->get('bassists')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('bass'), null);
                    if ($tag) {
                        $matchTags['instruments']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('bass_player'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('bassist'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('producers')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('producer'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
 
                if ($el == $this->app->translate->get('musicians')) {
                    $tag = \Helpers\Tags::findByText($this->app->translate->get('musician'), null);
                    if ($tag) {
                        $matchTags['profile']['should'][] = $tag->name;
                    }
                }
            }
        }
        
        $bool = [];
        $must = [];
        $should = [];
        
        if (isset($data['autocomplete']) && trim($data['autocomplete'])) {
            $searchStr = trim($data['autocomplete']);
            // phrase_prefix is the "poorman's autocomplete" of Elastic. Works great
            // In future we can create more complex autocomplete, but it's totally unnecessary for now
            $combineType = "phrase_prefix";
            $minLength = 2;
        } else {
            $searchStr = trim($data['search']);
            $combineType = "cross_fields";
            $minLength = 3;
        }
        
        if (strtolower($searchStr) == "search" || strtolower($searchStr) == "%%tag%%") {
            try {
                // we got weird "search" requests .. log em
//                \Helpers\Debug::log("weird search ", $_SERVER['HTTP_REFERER']);
//                \Helpers\Debug::log($_SERVER['REQUEST_URI']);
//                \Helpers\Debug::log($_SERVER['QUERY_STRING']);
//                \Helpers\Debug::log($_SERVER['HTTP_USER_AGENT']);
            } catch (\Exception $e) {
                
            }
        }
        
        if (strlen($searchStr) >= $minLength) {
            // keep search string for 'tag' cloud of frequent searches
            \Helpers\SearchTags::add($searchStr);
            $combine = [];
            $combine[] = [
                "multi_match" => [
                    "query" => $searchStr,
                    "analyzer" => $analyzer,
                    "type" => $combineType,
                    "fields" => [
                        "name^5", // full name of User / Page
                        "username^1",
//                        "about.title^4",
//                        "about.text^2",
//                        "type.{$lang}^2", // Pages only
//                        "tags.profile.{$lang}^3",
//                        "tags.equipment.{$lang}^2",
//                        "tags.genres.{$lang}^2", // Pages only
//                        "tags.genres_primary.{$lang}^2",
//                        "tags.genres_secondary.{$lang}",
//                        "tags.influences.{$lang}^2",
//                        "tags.instruments_primary.{$lang}^2",
//                        "tags.instruments_secondary.{$lang}",
//                        "tags.languages.{$lang}",
                    ]
                ]
            ];
            $combine[] = [
                "multi_match" => [
                    "query" => $searchStr,
                    "analyzer" => $analyzer,
                    "fields" => [
                        "name^3", // full name of User / Page
//                        "username^1",
//                        "about.title^3",
//                        "about.text^2",
//                        "type.{$lang}^2", // Pages only
//                        "tags.profile.{$lang}^3",
//                        "tags.equipment.{$lang}^2",
//                        "tags.genres.{$lang}^2", // Pages only
//                        "tags.genres_primary.{$lang}^2",
//                        "tags.genres_secondary.{$lang}",
//                        "tags.influences.{$lang}^2",
//                        "tags.instruments_primary.{$lang}^2",
//                        "tags.instruments_secondary.{$lang}",
//                        "tags.languages.{$lang}",
                    ],
                    "fuzziness" => "AUTO"
                ]
            ];
            $must[] = ['bool' => ['should' => $combine, "boost" => 5]];
        }
 
        /*        
        if ($data['teach'] == 1) {
            $must[] = ['match' => ['teach' => 1]];
        }
        
        if (intval($data['price_min']) > 0 || intval($data['price_max']) > 0) {
            $price = [];
            if (intval($data['price_min']) > 0) {
                $this_drooble_fee = (intval($data['price_min']) / (\Config::defaults()->DROOBLE_FEE_IN_PERCENTS + 100) * \Config::defaults()->DROOBLE_FEE_IN_PERCENTS);
                $price_from = intval($data['price_min']) - $this_drooble_fee;
                if ($price_from < 0) {
                    $price_from = 0;
                }
                $price['gte'] = intval($price_from);
            }
 
            if (intval($data['price_max']) > 0) {
                $this_drooble_fee = (intval($data['price_max']) / (\Config::defaults()->DROOBLE_FEE_IN_PERCENTS + 100) * \Config::defaults()->DROOBLE_FEE_IN_PERCENTS);
                $price_to = intval($data['price_max']) - $this_drooble_fee;
                if ($price_to < 0) {
                    $price_to = 0;
                }
                $price['lte'] = intval($price_to);
            }
            $must[] = ['range' => ['price' => $price] ];
        }
         */
        
 
        if ($data['place']) {
            $data['place'] = str_replace("'", "", $data['place']);
            $data['place'] = str_replace("`", "", $data['place']);
            $data['place'] = str_replace("\"", "", $data['place']);
            
            $place = \Helpers\Basic::getGooglePlace($data['place']);
            if ($place && $place->is_country) {
                $must[] = ['match' => ['country' => $place->country_code]];
            } else {
                $must[] = ['match' => ['city' => $data['place']]];
            }
            
        }elseif($data['location']){
            $google_places = new \Lib\GooglePlaces(\Config::googleplaces()->API_KEY);
 
            $cor = array($this->app->request->ipInfo['latitude'], $this->app->request->ipInfo['longitude']);
 
            $google_places->location = $cor;
            $google_places->radius   = 100;
            $google_places->rankby   = 'distance';
            $google_places->types    = '(cities)';
            $google_places->input    = $data['location'];
            $google_places_result = $google_places->autocomplete();
            $searched_places_ids = array();
 
            if($google_places_result['status'] == 'OK' AND count($google_places_result['predictions'])>0){
                foreach($google_places_result['predictions'] as $__prediction){
                    $searched_places_ids[] = $__prediction['place_id'];
                }
            }
            if (count($searched_places_ids)) {
                $tmp = [];
                foreach ($searched_places_ids as $placeid) {
                    $tmp[] = ['match' => ["city" => $placeid]];
                }
                $must[] = ['bool' => ['should' => $tmp]];
            }
        }
        
        if (count($data['tags'])) {
            foreach ($data['tags'] as $tag) {
                foreach ($matchTags as $type => $info) {
                    $matchTags[$type]['should'][] = $tag;
                }
            }
        }
        foreach ($matchTags as $type => $info) {
            foreach ($info as $boolOperator => $tags) { // $boolOperator fills $must or $should arrays
                if (count($tags)) {
                    foreach ($tags as $tag) {
                        if ($type == "genres") {
                            ${$boolOperator}[] = ['match' => ["tags.genres.{$lang}" => $tag]]; // this is for Pages
                            ${$boolOperator}[] = ['match' => ["tags.genres_primary.{$lang}" => $tag]];
                            ${$boolOperator}[] = ['match' => ["tags.genres_secondary.{$lang}" => $tag]];
 
                        } elseif ($type == "instruments") {
                            ${$boolOperator}[] = ['match' => ["tags.instruments_primary.{$lang}" => $tag]];
                            ${$boolOperator}[] = ['match' => ["tags.instruments_secondary.{$lang}" => $tag]];
                        } else {
                            ${$boolOperator}[] = ['match' => ["tags.{$type}.{$lang}" => $tag]];
                        }
                        
                        /* this here was again because someone doesn't follow YAGNI
                        if ($type == "genres") {
                            $tmp[] = ['match_phrase' => ["tags.genres.{$lang}" => $tag]]; // this is for Pages
                            $tmp[] = ['match_phrase' => ["tags.genres_primary.{$lang}" => $tag]];
                            $tmp[] = ['match_phrase' => ["tags.genres_secondary.{$lang}" => $tag]];
 
                        } elseif ($type == "instruments") {
                            $tmp[] = ['match_phrase' => ["tags.instruments_primary.{$lang}" => $tag]];
                            $tmp[] = ['match_phrase' => ["tags.instruments_secondary.{$lang}" => $tag]];
                        } else {
                            $tmp[] = ['match_phrase' => ["tags.{$type}.{$lang}" => $tag]];
                        }
                        if ($tmp) {
                            ${$boolOperator}[] = ['bool' => ['should' => $tmp]];
                        }*/
                        
                    }
                }
            }
        }
        
//        if ($data['visible'] == 'profile_completed') {
//            $must[] = [
//                'term' => [
//                    'visibility' => 'profile_completed'
//                ]
//            ];
//        }
 
        if (count($pageTypeIds)) {
            $must[] = ['terms' => ['page_type_id' => $pageTypeIds]];
        }
        
        if (count($must)) {
            $bool['must'] = $must;
        }
        
        if (count($should)) {
            $bool['should'] = $should;
            $bool['minimum_should_match'] = 1;
            // setting minimum_should_match would mess up the has_avatar boost
        }
        
//        if ($data['autocomplete']) {
//            $indexStr = \Config::elasticsearch()->INDICES->users . ',' . \Config::elasticsearch()->INDICES->pages;
            $indexStr = \Config::elasticsearch()->INDICES->users;
//        } else {
//            $indexStr = \Config::elasticsearch()->INDICES->users;
//        }
//        if ((int) $data['bands'] == 1 || count($pageTypeIds) || $data['show_pages'] || $data['type'] == "pages") {
//            $indexStr = \Config::elasticsearch()->INDICES->pages;
//        }
        if ($data['show_profiles'] /*|| $data['type'] == "people" */) {
            $indexStr = \Config::elasticsearch()->INDICES->users;
        }
        if (count($bool)) {
            // boost has_avatar. I personally argued that we should not boost, but rather filter by has_avatar, but it was pointless .. so enjoy
//            if (!isset($tagBoost)) { // NO SOLUTION for now ...
//                $bool['should'][] = ['term' => ['has_avatar' => ["value" => true, "boost" => 100 ]]];
//            }
//            // enough BS
//            if ($indexStr == \Config::elasticsearch()->INDICES->users) {
//                $bool['should'][] = ['exists' => ['field' => 'tags.profile', 'boost' => 80]];
//                $bool['should'][] = ['exists' => ['field' => 'tags.instruments_primary', 'boost' => 70]];
//                $bool['should'][] = ['exists' => ['field' => 'tags.genres_primary', 'boost' => 60]];
//            } else {
//                $bool['should'][] = ['exists' => ['field' => 'tags.genres', 'boost' => 80]];
//                $bool['should'][] = ['exists' => ['field' => 'tags.influences', 'boost' => 70]];
//            }
            $query = ["bool" => $bool];
        } else {
            // empty search
            $bool = [
                'must' => [
                    ['match' => ['has_avatar' => true]]
                ]
            ];
            // enough BS
//            if ($indexStr == \Config::elasticsearch()->INDICES->users) {
//                $boostTags = [
//                    ['exists' => ['field' => 'tags.profile', 'boost' => 80]],
//                    ['exists' => ['field' => 'tags.instruments_primary', 'boost' => 70]],
//                    ['exists' => ['field' => 'tags.genres_primary', 'boost' => 60]],
//                ];
//            } else {
//                $boostTags = [
//                    ['exists' => ['field' => 'tags.genres', 'boost' => 80]],
//                    ['exists' => ['field' => 'tags.influences', 'boost' => 70]],
//                ];
//            }
//            if (isset($boostTags)) {
//                $bool['should'] = $boostTags;
//            }
            $query = [
                'function_score' => [
                    'query' => ['bool' => $bool],
                    'random_score' => new \stdClass()
                ]
            ];
        }
        
        $sort = [["_score" => "desc"]]; // second and third sort params were ["has_avatar" => "desc"], ["lastseen" => "desc"] they are insignificant
        
        $params = [
            "from" => $offset,
            "size" => $results,
            "index" => $indexStr,
//            "explain" => 1,
            "body" => [
                "query" => $query,
                "sort" => $sort,
            ]
        ];
 
        //\Helpers\Debug::log('dht', json_encode($params));
 
        $result = $dsm->search($params);
        
        if ((int) $data['count'] === 1) { //OMG all the legacy .. humanity
            $count = $result['hits']['total'];
            return array(true, $count);
        } else {
            return $this->viewSearchUsersAndPages($result, $searchStr);
        }
    }
    
    /*
     * @result DroobleSearchManager search result (Elastic client search result)
     * @searchStr - string from search (needed to 'inject' the search string as a Tag to all Users in the View)
     */
    public function viewSearchUsersAndPages($result, $searchStr) {
        $lang = \Helpers\Basic::lang();
        $hitIds = [];
        foreach ($result['hits']['hits'] as $item){
            $hitIds[] = $item['_id'];
        }
        $hitIdsImploded = implode(",", $hitIds);
        $count = $result['hits']['total'];
        $ret = [];
        // get data for hits
        if ($count) {
            
            if (strlen($searchStr)) {
                $foundTag = \Helpers\Tags::findByText($searchStr, null);
            }
            
            $elsCtrl = new \Controllers\API\ElementsController();
            
            $sql="
                SELECT
                    `search_cache`.`short_info_json` ,
                    `up`.`id`,
                    `up`.`user_status`,
                    `up`.`system_status`,
                    `up`.`lastseen`,
                    `up`.`is_page`,
                    IF ( `ucm`.`id` IS NOT NULL, 1, 0 ) as `isInCircle`
                FROM
                    `search_cache`
                JOIN
                    `users_profiles` `up`
                        ON
                            `up`.`id` = `search_cache`.`user_id`
                LEFT JOIN
                    `users_circles_members` `ucm`
                        ON
                            (`ucm`.`user_id` = :fuid AND `ucm`.`target_user_id` = `up`.`id`)
                WHERE `up`.`id` IN(" . $hitIdsImploded . ")
                    ORDER BY FIELD(up.id, " . $hitIdsImploded . ")
                    LIMIT " . $count . "
                   ;";
 
            $prepare = [ 'fuid' => $_SESSION['profile']['id'] ];
            $qres = $this->app->db->query($sql, $prepare);
            $qres->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $qelements = $qres->fetchAll();
            foreach ($qelements as $el) {
                $a = json_decode($el['short_info_json'], true);
                if ($a != false) {
                    $a['uid'] = $el['id'];
                    $a['isInCircle'] = $el['isInCircle'];
//                    $a['status'] = $el['publicStatus'];
                    $a['is_page'] = $el['is_page'];
//                    if ($a['rates']['session_price'] and $el['id'] != $_SESSION['profile']['id']) {
//                        $a['rates']['session_price'] = floatval($a['rates']['session_price']) + floatval($a['rates']['session_price']) * ( \Config::defaults()->DROOBLE_FEE_IN_PERCENTS / 100 );
//                    }
                    if ((int) $count >= 0) {
                        $a['counter'] = $count;
                    }
                    $tags = \Helpers\Users::getUserTags($a['uid'], 3); // get with 3 - categorized with remapped type, cause otherwise you'll get random instruments (they have 4 subtypes)
                    
                    if ($a['uid'] == 1) {
                        $a['open_to_string'] = "All music";
                    } else {
                        $open_to_string = "";
                        if ( count($tags['profile']) > 1 ) {
                            $open_to_string = $tags['profile'][0] . " " . $this->app->translate->get('and') . " " . $tags['profile'][1];
                        } elseif (count($tags['profile']) == 1) {
                            $open_to_string = $tags['profile'][0];
                        }
                        $a['open_to_string'] = $open_to_string;
                    }
                    
                    $flatList = [];
                    foreach ($tags as $arr) {
                        foreach ($arr as $tag) {
                            $flatList[] = $tag;
                        }
                    }
                    if ($foundTag) {
                        $searchAsTag = mb_convert_case(stripslashes($foundTag->translation->{$lang}->text), MB_CASE_TITLE, 'UTF-8');
                        if (!in_array($searchAsTag, array_slice($flatList, 0, 2))) {
                            // 'inject' the search string as a Tag @ first position if it's not in the top 2 tags
                            foreach ($tags as $key => $arr) {
                                array_unshift($arr, $searchAsTag);
                                $tags[$key] = $arr;
                                break;
                            }
                        }
                    }
                    // remove duplicates
                    $firstParts = array_slice($tags, 0, 3);
                    $first3keys = array_keys($firstParts);
 
                    $countTypes = count($first3keys);
                    if ($countTypes > 1) {
                        $tags[$first3keys[1]] = array_diff($tags[$first3keys[1]], $tags[$first3keys[0]]);
                    } 
                    if ($countTypes > 2) {
                        $tags[$first3keys[2]] = array_diff($tags[$first3keys[2]], $tags[$first3keys[1]]);
                        $tags[$first3keys[2]] = array_diff($tags[$first3keys[2]], $tags[$first3keys[0]]);
                    }
                        
                    $a['tags'] = $tags;
                    $ret[] = $a;
                }
            }
        }
        return [true, $ret];
 
    }
 
    public function getContacts($data) {
        return array(true, \Helpers\Search::getContactsAndTheRest($data['uid'], $data));
    }
 
}
 
?>
#13search->advancedSearchPage(Array(10), true)
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/lib/client.class.php (294)
<?php
class client{
 
  public $t;
  public $p;
  public $l;
  public $c;
  public $pname;//page name
  private $db;
//  private $lib;
  public $lang;
  public $init=false;
  public $cached_profiles=array();// stre selected profiles;
  public $globalTexts;
  private $pageTexts;
  private $jsTexts;
  private $app;
 
  public function __construct($db,$app,$lang='en'){
    $this->db = $db;
//    $this->lib = $lib;
    $this->lang = $lang;
    $this->app = $app;
  }
 
 
// DEPRECATED
  //page in english (default)
  //TODO: move in init
//  public function initPageTexts($page) {
//            $di = Phalcon\DI\FactoryDefault::getDefault();
//            $translate = $di->getShared('translate');
//            $translate->initPageTexts($page);
//            $this->globalTexts = $translate->getGlobalTexts();
//            $this->pageTexts = $translate->getPageTexts();
//            $this->jsTexts = $translate->getJsTexts();
//            return;
//
//  }
 
// DEPRECATED
  //page in english (default)
//  public function getPageText ($code) {
//
//    if (!isset($this->globalTexts) || !isset($this->pageTexts)) {
//      $this->initPageTexts();
//    }
//
//        // Defautl
//        $text = $code;
//
//        if (isset($this->globalTexts[$code])) {
//            $text = $this->globalTexts[$code];
//        }
//
//    if (isset($this->pageTexts[$code])) {
//      $text = $this->pageTexts[$code];
//        }
//
//    return $text;
//  }
 
// DEPRECATED
//  public function getJsJsonTexts () {
//    if (!isset($this->jsTexts))
//      return "";
//
//    return json_encode($this->jsTexts);
//  }
 
  public function setTitle ($title) {
    if (!isset($this->globalTexts) || !isset($this->pageTexts))
      return;
 
    $this->pageTexts['title'] = $title;
  }
 
 
// DEPRECATED
//  public function get_page_button($page){
//    return $this->t["page"][$page]["button"];
//  }
 
  public function downloadAttach($file){ //'/'.$_b.'/'.$_c.'/'.$_d //DEPRECATED - see ElementsController.php DownloadAction()
    exit();
    if($uid=$_SESSION['profile']['id']){
            $tmp = explode('/', $file);
            $prof_id = $tmp[1]; //uid
            if ($prof_id != $uid) { // probably band, so check it's authorized
                $pr = \Models\UserPageRights::findFirst(array('page_id = :page_id:', 'bind' => array('page_id' => $prof_id)));
                if (is_object($pr) && $pr->user_id == $_SESSION['profile']['id']) {
                    $uid = $prof_id;
                }
            }
 
      $sql="SELECT `uma`.`original_filename` AS `filename`, `uma`.`stored_filename`
       FROM `users_messages_attachment` AS `uma`
    LEFT JOIN `users_messages` AS `um` ON `um`.`sender_id` = `uma`.`user_id` AND `um`.`attachments` LIKE CONCAT('%',`uma`.`attach_id`,'%')
      WHERE `uma`.`stored_filename` = :file  AND `um`.`reciever_id` = :rid";
 
      if(!isset($_GET['element']) AND $r=$this->db->q($sql,array("file"=>$file, "rid" => $uid))){ // SOME FILE
        $filename=$r[0]['filename'];
                if ($tmp[2] == 'attachments') {
                    $stored_filename = $r[0]['stored_filename'];
                    try {
                        require __DIR__."/../../../../resources/library/aws/aws-autoloader.php";
            $configArr = json_decode(json_encode(\Config::aws()->S3->CONFIG), true);
            $s3 = new \Aws\S3\S3Client($configArr);
 
                        $original_key = \Config::paths()->UPLOAD_WEB_STORAGE_ROOT_FOLDER . $stored_filename;
//\Helpers\Debug::log('$original_key', $original_key);
                        $result = $s3->getObject(array(
                            'Bucket' => \Config::aws()->S3->BUKCETS->ATTACHMENTS,
                            'Key' => $original_key,
                        ));
                         // Display the object in the browser
                        header('Content-Description: File Transfer');
                        header('Content-Type: application/octet-stream');
                        header("Content-Type: {$result['ContentType']}");
                        header('Content-Disposition: attachment; filename='.basename($filename));
                        header('Content-Transfer-Encoding: binary');
                        header('Expires: 0');
                        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                        header('Pragma: public');
                        echo $result['Body'];
                        exit;
                    } catch (Exception $e) {
                        \Helpers\Debug::log($e->getTraceAsString());
                        return array(false, "can't get object");
                    }
 
                }/* else {
                    $file = \Config::paths()->ATTACH_DIR . $file;
                }*/
 
        if (file_exists($file))
        {
          header('Content-Description: File Transfer');
          header('Content-Type: application/octet-stream');
          header('Content-Disposition: attachment; filename='.basename($filename));
          header('Content-Transfer-Encoding: binary');
          header('Expires: 0');
          header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
          header('Pragma: public');
          header('Content-Length: ' . filesize($file)); //Remove
          ob_clean();
          flush();
          readfile($file);
          exit;
        }
        exit ('File does not exist!');
            } else if (isset($_GET['element'])) { // ELEMENT (AUDIO FOR NOW)
                if ($_GET['b'] == 'audio') {
                    $check = \Models\UserElements::findFirst(array(
                        'conditions' => "type='audio' AND id=:id: AND is_downloadable=1",
                        'bind' => array(
                            'id' => (int)$_GET['element']
                        )
                    ));
 
                    if ($check) {
                        // $original_filename = explode(".", $check->original_filename);
                        // To remove extension
                        // array_pop($original_filename);
                        // $original_filename = implode("_", $original_filename);
 
                        $new_extension = explode(".", $check->external_media_url);
                        $new_extension = array_pop($new_extension);
 
                        $creator_name = $check->Creator->first_name;
                        if ($check->Creator->last_name) {
                            $creator_name .= " ".$check->Creator->last_name;
                        }
 
                        $title = \Helpers\Inflector::urlize($creator_name);
                        $title .= "-".\Helpers\Inflector::urlize($check->title);
 
                        header('Content-Description: File Transfer');
                        header('Content-Type: application/octet-stream');
                        header('Content-Disposition: attachment; filename='.$title.'.'.$new_extension);
                        header('Content-Transfer-Encoding: binary');
                        header('Expires: 0');
                        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                        header('Pragma: public');
                        header('Content-Length: ' . filesize($check->external_media_url)); //Remove
                        ob_clean();
                        flush();
                        readfile($check->external_media_url);
                        exit;
                    } else {
                        exit("error 1");
                    }
                } else {
                    exit("error 2");
                }
      } else {
        exit("error");
      }
    }else{
      exit("access denied");
    }
  }
 
 
  public function load_square($page){
 
//    $this->initPageTexts();
    if (!isset($c)) {
      $c = $this;
    }
 
    ob_start();
    if(file_exists( SQUARE_PAGES_LOGIC . $page . ".php" )){
      include( SQUARE_PAGES_LOGIC . $page . ".php" );
    }
 
    if(file_exists( SQUARE_PAGES_CSS . $page . ".css" )){
      include( SQUARE_PAGES_CSS . $page . ".css" );
    }
 
    if(file_exists( SQUARE_PAGES_HTML . $page . ".html" )){
      include( SQUARE_PAGES_HTML . $page . ".html" );
    }
 
    if(file_exists(SQUARE_PAGES_JS . $page . ".js")){
      echo "<script type=\"text/javascript\">";
      include( SQUARE_PAGES_JS . $page . ".js" );
      echo "</script>";
    }
 
    $content = ob_get_clean();
 
    echo $content;
 
  }
 
  public function api($type,$action=false,$data=false,$internal=true){
 
    //$this->lib->api($type,$action=false,$data=false);
        $app = $this->app; // expose app (to use in squares)
 
    switch($type){
      case "php":
        if(!isset($action)){
          $action=$data['action'];
        }
                
                //$API1_MAP = $this->lib->config['API1']['MAP'];
 
        if(\Config::api()->MAP->{$action}->status===true){
                    
                    
          if(\Config::api()->MAP->{$action}->sessionRequire==true and !isset($_SESSION['profile']['id'])){
            return(array(false,"nosess"));
          }
          $object=\Config::api()->MAP->{$action}->class;
          //TODO check za input data-ta
                    
                    
          file_exists(LOGIC . $object . ".class.php") || exit("wrong file");
 
                    
          class_exists($object) || include(LOGIC . $object . ".class.php");
                    
 
          class_exists($object) || exit("wrong class1");
                    
 
//          $module = new $object($this->db,$this->lib, $this, $this->app);
          $module = new $object($this->db, $this, $this->app);
          method_exists($module, $action) || exit("wrong method");
                    
                    
// Valio: Turned validator off for 'php' calls, because there was a bad case with Advanced search page. I actually fixed it since then, but haven't tested if its ok in this case. But anyway - this api function will be deprecated at some point.
//                    $validator = \Helpers\Basic::validator();
//                    if ( $data && !is_array($data) ) { // In theory we should not get inside this if statement.
//                        $data = array();
//                       \Helpers\Debug::log('validateActionData expects array as first param');
//                    }
//                    if (!$data) {
//                        $data = array();
//                    }
//                    $validator->validateActionData($data, $action);
//                    $cleanData = $validator->getInput();
 
                    //Valio: keep the legacy filter for now
//                     strip html
                    $filter = new \Phalcon\Filter();
                    $stripHTML = function(&$val) use ($filter) {
                        $val = $filter->sanitize($val, 'striptags');
                    };
                    array_walk_recursive($data, $stripHTML);
                    
          return( $module->$action( $data , $internal) );
        }else{
          return( array(false,"api error1"));
        }
      break;
      //ajax
      case "api1":
 
        //\Helpers\Debug::log("action name",$this->app->router->getActionName(),$this->app->router->getParams());
 
        //$start = microtime(1);
        if($this->app->router->getActionName()){
          $action = $this->app->router->getActionName();
        }elseif($_POST['action']){
          $action=$_POST['action'];
        }else{
          $a=json_decode($_POST['data'],true);
          $action=$a['action'];
        }
        header('Content-Type: application/json');
        if($action AND \Config::api()->MAP->{$action}->status===true){
 
          if(\Config::api()->MAP->{$action}->sessionRequire==true and !isset($_SESSION['profile']['id'])){
            exit(json_encode(array(false,"nosess")));
          }
          $object=\Config::api()->MAP->{$action}->class;
          file_exists(LOGIC . $object . ".class.php") || exit("wrong file");
 
          class_exists($object) || include(LOGIC . $object . ".class.php");
 
          class_exists($object) || exit("wrong class2");
//          $module = new $object($this->db,$this->lib, $this, $this->app);
          $module = new $object($this->db, $this, $this->app);
          method_exists($module, $action) || exit("wrong method");
 
                    // Valio: I commented the old filters
          //check za input data-ta
//          if(isset($_POST["data"])){
//            foreach($_POST["data"] as $key=>$val){
//              switch(\Config::api()->MAP->{$action}->data->{$key}){
//                case "text":
//                //  $val=$this->db->quote($val);
//                  $_POST["data"][$key]=$val;
//                break;
//                case "int":
//                  $val=intval($val);
//                  $_POST["data"][$key]=$val;
//                break;
//
//              }
//            }
//          }
 
          //$this->requests_intensity_checker($_SERVER['REMOTE_ADDR'],$object,$action);
          if($this->app->router->getParams()){
            $cleanData = $this->app->router->getParams();
          }else{
                      $dirtyData = [];
                      if (is_array($_POST['data'])) {
                          $dirtyData = $_POST['data'];
                      } else if (is_numeric($_POST['data'])) {
                          $dirtyData['id'] = (int)$_POST['data'];
                      }
 
                      // $dirtyData = is_array($_POST['data']) ? $_POST['data'] : array();
                      $di = \Phalcon\DI\FactoryDefault::getDefault();
                      $validator = $di->getValidator();
                      $validator->validateActionData($dirtyData, $action);
                      $cleanData = $validator->getInput();
                  }
                    // LEGACY (we now use DroobleValidator):
                    // strip html
//                    $filter = new \Phalcon\Filter();
//                    $stripHTML = function(&$val) use ($filter) {
//                        $val = str_replace('<3', '&lt;3', $val); // temporary fix for <3 emoticon
//                        $val = $filter->sanitize($val, 'striptags');
//                    };
//                    array_walk_recursive($_POST['data'], $stripHTML);
 
                    $result=$module->$action( $cleanData );
          //$result['queryCount']=$this->db->queryCount;
          //$result['queries']=$this->db->queries;
          // \Helpers\Debug::log("metchod :".$action." time: ".(microtime(1)-$start));
          exit( json_encode( $result ) );
        }else{
          exit( json_encode(array(false,"api error 1")));
        }
      break;
      case "square":
        $this->init();
//        $this->initPageTexts();
 
        $page = $_POST["square"];
 
        if (!isset($c)) {
          $c = $this;
        }
 
        //$_SESSION["t"]["square"][$page] || exit("wrong page");
        $this->t["square"][$page] || exit("wrong page");
 
        ob_start();
 
        if(file_exists( SQUARE_PAGES_LOGIC . $page . ".php" )){
          include( SQUARE_PAGES_LOGIC . $page . ".php" );
        }
 
        if(file_exists( SQUARE_PAGES_CSS . $page . ".css" )){
          include( SQUARE_PAGES_CSS . $page . ".css" );
        }
 
        if(file_exists( SQUARE_PAGES_HTML . $page . ".html" )){
          include( SQUARE_PAGES_HTML . $page . ".html" );
        }
 
        if(file_exists(SQUARE_PAGES_JS . $page . ".js")){
          echo "<script type=\"text/javascript\">";
          include( SQUARE_PAGES_JS . $page . ".js" );
          echo "</script>";
        }
 
        $content = ob_get_clean();
 
        exit($content);
 
      break;
 
      default: exit("wrong type"); break;
 
    }
  }
}
 
$c = new client($db,$app);
?>
#14client->api(php, advancedSearchPage, Array(10))
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/0_php_scripting/search_result_page.php (35)
<?php
$type = $_GET['type']== "people" ? "people" : "pages";
 
$looking_bandmates = $_GET['looking_bandmates'] ? true : false;
 
$search = $_b;
$profile = (array) $_GET['profile'];
$instruments = (array) $_GET['instruments'];
$genres = (array) $_GET['genres'];
$influences = (array) $_GET['influences'];
$equipment = (array) $_GET['equipment'];
$place = (string) $_GET['place'];
$languages = (string) $_GET['languages'];
 
 
$search_params = [
    "search" => $search,
    "profile" => $profile,
    "instruments" => $instruments,
    "genres" => $genres,
    "influences" => $influences,
    "equipment" => $equipment,
    "place" => $place,
    "languages" => $languages,
];
 
 
$page_number = (int)$_GET['page'] ? (int)$_GET['page'] : 1;
$elements_per_page = 10;
$offset = ($page_number - 1) * $elements_per_page ? ($page_number - 1) * $elements_per_page : 0; 
 
$search_params['offset'] = $offset;
$search_params['limit'] = $elements_per_page+1;
 
$s_results = $c->api("php", "advancedSearchPage", $search_params);
 
if($s_results[0]){
    $results = $s_results[1];
}
 
 
$query = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY);
parse_str($query, $url_params);
 
//print_r($url_params);
// Returns a string if the URL has parameters or NULL if not
 
if ($query) {
    $url_params['page'] = $page_number - 1;
    $prev_url = http_build_query($url_params);
    $prev_url = '/search/?' . preg_replace('/%5B[0-9]+%5D/simU', '[]', $prev_url);
 
    $url_params['page'] = $page_number + 1;
    $next_url = http_build_query($url_params);
    $next_url = '/search/?' . preg_replace('/%5B[0-9]+%5D/simU', '[]', $next_url);
 
 
} else {
    $prev_url = $_SERVER['REQUEST_URI'] . '?page=' . ($page_number - 1);
    $next_url = $_SERVER['REQUEST_URI'] . '?page=' . ($page_number + 1);
}
 
//print_r([$query, $prev_url, $next_url, $url_params]);
 
if (count($results) > $elements_per_page) {
    $has_next_page = true;
    array_pop($results);
} else {
    $has_next_page = false;
}
 
 
#15include(/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/0_php_scripting/search_result_page.php)
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/layout.php (9)
<?php
 
// needed for frontend segment events
$forward_segment = \Drooble\FW\DroobleSegment::isForward();
setcookie("forward_segment", $forward_segment, time() + 2 * 3600, '/');
 
/** PHP SCRIPTIONG ON PAGE BEGIN */
if(file_exists( $_SCRIPTING_FILE = BACKEND_GENERATED_PHP_SCRIPTING_DIR ."/" .$app->getLegacyPageName() . ".php")){ 
  include( $_SCRIPTING_FILE );
}
/** PHP SCRIPTIONG ON PAGE END */
 
 
/** GENERATING HEADER HTML BEGIN */
ob_start();
if(file_exists( $FILE = BACKEND_GENERATED_HEADERS_DIR . "/" .$app->getLegacyPageName() . ".html"))
{
  include( $FILE );
}
else
{
  include( BACKEND_GENERATED_HEADERS_DIR . "/__default_header.html" );
}
$header_html = ob_get_clean();
/** GENERATING HEADER HTML END */
 
 
 
 
 
 
/** GENERATING BODY HTML BEGIN */
 
ob_start();
 
if(file_exists( $FILE = BACKEND_GENERATED_BODIES_DIR . "/" .$app->getLegacyPageName() . ".html"))
{
  include($FILE );
}
else
{
  include( BACKEND_GENERATED_BODIES_DIR . "/__default_body.html" );
}
 
$body_html = ob_get_clean();
 
/** GENERATING BODY HTML END */
 
 
 
 
/** GENERATING BODY HTML BEGIN */
 
ob_start();
 
if(file_exists( $FILE = BACKEND_GENERATED_FOOTERS_DIR . "/" .$app->getLegacyPageName() . ".html"))
{
  include( $FILE );
}
else
{
  include( BACKEND_GENERATED_FOOTERS_DIR . "/__default_footer.html" );
}
 
$footer_html = ob_get_clean();
 
/** GENERATING BODY HTML END */
 
 
 
 
 
 
ob_start();
 
echo $header_html;
echo $body_html;
echo $footer_html;
echo "<!-- ". $app->getLegacyPageName() ." -->";
 
$html = ob_get_clean();
 
echo $html;
 
//echo \Helpers\Basic::getInstance()->sanitize_output($html);
#16include(/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/layout.php)
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/public/index.php (158)
<?php
 
error_reporting(E_ERROR | E_PARSE);
 
if (preg_match('~MSIE|Internet Explorer~i', $_SERVER['HTTP_USER_AGENT'])
  or (strpos($_SERVER['HTTP_USER_AGENT'], 'Trident/7.0; rv:11.0') !== false)
  or (strpos($_SERVER['HTTP_USER_AGENT'], 'Trident/7.0; Touch; rv:11.0') !== false)) {
  //is IE 11 or below
  header('Location: /no-support-browser.html');
  exit;
}
 
try {
  define('CURRENT_MODULE',"web");
 
  chdir(dirname(__DIR__));
 
  $_SERVER["REMOTE_ADDR"]=isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER["REMOTE_ADDR"]; //cloudflare IP address parsing;
 
 
  if ($_GET['rb']) {
    setcookie("rb", $_GET['rb'], time() + 2 * 3600, '/');
    $_COOKIE["rb"] = $_GET['rb'];
  }
    if (isset($_GET['rb']) && !$_GET['rb']) {
        setcookie("rb", $_GET['rb'], time() - 2 * 3600, '/');
    }
 
  if(file_exists(__DIR__ . '/../../../config/defines.php')){ include(__DIR__ . '/../../../config/defines.php'); }
 
  date_default_timezone_set('Etc/UTC');
 
  mb_internal_encoding("UTF-8");
 
  if(file_exists("private/config/common.php")) $_CONFIG = include("private/config/common.php");
 
  if(file_exists("/etc/drooble/configs/".CURRENT_MODULE."/config.php")) $_CONFIG = array_replace_recursive($_CONFIG, include("/etc/drooble/configs/".CURRENT_MODULE."/config.php"));
 
  require __DIR__ . '/../../../config/wrapper.php';
 
  $config = include __DIR__ . '/../../../config/config.php';
 
  $loader = require __DIR__ . '/../../../resources/lib/autoload.php';
 
  include __DIR__ . "/../../../config/services.php";
 
 
  $di['router'] = function () {
        return include __DIR__ . "/../private/config/router.php";
    };
 
//  include __DIR__ . "/../../../config/segment.php";
  
 
    $segment_options = [];
    if ($_GET['test_segment']) {
        $test_segment = ($_GET['test_segment'] == "true" || $_GET['test_segment'] == 1) ? 1 : 0;
        // need to overwrite segment ip rules when testing
        setcookie("test_segment", $test_segment, time() + 2 * 3600, '/');
        $segment_options['forward_anyway'] =  $test_segment ? true : false;
    }
    if (isset($_COOKIE['test_segment'])) {
        $segment_options['forward_anyway'] = $_COOKIE['test_segment'] == 1 ? true : false;
    }
    \Drooble\FW\DroobleSegment::init(\Config::segment_analytics()->PROJECT_KEYS->WRITE_KEY, $segment_options);
 
    $app = new \Drooble\FW\DroobleApp($di); //10ms
 
    $app->view->setViewsDir( __DIR__ . "/../private/views/" );
 
    $handle = $app->handle();
 
  $content = $handle->getContent();
 
    \Drooble\FW\DroobleSegment::flush();
 
  exit( $content );
 
} catch (\Phalcon\Exception $e) {
  echo $e->getMessage();
  exit;
} catch (\PDOException $e){
  echo $e->getMessage();
  exit;
} catch (\Drooble\Exceptions\InvalidParameters $e){
  exit(json_encode(array(false,["error"=>"InvalidParameters","hint"=>$e->getMessage()])));
} catch (\Drooble\Exceptions\NotFound $e){
  exit(json_encode(array(false,["error"=>"NotFound","hint"=>$e->getMessage()])));
} catch (\Drooble\Exceptions\AccessDenied $e){
  exit(json_encode(array(false,["error"=>"AccessDenied","hint"=>$e->getMessage()])));
} catch (\Drooble\Exceptions\GeneralError $e){
  exit(json_encode(array(false,["error"=>"GeneralError","hint"=>$e->getMessage()])));
} catch (\Exception $e){
 
  if($e->getMessage()=='nosess'){
    exit(json_encode(array(false,"nosess")));
  }
 
  if($e->getMessage()=='wrong_method'){
    exit(json_encode([false,"api error"]));
  }
 
  if($e->getMessage()=='action_not_alowed' || $e->getCode()==103){
    exit(json_encode([false,"not alowd"]));
  }
 
  if($e->getMessage()!='action_not_found' and $e->getCode()!=101 and $e->getCode() != 100){
    echo $e->getMessage();
  }
/* 
  if( $e->getMessage()=='not_implemented' AND ( $e->getCode() == 100 OR $e->getCode() == 101 ) ){
    //GO TO CONTENT BUILDER
  }else{
    exit(json_encode([false,"general error"]));
  } */
 
}
// END Phalcon
 
foreach(array( "a", "b", "c", "d", "e" ) as $get)
    ${"_" . $get} = isset($_GET[$get]) ? $_GET[$get] : "";
 
if (\Helpers\Basic::getAppMode() == DEVELOPMENT_MODE)
{
  (new \Phalcon\Debug)->listen();
}
 
//include(LIB . "cache.class.php"); // deprecated
include(LIB . "mysql.class.php");
include(LIB . "client.class.php");
 
if ($_POST['ajax'] == 'api1' || ($app->getRoutingType() == "ajaxAPI")){
  $c->api('api1'); //GO TO API AND EXIT
}
 
 
 
//handle action
if($_a=='a'){
  include(LIB."action_handler.php");
}
 
if($_a=='fbHandler'){
  include(LIB."facebook_handler.php");
}
 
 
$router = $di->getRouter();
if ($router->getClientPlatformType() == 'MB') {
//    $loadSmartBanner = false; TODO set to true if you want it back
}
 
 
 
 
//PREPARE HTML
 
include(BACKEND_GENERATED_CONTENT . "/layout.php");
 
 
\Drooble\FW\DroobleSegment::flush();
KeyValue
asearch
b
c
d
e
instrumentsArray ( [0] => Keyboard )
KeyValue
USERwww-data
HOME/var/www
HTTP_CONNECTIONkeep-alive
HTTP_X_FORWARDED_PROTOhttps
HTTP_X_FORWARDED_PORT443
HTTP_X_FORWARDED_FOR54.224.117.125, 172.70.34.20
HTTP_USER_AGENTCCBot/2.0 (https://commoncrawl.org/faq/)
HTTP_IF_MODIFIED_SINCESat, 16 Oct 2021 02:17:13 GMT
HTTP_CF_VISITOR{"scheme":"https"}
HTTP_CF_RAY6d2cb7f54e665a0f-IAD
HTTP_CF_IPCOUNTRYUS
HTTP_CF_CONNECTING_IP54.224.117.125
HTTP_CDN_LOOPcloudflare
HTTP_ACCEPT_LANGUAGEen-US,en;q=0.5
HTTP_ACCEPT_ENCODINGgzip
HTTP_ACCEPTtext/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
HTTP_HOSTrc.drooble.com
REDIRECT_STATUS200
SERVER_NAMErc.drooble.com
SERVER_PORT443
SERVER_ADDR172.31.25.120
REMOTE_PORT51344
REMOTE_ADDR54.224.117.125
SERVER_SOFTWAREnginx/1.14.0
GATEWAY_INTERFACECGI/1.1
HTTPSon
REQUEST_SCHEMEhttps
SERVER_PROTOCOLHTTP/1.1
DOCUMENT_ROOT/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/public
DOCUMENT_URI/index.php
REQUEST_URI/search/?instruments%5B%5D=Keyboard
SCRIPT_NAME/index.php
CONTENT_LENGTH
CONTENT_TYPE
REQUEST_METHODGET
QUERY_STRINGa=search&b=&c=&d=&e=&instruments%5B%5D=Keyboard
SCRIPT_FILENAME/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/public/index.php
PATH_INFO
FCGI_ROLERESPONDER
PHP_SELF/index.php
REQUEST_TIME_FLOAT1643063555.5189
REQUEST_TIME1643063555
#Path
0/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/public/index.php
1/var/www/rc/drooble/trunk/projects/drooble_v1/config/defines.php
2/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/config/common.php
3/var/www/rc/drooble/trunk/projects/drooble_v1/config/wrapper.php
4/var/www/rc/drooble/trunk/projects/drooble_v1/config/config.php
5/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/autoload.php
6/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/composer/autoload_real.php
7/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/composer/ClassLoader.php
8/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/composer/autoload_static.php
9/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/ralouphie/getallheaders/src/getallheaders.php
10/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/promises/src/functions_include.php
11/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/promises/src/functions.php
12/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/psr7/src/functions_include.php
13/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/psr7/src/functions.php
14/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/guzzle/src/functions_include.php
15/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/guzzle/src/functions.php
16/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/functions_include.php
17/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/functions.php
18/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mtdowling/jmespath.php/src/JmesPath.php
19/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/aws/aws-sdk-php/src/functions.php
20/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/facebook/php-sdk-v4/src/Facebook/polyfills.php
21/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/Mixpanel.php
22/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/Base/MixpanelBase.php
23/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/Producers/MixpanelPeople.php
24/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/Producers/MixpanelBaseProducer.php
25/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/ConsumerStrategies/FileConsumer.php
26/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/ConsumerStrategies/AbstractConsumer.php
27/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/ConsumerStrategies/CurlConsumer.php
28/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/ConsumerStrategies/SocketConsumer.php
29/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mixpanel/mixpanel-php/lib/Producers/MixpanelEvents.php
30/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mongodb/mongodb/src/functions.php
31/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment.php
32/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Client.php
33/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Consumer.php
34/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/QueueConsumer.php
35/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Consumer/File.php
36/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Consumer/ForkCurl.php
37/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Consumer/LibCurl.php
38/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Consumer/Socket.php
39/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/segmentio/analytics-php/lib/Segment/Version.php
40/var/www/rc/drooble/trunk/projects/drooble_v1/config/services.php
41/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/plugins/APIPrefetcher.php
42/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleSegment.php
43/var/www/rc/drooble/trunk/projects/drooble_v1/resources/helpers/DroobleApc.php
44/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleApp.php
45/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/config/router.php
46/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleRouter.php
47/var/www/rc/drooble/trunk/projects/drooble_v1/resources/helpers/Basic.php
48/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleHelper.php
49/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleSessionManager.php
50/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleRedisSessionAdapter.php
51/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleRedisDriver.php
52/var/www/rc/drooble/trunk/projects/drooble_v1/resources/helpers/UserSessions.php
53/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/lib/mysql.class.php
54/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/lib/client.class.php
55/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/layout.php
56/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/0_php_scripting/search_result_page.php
57/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/logic/search.class.php
58/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/Search/DroobleSearchManager.php
59/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php
60/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/NullLogger.php
61/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/AbstractLogger.php
62/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/LoggerInterface.php
63/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/CurlHandler.php
64/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/CurlFactory.php
65/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php
66/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/Middleware.php
67/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php
68/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php
69/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php
70/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php
71/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php
72/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php
73/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php
74/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php
75/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php
76/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php
77/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php
78/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php
79/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Client.php
80/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php
81/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php
82/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php
83/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php
84/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php
85/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php
86/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php
87/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php
88/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/RemoteNamespace.php
89/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/Search/DroobleSearchClient.php
90/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php
91/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php
92/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Core.php
93/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php
94/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php
95/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/FutureInterface.php
96/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/PromiseInterface.php
97/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/PromisorInterface.php
98/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php
99/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/FutureArray.php
100/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php
101/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php
102/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/FulfilledPromise.php
103/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/ExtendedPromiseInterface.php
104/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/CancellablePromiseInterface.php
105/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/BadRequest400Exception.php
106/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/ElasticsearchException.php
107/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Common/Exceptions/Forbidden403Exception.php
108/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/LogLevel.php
109/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/RejectedPromise.php
Memory
Usage4194304