Phalcon Framework 3.4.1

MongoDB\Driver\Exception\ConnectionTimeoutException: No suitable servers found (`serverSelectionTryOnce` set): [connection timeout calling ismaster on 'mongo-prod:27017']

/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mongodb/mongodb/src/Collection.php (552)
#0MongoDB\Driver\Manager->selectServer(Object(MongoDB\Driver\ReadPreference))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mongodb/mongodb/src/Collection.php (552)
<?php
/*
 * Copyright 2015-2017 MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
namespace MongoDB;
 
use MongoDB\BSON\JavascriptInterface;
use MongoDB\BSON\Serializable;
use MongoDB\ChangeStream;
use MongoDB\Driver\Cursor;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\IndexInfoIterator;
use MongoDB\Operation\Aggregate;
use MongoDB\Operation\BulkWrite;
use MongoDB\Operation\CreateIndexes;
use MongoDB\Operation\Count;
use MongoDB\Operation\DeleteMany;
use MongoDB\Operation\DeleteOne;
use MongoDB\Operation\Distinct;
use MongoDB\Operation\DropCollection;
use MongoDB\Operation\DropIndexes;
use MongoDB\Operation\Find;
use MongoDB\Operation\FindOne;
use MongoDB\Operation\FindOneAndDelete;
use MongoDB\Operation\FindOneAndReplace;
use MongoDB\Operation\FindOneAndUpdate;
use MongoDB\Operation\InsertMany;
use MongoDB\Operation\InsertOne;
use MongoDB\Operation\ListIndexes;
use MongoDB\Operation\MapReduce;
use MongoDB\Operation\ReplaceOne;
use MongoDB\Operation\UpdateMany;
use MongoDB\Operation\UpdateOne;
use MongoDB\Operation\Watch;
use Traversable;
 
class Collection
{
    private static $defaultTypeMap = [
        'array' => 'MongoDB\Model\BSONArray',
        'document' => 'MongoDB\Model\BSONDocument',
        'root' => 'MongoDB\Model\BSONDocument',
    ];
    private static $wireVersionForFindAndModifyWriteConcern = 4;
    private static $wireVersionForReadConcern = 4;
    private static $wireVersionForWritableCommandWriteConcern = 5;
 
    private $collectionName;
    private $databaseName;
    private $manager;
    private $readConcern;
    private $readPreference;
    private $typeMap;
    private $writeConcern;
 
    /**
     * Constructs new Collection instance.
     *
     * This class provides methods for collection-specific operations, such as
     * CRUD (i.e. create, read, update, and delete) and index management.
     *
     * Supported options:
     *
     *  * readConcern (MongoDB\Driver\ReadConcern): The default read concern to
     *    use for collection operations. Defaults to the Manager's read concern.
     *
     *  * readPreference (MongoDB\Driver\ReadPreference): The default read
     *    preference to use for collection operations. Defaults to the Manager's
     *    read preference.
     *
     *  * typeMap (array): Default type map for cursors and BSON documents.
     *
     *  * writeConcern (MongoDB\Driver\WriteConcern): The default write concern
     *    to use for collection operations. Defaults to the Manager's write
     *    concern.
     *
     * @param Manager $manager        Manager instance from the driver
     * @param string  $databaseName   Database name
     * @param string  $collectionName Collection name
     * @param array   $options        Collection options
     * @throws InvalidArgumentException for parameter/option parsing errors
     */
    public function __construct(Manager $manager, $databaseName, $collectionName, array $options = [])
    {
        if (strlen($databaseName) < 1) {
            throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName);
        }
 
        if (strlen($collectionName) < 1) {
            throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName);
        }
 
        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], 'MongoDB\Driver\ReadConcern');
        }
 
        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
        }
 
        if (isset($options['typeMap']) && ! is_array($options['typeMap'])) {
            throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array');
        }
 
        if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
            throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
        }
 
        $this->manager = $manager;
        $this->databaseName = (string) $databaseName;
        $this->collectionName = (string) $collectionName;
        $this->readConcern = isset($options['readConcern']) ? $options['readConcern'] : $this->manager->getReadConcern();
        $this->readPreference = isset($options['readPreference']) ? $options['readPreference'] : $this->manager->getReadPreference();
        $this->typeMap = isset($options['typeMap']) ? $options['typeMap'] : self::$defaultTypeMap;
        $this->writeConcern = isset($options['writeConcern']) ? $options['writeConcern'] : $this->manager->getWriteConcern();
    }
 
    /**
     * Return internal properties for debugging purposes.
     *
     * @see http://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
     * @return array
     */
    public function __debugInfo()
    {
        return [
            'collectionName' => $this->collectionName,
            'databaseName' => $this->databaseName,
            'manager' => $this->manager,
            'readConcern' => $this->readConcern,
            'readPreference' => $this->readPreference,
            'typeMap' => $this->typeMap,
            'writeConcern' => $this->writeConcern,
        ];
    }
 
    /**
     * Return the collection namespace (e.g. "db.collection").
     *
     * @see https://docs.mongodb.org/manual/faq/developers/#faq-dev-namespace
     * @return string
     */
    public function __toString()
    {
        return $this->databaseName . '.' . $this->collectionName;
    }
 
    /**
     * Executes an aggregation framework pipeline on the collection.
     *
     * Note: this method's return value depends on the MongoDB server version
     * and the "useCursor" option. If "useCursor" is true, a Cursor will be
     * returned; otherwise, an ArrayIterator is returned, which wraps the
     * "result" array from the command response document.
     *
     * @see Aggregate::__construct() for supported options
     * @param array $pipeline List of pipeline operations
     * @param array $options  Command options
     * @return Traversable
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function aggregate(array $pipeline, array $options = [])
    {
        $hasOutStage = \MongoDB\is_last_pipeline_operator_out($pipeline);
 
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        if ($hasOutStage) {
            $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        /* A "majority" read concern is not compatible with the $out stage, so
         * avoid providing the Collection's read concern if it would conflict.
         */
        if ( ! isset($options['readConcern']) &&
             ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) &&
            \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        if ($hasOutStage && ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new Aggregate($this->databaseName, $this->collectionName, $pipeline, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Executes multiple write operations.
     *
     * @see BulkWrite::__construct() for supported options
     * @param array[] $operations List of write operations
     * @param array   $options    Command options
     * @return BulkWriteResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function bulkWrite(array $operations, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Gets the number of documents matching the filter.
     *
     * @see Count::__construct() for supported options
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
     * @return integer
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function count($filter = [], array $options = [])
    {
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        $operation = new Count($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Create a single index for the collection.
     *
     * @see Collection::createIndexes()
     * @see CreateIndexes::__construct() for supported command options
     * @param array|object $key     Document containing fields mapped to values,
     *                              which denote order or an index type
     * @param array        $options Index and command options
     * @return string The name of the created index
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function createIndex($key, array $options = [])
    {
        $commandOptionKeys = ['maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
        $indexOptions = array_diff_key($options, $commandOptionKeys);
        $commandOptions = array_intersect_key($options, $commandOptionKeys);
 
        return current($this->createIndexes([['key' => $key] + $indexOptions], $commandOptions));
    }
 
    /**
     * Create one or more indexes for the collection.
     *
     * Each element in the $indexes array must have a "key" document, which
     * contains fields mapped to an order or type. Other options may follow.
     * For example:
     *
     *     $indexes = [
     *         // Create a unique index on the "username" field
     *         [ 'key' => [ 'username' => 1 ], 'unique' => true ],
     *         // Create a 2dsphere index on the "loc" field with a custom name
     *         [ 'key' => [ 'loc' => '2dsphere' ], 'name' => 'geo' ],
     *     ];
     *
     * If the "name" option is unspecified, a name will be generated from the
     * "key" document.
     *
     * @see http://docs.mongodb.org/manual/reference/command/createIndexes/
     * @see http://docs.mongodb.org/manual/reference/method/db.collection.createIndex/
     * @see CreateIndexes::__construct() for supported command options
     * @param array[] $indexes List of index specifications
     * @param array   $options Command options
     * @return string[] The names of the created indexes
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function createIndexes(array $indexes, array $options = [])
    {
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Deletes all documents matching the filter.
     *
     * @see DeleteMany::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/delete/
     * @param array|object $filter  Query by which to delete documents
     * @param array        $options Command options
     * @return DeleteResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function deleteMany($filter, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Deletes at most one document matching the filter.
     *
     * @see DeleteOne::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/delete/
     * @param array|object $filter  Query by which to delete documents
     * @param array        $options Command options
     * @return DeleteResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function deleteOne($filter, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Finds the distinct values for a specified field across the collection.
     *
     * @see Distinct::__construct() for supported options
     * @param string $fieldName Field for which to return distinct values
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
     * @return mixed[]
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function distinct($fieldName, $filter = [], array $options = [])
    {
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Drop this collection.
     *
     * @see DropCollection::__construct() for supported options
     * @param array $options Additional options
     * @return array|object Command result document
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function drop(array $options = [])
    {
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new DropCollection($this->databaseName, $this->collectionName, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Drop a single index in the collection.
     *
     * @see DropIndexes::__construct() for supported options
     * @param string $indexName Index name
     * @param array  $options   Additional options
     * @return array|object Command result document
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function dropIndex($indexName, array $options = [])
    {
        $indexName = (string) $indexName;
 
        if ($indexName === '*') {
            throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Drop all indexes in the collection.
     *
     * @see DropIndexes::__construct() for supported options
     * @param array $options Additional options
     * @return array|object Command result document
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function dropIndexes(array $options = [])
    {
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Finds documents matching the query.
     *
     * @see Find::__construct() for supported options
     * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
     * @return Cursor
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function find($filter = [], array $options = [])
    {
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Finds a single document matching the query.
     *
     * @see FindOne::__construct() for supported options
     * @see http://docs.mongodb.org/manual/core/read-operations-introduction/
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
     * @return array|object|null
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function findOne($filter = [], array $options = [])
    {
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Finds a single document and deletes it, returning the original.
     *
     * The document to return may be null if no document matched the filter.
     *
     * @see FindOneAndDelete::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
     * @return array|object|null
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function findOneAndDelete($filter, array $options = [])
    {
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Finds a single document and replaces it, returning either the original or
     * the replaced document.
     *
     * The document to return may be null if no document matched the filter. By
     * default, the original document is returned. Specify
     * FindOneAndReplace::RETURN_DOCUMENT_AFTER for the "returnDocument" option
     * to return the updated document.
     *
     * @see FindOneAndReplace::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
     * @param array|object $filter      Query by which to filter documents
     * @param array|object $replacement Replacement document
     * @param array        $options     Command options
     * @return array|object|null
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function findOneAndReplace($filter, $replacement, array $options = [])
    {
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Finds a single document and updates it, returning either the original or
     * the updated document.
     *
     * The document to return may be null if no document matched the filter. By
     * default, the original document is returned. Specify
     * FindOneAndUpdate::RETURN_DOCUMENT_AFTER for the "returnDocument" option
     * to return the updated document.
     *
     * @see FindOneAndReplace::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/findAndModify/
     * @param array|object $filter  Query by which to filter documents
     * @param array|object $update  Update to apply to the matched document
     * @param array        $options Command options
     * @return array|object|null
     * @throws UnexpectedValueException if the command response was malformed
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function findOneAndUpdate($filter, $update, array $options = [])
    {
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Return the collection name.
     *
     * @return string
     */
    public function getCollectionName()
    {
        return $this->collectionName;
    }
 
    /**
     * Return the database name.
     *
     * @return string
     */
    public function getDatabaseName()
    {
        return $this->databaseName;
    }
 
    /**
     * Return the Manager.
     *
     * @return Manager
     */
    public function getManager()
    {
        return $this->manager;
    }
 
    /**
     * Return the collection namespace.
     *
     * @see https://docs.mongodb.org/manual/reference/glossary/#term-namespace
     * @return string
     */
    public function getNamespace()
    {
        return $this->databaseName . '.' . $this->collectionName;
    }
 
    /**
     * Return the read concern for this collection.
     *
     * @see http://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
     * @return ReadConcern
     */
    public function getReadConcern()
    {
        return $this->readConcern;
    }
 
    /**
     * Return the read preference for this collection.
     *
     * @return ReadPreference
     */
    public function getReadPreference()
    {
        return $this->readPreference;
    }
 
    /**
     * Return the type map for this collection.
     *
     * @return array
     */
    public function getTypeMap()
    {
        return $this->typeMap;
    }
 
    /**
     * Return the write concern for this collection.
     *
     * @see http://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
     * @return WriteConcern
     */
    public function getWriteConcern()
    {
        return $this->writeConcern;
    }
 
    /**
     * Inserts multiple documents.
     *
     * @see InsertMany::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/insert/
     * @param array[]|object[] $documents The documents to insert
     * @param array            $options   Command options
     * @return InsertManyResult
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function insertMany(array $documents, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Inserts one document.
     *
     * @see InsertOne::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/insert/
     * @param array|object $document The document to insert
     * @param array        $options  Command options
     * @return InsertOneResult
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function insertOne($document, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Returns information for all indexes for the collection.
     *
     * @see ListIndexes::__construct() for supported options
     * @return IndexInfoIterator
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function listIndexes(array $options = [])
    {
        $operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Executes a map-reduce aggregation on the collection.
     *
     * @see MapReduce::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/mapReduce/
     * @param JavascriptInterface $map            Map function
     * @param JavascriptInterface $reduce         Reduce function
     * @param string|array|object $out            Output specification
     * @param array               $options        Command options
     * @return MapReduceResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     * @throws UnexpectedValueException if the command response was malformed
     */
    public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = [])
    {
        $hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($out);
 
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        // Check if the out option is inline because we will want to coerce a primary read preference if not
        if ($hasOutputCollection) {
            $options['readPreference'] = new ReadPreference(ReadPreference::RP_PRIMARY);
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        /* A "majority" read concern is not compatible with inline output, so
         * avoid providing the Collection's read concern if it would conflict.
         */
        if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        if (! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new MapReduce($this->databaseName, $this->collectionName, $map, $reduce, $out, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Replaces at most one document matching the filter.
     *
     * @see ReplaceOne::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/update/
     * @param array|object $filter      Query by which to filter documents
     * @param array|object $replacement Replacement document
     * @param array        $options     Command options
     * @return UpdateResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function replaceOne($filter, $replacement, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Updates all documents matching the filter.
     *
     * @see UpdateMany::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/update/
     * @param array|object $filter  Query by which to filter documents
     * @param array|object $update  Update to apply to the matched documents
     * @param array        $options Command options
     * @return UpdateResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function updateMany($filter, $update, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Updates at most one document matching the filter.
     *
     * @see UpdateOne::__construct() for supported options
     * @see http://docs.mongodb.org/manual/reference/command/update/
     * @param array|object $filter  Query by which to filter documents
     * @param array|object $update  Update to apply to the matched document
     * @param array        $options Command options
     * @return UpdateResult
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function updateOne($filter, $update, array $options = [])
    {
        if ( ! isset($options['writeConcern'])) {
            $options['writeConcern'] = $this->writeConcern;
        }
 
        $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
        $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
 
        return $operation->execute($server);
    }
 
    /**
     * Create a change stream for watching changes to the collection.
     *
     * @see Watch::__construct() for supported options
     * @param array $pipeline List of pipeline operations
     * @param array $options  Command options
     * @return ChangeStream
     * @throws InvalidArgumentException for parameter/option parsing errors
     */
    public function watch(array $pipeline = [], array $options = [])
    {
        if ( ! isset($options['readPreference'])) {
            $options['readPreference'] = $this->readPreference;
        }
 
        $server = $this->manager->selectServer($options['readPreference']);
 
        /* Although change streams require a newer version of the server than
         * read concerns, perform the usual wire version check before inheriting
         * the collection's read concern. In the event that the server is too
         * old, this makes it more likely that users will encounter an error
         * related to change streams being unsupported instead of an
         * UnsupportedException regarding use of the "readConcern" option from
         * the Aggregate operation class. */
        if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        if ( ! isset($options['typeMap'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        $operation = new Watch($this->manager, $this->databaseName, $this->collectionName, $pipeline, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Get a clone of this collection with different options.
     *
     * @see Collection::__construct() for supported options
     * @param array $options Collection constructor options
     * @return Collection
     * @throws InvalidArgumentException for parameter/option parsing errors
     */
    public function withOptions(array $options = [])
    {
        $options += [
            'readConcern' => $this->readConcern,
            'readPreference' => $this->readPreference,
            'typeMap' => $this->typeMap,
            'writeConcern' => $this->writeConcern,
        ];
 
        return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
    }
}
#1MongoDB\Collection->findOne(Array([id] => 12268), Array([readPreference] => Object(MongoDB\Driver\ReadPreference)))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleCollection.php (379)
<?php 
 
namespace Drooble\FW;
 
use Phalcon\Mvc\Collection\Document;
use Phalcon\Mvc\CollectionInterface;
use Phalcon\Di\InjectionAwareInterface;
//use Phalcon\Mvc\Collection\ManagerInterface;
//use Phalcon\Mvc\Collection\BehaviorInterface;
//use Phalcon\Mvc\Collection\Exception;
use Phalcon\Mvc\Model\MessageInterface;
//use Phalcon\Mvc\Model\Message as Message;
 
abstract class DroobleCollection implements CollectionInterface//, InjectionAwareInterface //, EntityInterface \Serializable
{
 
  public $_id;
 
  protected $_dependencyInjector;
 
  protected $_modelsManager;
 
  protected $_source;
 
  protected $_operationMade = 0;
 
  protected $_connection;
 
  protected $_errorMessages = [];
 
  protected static $_reserved;
 
  protected static $_disableEvents;
 
  protected $_skipped = false;
 
  const OP_NONE = 0;
 
  const OP_CREATE = 1;
 
  const OP_UPDATE = 2;
 
  const OP_DELETE = 3;
  
  public function setDirtyState($dirtyState) {
  //TODO
  }
  public function getDirtyState(){
  //TODO
  }
 
  /**
   * Phalcon\Mvc\Collection constructor
   */
  public final function __construct($dependencyInjector = null, $modelsManager = null)
  {
     
    /**
     * We use a default DI if the user doesn't define one
     */
    
 
        if ($dependencyInjector !== null) {
            $this->_dependencyInjector = $dependencyInjector;
        } else {
            $this->_dependencyInjector = \Phalcon\DI\FactoryDefault::getDefault();
        }
    
 
    /**
     * Inject the manager service from the DI
     */
        
        $this->_modelsManager = $this->_dependencyInjector->getShared("collectionManager");
        $this->_modelsManager->initialize($this);
 
  }
  
  /**
  * set atributes to $this
  */
  public function setSchema($schema){
    $reserved = $this->getReservedAttributes();
 
    
    foreach ($schema as $key => $value) {
      if (!in_array($key, $reserved) and $key != "_id") {
        $this->{$key} =  $value;
      }
    }
  }
 
  /**
   * Sets a value for the _id property
   *
   * @param mixed id
   */
  public final function setId($id)
  {
      if (is_object($id)) {
            $class = get_class($id);
            if ( ($class == "MongoDB\BSON\ObjectID") || ($class == "MongoDB\BSON\ObjectId") ) {
                $this->_id = $id;
            }
        }
  }
 
  /**
   * Returns the value of the _id property
   *
   * @return
   */
  public final function getId()
  {
    return $this->_id; // cast to string if you want string value
  }
 
  /**
   * Sets the dependency injection container
   */
  public function setDI($dependencyInjector)
  {
    $this->_dependencyInjector = $dependencyInjector;
  }
 
  /**
   * Returns the dependency injection container
   */
  public function getDI()
  {
    return $this->_dependencyInjector;
  }
 
  /**
   * Sets a custom events manager
   */
  protected function setEventsManager($eventsManager)
  {
    $this->_modelsManager->setCustomEventsManager($this, $eventsManager);
  }
 
  /**
   * Returns the custom events manager
   */
  protected function getEventsManager() 
  {
    return $this->_modelsManager->getCustomEventsManager($this);
  }
 
  /**
   * Returns the models manager related to the entity instance
   */
  public function getCollectionManager() 
  {
    return $this->_modelsManager;
  }
 
  /**
   * Returns an array with reserved properties that cannot be part of the insert/update
   */
  public function getReservedAttributes() 
  {
    $reserved = self::$_reserved;
    if($reserved === null){
      $reserved = [
        "_connection",
        "_modelsManager",
        "_dependencyInjector",
        "_source",
        "_operationMade",
        "_errorMessages",
        "_skipped"
      ];
            self::$_reserved = $reserved;
    }
    return $reserved;
  }
 
  /**
   * Sets if a model must use implicit objects ids
   */
  protected function useImplicitObjectIds($useImplicitObjectIds)
  {
    $this->_modelsManager->useImplicitObjectIds($this, $useImplicitObjectIds);
  }
 
  /**
   * Sets collection name which model should be mapped
   */
  protected function setSource($source)
  {
    $this->_source = $source;
    return $this;
  }
 
  /**
   * Returns collection name mapped in the model
   */
  public function getSource()
  {
    if (!$this->_source) {
      $this->_source = \Phalcon\Text::uncamelize(substr(strrchr(get_class($this), '\\'), 1));
    }
    return $this->_source;
  }
 
  /**
   * Sets the DependencyInjection connection service name
   */
  public function setConnectionService($connectionService)
  {
    $this->_modelsManager->setConnectionService($this, $connectionService);
    return $this;
  }
 
  /**
   * Returns DependencyInjection connection service
   */
  public function getConnectionService()
  {
    return $this->_modelsManager->getConnectionService($this);
  }
 
  /**
   * Retrieves a database connection
   *
   * @return \MongoDb
   */
  public function getConnection()
  {
    if($this->_connection === null) {
      $this->_connection = $this->_modelsManager->getConnection($this);
    }
 
    return $this->_connection;
  }
 
  /**
   * Reads an attribute value by its name
   *
   *<code>
   *  echo $robot->readAttribute('name');
   *</code>
   *
   * @param string attribute
   * @return mixed
   */
//  public function readAttribute($attribute)
//  {
//    if !isset $this->{attribute} {
//      return null;
//    }
//
//    return $this->{attribute};
//  }
 
  /**
   * Writes an attribute value by its name
   *
   *<code>
   *  $robot->writeAttribute('name', 'Rosey');
   *</code>
   *
   * @param string attribute
   * @param mixed value
   */
  public function writeAttribute($attribute, $value)
  {
    $this->{$attribute} = $value;
  }
//
//  /**
//   * Returns a cloned collection
//   */
  public static function cloneResult(CollectionInterface $collection, array $document)
  {
        $clonedCollection = clone $collection;
        foreach($document as $key => $value) {
            if ($key == "_id") {
                $clonedCollection->setId($value); // have fun http://php.net/manual/en/mongodb-bson-objectid.tostring.php
            } else {
                $clonedCollection->writeAttribute($key, $value);
            }
        }
        
        if (method_exists($clonedCollection, "afterFetch")) {
            $clonedCollection->afterFetch();
        }
        return $clonedCollection;
 
  }
 
  /**
   * Returns a collection resultset
   *
   * @param array params
   * @param \Phalcon\Mvc\Collection collection
   * @param \MongoDb connection
   * @param boolean unique
   * @return array
   */
  protected static function _getResultset($params, CollectionInterface $collection, $connection, $first = false)
  {
        
        /**
     * Check if "class" clause was defined
     */
        if (array_key_exists("class", $params) && $params["class"]) {
            $className = $params["class"];
            $base = new $className();
        } else {
            $className = get_class($collection);
            $base = $collection;
        }
        
        if (!($base instanceof CollectionInterface || $base instanceof Document)) {
            throw new \Exception("Object of class '" . $className . "' must be an implementation of Phalcon\\Mvc\\CollectionInterface or an instance of Phalcon\\Mvc\\Collection\\Document");            
        }
        
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
 
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
 
        $conditions = [];
        if (array_key_exists("conditions", $params)) {
            $conditions = $params["conditions"];
        } else if (is_array($params[0])) {
            $conditions = $params[0];
        } else {
            foreach($params as $key => $value) {
                if (!in_array($key, ["limit", "sort", "skip", "fields"])) {
                    $conditions[$key] = $value; 
                }
            }
        }
 
    if (!is_array($conditions)){
      throw new \Exception("Find parameters must be an array");
    }
 
    /**
     * Perform the find
     */
 
        $options = [];
        if (array_key_exists("sort", $params)) {
            $options["sort"] = $params["sort"];
        }
        if (array_key_exists("limit", $params)) {
            $options["limit"] = $params["limit"];
        }
        if (array_key_exists("skip", $params)) {
            $options["skip"] = $params["skip"];
        }
        if (array_key_exists("fields", $params)) {
            foreach ($params["fields"] as $field) {
                $options["projection"][$field] = 1;
            }
        }
        
        if (!$first) {
            $documentsCursor = $mongoCollection->find($conditions, $options);
//        $documentsCursor->setTypeMap(['root' => 'array', 'document' => 'array', 'array' => 'array']); whatever for now
            $collections = [];
            foreach ($documentsCursor as $document) {
 
                $collections[] = static::cloneResult($base, (array) $document);
            }
            return $collections;
            
        } else {
            $res = $mongoCollection->findOne($conditions, $options);
            if ($res !== null) {
                return static::cloneResult($base, (array) $res);
            } else {
                return null;
            }
        }
  }
 
  /**
   * Perform a count over a resultset
   *
   * @param array params
   * @param \Phalcon\Mvc\Collection collection
   * @param \MongoDb connection
   * @return int
   */
//  protected static function _getGroupResultset(params, <Collection> collection, connection) -> int
//  {
//    $source, mongoCollection, conditions, limit, sort, documentsCursor;
//
//    let source = collection->getSource();
//    if empty source {
//      throw new \Exception("Method getSource() returns empty string");
//    }
//
//    let mongoCollection = connection->selectCollection(source);
//
//    /**
//     * Convert the string to an array
//     */
//    if !fetch conditions, params[0] {
//      if !fetch conditions, params["conditions"] {
//        let conditions = [];
//      }
//    }
//
//    if isset params["limit"] || isset params["sort"] || isset params["skip"] {
//
//      /**
//       * Perform the find
//       */
//      let documentsCursor = mongoCollection->find(conditions);
//
//      /**
//       * Check if a "limit" clause was defined
//       */
//      if fetch limit, params["limit"] {
//        documentsCursor->limit(limit);
//      }
//
//      /**
//       * Check if a "sort" clause was defined
//       */
//      if fetch sort, params["sort"] {
//        documentsCursor->sort(sort);
//      }
//
//      /**
//       * Check if a "skip" clause was defined
//       */
//      if fetch sort, params["skip"] {
//        documentsCursor->skip(sort);
//      }
//
//      /**
//       * Only "count" is supported
//       */
//      return count(documentsCursor);
//    }
//
//    return mongoCollection->count(conditions);
//  }
//
//  /**
//   * Executes internal hooks before save a document
//   *
//   * @param \Phalcon\DiInterface $dependencyInjector
//   * @param boolean disableEvents
//   * @param boolean exists
//   * @return boolean
//   */
  protected final function _preSave($dependencyInjector, $disableEvents, $exists) 
  {
    /**
     * Run Validation Callbacks Before
     */
    if (!$disableEvents) {
 
      if ($this->fireEventCancel("beforeValidation") === false) {
        return false;
      }
 
      if (!$exists) {
        $eventName = "beforeValidationOnCreate";
      } else {
        $eventName = "beforeValidationOnUpdate";
      }
 
      if ($this->fireEventCancel($eventName) === false) {
        return false;
      }
 
    }
 
    /**
     * Run validation
     */
    if ($this->fireEventCancel("validation") === false) {
      if (!$disableEvents) {
        $this->fireEvent("onValidationFails");
      }
      return false;
    }
 
    if (!$disableEvents) {
 
      /**
       * Run Validation Callbacks After
       */
      if (!$exists) {
        $eventName = "afterValidationOnCreate";
      } else {
        $eventName = "afterValidationOnUpdate";
      }
 
      if ($this->fireEventCancel($eventName) === false) {
        return false;
      }
 
      if ($this->fireEventCancel("afterValidation") === false) {
        return false;
      }
 
      /**
       * Run Before Callbacks
       */
      if ($this->fireEventCancel("beforeSave") === false) {
        return false;
      }
 
      if ($exists) {
        $eventName = "beforeUpdate";
      } else {
        $eventName = "beforeCreate";
      }
 
      if ($this->fireEventCancel($eventName) === false) {
        return false;
      }
 
    }
 
    return true;
  }
 
  /**
   * Executes internal events after save a document
   */
  protected final function _postSave($disableEvents, $success, $exists) 
  {
    if ($success) {
 
      $this->setId($success);
      if (!$disableEvents) {
        if ($exists) {
          $eventName = "afterUpdate";
        } else {
          $eventName = "afterCreate";
        }
 
        $this->fireEvent($eventName);
 
        $this->fireEvent("afterSave");
      }
      
      return $success;
    }
 
    if (!$disableEvents) {
      $this->fireEvent("notSave");
    }
 
    $this->_cancelOperation($disableEvents);
    return false;
  }
 
  /**
   * Executes validators on every validation call
   *
   *<code>
   *use Phalcon\Mvc\Model\Validator\ExclusionIn as ExclusionIn;
   *
   *class Subscriptors extends \Phalcon\Mvc\Collection
   *{
   *
   *  public function validation()
   *  {
   *    $this->validate(new ExclusionIn(array(
   *      'field' => 'status',
   *      'domain' => array('A', 'I')
   *    )));
   *    if ($this->validationHasFailed() == true) {
   *      return false;
   *    }
   *  }
   *
   *}
   *</code>
   */
//  protected function validate(<Model\ValidatorInterface> validator) -> void
//  {
//    $message;
//
//    if validator->validate($this) === false {
//      for message in validator->getMessages() {
//        $this->_errorMessages[] = message;
//      }
//    }
//  }
 
  /**
   * Check whether validation process has generated any messages
   *
   *<code>
   *use Phalcon\Mvc\Model\Validator\ExclusionIn as ExclusionIn;
   *
   *class Subscriptors extends \Phalcon\Mvc\Collection
   *{
   *
   *  public function validation()
   *  {
   *    $this->validate(new ExclusionIn(array(
   *      'field' => 'status',
   *      'domain' => array('A', 'I')
   *    )));
   *    if ($this->validationHasFailed() == true) {
   *      return false;
   *    }
   *  }
   *
   *}
   *</code>
   */
  public function validationHasFailed() 
  {
    return (count($this->_errorMessages) > 0);
  }
//
//  /**
//   * Fires an internal event
//   */
  public function fireEvent($eventName) 
  {
    /**
     * Check if there is a method with the same name of the event
     */
        if (method_exists($this, $eventName)) {
            $this->{$eventName}();
        }
 
    /**
     * Send a notification to the events manager
     */
    return $this->_modelsManager->notifyEvent($eventName, $this);
  }
//
//  /**
//   * Fires an internal event that cancels the operation
//   */
  public function fireEventCancel($eventName) 
  {
    /**
     * Check if there is a method with the same name of the event
     */
        if (method_exists($this, $eventName)) {
            if ($this->{$eventName}() === false) {
                return false;
            }
        }
 
    /**
     * Send a notification to the events manager
     */
    if ($this->_modelsManager->notifyEvent($eventName, $this) === false) {
      return false;
    }
 
    return true;
  }
 
  /**
   * Cancel the current operation
   */
  protected function _cancelOperation($disableEvents) 
  {
 
        if (!$disableEvents) {
      if ($this->_operationMade == self::OP_DELETE) {
        $eventName = "notDeleted";
      } else {
        $eventName = "notSaved";
      }
      $this->fireEvent($eventName);
    }
    return false;
  }
 
  /**
   * Checks if the document exists in the collection
   *
   * @param \MongoCollection collection
   * @return boolean
   */
  protected function _exists() 
  {
        if (!$this->_id) {
            return false;
        } else {
            return true;
        }
  }
 
  /**
   * Returns all the validation messages
   *
   * <code>
   * $robot = new Robots();
   * $robot->type = 'mechanical';
   * $robot->name = 'Astro Boy';
   * $robot->year = 1952;
   * if ($robot->save() == false) {
   *  echo "Umh, We can't store robots right now ";
   *  foreach ($robot->getMessages() as message) {
   *    echo message;
   *  }
   *} else {
   *  echo "Great, a new robot was saved successfully!";
   *}
   * </code>
   */
  public function getMessages()
  {
    return $this->_errorMessages;
  }
 
  /**
   * Appends a customized message on the validation process
   *
   *<code>
   *  use Phalcon\Mvc\Model\Message as Message;
   *
   *  class Robots extends \Phalcon\Mvc\Model
   *  {
   *
   *    public function beforeSave()
   *    {
   *      if ($$this->name == 'Peter') {
   *        message = new Message("Sorry, but a robot cannot be named Peter");
   *        $$this->appendMessage(message);
   *      }
   *    }
   *  }
   *</code>
   */
  public function appendMessage(MessageInterface $message)
  {
    $this->_errorMessages[] = $message;
  }
//
//  /**
//   * Shared Code for CU Operations
//   * Prepares Collection
//   */
//
  protected function prepareCU()
  {
        $connection = $this->getConnection();
        $collection = $connection->selectCollection($this->getSource());
        
    return $collection;
  }
 
  /**
   * Creates/Updates a collection based on the values in the attributes
   */
  public function save() 
  {
    $collection = $this->prepareCU();
        
    /**
     * Check the dirty state of the current operation to update the current operation
     */
    $exists = $this->_exists();
        if ($exists) { // phalcon's ODM (collections) doesn't work like phalcon's ORM (models)
            return $this->update();
        }
 
    if (!$exists) {
      $this->_operationMade = self::OP_CREATE;
    } else {
      $this->_operationMade = self::OP_UPDATE;
    }
 
    /**
     * The messages added to the validator are reset here
     */
    $this->_errorMessages = [];
 
    /**
     * Execute the preSave hook
     */
    if ($this->_preSave($this->_dependencyInjector, self::$_disableEvents, $exists) === false) {
      return false;
    }
 
    $data = $this->toArray();
 
    /**
     * We always use safe stores to get the success state
     * Save the document
     */
    $insertOneResult = $collection->insertOne($data);
//    if typeof status == "array" {
//      if fetch ok, status["ok"] {
//        if ok {
//          let success = true;
//          if ($exists) === false {
//            if fetch id, data["_id"] {
//              $this->_id = id;
//            }
//          }
//        }
//      }
//    }
 
    /**
     * Call the postSave hooks
     */
    return $this->_postSave(self::$_disableEvents, $insertOneResult->getInsertedId(), $exists);
  }
 
  /**
   * Creates a collection based on the values in the attributes
   */
  public function create() 
  {
 
    $collection = $this->prepareCU();
 
    /**
     * Check the dirty state of the current operation to update the current operation
     */
        $exists = false;
    $this->_operationMade = self::OP_CREATE;
 
    /**
     * The messages added to the validator are reset here
     */
    $this->_errorMessages = [];
 
    /**
     * Execute the preSave hook
     */
    if ($this->_preSave($this->_dependencyInjector, self::$_disableEvents, $exists) === false) {
      return false;
    }
 
    $data = $this->toArray();
 
    /**
     * Save the document
     */
    $insertOneResult = $collection->insertOne($data);
 
    /**
     * Call the postSave hooks
     */
    return $this->_postSave(self::$_disableEvents, $insertOneResult->getInsertedId(), $exists);
  }
//
//  /**
//   * Creates a document based on the values in the attributes, if not found by criteria
//   * Preferred way to avoid duplication is to create index on attribute
//   *
//   * $robot = new Robot();
//   * $robot->name = "MyRobot";
//   * $robot->type = "Droid";
//   *
//   * //create only if robot with same name and type does not exist
//   * $robot->createIfNotExist( array( "name", "type" ) );
//   */
//  public function createIfNotExist(array! criteria) 
//  {
//    $exists, data, keys, query,
//      success, status, doc, collection;
//
//    if empty criteria {
//      throw new \Exception("Criteria parameter must be array with one or more attributes of the model");
//    }
//
//    /**
//     * Choose a collection according to the collection name
//     */
//    let collection = $this->prepareCU();
//
//    /**
//     * Assume non-existance to fire beforeCreate events - no update does occur anyway
//     */
//    let exists = false;
//
//    /**
//     * Reset current operation
//     */
//
//    $this->_operationMade = self::OP_NONE;
//
//    /**
//     * The messages added to the validator are reset here
//     */
//    $this->_errorMessages = [];
//
//    /**
//     * Execute the preSave hook
//     */
//    if $this->_preSave($this->_dependencyInjector, self::$_disableEvents, exists) === false {
//      return false;
//    }
//
//    let keys = array_flip( criteria );
//    let data = $this->toArray();
//
//    if array_diff_key( keys, data ) {
//      throw new \Exception("Criteria parameter must be array with one or more attributes of the model");
//    }
//
//    let query = array_intersect_key( data, keys );
//
//    let success = false;
//
//    /**
//     * $setOnInsert in conjunction with upsert ensures creating a new document
//     * "new": false returns null if new document created, otherwise new or old document could be returned
//     */
//    let status = collection->findAndModify(query,
//      ["$setOnInsert": data],
//      null,
//      ["new": false, "upsert": true]);
//    if status == null {
//      let doc = collection->findOne(query);
//      if typeof doc == "array" {
//        let success = true;
//        $this->_operationMade = self::OP_CREATE;
//        $this->_id = doc["_id"];
//      }
//    } else {
//      $this->appendMessage( new Message("Document already exists") );
//    }
//
//    /**
//     * Call the postSave hooks
//     */
//    return $this->_postSave(self::$_disableEvents, success, exists);
//  }
 
  /**
   * Creates/Updates a collection based on the values in the attributes
   */
  public function update() 
  {
    $collection = $this->prepareCU();
 
    /**
     * Check the dirty state of the current operation to update the current operation
     */
    $exists = $this->_exists();
 
    if (!$exists){
      throw new \Exception("The document cannot be updated because it doesn't exist");
    }
 
    $this->_operationMade = self::OP_UPDATE;
 
    /**
     * The messages added to the validator are reset here
     */
    $this->_errorMessages = [];
 
    /**
     * Execute the preSave hook
     */
    if ($this->_preSave($this->_dependencyInjector, self::$_disableEvents, $exists) === false ) {
      return false;
    }
 
    $data = $this->toArray();
        unset($data['_id']);
 
    /**
     * Save the document
     */
        
    $updateRes = $collection->updateOne(["_id" => $this->_id], ['$set' => $data]);
    /**
     * Call the postSave hooks
     */
    return $this->_postSave(self::$_disableEvents, $updateRes->getModifiedCount(), $exists);
  }
    
     /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-updateOne/
     * 
     * @filter array|stdClass
     * @update array|stdClass
     */
    public function updateOne($filter, $update, $options = []) {
        $collection = $this->prepareCU();
        $updateRes = $collection->updateOne($filter, $update, $options);
        return $updateRes->getModifiedCount();
    }
    
     /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-findOneAndUpdate/
     * 
     * @filter array|stdClass
     * @update array|stdClass
     */
    public function findOneAndUpdate($filter, $update, $options = []) {
        $collection = $this->prepareCU();
        $return = $collection->findOneAndUpdate($filter, $update, $options);
        return $return;
    }
    
    /*
     * !IMPORTANT this does not support Phalcon events (afterCreate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-insertMany/
     * 
     * @filter array|stdClass
     * @update array|stdClass
     */
    public function insertMany(array $documents, $options = []) {
        $collection = $this->prepareCU();
        try {
            $insertRes = $collection->insertMany($documents, $options);
        } catch(\Exception $e) {
            \Helpers\Debug::error($e->getTraceAsString());
            return false;
        }
        $ret = [];
        foreach ($insertRes->getInsertedIds() as $id) {
            $ret[] = (string) $id;
        }
        return $ret;
    }
    
    /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-updateMany/
     * 
     * @documents array
     * @options array
     */
    public function updateMany($filter, $update, $options = []) {
        $collection = $this->prepareCU();
        $updateRes = $collection->updateMany($filter, $update, $options);
        return $updateRes->getModifiedCount();
    }
 
     /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-deleteMany/
     * 
     * @documents array
     * @options array
     */
    public function deleteMany($filter, $options = []) {
        $collection = $this->prepareCU();
        $delRes = $collection->deleteMany($filter, $options);
        return $delRes->getDeletedCount();
    }
 
  /**
   * Find a document by its id (_id)
   *
   * <code>
   * // Find user by using \MongoId object
   * $user = Users::findById(new \MongoId('545eb081631d16153a293a66'));
   *
   * // Find user by using id as sting
   * $user = Users::findById('45cbc4a0e4123f6920000002');
   *
   * // Validate input
   * if ($user = Users::findById($_POST['id'])) {
   *     // ...
   * }
   * </code>
   */
    
  public static function findById($id)
  {
        $objId = new \MongoDB\BSON\ObjectId($id);
    return static::findFirst(["_id" => $objId]);
  }
 
  /**
   * Allows to query the first record that match the specified conditions
   *
   * <code>
   * // What's the first robot in the robots table?
   * $robot = Robots::findFirst();
   * echo 'The robot name is ', $robot->name, "\n";
   *
   * // What's the first mechanical robot in robots table?
   * $robot = Robots::findFirst([
   *     ['type' => 'mechanical']
   * ]);
   * echo 'The first mechanical robot name is ', $robot->name, "\n";
   *
   * // Get first virtual robot ordered by name
   * $robot = Robots::findFirst([
   *     ['type' => 'mechanical'],
   *     'order' => ['name' => 1]
   * ]);
   * echo 'The first virtual robot name is ', $robot->name, "\n";
   *
   * // Get first robot by id (_id)
   * $robot = Robots::findFirst([
   *     ['_id' => new \MongoId('45cbc4a0e4123f6920000002')]
   * ]);
   * echo 'The robot id is ', $robot->_id, "\n";
   * </code>
   */
  public static function findFirst(array $parameters = NULL) 
  {
        $className = get_called_class();
    $collection = new $className();
    $connection = $collection->getConnection();
    return static::_getResultset($parameters, $collection, $connection, true);
  }
 
  /**
   * Allows to query a set of records that match the specified conditions
   *
   * <code>
   *
   * //How many robots are there?
   * $robots = Robots::find();
   * echo "There are ", count($robots), "\n";
   *
   * //How many mechanical robots are there?
   * $robots = Robots::find(array(
   *     array("type" => "mechanical")
   * ));
   * echo "There are ", count(robots), "\n";
   *
   * //Get and print virtual robots ordered by name
   * $robots = Robots::findFirst(array(
   *     array("type" => "virtual"),
   *     "order" => array("name" => 1)
   * ));
   * foreach ($robots as $robot) {
   *     echo $robot->name, "\n";
   * }
   *
   * //Get first 100 virtual robots ordered by name
   * $robots = Robots::find(array(
   *     array("type" => "virtual"),
   *     "order" => array("name" => 1),
   *     "limit" => 100
   * ));
   * foreach ($robots as $robot) {
   *     echo $robot->name, "\n";
   * }
   * </code>
   */
  public static function find(array $parameters = NULL) 
  {
 
    $className = get_called_class();
    $collection = new $className();
        
    return static::_getResultset($parameters, $collection, $collection->getConnection());
  }
 
  /**
   * Perform a count over a collection
   *
   *<code>
   * echo 'There are ', Robots::count(), ' robots';
   *</code>
   */
  public static function count(array $parameters = NULL)
  {
        $className = get_called_class();
    $collection = new $className();
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
        $connection = $collection->getConnection();
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
 
    $parameters = (array)$parameters;
 
        if (array_key_exists("conditions", $parameters)) {
            $filter = $parameters['conditions'];
        } else {
            $filter = $parameters;
        }
 
    return $mongoCollection->count($filter);
  }
 
  /**
   * Perform an aggregation using the Mongo aggregation framework
   */
  public static function aggregate(array $parameters = []) 
  {
        $className = get_called_class();
    $collection = new $className();
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
        $connection = $collection->getConnection();
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
        if (array_key_exists("conditions", $parameters)) {
            $filter = $parameters['conditions'];
        } else {
            $filter = $parameters;
        }
    return $mongoCollection->aggregate($filter);
  }
 
  /**
   * Allows to perform a summatory group for a column in the collection
   */
//  public static function summatory($field, conditions = null, finalize = null) 
//  {
//    $className, model, connection, source, collection, initial,
//      reduce, group, retval, firstRetval;
//
//    let className = get_called_class();
//
//    let model = new {className}();
//
//    let connection = model->getConnection();
//
//    let source = model->getSource();
//    if empty source {
//      throw new \Exception("Method getSource() returns empty string");
//    }
//
//    let collection = connection->selectCollection(source);
//
//    /**
//     * Uses a javascript hash to group the results
//     */
//    let initial = ["summatory": []];
//
//    /**
//     * Uses a javascript hash to group the results, however this is slow with larger datasets
//     */
//    let reduce = "function (curr, result) { if (typeof result.summatory[curr." . field . "] === \"undefined\") { result.summatory[curr." . field . "] = 1; } else { result.summatory[curr." . field . "]++; } }";
//
//    let group = collection->group([], initial, reduce);
//
//    if fetch retval, group["retval"] {
//      if fetch firstRetval, retval[0] {
//        if isset firstRetval["summatory"] {
//          return firstRetval["summatory"];
//        }
//        return firstRetval;
//      }
//      return retval;
//    }
//
//    return [];
//  }
 
  /**
   * Deletes a model instance. Returning true on success or false otherwise.
   *
   * <code>
   *  $robot = Robots::findFirst();
   *  $robot->delete();
   *
   *  foreach (Robots::find() as $robot) {
   *    $robot->delete();
   *  }
   * </code>
   */
  public function delete() 
  {
        $exists = $this->_exists();
        if (!$exists) {
      throw new \Exception("The document cannot be deleted because it doesn't exist");
        }
        
    $disableEvents = self::$_disableEvents;
    if (!$disableEvents) {
      if ($this->fireEventCancel("beforeDelete") === false) {
        return false;
      }
    }
    if ($this->_skipped === true) {
      return true;
    }
 
        $className = get_called_class();
    $collection = new $className();
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
        $connection = $collection->getConnection();
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
        $res = $mongoCollection->deleteOne(["_id" => $this->_id]);
        return $res->getDeletedCount();
  }
 
//  /**
//   * Sets up a behavior in a collection
//   */
//  protected function addBehavior(<BehaviorInterface> behavior) -> void
//  {
//    ($this->_modelsManager)->addBehavior($this, behavior);
//  }
//
//  /**
//   * Skips the current operation forcing a success state
//   */
//  public function skipOperation(boolean skip)
//  {
//    $this->_skipped = skip;
//  }
 
  /**
   * Returns the instance as an array representation
   *
   *<code>
   * print_r($robot->toArray());
   *</code>
   */
  public function toArray() 
  {
 
    $reserved = $this->getReservedAttributes();
 
    /**
     * Get an array with the values of the object
     * We only assign values to the public properties
     */
    $data = [];
        $objVars = get_object_vars($this);
    foreach ($objVars as $key => $value) {
      if ($key == "_id") {
        if ($value) {
          $data[$key] = $value;
        }
      } else {
        if (!in_array($key, $reserved)) {
          $data[$key] = $value;
        }
      }
    }
    return $data;
  }
    
  /**
   * Serializes the object ignoring connections or protected properties
   */
//  public function serialize()
//  {
//    /**
//     * Use the standard serialize function to serialize the array data
//     */
//    return serialize($this->toArray());
//  }
 
  /**
   * Unserializes the object from a serialized string
   */
//  public function unserialize($data)
//  {
//    $attributes, $dependencyInjector, manager, key, value;
//
//    let attributes = unserialize(data);
//    if typeof attributes == "array" {
//
//      /**
//       * Obtain the default DI
//       */
//      let $dependencyInjector = Di::getDefault();
//      if typeof $dependencyInjector != "object" {
//        throw new \Exception("A dependency injector container is required to obtain the services related to the ODM");
//      }
//
//      /**
//       * Update the dependency injector
//       */
//      $this->_dependencyInjector = $dependencyInjector;
//
//      /**
//       * Gets the default $modelsManager service
//       */
//      let manager = $dependencyInjector->getShared("collectionManager");
//      if typeof manager != "object" {
//        throw new \Exception("The injected service 'collectionManager' is not valid");
//      }
//
//      /**
//       * Update the models manager
//       */
//      $this->_modelsManager = manager;
//
//      /**
//       * Update the objects attributes
//       */
//      for key, value in attributes {
//        $this->{key} = value;
//      }
//    }
//  }
}
#2Drooble\FW\DroobleCollection::_getResultset(Array([conditions] => Array([id] => 12268)), Object(Collections\Users), Object(MongoDB\Database), true)
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleCollection.php (1118)
<?php 
 
namespace Drooble\FW;
 
use Phalcon\Mvc\Collection\Document;
use Phalcon\Mvc\CollectionInterface;
use Phalcon\Di\InjectionAwareInterface;
//use Phalcon\Mvc\Collection\ManagerInterface;
//use Phalcon\Mvc\Collection\BehaviorInterface;
//use Phalcon\Mvc\Collection\Exception;
use Phalcon\Mvc\Model\MessageInterface;
//use Phalcon\Mvc\Model\Message as Message;
 
abstract class DroobleCollection implements CollectionInterface//, InjectionAwareInterface //, EntityInterface \Serializable
{
 
  public $_id;
 
  protected $_dependencyInjector;
 
  protected $_modelsManager;
 
  protected $_source;
 
  protected $_operationMade = 0;
 
  protected $_connection;
 
  protected $_errorMessages = [];
 
  protected static $_reserved;
 
  protected static $_disableEvents;
 
  protected $_skipped = false;
 
  const OP_NONE = 0;
 
  const OP_CREATE = 1;
 
  const OP_UPDATE = 2;
 
  const OP_DELETE = 3;
  
  public function setDirtyState($dirtyState) {
  //TODO
  }
  public function getDirtyState(){
  //TODO
  }
 
  /**
   * Phalcon\Mvc\Collection constructor
   */
  public final function __construct($dependencyInjector = null, $modelsManager = null)
  {
     
    /**
     * We use a default DI if the user doesn't define one
     */
    
 
        if ($dependencyInjector !== null) {
            $this->_dependencyInjector = $dependencyInjector;
        } else {
            $this->_dependencyInjector = \Phalcon\DI\FactoryDefault::getDefault();
        }
    
 
    /**
     * Inject the manager service from the DI
     */
        
        $this->_modelsManager = $this->_dependencyInjector->getShared("collectionManager");
        $this->_modelsManager->initialize($this);
 
  }
  
  /**
  * set atributes to $this
  */
  public function setSchema($schema){
    $reserved = $this->getReservedAttributes();
 
    
    foreach ($schema as $key => $value) {
      if (!in_array($key, $reserved) and $key != "_id") {
        $this->{$key} =  $value;
      }
    }
  }
 
  /**
   * Sets a value for the _id property
   *
   * @param mixed id
   */
  public final function setId($id)
  {
      if (is_object($id)) {
            $class = get_class($id);
            if ( ($class == "MongoDB\BSON\ObjectID") || ($class == "MongoDB\BSON\ObjectId") ) {
                $this->_id = $id;
            }
        }
  }
 
  /**
   * Returns the value of the _id property
   *
   * @return
   */
  public final function getId()
  {
    return $this->_id; // cast to string if you want string value
  }
 
  /**
   * Sets the dependency injection container
   */
  public function setDI($dependencyInjector)
  {
    $this->_dependencyInjector = $dependencyInjector;
  }
 
  /**
   * Returns the dependency injection container
   */
  public function getDI()
  {
    return $this->_dependencyInjector;
  }
 
  /**
   * Sets a custom events manager
   */
  protected function setEventsManager($eventsManager)
  {
    $this->_modelsManager->setCustomEventsManager($this, $eventsManager);
  }
 
  /**
   * Returns the custom events manager
   */
  protected function getEventsManager() 
  {
    return $this->_modelsManager->getCustomEventsManager($this);
  }
 
  /**
   * Returns the models manager related to the entity instance
   */
  public function getCollectionManager() 
  {
    return $this->_modelsManager;
  }
 
  /**
   * Returns an array with reserved properties that cannot be part of the insert/update
   */
  public function getReservedAttributes() 
  {
    $reserved = self::$_reserved;
    if($reserved === null){
      $reserved = [
        "_connection",
        "_modelsManager",
        "_dependencyInjector",
        "_source",
        "_operationMade",
        "_errorMessages",
        "_skipped"
      ];
            self::$_reserved = $reserved;
    }
    return $reserved;
  }
 
  /**
   * Sets if a model must use implicit objects ids
   */
  protected function useImplicitObjectIds($useImplicitObjectIds)
  {
    $this->_modelsManager->useImplicitObjectIds($this, $useImplicitObjectIds);
  }
 
  /**
   * Sets collection name which model should be mapped
   */
  protected function setSource($source)
  {
    $this->_source = $source;
    return $this;
  }
 
  /**
   * Returns collection name mapped in the model
   */
  public function getSource()
  {
    if (!$this->_source) {
      $this->_source = \Phalcon\Text::uncamelize(substr(strrchr(get_class($this), '\\'), 1));
    }
    return $this->_source;
  }
 
  /**
   * Sets the DependencyInjection connection service name
   */
  public function setConnectionService($connectionService)
  {
    $this->_modelsManager->setConnectionService($this, $connectionService);
    return $this;
  }
 
  /**
   * Returns DependencyInjection connection service
   */
  public function getConnectionService()
  {
    return $this->_modelsManager->getConnectionService($this);
  }
 
  /**
   * Retrieves a database connection
   *
   * @return \MongoDb
   */
  public function getConnection()
  {
    if($this->_connection === null) {
      $this->_connection = $this->_modelsManager->getConnection($this);
    }
 
    return $this->_connection;
  }
 
  /**
   * Reads an attribute value by its name
   *
   *<code>
   *  echo $robot->readAttribute('name');
   *</code>
   *
   * @param string attribute
   * @return mixed
   */
//  public function readAttribute($attribute)
//  {
//    if !isset $this->{attribute} {
//      return null;
//    }
//
//    return $this->{attribute};
//  }
 
  /**
   * Writes an attribute value by its name
   *
   *<code>
   *  $robot->writeAttribute('name', 'Rosey');
   *</code>
   *
   * @param string attribute
   * @param mixed value
   */
  public function writeAttribute($attribute, $value)
  {
    $this->{$attribute} = $value;
  }
//
//  /**
//   * Returns a cloned collection
//   */
  public static function cloneResult(CollectionInterface $collection, array $document)
  {
        $clonedCollection = clone $collection;
        foreach($document as $key => $value) {
            if ($key == "_id") {
                $clonedCollection->setId($value); // have fun http://php.net/manual/en/mongodb-bson-objectid.tostring.php
            } else {
                $clonedCollection->writeAttribute($key, $value);
            }
        }
        
        if (method_exists($clonedCollection, "afterFetch")) {
            $clonedCollection->afterFetch();
        }
        return $clonedCollection;
 
  }
 
  /**
   * Returns a collection resultset
   *
   * @param array params
   * @param \Phalcon\Mvc\Collection collection
   * @param \MongoDb connection
   * @param boolean unique
   * @return array
   */
  protected static function _getResultset($params, CollectionInterface $collection, $connection, $first = false)
  {
        
        /**
     * Check if "class" clause was defined
     */
        if (array_key_exists("class", $params) && $params["class"]) {
            $className = $params["class"];
            $base = new $className();
        } else {
            $className = get_class($collection);
            $base = $collection;
        }
        
        if (!($base instanceof CollectionInterface || $base instanceof Document)) {
            throw new \Exception("Object of class '" . $className . "' must be an implementation of Phalcon\\Mvc\\CollectionInterface or an instance of Phalcon\\Mvc\\Collection\\Document");            
        }
        
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
 
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
 
        $conditions = [];
        if (array_key_exists("conditions", $params)) {
            $conditions = $params["conditions"];
        } else if (is_array($params[0])) {
            $conditions = $params[0];
        } else {
            foreach($params as $key => $value) {
                if (!in_array($key, ["limit", "sort", "skip", "fields"])) {
                    $conditions[$key] = $value; 
                }
            }
        }
 
    if (!is_array($conditions)){
      throw new \Exception("Find parameters must be an array");
    }
 
    /**
     * Perform the find
     */
 
        $options = [];
        if (array_key_exists("sort", $params)) {
            $options["sort"] = $params["sort"];
        }
        if (array_key_exists("limit", $params)) {
            $options["limit"] = $params["limit"];
        }
        if (array_key_exists("skip", $params)) {
            $options["skip"] = $params["skip"];
        }
        if (array_key_exists("fields", $params)) {
            foreach ($params["fields"] as $field) {
                $options["projection"][$field] = 1;
            }
        }
        
        if (!$first) {
            $documentsCursor = $mongoCollection->find($conditions, $options);
//        $documentsCursor->setTypeMap(['root' => 'array', 'document' => 'array', 'array' => 'array']); whatever for now
            $collections = [];
            foreach ($documentsCursor as $document) {
 
                $collections[] = static::cloneResult($base, (array) $document);
            }
            return $collections;
            
        } else {
            $res = $mongoCollection->findOne($conditions, $options);
            if ($res !== null) {
                return static::cloneResult($base, (array) $res);
            } else {
                return null;
            }
        }
  }
 
  /**
   * Perform a count over a resultset
   *
   * @param array params
   * @param \Phalcon\Mvc\Collection collection
   * @param \MongoDb connection
   * @return int
   */
//  protected static function _getGroupResultset(params, <Collection> collection, connection) -> int
//  {
//    $source, mongoCollection, conditions, limit, sort, documentsCursor;
//
//    let source = collection->getSource();
//    if empty source {
//      throw new \Exception("Method getSource() returns empty string");
//    }
//
//    let mongoCollection = connection->selectCollection(source);
//
//    /**
//     * Convert the string to an array
//     */
//    if !fetch conditions, params[0] {
//      if !fetch conditions, params["conditions"] {
//        let conditions = [];
//      }
//    }
//
//    if isset params["limit"] || isset params["sort"] || isset params["skip"] {
//
//      /**
//       * Perform the find
//       */
//      let documentsCursor = mongoCollection->find(conditions);
//
//      /**
//       * Check if a "limit" clause was defined
//       */
//      if fetch limit, params["limit"] {
//        documentsCursor->limit(limit);
//      }
//
//      /**
//       * Check if a "sort" clause was defined
//       */
//      if fetch sort, params["sort"] {
//        documentsCursor->sort(sort);
//      }
//
//      /**
//       * Check if a "skip" clause was defined
//       */
//      if fetch sort, params["skip"] {
//        documentsCursor->skip(sort);
//      }
//
//      /**
//       * Only "count" is supported
//       */
//      return count(documentsCursor);
//    }
//
//    return mongoCollection->count(conditions);
//  }
//
//  /**
//   * Executes internal hooks before save a document
//   *
//   * @param \Phalcon\DiInterface $dependencyInjector
//   * @param boolean disableEvents
//   * @param boolean exists
//   * @return boolean
//   */
  protected final function _preSave($dependencyInjector, $disableEvents, $exists) 
  {
    /**
     * Run Validation Callbacks Before
     */
    if (!$disableEvents) {
 
      if ($this->fireEventCancel("beforeValidation") === false) {
        return false;
      }
 
      if (!$exists) {
        $eventName = "beforeValidationOnCreate";
      } else {
        $eventName = "beforeValidationOnUpdate";
      }
 
      if ($this->fireEventCancel($eventName) === false) {
        return false;
      }
 
    }
 
    /**
     * Run validation
     */
    if ($this->fireEventCancel("validation") === false) {
      if (!$disableEvents) {
        $this->fireEvent("onValidationFails");
      }
      return false;
    }
 
    if (!$disableEvents) {
 
      /**
       * Run Validation Callbacks After
       */
      if (!$exists) {
        $eventName = "afterValidationOnCreate";
      } else {
        $eventName = "afterValidationOnUpdate";
      }
 
      if ($this->fireEventCancel($eventName) === false) {
        return false;
      }
 
      if ($this->fireEventCancel("afterValidation") === false) {
        return false;
      }
 
      /**
       * Run Before Callbacks
       */
      if ($this->fireEventCancel("beforeSave") === false) {
        return false;
      }
 
      if ($exists) {
        $eventName = "beforeUpdate";
      } else {
        $eventName = "beforeCreate";
      }
 
      if ($this->fireEventCancel($eventName) === false) {
        return false;
      }
 
    }
 
    return true;
  }
 
  /**
   * Executes internal events after save a document
   */
  protected final function _postSave($disableEvents, $success, $exists) 
  {
    if ($success) {
 
      $this->setId($success);
      if (!$disableEvents) {
        if ($exists) {
          $eventName = "afterUpdate";
        } else {
          $eventName = "afterCreate";
        }
 
        $this->fireEvent($eventName);
 
        $this->fireEvent("afterSave");
      }
      
      return $success;
    }
 
    if (!$disableEvents) {
      $this->fireEvent("notSave");
    }
 
    $this->_cancelOperation($disableEvents);
    return false;
  }
 
  /**
   * Executes validators on every validation call
   *
   *<code>
   *use Phalcon\Mvc\Model\Validator\ExclusionIn as ExclusionIn;
   *
   *class Subscriptors extends \Phalcon\Mvc\Collection
   *{
   *
   *  public function validation()
   *  {
   *    $this->validate(new ExclusionIn(array(
   *      'field' => 'status',
   *      'domain' => array('A', 'I')
   *    )));
   *    if ($this->validationHasFailed() == true) {
   *      return false;
   *    }
   *  }
   *
   *}
   *</code>
   */
//  protected function validate(<Model\ValidatorInterface> validator) -> void
//  {
//    $message;
//
//    if validator->validate($this) === false {
//      for message in validator->getMessages() {
//        $this->_errorMessages[] = message;
//      }
//    }
//  }
 
  /**
   * Check whether validation process has generated any messages
   *
   *<code>
   *use Phalcon\Mvc\Model\Validator\ExclusionIn as ExclusionIn;
   *
   *class Subscriptors extends \Phalcon\Mvc\Collection
   *{
   *
   *  public function validation()
   *  {
   *    $this->validate(new ExclusionIn(array(
   *      'field' => 'status',
   *      'domain' => array('A', 'I')
   *    )));
   *    if ($this->validationHasFailed() == true) {
   *      return false;
   *    }
   *  }
   *
   *}
   *</code>
   */
  public function validationHasFailed() 
  {
    return (count($this->_errorMessages) > 0);
  }
//
//  /**
//   * Fires an internal event
//   */
  public function fireEvent($eventName) 
  {
    /**
     * Check if there is a method with the same name of the event
     */
        if (method_exists($this, $eventName)) {
            $this->{$eventName}();
        }
 
    /**
     * Send a notification to the events manager
     */
    return $this->_modelsManager->notifyEvent($eventName, $this);
  }
//
//  /**
//   * Fires an internal event that cancels the operation
//   */
  public function fireEventCancel($eventName) 
  {
    /**
     * Check if there is a method with the same name of the event
     */
        if (method_exists($this, $eventName)) {
            if ($this->{$eventName}() === false) {
                return false;
            }
        }
 
    /**
     * Send a notification to the events manager
     */
    if ($this->_modelsManager->notifyEvent($eventName, $this) === false) {
      return false;
    }
 
    return true;
  }
 
  /**
   * Cancel the current operation
   */
  protected function _cancelOperation($disableEvents) 
  {
 
        if (!$disableEvents) {
      if ($this->_operationMade == self::OP_DELETE) {
        $eventName = "notDeleted";
      } else {
        $eventName = "notSaved";
      }
      $this->fireEvent($eventName);
    }
    return false;
  }
 
  /**
   * Checks if the document exists in the collection
   *
   * @param \MongoCollection collection
   * @return boolean
   */
  protected function _exists() 
  {
        if (!$this->_id) {
            return false;
        } else {
            return true;
        }
  }
 
  /**
   * Returns all the validation messages
   *
   * <code>
   * $robot = new Robots();
   * $robot->type = 'mechanical';
   * $robot->name = 'Astro Boy';
   * $robot->year = 1952;
   * if ($robot->save() == false) {
   *  echo "Umh, We can't store robots right now ";
   *  foreach ($robot->getMessages() as message) {
   *    echo message;
   *  }
   *} else {
   *  echo "Great, a new robot was saved successfully!";
   *}
   * </code>
   */
  public function getMessages()
  {
    return $this->_errorMessages;
  }
 
  /**
   * Appends a customized message on the validation process
   *
   *<code>
   *  use Phalcon\Mvc\Model\Message as Message;
   *
   *  class Robots extends \Phalcon\Mvc\Model
   *  {
   *
   *    public function beforeSave()
   *    {
   *      if ($$this->name == 'Peter') {
   *        message = new Message("Sorry, but a robot cannot be named Peter");
   *        $$this->appendMessage(message);
   *      }
   *    }
   *  }
   *</code>
   */
  public function appendMessage(MessageInterface $message)
  {
    $this->_errorMessages[] = $message;
  }
//
//  /**
//   * Shared Code for CU Operations
//   * Prepares Collection
//   */
//
  protected function prepareCU()
  {
        $connection = $this->getConnection();
        $collection = $connection->selectCollection($this->getSource());
        
    return $collection;
  }
 
  /**
   * Creates/Updates a collection based on the values in the attributes
   */
  public function save() 
  {
    $collection = $this->prepareCU();
        
    /**
     * Check the dirty state of the current operation to update the current operation
     */
    $exists = $this->_exists();
        if ($exists) { // phalcon's ODM (collections) doesn't work like phalcon's ORM (models)
            return $this->update();
        }
 
    if (!$exists) {
      $this->_operationMade = self::OP_CREATE;
    } else {
      $this->_operationMade = self::OP_UPDATE;
    }
 
    /**
     * The messages added to the validator are reset here
     */
    $this->_errorMessages = [];
 
    /**
     * Execute the preSave hook
     */
    if ($this->_preSave($this->_dependencyInjector, self::$_disableEvents, $exists) === false) {
      return false;
    }
 
    $data = $this->toArray();
 
    /**
     * We always use safe stores to get the success state
     * Save the document
     */
    $insertOneResult = $collection->insertOne($data);
//    if typeof status == "array" {
//      if fetch ok, status["ok"] {
//        if ok {
//          let success = true;
//          if ($exists) === false {
//            if fetch id, data["_id"] {
//              $this->_id = id;
//            }
//          }
//        }
//      }
//    }
 
    /**
     * Call the postSave hooks
     */
    return $this->_postSave(self::$_disableEvents, $insertOneResult->getInsertedId(), $exists);
  }
 
  /**
   * Creates a collection based on the values in the attributes
   */
  public function create() 
  {
 
    $collection = $this->prepareCU();
 
    /**
     * Check the dirty state of the current operation to update the current operation
     */
        $exists = false;
    $this->_operationMade = self::OP_CREATE;
 
    /**
     * The messages added to the validator are reset here
     */
    $this->_errorMessages = [];
 
    /**
     * Execute the preSave hook
     */
    if ($this->_preSave($this->_dependencyInjector, self::$_disableEvents, $exists) === false) {
      return false;
    }
 
    $data = $this->toArray();
 
    /**
     * Save the document
     */
    $insertOneResult = $collection->insertOne($data);
 
    /**
     * Call the postSave hooks
     */
    return $this->_postSave(self::$_disableEvents, $insertOneResult->getInsertedId(), $exists);
  }
//
//  /**
//   * Creates a document based on the values in the attributes, if not found by criteria
//   * Preferred way to avoid duplication is to create index on attribute
//   *
//   * $robot = new Robot();
//   * $robot->name = "MyRobot";
//   * $robot->type = "Droid";
//   *
//   * //create only if robot with same name and type does not exist
//   * $robot->createIfNotExist( array( "name", "type" ) );
//   */
//  public function createIfNotExist(array! criteria) 
//  {
//    $exists, data, keys, query,
//      success, status, doc, collection;
//
//    if empty criteria {
//      throw new \Exception("Criteria parameter must be array with one or more attributes of the model");
//    }
//
//    /**
//     * Choose a collection according to the collection name
//     */
//    let collection = $this->prepareCU();
//
//    /**
//     * Assume non-existance to fire beforeCreate events - no update does occur anyway
//     */
//    let exists = false;
//
//    /**
//     * Reset current operation
//     */
//
//    $this->_operationMade = self::OP_NONE;
//
//    /**
//     * The messages added to the validator are reset here
//     */
//    $this->_errorMessages = [];
//
//    /**
//     * Execute the preSave hook
//     */
//    if $this->_preSave($this->_dependencyInjector, self::$_disableEvents, exists) === false {
//      return false;
//    }
//
//    let keys = array_flip( criteria );
//    let data = $this->toArray();
//
//    if array_diff_key( keys, data ) {
//      throw new \Exception("Criteria parameter must be array with one or more attributes of the model");
//    }
//
//    let query = array_intersect_key( data, keys );
//
//    let success = false;
//
//    /**
//     * $setOnInsert in conjunction with upsert ensures creating a new document
//     * "new": false returns null if new document created, otherwise new or old document could be returned
//     */
//    let status = collection->findAndModify(query,
//      ["$setOnInsert": data],
//      null,
//      ["new": false, "upsert": true]);
//    if status == null {
//      let doc = collection->findOne(query);
//      if typeof doc == "array" {
//        let success = true;
//        $this->_operationMade = self::OP_CREATE;
//        $this->_id = doc["_id"];
//      }
//    } else {
//      $this->appendMessage( new Message("Document already exists") );
//    }
//
//    /**
//     * Call the postSave hooks
//     */
//    return $this->_postSave(self::$_disableEvents, success, exists);
//  }
 
  /**
   * Creates/Updates a collection based on the values in the attributes
   */
  public function update() 
  {
    $collection = $this->prepareCU();
 
    /**
     * Check the dirty state of the current operation to update the current operation
     */
    $exists = $this->_exists();
 
    if (!$exists){
      throw new \Exception("The document cannot be updated because it doesn't exist");
    }
 
    $this->_operationMade = self::OP_UPDATE;
 
    /**
     * The messages added to the validator are reset here
     */
    $this->_errorMessages = [];
 
    /**
     * Execute the preSave hook
     */
    if ($this->_preSave($this->_dependencyInjector, self::$_disableEvents, $exists) === false ) {
      return false;
    }
 
    $data = $this->toArray();
        unset($data['_id']);
 
    /**
     * Save the document
     */
        
    $updateRes = $collection->updateOne(["_id" => $this->_id], ['$set' => $data]);
    /**
     * Call the postSave hooks
     */
    return $this->_postSave(self::$_disableEvents, $updateRes->getModifiedCount(), $exists);
  }
    
     /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-updateOne/
     * 
     * @filter array|stdClass
     * @update array|stdClass
     */
    public function updateOne($filter, $update, $options = []) {
        $collection = $this->prepareCU();
        $updateRes = $collection->updateOne($filter, $update, $options);
        return $updateRes->getModifiedCount();
    }
    
     /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-findOneAndUpdate/
     * 
     * @filter array|stdClass
     * @update array|stdClass
     */
    public function findOneAndUpdate($filter, $update, $options = []) {
        $collection = $this->prepareCU();
        $return = $collection->findOneAndUpdate($filter, $update, $options);
        return $return;
    }
    
    /*
     * !IMPORTANT this does not support Phalcon events (afterCreate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-insertMany/
     * 
     * @filter array|stdClass
     * @update array|stdClass
     */
    public function insertMany(array $documents, $options = []) {
        $collection = $this->prepareCU();
        try {
            $insertRes = $collection->insertMany($documents, $options);
        } catch(\Exception $e) {
            \Helpers\Debug::error($e->getTraceAsString());
            return false;
        }
        $ret = [];
        foreach ($insertRes->getInsertedIds() as $id) {
            $ret[] = (string) $id;
        }
        return $ret;
    }
    
    /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-updateMany/
     * 
     * @documents array
     * @options array
     */
    public function updateMany($filter, $update, $options = []) {
        $collection = $this->prepareCU();
        $updateRes = $collection->updateMany($filter, $update, $options);
        return $updateRes->getModifiedCount();
    }
 
     /*
     * !IMPORTANT this does not support Phalcon events (afterUpdate etc)
     * its just a wrapper for https://docs.mongodb.com/php-library/master/reference/method/MongoDBCollection-deleteMany/
     * 
     * @documents array
     * @options array
     */
    public function deleteMany($filter, $options = []) {
        $collection = $this->prepareCU();
        $delRes = $collection->deleteMany($filter, $options);
        return $delRes->getDeletedCount();
    }
 
  /**
   * Find a document by its id (_id)
   *
   * <code>
   * // Find user by using \MongoId object
   * $user = Users::findById(new \MongoId('545eb081631d16153a293a66'));
   *
   * // Find user by using id as sting
   * $user = Users::findById('45cbc4a0e4123f6920000002');
   *
   * // Validate input
   * if ($user = Users::findById($_POST['id'])) {
   *     // ...
   * }
   * </code>
   */
    
  public static function findById($id)
  {
        $objId = new \MongoDB\BSON\ObjectId($id);
    return static::findFirst(["_id" => $objId]);
  }
 
  /**
   * Allows to query the first record that match the specified conditions
   *
   * <code>
   * // What's the first robot in the robots table?
   * $robot = Robots::findFirst();
   * echo 'The robot name is ', $robot->name, "\n";
   *
   * // What's the first mechanical robot in robots table?
   * $robot = Robots::findFirst([
   *     ['type' => 'mechanical']
   * ]);
   * echo 'The first mechanical robot name is ', $robot->name, "\n";
   *
   * // Get first virtual robot ordered by name
   * $robot = Robots::findFirst([
   *     ['type' => 'mechanical'],
   *     'order' => ['name' => 1]
   * ]);
   * echo 'The first virtual robot name is ', $robot->name, "\n";
   *
   * // Get first robot by id (_id)
   * $robot = Robots::findFirst([
   *     ['_id' => new \MongoId('45cbc4a0e4123f6920000002')]
   * ]);
   * echo 'The robot id is ', $robot->_id, "\n";
   * </code>
   */
  public static function findFirst(array $parameters = NULL) 
  {
        $className = get_called_class();
    $collection = new $className();
    $connection = $collection->getConnection();
    return static::_getResultset($parameters, $collection, $connection, true);
  }
 
  /**
   * Allows to query a set of records that match the specified conditions
   *
   * <code>
   *
   * //How many robots are there?
   * $robots = Robots::find();
   * echo "There are ", count($robots), "\n";
   *
   * //How many mechanical robots are there?
   * $robots = Robots::find(array(
   *     array("type" => "mechanical")
   * ));
   * echo "There are ", count(robots), "\n";
   *
   * //Get and print virtual robots ordered by name
   * $robots = Robots::findFirst(array(
   *     array("type" => "virtual"),
   *     "order" => array("name" => 1)
   * ));
   * foreach ($robots as $robot) {
   *     echo $robot->name, "\n";
   * }
   *
   * //Get first 100 virtual robots ordered by name
   * $robots = Robots::find(array(
   *     array("type" => "virtual"),
   *     "order" => array("name" => 1),
   *     "limit" => 100
   * ));
   * foreach ($robots as $robot) {
   *     echo $robot->name, "\n";
   * }
   * </code>
   */
  public static function find(array $parameters = NULL) 
  {
 
    $className = get_called_class();
    $collection = new $className();
        
    return static::_getResultset($parameters, $collection, $collection->getConnection());
  }
 
  /**
   * Perform a count over a collection
   *
   *<code>
   * echo 'There are ', Robots::count(), ' robots';
   *</code>
   */
  public static function count(array $parameters = NULL)
  {
        $className = get_called_class();
    $collection = new $className();
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
        $connection = $collection->getConnection();
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
 
    $parameters = (array)$parameters;
 
        if (array_key_exists("conditions", $parameters)) {
            $filter = $parameters['conditions'];
        } else {
            $filter = $parameters;
        }
 
    return $mongoCollection->count($filter);
  }
 
  /**
   * Perform an aggregation using the Mongo aggregation framework
   */
  public static function aggregate(array $parameters = []) 
  {
        $className = get_called_class();
    $collection = new $className();
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
        $connection = $collection->getConnection();
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
        if (array_key_exists("conditions", $parameters)) {
            $filter = $parameters['conditions'];
        } else {
            $filter = $parameters;
        }
    return $mongoCollection->aggregate($filter);
  }
 
  /**
   * Allows to perform a summatory group for a column in the collection
   */
//  public static function summatory($field, conditions = null, finalize = null) 
//  {
//    $className, model, connection, source, collection, initial,
//      reduce, group, retval, firstRetval;
//
//    let className = get_called_class();
//
//    let model = new {className}();
//
//    let connection = model->getConnection();
//
//    let source = model->getSource();
//    if empty source {
//      throw new \Exception("Method getSource() returns empty string");
//    }
//
//    let collection = connection->selectCollection(source);
//
//    /**
//     * Uses a javascript hash to group the results
//     */
//    let initial = ["summatory": []];
//
//    /**
//     * Uses a javascript hash to group the results, however this is slow with larger datasets
//     */
//    let reduce = "function (curr, result) { if (typeof result.summatory[curr." . field . "] === \"undefined\") { result.summatory[curr." . field . "] = 1; } else { result.summatory[curr." . field . "]++; } }";
//
//    let group = collection->group([], initial, reduce);
//
//    if fetch retval, group["retval"] {
//      if fetch firstRetval, retval[0] {
//        if isset firstRetval["summatory"] {
//          return firstRetval["summatory"];
//        }
//        return firstRetval;
//      }
//      return retval;
//    }
//
//    return [];
//  }
 
  /**
   * Deletes a model instance. Returning true on success or false otherwise.
   *
   * <code>
   *  $robot = Robots::findFirst();
   *  $robot->delete();
   *
   *  foreach (Robots::find() as $robot) {
   *    $robot->delete();
   *  }
   * </code>
   */
  public function delete() 
  {
        $exists = $this->_exists();
        if (!$exists) {
      throw new \Exception("The document cannot be deleted because it doesn't exist");
        }
        
    $disableEvents = self::$_disableEvents;
    if (!$disableEvents) {
      if ($this->fireEventCancel("beforeDelete") === false) {
        return false;
      }
    }
    if ($this->_skipped === true) {
      return true;
    }
 
        $className = get_called_class();
    $collection = new $className();
        $source = $collection->getSource();
        if (!$source) {
            throw new \Exception("Method getSource() returns empty string");
        }
        $connection = $collection->getConnection();
    $mongoCollection = $connection->selectCollection($source);
 
    if(!$mongoCollection){
      throw new \Exception("Couldn't select mongo collection");
    }
        $res = $mongoCollection->deleteOne(["_id" => $this->_id]);
        return $res->getDeletedCount();
  }
 
//  /**
//   * Sets up a behavior in a collection
//   */
//  protected function addBehavior(<BehaviorInterface> behavior) -> void
//  {
//    ($this->_modelsManager)->addBehavior($this, behavior);
//  }
//
//  /**
//   * Skips the current operation forcing a success state
//   */
//  public function skipOperation(boolean skip)
//  {
//    $this->_skipped = skip;
//  }
 
  /**
   * Returns the instance as an array representation
   *
   *<code>
   * print_r($robot->toArray());
   *</code>
   */
  public function toArray() 
  {
 
    $reserved = $this->getReservedAttributes();
 
    /**
     * Get an array with the values of the object
     * We only assign values to the public properties
     */
    $data = [];
        $objVars = get_object_vars($this);
    foreach ($objVars as $key => $value) {
      if ($key == "_id") {
        if ($value) {
          $data[$key] = $value;
        }
      } else {
        if (!in_array($key, $reserved)) {
          $data[$key] = $value;
        }
      }
    }
    return $data;
  }
    
  /**
   * Serializes the object ignoring connections or protected properties
   */
//  public function serialize()
//  {
//    /**
//     * Use the standard serialize function to serialize the array data
//     */
//    return serialize($this->toArray());
//  }
 
  /**
   * Unserializes the object from a serialized string
   */
//  public function unserialize($data)
//  {
//    $attributes, $dependencyInjector, manager, key, value;
//
//    let attributes = unserialize(data);
//    if typeof attributes == "array" {
//
//      /**
//       * Obtain the default DI
//       */
//      let $dependencyInjector = Di::getDefault();
//      if typeof $dependencyInjector != "object" {
//        throw new \Exception("A dependency injector container is required to obtain the services related to the ODM");
//      }
//
//      /**
//       * Update the dependency injector
//       */
//      $this->_dependencyInjector = $dependencyInjector;
//
//      /**
//       * Gets the default $modelsManager service
//       */
//      let manager = $dependencyInjector->getShared("collectionManager");
//      if typeof manager != "object" {
//        throw new \Exception("The injected service 'collectionManager' is not valid");
//      }
//
//      /**
//       * Update the models manager
//       */
//      $this->_modelsManager = manager;
//
//      /**
//       * Update the objects attributes
//       */
//      for key, value in attributes {
//        $this->{key} = value;
//      }
//    }
//  }
}
#3Drooble\FW\DroobleCollection::findFirst(Array([conditions] => Array([id] => 12268)))
/var/www/rc/drooble/trunk/projects/drooble_v1/resources/helpers/Users.php (2651)
<?php
 
namespace Helpers;
 
use \Models\Users as User;
use \Models\UserInvitations as Invite;
use \Lib\Cryptography as Cryptography;
use \Models\UsersKeys as Keys;
use \Config as Config;
use \Lib\ActionURLGenerator as ActionURLGenerator;
use \Lib\MailGenerator as MailGenerator;
use \Lib\CacheSimple as CacheSimple;
use \Lib\PushNotificationManager as PushNotificationManager;
use \Models\UserElements as Element;
use \Models\UserNotificationSubscription as Subscription;
use \Helpers\Debug;
use \Phalcon\Mvc\Model\Query\Builder as QueryBuilder;
 
class Users extends \Drooble\FW\DroobleHelper
{
 
    public function isUserOnline($uid){
        if(!$uid) $uid = self::session()->get('profile')['id'];
        if(!$uid) return false;
        if(\Helpers\UsersSessionStorage::getInstance()->findIdsByUserId($uid)){
            return true;
        }else{
            return false;
        }
    }
 
    public function isUserOnlineInBrowser($uid){
        if(!$uid) $uid = self::session()->get('profile')['id'];
        if(!$uid) return false;
        if(\Helpers\UsersSessionStorage::getInstance()->findByUidOnlineInBrowser($uid)){
            return true;
        }else{
            return false;
        }
    }
 
  public function getCurrentTimezoneFromId($uid) {
        $uid = $uid ? (int) $uid : self::session()->get('profile')['id'];
        $conds = 'user_id = :uid:';
        $bind = array('uid' => $uid);
 
        //$user_sessions = \Helpers\Basic::session_unserialize(self::session()->read(\Helpers\UsersSessionStorage::getInstance()->findIdsByUserId($uid)[0]));
       $user_sessions = \Helpers\UsersSessionStorage::getInstance()->get(\Helpers\UsersSessionStorage::getInstance()->findIdsByUserId($uid)[0]);
 
        if($user_sessions['profile'] && $user_sessions['profile']['timezone']){
            return $user_sessions['profile']['timezone'];
        }else{
            return defined("SERVER_TIME_ZONE") ? SERVER_TIME_ZONE : "Etc/UTC";
        }
        /*
        $o = \Models\UserSessions::findFirst(array(
            'conditions' => $conds,
            'bind' => $bind,
            'cache' => array('key' => "usersessions".md5(serialize(array($conds,$cols, $bind))),
                'lifetime' => 300)
        ));
    if (is_object($o) && property_exists($o, 'timezone') ) {
      return $o->timezone;
    } else {
      return SERVER_TIME_ZONE;
    }*/
  }
 
 
    /**
     * Build username function
     *
     * @param string $fname First name or username
     * @param string $lname Last name. It is not required
     * @param int $recursion Recursion - used only when some username is existing
     * @return string Free username
     */
  static function build_username($fname, $lname = false, $recursion = false)
    {
        $minLength = 6;
        $maxLength = 30;
 
        $username = $fname;
 
        // Make username changes and validations
        if ($recursion === false) {
            if ($lname) {
                $username = $fname.'.'.$lname;
            }
 
            $username = strtolower(\Helpers\Inflector::latinize($username));
            $username = \Helpers\Inflector::underscore($username, '.');
            $username = trim($username, '.');
 
            if (strlen($username) < $minLength) {
                $username = $username . rand(100000, 999999);
            }
 
            if (strlen($username) > $maxLength) {
                $username = substr($username, 0, $maxLength);
            }
        }
 
        $check_username = $username;
        if ($recursion) {
            $check_username = $username.$recursion;
        }
 
        // Check if exists
        $check = \Models\Users::findFirst([
            "conditions" => "username = :username: OR username_old = :username:",
            "bind" => ["username" => $check_username]
        ]);
 
 
        if ($check OR in_array($check_username, array_keys((array) \Config::defaults()->RESERVED_NAMES))
      || in_array('/' . $check_username, array_keys((array) \Config::routing()->routes))) {
            if ($recursion === false) {
                $recursion = 1;
            } else {
                $recursion++;
            }
 
            return self::build_username($username, false, $recursion);
        } else {
            return $check_username;
        }
 
        /*
        if ($recursion AND isset($fname)) {
            $user = $fname;
            $user_try = $fname . $recursion;
        } else {
            $fname = strtolower($fname);
            $lname = strtolower($lname);
            if ($lname) {
                $user = $fname . "." . $lname; //mb_substr($lname, 0, 1);
            } else {
                $user = $fname;
            }
 
            if (!$recursion AND strlen($user) < $minLength) {
                $user = $user . rand(100000, 999999);
            }
 
            if (!$recursion AND strlen($user) > $maxLength) { // limit maximum length of
                $user = substr($user, $maxLength - strlen($user));
            }
 
 
            $user_try = $user;
            $recursion = 0;
        }
 
        $user = trim($user);
        $user_try = trim($user_try);
        $user = str_replace(" ", "", $user);
        $user_try = str_replace(" ", "", $user_try);
        $user = trim(preg_replace('/\.+/', '.', $user), '.');
        $user_try = trim(preg_replace('/\.+/', '.', $user_try), '.');
 
        $check = \Models\Users::findFirst(array(
            "conditions" => "username = :username: OR username_old = :username:",
            "bind" => array("username" => $user_try)
        ));
 
        if ($check OR in_array($user_try, array_keys((array) \Config::defaults()->RESERVED_NAMES))) {
            $recursion++;
            return self::build_username($user, false, $recursion);
        } else {
            return $user_try;
        }*/
    }
 
    static function validateUsername($username) {
        $match = preg_match('/^[0-9a-z\.\-_]+$/i', $username);// hyphen is not allowed probably ... but I put it there
        if ($match) {
            return $username;
        } else {
            return false;
        }
    }
 
    static function prepareDisplayName($fname,$lname){
        if (strlen($lname)) {
            return $fname." ".$lname;
        } else {
            return $fname;
        }
  }
 
  static function getMyDetails(){
    return self::getDetailsById();
  }
 
  static function getDetailsByUsername($username, $lang = null, $getMail = false){
        $id = self::getIdFromUsername($username);
        if ($id) {
            return self::getDetailsById($id, $lang, $getMail);
        } else {
            return false;
        }
  }
 
  static function getDetailsById($uid=false, $lang = null, $getMail = false){
    $di = \Phalcon\DI\FactoryDefault::getDefault();
    $lang = $lang ? $lang : \Helpers\Basic::lang();
    $uid = $uid ? $uid : $di['session']->get('profile')['id'];
 
    if ($di['session']->has('profile')) {
      $my_id = $di['session']->get('profile')['id'];
    } else {
      if ($uid) {
        $my_id = $uid;
      }
    }
 
    $return = array();
 
    $start = microtime(true);
 
    $sql="SELECT
            `u`.`id`,
          /*  `u`.`page_id`, */
          /*  `u`.`is_page`, */
            `u`.`is_musician_enabled`,
                        `u`.`page_type_id`,
            `u`.`username`,
            `u`.`username_old`,
            `u`.`email`,
            `u`.`email_private`,
            `u`.`first_name`,
            `u`.`last_name`,
            `u`.`lastseen`,
            `u`.`chat_status`,
            `u`.`user_status`,
            `u`.`system_status`,
            `u`.`gender`,
            `u`.`gender_private`,
            `u`.`profile_album_id`,
            `u`.`wall_album_id`,
            `u`.`cover_album_id`,
            `u`.`video_album_id`,
            `u`.`audio_album_id`,
            DATE_FORMAT(`birthday`, '%d') as `bd_day`,
            DATE_FORMAT(`birthday`, '%m') as `bd_month`,
            DATE_FORMAT(`birthday`, '%Y') as `bd_year`,
            DATE_FORMAT(`birthday`, '%Y-%m-%d') AS `birthday`,
            `u`.`birthday_day_month_private`,
            `u`.`birthday_year_private`,
            `u`.`registration_time`,
            `c`.`name_en` AS `country`,
            `c`.`country_code` AS `country_code`,
            `u`.`country_private`,
            `u`.`city`,
            `u`.`city_private`,
            `u`.`websites`,
            `u`.`balance`,
            `u`.`description`,
            `u`.`experience`,
            `u`.`education`,
            `u`.`is_jam`,
            `u`.`is_teach`,
            `u`.`rating`,
            `u`.`payout_pp_account`,
            `u`.`getting_started`,
                        `u`.`is_looking_for_a_band`,
                        `u`.`is_looking_for_a_bandmates`,
                        `u`.`total_pictures`,
            `u`.`record_label`,
            `u`.`telephone_number`,
            `u`.`share_image_url`,
                        `u`.`available_to_rehearse`,
                        `u`.`available_to_gig`,
                        `u`.`trends_to_practice`,
                        `u`.`recording_experience`,
                        `u`.`gigs_played`,
                        `u`.`years_of_playing_music`,
                        `u`.`is_chat_disabled`,
                        `u`.`is_email_verified`,
                        `u`.`gradient`,
                        `u`.`user_type_id`,
            /*`u`.`profile_tags`,
            `tz`.`utc_offset`,
            `tz`.`timezone`,*/
            `up`.`id` as `avatar_id`,
            `up`.`local_picture_filename` as `profile_pic_filename`,
            `up`.`modified_time` as `profile_pic_modified`,
            /* `up`.`deployed` AS `avatar_deployed`, */
            `up2`.`id` as `cover_id`,
            `up2`.`local_picture_filename` AS `cover_filename`,
            `up2`.`modified_time` AS `cover_modified`,
            /*`up2`.`deployed` AS `cover_deployed`,*/
            `uf`.`id` as `follow`,
            `uf`.`circle_id` AS `circle_id`
            /*count(distinct `ueq`.`id`) as `question_count`,*/
            /*COUNT(distinct `uv`.`id`) as `videos`,*/
                        /*COUNT(distinct `usng`.`id`) as `songs`*/
         FROM `users_profiles` AS `u`
      LEFT JOIN `countries` AS `c` ON `u`.`country` = `c`.`country_code`
      /*LEFT JOIN `users_timezone` AS `tz` ON `u`.`id` = `tz`.`profile_id`*/
      /*LEFT JOIN `users_elements` AS `ueq` ON (`ueq`.`on_user_id` = `u`.`id` AND `ueq`.`deep` = 0 AND (`ueq`.`deleted_time` IS NULL) AND `ueq`.`is_question` = 1 AND `ueq`.`published_time` IS NOT NULL)*/
      LEFT JOIN `users_elements` AS `up` ON `u`.`id` = `up`.`creator_id` AND `up`.`is_default` = '1' AND `up`.`album_id` = `u`.`profile_album_id`  AND `up`.`deleted_time` IS NULL AND `up`.`published_time` IS NOT NULL AND `up`.`deployed_time` IS NOT NULL
            LEFT JOIN `users_elements` AS `up2` ON `u`.`id` = `up2`.`creator_id` AND `up2`.`is_default` = '1' AND `up2`.`album_id` = `u`.`cover_album_id`  AND `up2`.`deleted_time` IS NULL AND `up2`.`published_time` IS NOT NULL AND `up2`.`deployed_time` IS NOT NULL
      LEFT JOIN `users_circles_members` AS `uf` ON `u`.`id` = `uf`.`target_user_id` AND `uf`.`user_id` = :mid
            /*LEFT JOIN `users_elements` AS `usng` ON ( `usng`.`type` = 'audio' AND `usng`.`creator_id` = `u`.`id` AND `usng`.`deleted_time` IS NULL AND `usng`.`published_time` IS NOT NULL AND `usng`.`deployed_time` IS NOT NULL )*/
      /*LEFT JOIN `users_elements` AS `uv` ON ( `uv`.`type` = 'video' AND `uv`.`album_id` IN (SELECT id FROM users_elements WHERE type='video_album' AND creator_id = :uid) AND `uv`.`creator_id` = `u`.`id` AND `uv`.`deleted_time` IS NULL AND `uv`.`published_time` IS NOT NULL AND `uv`.`deployed_time` IS NOT NULL )*/
        WHERE `u`.`id`= :uid AND `u`.`is_disabled` = '0' AND (`u`.`is_banned` = '0' OR `u`.`is_banned` IS NULL)
        LIMIT 1
      ;";
 
 
      $a = $di['db']->query( $sql, array( "uid"=>$uid, "mid" => $my_id ) );
 
      $log = [];
      $log[] = array(__FUNCTION__, "point 1", round(microtime(true)-$start,3));
 
      $a->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
          $a = $a->fetchAll();
          $a = $a[0];
 
            // \Helpers\Debug::log($a['circle_id'], $my_id, $uid);
 
          $total_pictures = $a['total_pictures'];
      if($a AND (int)$a['id'] > 0 ) {
 
 
        $return=array();
 
                if ($di['session']->has('profile')) {
                    if ($di['session']->get('profile')['id'] == $uid and $di['session']->get('profile')['super_powers']) {
                        $return['super_powers'] = true;
                    }
                }
 
        //public
        $return['id']=$a['id'];
        //$return['page_id']=$a['id'];
        $return['user_type_id'] = $a['user_type_id'];
 
        $return['is_musician_enabled'] = $a['is_musician_enabled'];
 
        $return['username']=$a['username'];
        $return['username_old']=$a['username_old'];
        $return['firstname']=trim($a['first_name']);
        $return['lastname']=trim($a['last_name']);
        $return['display_name'] = self::prepareDisplayName($return['firstname'], $return['lastname']);
        $return['lastseen']=$a['lastseen'];
        $return['description']=$a['description'];
        $return['experience']=$a['experience'];
        $return['registration_time']=$a['registration_time'];
        $return['education']=$a['education'];
        $return['profile_album_id']=$a['profile_album_id'];
        $return['wall_album_id']=$a['wall_album_id'];
        $return['cover_album_id']=$a['cover_album_id'];
        $return['video_album_id']=$a['video_album_id'];
        $return['audio_album_id']=$a['audio_album_id'];
        $return['jam']=$a['is_jam'];
        $return['teach']=$a['is_teach'];
        $return['rating']=isset( $a['rating'] ) ? $a['rating'] : 0;
 
                // Is i am block that user
                $return['blocked'] = false;
 
                $check_blocked = \Models\UsersBlockedMembers::findFirst([
                    'conditions' => 'user_id={user:int} AND member_id={member:int}',
                    'bind' => [
                        'user' => (int) $my_id, 'member' => (int) $a['id']
                    ]
                ]);
 
                if ($check_blocked) {
                    $return['blocked'] = true;
                }
 
                $return['team_member'] = '';
                $check_team_member = \Models\DroobleTeamMembers::findFirst([
                    'columns' => 'user_id, is_volunteer, is_endorse',
                    'conditions' => 'user_id={user:int} AND is_active=1',
                    'bind' => ['user' => (int) $a['id']]
                ]);
 
                if ($check_team_member) {
                    $return['team_member'] = 'member';
 
                    $programs = [];
 
                    if ($check_team_member->is_volunteer) {
                        $programs[] = 'volunteer';
                    }
 
                    if ($check_team_member->is_endorse) {
                      $programs[] = 'endorsed_artist';
          }
 
          if ($programs) {
                      $return['team_member'] = join(' | ', $programs);
          }
                }
 
        /*$return['utc_offset']=$a['utc_offset'];
        $return['timezone']=$a['timezone'];*/
        $return['cover_id'] = $a['cover_id'];
        $return['cover'] = \Helpers\Pictures::prepareImage($a['cover_filename'],$a['id'],'cover', $a['cover_modified']);
        $return['cover_original_size'] = \Helpers\Pictures::prepareImage($a['cover_filename'],$a['id'],'original', $a['cover_modified']);
        $return['avatar_id'] = $a['avatar_id'];
        $return['avatar'] = \Helpers\Pictures::prepareImage($a['profile_pic_filename'],$a['id'],'avatar', $a['profile_pic_modified']);
        $return['avatar_icon'] = \Helpers\Pictures::prepareImage($a['profile_pic_filename'],$a['id'], 'icon', $a['profile_pic_modified']);
        $return['avatar_original_size'] = \Helpers\Pictures::prepareImage($a['profile_pic_filename'],$a['id'],'original', $a['profile_pic_modified']);
        $return['response_rate']=0;
        $return['follow']=$a['follow'];
        $return['circle_id']=$a['circle_id'];
        $log[] = array(__FUNCTION__, "point 2", round(microtime(true)-$start,3));
        //$return['videos']=$a['videos'];
        $return['videos'] = \Models\UserElements::count([
                        "conditions" => "creator_id = :creator_id: AND on_user_id = :creator_id: AND deep=0 AND album_id IN (SELECT id FROM \Models\UserElements WHERE type='video_album' AND creator_id=:creator_id: AND (secret IS NULL OR secret = 0) ) AND deleted_time IS NULL AND banned_time IS NULL AND published_time IS NOT NULL AND deployed_time IS NOT NULL AND type = 'video'",
                        "bind" => array("creator_id" => $uid)
                    ]);
        $log[] = array(__FUNCTION__, "point 3", round(microtime(true)-$start,3));
        $return['photos'] = \Models\UserElements::count([
                        "conditions" => "creator_id = :creator_id: AND on_user_id = :creator_id: AND deep=0 AND album_id IN (SELECT id FROM \Models\UserElements WHERE type='picture_album' AND creator_id=:creator_id: AND title = 'Wall photos' ) AND deleted_time IS NULL AND banned_time IS NULL AND published_time IS NOT NULL AND deployed_time IS NOT NULL AND type = 'picture'",
                        "bind" => array("creator_id" => $uid)
                    ]);
        $return['songs'] = \Models\UserElements::count([
                        "conditions" => "creator_id = :creator_id: AND on_user_id = :creator_id: AND deep=0 AND album_id IN (SELECT id FROM \Models\UserElements WHERE type='audio_album' AND creator_id=:creator_id: AND (secret IS NULL OR secret = 0) ) AND deleted_time IS NULL AND banned_time IS NULL AND published_time IS NOT NULL AND deployed_time IS NOT NULL AND type = 'audio'",
                        "bind" => array("creator_id" => $uid)
                    ]);
        $log[] = array(__FUNCTION__, "point 4", round(microtime(true)-$start,3));
//                $return['songs']=$a['songs'];
        $return['getting_started']=$a['getting_started'];
                $return['is_looking_for_a_band'] = (int)$a['is_looking_for_a_band'];
                $return['is_looking_for_a_bandmates'] = (int)$a['is_looking_for_a_bandmates'];
        $return['question_count']=$a['question_count'];
        $return['record_label']=$a['record_label'];
        $return['telephone_number']= $a['telephone_number'];
        $return['websites']=json_decode($a['websites']);
        $return['share_image_url']=$a['share_image_url'];
        if (!$return['websites']) {
          $return['websites'] = array();
        }
 
                $return['available_to_rehearse']=$a['available_to_rehearse'];
                $return['available_to_gig']=$a['available_to_gig'];
                $return['trends_to_practice']=$a['trends_to_practice'];
                $return['recording_experience']=$a['recording_experience'];
                $return['gigs_played']=$a['gigs_played'];
                $return['years_of_playing_music']=$a['years_of_playing_music'];
                $return['is_chat_disabled']= (int) $a['is_chat_disabled'];
                $return['is_email_verified']=$a['is_email_verified'];
 
                $karma = \Helpers\Karma::getUserBalances($a['id']);
                $return['karma_total'] = $karma['karma_total'];
                $return['karma_balance'] = $karma['karma_balance'];
                $profileCtrl = new \Controllers\API\ProfileController();
                $return['privacy_settings'] = $profileCtrl->getPrivacySettingsParsed($uid);
                $return['gradient'] = json_decode($a['gradient'], true);
 
                $year = (int) date("Y");
                $week = (int) date("W");
                $ct = \Collections\ChartNow::findFirst([
                    'conditions' => [
                        'entity_owner_id' => (int) $a['id'],
                        'etype' => 'element'
                    ],
                    'sort' => ['updated' => -1]
                ]);
                if ($ct) {
                    $chartLink = \Helpers\Charts::getChartLink($ct);
                    $return['in_chart'] = $chartLink;
                } else {
                    $return['in_chart'] = null;
                }
 
                if ($di['session']->has('profile')) {
          $profile = self::session()->get("profile");
 
          $return['show_welcome_back'] = $profile['show_welcome_back'] ? true : false;
          $profile['show_welcome_back'] = false;
          self::session()->set("profile", $profile);
        }
 
        // \Helpers\Debug::log("Question count: " . $a['question_count']);
        // $return['profile_tags']=json_decode( $a['profile_tags'] );
 
                // Check if we have ansered interviews
                $return['have_interview'] = false;
 
                $have_answers = \Models\UserInterviewAnswers::query();
                $have_answers->columns('id');
                $have_answers->where("content IS NOT NULL AND content != '' AND user_id={user:int}", ['user' => (int)$a['id']]);
 
//                if ($a['is_page']) {
//                    $have_answers->andWhere("interview_question_id IN (SELECT a.id FROM \Models\InterviewQuestions as a WHERE a.page_type_id={page_type_id:int})", ['page_type_id' => (int)$a['page_type_id']]);
//                } else {
        $have_answers->andWhere("interview_question_id IN (SELECT a.id FROM \Models\InterviewQuestions as a WHERE a.page_type_id IS NULL)");
//                }
 
                $check_for_answers = $have_answers->execute();
                if (count($check_for_answers) > 0) {
                    $return['have_interview'] = true;
                }
                $log[] = array(__FUNCTION__, "point 5", round(microtime(true)-$start,3));
                // End interview checker
 
                // Check for playlist
                $return['playlists'] = false;
                if ($di['session']->has('profile')) {
                    if ($di['session']->get('profile')['id'] == $uid) {
                        $return['playlists'] = \Collections\RadioPlaylists::count([
                            'conditions' => [
                                'user_id' => (int)$uid
                            ]
                        ]);
                    }
                }
 
                if ($return['playlists'] === false) {
                    $return['playlists'] = \Collections\RadioPlaylists::count([
                        'conditions' => [
                            'user_id' => (int)$uid,
                            '$or' => [
                                [
                                    'is_private' => null
                                ],
                                [
                                    'is_private' => false
                                ]
                            ]
                        ]
                    ]);
                }
                $log[] = array(__FUNCTION__, "point 6", round(microtime(true)-$start,3));
                // End for check playlists
 
        /**
                if ($a['is_page']) {
                    if ($a['page_type_id']) {
                        $page_type = \Models\UserPageTypes::findFirst($a['page_type_id']);
                    } else {
                        // Default (for old cases)
                        $page_type = \Models\UserPageTypes::findFirst(100);
                    }
 
                    if ($page_type) {
                        $parent_page_type = $page_type->Parent;
 
                        if ($parent_page_type) {
 
                            if ($page_type->id) {
                                $intQ = \Models\InterviewQuestions::findFirst("page_type_id = {$page_type->id}");
                                $has_interview = (int) $intQ;
                            } else {
                                $has_interview = 0;
                            }
 
                            $return['page_type'] = [
                                'parent' => $parent_page_type->id,
                                'parent_title' => $parent_page_type->title,
                                'id' => $page_type->id,
                                'title' => $page_type->title,
 
                                'has_interview' => $has_interview,
                                'has_songs' => $page_type->has_songs,
                                'has_influences' => $page_type->has_influences,
                                'has_record_label' => $page_type->has_record_label,
                                'has_birthday' => $page_type->has_birthday
                            ];
                        }
                    }
                }
         * **/
 
        // GET USER TYPE
        $userType = \Models\UsersTypes::findFirst([
          'conditions' => 'id = {id:int}',
          'bind' => [
            'id' => $return['user_type_id']
          ]
        ]);
 
        if ($userType instanceof \Models\UsersTypes) {
 
          $return['user_type'] = $userType;
 
          // if the property is null we need to get value from the parent
          if ($userType->is_musician === null) {
            $userType->is_musician = $userType->Parent->is_musician;
          }
 
          if ($userType->is_musician) {
            $return['is_musician_enabled'] = 1;
          }
        }
 
                if ($return['cover'] == DEF_COVER_PIC) {
                    $check = \Helpers\UserValues::get('default-cover', ['user_id' => $return['id']]);
                    if ($check and $check->value and (int)$check->value > 0) {
                        $return['cover'] = 'https://common.drooblecdn.com/images/default-covers/covers_'.$check->value.'.jpg';
                    }
 
                    $return['default_cover'] = true;
                }
 
                $return['profile_tags'] = array();
                /*
        if ( !is_array($return['profile_tags']) ) {
          $return['profile_tags'] = array();
        }*/
 
        $return['default_search_url'] = \Helpers\Search::getUrl('%%tag%%');
 
                $privacy_following_only = \Models\UserCircleMembers::findFirst(array(
                            "conditions"  => "user_id = :uid: AND target_user_id = :tid:",
                            "bind"      => array(
                                "uid"    => $uid,
                                "tid"    => $my_id
                            )
                ));
$log[] = array(__FUNCTION__, "point 7", round(microtime(true)-$start,3));
        if($my_id==$a['id'] or $a['gender_private']==0 or ($privacy_following_only && $a['gender_private']==2)){
          $return['gender'] = ucfirst($a['gender']);
        }
 
        if ($getMail OR $my_id==$a['id'] or $a['email_private']==0 or ($privacy_following_only && $a['email_private']==2) or \Helpers\Basic::checkUserRights($a['id'], $_SESSION['profile']['id'])) {
          $return['email'] = $a['email'];
        } else if ($a['is_page']) {
                    $return['email'] = $a['email'];
                }
 
        $location=array();
 
        if($my_id==$a['id'] or $a['country_private']==0 or ($privacy_following_only && $a['country_private']==2)){
          $return['country']=$a['country'];
          $return['country_code']=$a['country_code'];
 
                    if ($a['country']) {
                        $location[]=$a['country'];
                    }
        }
 
        if($my_id==$a['id'] or $a['city_private'] == 0 or ($privacy_following_only && $a['city_private']==2)){
          $return['city']=$a['city'];
 
                    if ($a['city']) {
                        $place = \Models\GooglePlaces::findFirst(array(
                            "conditions" => "google_place_id = :google_place_id:",
                            "bind" => array("google_place_id"=>$a['city'])
                        ));
                    }
 
                  if($place){
                    $return['place'] = $place->google_place_details_en;
                        $return['place_id'] = $place->google_place_id;
                        if ($place->google_place_details_en) {
                            $location[] = $place->google_place_details_en;
                        }
                  }else{
                        if ($a['city']) {
                            $location[]=$a['city'];
                        }
                  }
        }
 
        if(count($location)){
          $return['location']=implode("/", $location);
        }else{
          $return['location']=null;
        }
 
                // New city stuff
                if ($my_id == $a['id'] OR $a['city_private'] == 0 OR ($privacy_following_only && $a['city_private']==2)) {
 
                  /*
                    $place = \Models\Cities::findFirst((int)$a['city']);
                    if ($place) {
                        $suffix = $place->country;
                        if ($place->region) {
                            $suffix = $place->region.", ".$suffix;
                        }
 
                        $return['place'] = $place->name.", ".$suffix;
                        $return['place_id'] = $place->id;
                    }*/
                }
 
        if(\Helpers\Time::validateDate($a['birthday'],"Y-m-d")){
          $bday= new \DateTime($a['birthday']);
          if ($a['is_page'] or (int)$a['birthday_day_month_private'] == 0 or $_SESSION['profile']['id'] == $a['id'] or \Helpers\Basic::checkUserRights($a['id'], $_SESSION['profile']['id'])){
            $return['birthday']['day'] = $bday->format('d');
            $return['birthday']['month'] = $bday->format('m');
 
                        if ((int)$a['birthday_year_private'] == 0) {
                            $return['birthday']['year'] = $bday->format('Y');
                        }
                    } else{
            $return['birthday']=null;
          }
 
          if($a['birthday_year_private']==0 OR ($privacy_following_only && $a['birthday_year_private']==2)){
            $return['birthday_formated']=$bday->format('d/m/Y');
                    }else{
                        $return['birthday_formated']=null;
                    }
        }else{
          $return['birthday_formated']=null;
        }
 
        if($my_id==$a['id']){
          $return['birthday_day_month_private']=$a['birthday_day_month_private'];
          $return['birthday_year_private']=$a['birthday_year_private'];
          $return['country_private']=$a['country_private'];
          $return['city_private']=$a['city_private'];
          $return['email_private']=$a['email_private'];
          $return['gender_private']=$a['gender_private'];
          $return['payout_pp_account']=$a['payout_pp_account'];
        }
 
                //$return['status']=self::calculatePublicStatus($a['user_status'],$a['system_status'],$a['lastseen']);
        $return['status']=\Helpers\Users::getInstance()->getProfilePublicStatus($uid);
                //$return['chat_status']=$a['chat_status'] > 0 ? $return['status'] : $a['chat_status'];
        $return['chat_status']=\Helpers\Users::getInstance()->getProfilePublicChatStatus($uid);
               // \Helpers\Debug::log($return);
        $return['open_to']=array();
        foreach( array(
              'jam'  =>  $a['is_jam'],
              'teach'  =>  $a['is_teach']) as $k=>$e){
          if($e){
            $return['open_to'][]=$k;
          }
        }
 
        if($return['teach']){
          $return['rates'] = \Helpers\Teaching::getTeachingRates( $a['id'] );
        }
 
        $return['has_tags'] = false;
        //tags
        foreach(unserialize(TAGS_TYPES) as $ttype){
                    if ($ttype != 'posts') {
                        $return['tags'][$ttype]=array();
                    }
        }
 
//                $usertags = \Models\UserTags::find([
//                    'conditions' => 'user_id={id:int}',
//                    'bind' => ['id' => (int)$a['id']]
//                ]);
                $usertags = self::getUserTags($a['id'], 0, true, $lang);
        foreach($usertags as $tag) {
          if (!$return['has_tags']) {
                        $return['has_tags'] = true;
                    }
                    $tmp = [
                        "url" => \Helpers\Search::getUrl(ucwords(strtolower($tag->name)),$tag->type),
                        "name" => ucwords(strtolower($tag->name)),
                        "order" => (int) $tag->order
                    ];
 
                    if ($tag->subtype) {
                        $tmp['subtype'] = $tag->subtype;
                    }
 
                    // *keepcalmandorder*
          $return['tags'][$tag->type][] = $tmp;
        }
 
                // order and reindex tags *keepcalmandorder*
                foreach ($return['tags'] as $type => $tagsData) {
                    usort($tagsData, ["\Helpers\Tags", "sortTags"]);
                    $return['tags'][$type] = $tagsData;
                }
$log[] = array(__FUNCTION__, "point 8", round(microtime(true)-$start,3));
        if ( empty( $return['tags']['profile'] ) ) {
                    $return['open_to_string'] = '';
                    // $return['open_to_string'] = "Open to " . implode(" AND ",$return['open_to']);
        } else {
          $tags = array_slice($return['tags']['profile'], 0, 2); // show only the first two tags
          $text = '';
 
          if ( count($tags) > 1 ) {
            $last  = array_pop( $tags );
            $first = array_shift( $tags );
 
            $text = $first['name'] . " and " . $last['name'];
          } else {
            $text = $tags[0]['name'];
          }
 
          $return['open_to_string'] = $text;
 
                    foreach($return['tags']['profile'] as $key => $tag){
                        $return['profile_tags'][$key] = $tag;
                    }
        }
 
                // Set subtitle to this. (see task 646)
                if ($return['id'] == 1)
                {
                    $return['open_to_string'] = "All Music";
                }
 
        //about texts
        /*
        $sql = "SELECT `id`,`title`, `text`, `scene` FROM `users_texts` WHERE `scene` = 'about' AND `user_id` = :uid AND `delete`='0' LIMIT 1";
                $text = $di['db']->query( $sql, array( "uid" => $a['id'] ) );
        $text->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $text = $text->fetchAll($text);
            $text = $text[0];
        */
        $text = \Models\UserTexts::findFirst(array(
            "conditions" => "scene = 'about' AND user_id = :uid: AND is_deleted = '0'",
            "bind" => array("uid" => $a['id'])
          ));
        $return['about_text'] = array(
                      "id" => $text->id,
                      "title" => $text->title,
                      "text" => $text->text,
                      "scene" => $text->scene,
                    );
 
        /*
        //videos texts
        $sql="SELECT `title`, `text` FROM `users_texts` WHERE `scene` = 'videos' AND `user_id` = :uid";
        foreach($this->db->q($sql, array("uid" => $a['id'])) as $text){
          $return['videos_text'][]=$text;
        }
 
        //photos texts
        $sql="SELECT `title`, `text` FROM `users_texts` WHERE `scene` = 'photos' AND `user_id` = :uid";
        foreach($this->db->q($sql, array("uid" => $a['id'])) as $text){
          $return['photos_text'][]=$text;
        }
        */
 
        //about links
        /*
        $sql = "SELECT `id`, `title`, `link` FROM `users_links` WHERE `scene` = 'about' AND `user_id` = :uid";
        $links = $di['db']->query( $sql, array( "uid" => $a['id'] ) );
        $links->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $links = $links->fetchAll($links);
        */
 
            $links = \Models\UserLinks::find(array(
                "conditions" => "scene = 'about' AND user_id = :uid:",
                "bind" => array("uid" => $a['id'])
              ));
        foreach($links as $link){
          $return['about_links'][]=array(
              "id"=>$link->id,
              "title"=>$link->title,
              "link"=>$link->link
            );
        }
 
                // semi-toasts in header: verify mail | enter mail
                if ($di['session']->has('profile')) {
                    $return['show_verify_mail'] = false;
                    $return['show_enter_mail'] = false;
                    $return['show_invite_for_karma'] = false;
                    $session = self::session()->get('profile');
                    if ($my_id == $uid) {
                        $uval = \Helpers\UserValues::get('show_invite_for_karma');
                        $return['show_invite_for_karma'] = $uval->value;
                    }
//                    if (!empty($session['email']) && $session['is_email_verified'] == 0 && !\DroobleMemcache()->read($session['id'] . '.send_email_verification') ) { // old in menu_default !isset($emailVerifyError)
//                        $return['show_verify_mail'] = true;
//                    }
                    if (empty($session['email'])) {
                        $return['show_enter_mail'] = true;
                    }
                }
 
 
            // Count all picures
 
                $return['counter_all_pictures'] = $total_pictures;
$log[] = array(__FUNCTION__, "point 10", round(microtime(true)-$start,3));
                // Count all videos
                $total_videos = \Models\UserElements::count([
                    "conditions" => "type = 'video' AND deep=0 AND deployed_time IS NOT NULL AND deleted_time IS NULL AND published_time IS NOT NULL AND creator_id = :creator_id: AND on_user_id = :creator_id:
                        AND album_id IN (SELECT id FROM \Models\UserElements WHERE type='video_album' AND creator_id = :creator_id: AND deployed_time IS NOT NULL AND deleted_time IS NULL AND published_time IS NOT NULL)",
                    "bind" => array("creator_id" => $uid)
                ]);
                $return['counter_all_videos'] = $total_videos;
$log[] = array(__FUNCTION__, "point 11", round(microtime(true)-$start,3));
                // Get last 3 pictures
/* OMG heavy
                $images = array();
                $last3pics = \Models\UserElements::find(array("conditions"=>" type = 'picture' AND creator_id  = :uid:  AND deleted_time IS NULL AND published_time IS NOT NULL AND deployed_time IS NOT NULL","bind"=>array("uid"=>$a['id']),"order" => "published_time DESC", "limit" => 3));
                foreach($last3pics as $el){
                    $img = array(
                        "id" => $el->id,
                        "albumID" => $el->album_id,
                        "isCover" => $el->is_default,
                        "position" => $el->position,
                        "likes" => $el->likes,
                        "comment_id" => $el->id,
                        "width" => $el->width,
                        "height" => $el->height,
                        "published" => $el->published
                    );
 
                    $img['path'] = \Helpers\Pictures::prepareImage($el->local_picture_filename, $a['id'], 'tn', $el->modified_time);
                    $img['origPath'] = \Helpers\Pictures::prepareImage($el->local_picture_filename, $a['id'], 'originals', $el->modified_time);
 
                    $images[] = $img;
                }
                $return['last_images'] = $images;
*/
 
        $return['followers'] = \Models\UserCircleMembers::count(array(
            "conditions"=>" target_user_id = :uid: ",
            "bind" => array("uid"=>$a['id'])
          ));
 
        $return['followings'] = \Models\UserCircleMembers::count(array(
            "conditions"=>" user_id = :uid: ",
            "bind" => array("uid"=>$a['id'])
          ));
 
                $return['presskit_info'] = \Controllers\API\PresskitController::info($return);
 
      }else{
        return false;
      }
 
            $return['can_modify'] = false;
            $return['is_member'] = false;
 
//      if($a['is_page']){
//        $return['page_id'] = $return['id'];
//        $return['name'] = $return['firstname'];
//        $return['alias'] = $return['username'];
//        $return['is_page'] = 1;
//
//                if ($di['session']->has('profile')) {
//                    $return['can_modify'] = \Helpers\Basic::checkUserRights($return['id'], self::session()->get('profile')['id']);
//
//                    $return['is_member'] = \Helpers\Basic::checkUserRights($return['id'], self::session()->get('profile')['id'], true);
//                }
//            } else {
                if ($di['session']->has('profile') and (int)self::session()->get('profile')['super_powers']) {
                    $return['can_modify'] = true;
                }
 
//                $return['is_page'] = 0;
//            }
 
            if ($di['session']->has('profile') and $return['id'] == (int) self::session()->get('profile')['id'] and self::session()->has('admin_behind_user') and self::session()->get('admin_behind_user')) {
                $return['admin_behind_user'] = true;
            }
 
            if ($di['session']->has('profile') AND $return['id'] == self::session()->get('profile')['id']) {
        if (isset($_COOKIE['asrc']) AND !empty($_COOKIE['asrc'])) {
          $asrc = \Lib\Cryptography::decrypt($_COOKIE['asrc']);
          $asrc = json_decode($asrc, true);
 
          if (isset($asrc['hash']) AND !empty($asrc['hash'])) {
            $check = \Models\CookieLinkedProfiles::find([
              'conditions' => 'hash = {hash:str}',
              'bind' => [
                'hash' => $asrc['hash']
              ]
            ]);
 
            if ($check) {
              $return['asrc'] = [];
 
              foreach ($check as $i => $linkedAccount) {
                $getLinkedAccount = \Models\Users::findFirst([
                  'conditions' => 'id = {id:int}',
                  'bind' => [
                    'id' => $linkedAccount->user_id
                  ]
                ]);
 
                if ($getLinkedAccount instanceof \Models\Users) {
                  $return['asrc'][$i] = [
                    'username' => $getLinkedAccount->username,
                    'display_name' => $getLinkedAccount->getDisplayName(),
                    'email' => $getLinkedAccount->email
                  ];
 
                  $return['asrc'][$i] = array_merge($return['asrc'][$i], \Helpers\Pictures::getDefault($getLinkedAccount->id));
 
                  $return['asrc'][$i]['token'] = \Lib\Cryptography::encrypt(json_encode([
                    'hash' => $asrc['hash'],
                    'user_id' => $getLinkedAccount->id
                  ]));
 
                  $return['asrc'][$i]['password_required'] = $linkedAccount->password_required;
                }
              }
            }
          }
        }
      }
 
      $log[] = array(__FUNCTION__, "point 1", round(microtime(true)-$start,3));
    return $return;
 
  }
 
 
 
  static function getUsernameFromId($uid){
    if($username = \DroobleMemcache()->read('cached_username_from_id_'.$uid)){
      return $username;
    }else{
      $user=\Models\Users::findFirst(array("conditions"=>"id = :uid:","bind"=>array("uid"=>$uid) ));
      if($user){
        \DroobleMemcache()->write('cached_username_from_id_'.$uid, $user->username, (60*60*24) );
      }
      return $user->username;
    }
  }
 
  static function getIdFromUsername($username){
    $username = str_replace(" ", "_", $username);
        $username = \Helpers\Users::validateUsername($username);
        if (trim($username) == "") {
            return false;
        }
    if($id = \DroobleMemcache()->read('cached_id_from_username_'.$username)){
      return $id;
    }else{
 
            $user = \Models\Users::findFirst( array("conditions"=>"username = :username:","bind"=>array("username"=>$username) ) );
            if (!$user) {
                $user = \Models\Users::findFirst( array("conditions"=>"username_old = :username:","bind"=>array("username"=>$username) ) );
            }
 
      if ($user) {
                try {
          \DroobleMemcache()->write('cached_id_from_username_'.$username, $user->id, (60*60*24) );
                } catch(\Exception $e) {
                    \Helpers\Debug::error("Fatal FAIL on cache key: " . 'cached_id_from_username_'.$username);
                    \Helpers\Debug::error($e->getTraceAsString());
                }
        return $user->id;
      } else {
                try {
                    \DroobleMemcache()->write('cached_id_from_username_'.$username, 999999999999, (60*60*24) );
                } catch(\Exception $e) {
                    \Helpers\Debug::error("Fatal FAIL on cache key: " . 'cached_id_from_username_'.$username);
                    \Helpers\Debug::error($e->getTraceAsString());
                }
        return false;
      }
    }
 
  }
 
  static function getUserDetailsFromId($uid){
    if($user = \DroobleMemcache()->read('cached_id_from_uedata_id'.$uid)){
      return $user;
    }else{
      $user = self::getDetailsById($uid);
      if($user){
        \DroobleMemcache()->write('cached_id_from_uedata_id'.$uid, $user, (60*60*24) );
        return $user;
      }else{
        return false;
      }
    }
  }
 
  static function getUserFromUsernameOrEmail($udata){
    if($user = \DroobleMemcache()->read('cached_id_from_uedata_'.$udata)){
      return $user;
    }else{
      $user = \Models\Users::findFirst( array("conditions"=>"username = :username: OR email = :email:","bind"=>array("username"=>$udata, "email"=>$udata) ) );
      if($user){
        \DroobleMemcache()->write('cached_id_from_uedata_'.$udata, $user, (60*60*24) );
        return $user;
      }else{
        return false;
      }
    }
  }
 
  static function getIdFromUsernameOrEmail($udata){
    $user = self::getUserFromUsernameOrEmail($udata);
 
    if ( $user ) {
      return $user->id;
    }
 
    return false;
  }
 
  static function getUserShortInfoFromUsername($username,$showStatus=false){
 
    if($id=self::getIdFromUsername($username)){
      return self::getUserShortInfoFromId($id,$showStatus);
    }
    return false;
  }
  static function clearShortInfoCache($uid){
    return \DroobleMemcache()->delete('cached_short_info_for_user_id_'.$uid);
  }
 
  static function getUserShortInfoFromId($uid, $showStatus=false)
    {
    $shortInof = array();
        $uid = (int)$uid;
 
    if($shortInof = \DroobleMemcache()->read('cached_short_info_for_user_id_'.$uid)) {
      //cool
      $shortInof = json_decode($shortInof, true);
    } else {
      $userCace = \Models\UserCache::findFirst("user_id = '".$uid."'");
      $shortInof = json_decode($userCace->short_info_json, true);
      $shortInof['id'] = (int) $shortInof['id'];
            $shortInof['karma_total'] = $userCace->karma_total;
      \DroobleMemcache()->write('cached_short_info_for_user_id_'.$uid, $userCace->short_info_json, 60*60*24);
    }
 
    if ($showStatus) {
      if ($status = \DroobleMemcache()->read('cached_status_for_user_id_'.$uid)){
        //cool
      } else {
        /*$di = \Phalcon\DI\FactoryDefault::getDefault();
        $query = "SELECT fn_calculateProfilePublicStatus(user_status, system_status, lastseen) as status FROM `users_profiles` WHERE id = :id";
        $status = $di['db']->query($query, array('id' => $user_id));
                */
                $status = \Helpers\Users::getInstance()->getProfilePublicStatus($uid);
        \DroobleMemcache()->write('cached_status_for_user_id_'.$uid, $status, 60);
      }
 
      $shortInof['status'] = $status;
    }
 
    return $shortInof;
  }
    /*
  static function calculatePublicStatus($user_status,$system_status,$lastseen){
    if($lastseen+\Config::defaults()->DEF_LASTSEEN_OFFLINE_TIME>time()){
      switch($user_status){
        case 0: //invisible
          $status=0; //invisible
        break;
 
        case 2: //available
          if($system_status==0){
            $status=1; //busy
          }else{
            $status=2; //available
          }
        break;
 
        default: //available
          $status=1; //available
      }
    }else{
      $status=0;//offline
    }
    return $status;
  }
    */
  public function getProfilePublicChatStatus($uid=false){
    if(!$uid) $uid=self::session()->get('profile')['id'];
        if(!$uid) return CHAT_STATUS_OFFLINE;
 
        $public_status = \Helpers\Users::getInstance()->getProfilePublicStatus($uid);
 
        $chat_status = CHAT_STATUS_OFFLINE;
 
        if( $public_status == USER_STATUS_AVAILABLE || $public_status == USER_STATUS_AVAILABLE ){
            $user = \Models\Users::findFirst( [
                                    "conditions"=>"id = :id:",
                                    "bind"=>['id'=>$uid]]
                                );
            $chat_status = $user->chat_status;
        }else{
            $chat_status = CHAT_STATUS_OFFLINE;
        }
 
        return $chat_status;
 
        /*
    $res = self::db()->query('SELECT if(`chat_status` > 0,fn_calculateProfilePublicStatus(`user_status`, `system_status`, `lastseen`),0) as `cstatus` FROM `users_profiles` WHERE `id` = :uid',
                array('uid' => $uid),
                array('uid' => \PDO::PARAM_INT) );
        if ($res) {
            $res->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $res = $res->fetchAll();
            $res = $res[0];
            return $res['cstatus'];
        } else {
           return false;
        }*/
  }
 
    public function getProfilePublicStatus($uid=false){
    if(!$uid) $uid=self::session()->get('profile')['id'];
    if(!$uid) return USER_STATUS_OFFLINE;
 
        $status = USER_STATUS_OFFLINE;
 
        if( \Helpers\Users::getInstance()->isUserOnline($uid) ){
      $status = USER_STATUS_AVAILABLE;
        }else{
            $status = USER_STATUS_OFFLINE;
        }
        return $status;
 
        /*
        $res = self::db()->query('SELECT fn_calculateProfilePublicStatus(`user_status`, `system_status`, `lastseen`) as `status` FROM `users_profiles` WHERE `id` = :uid',
                array('uid' => $uid),
                array('uid' => \PDO::PARAM_INT) );
        if ($res) {
            $res->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
            $res = $res->fetchAll();
            $res = $res[0];
            return $res['status'];
        } else {
           return false;
        }*/
  }
 
  /**
   * getFormattedTagsFromShortInfo - Returns a single array of tags from
   *   the short info.
     * @member
   *
   * @param  {type} $shortInfo A user's short info.
   * @return {type}            An array of formatted tags
   */
  public static function getFormattedTagsFromShortInfo ($shortInfo)
  {
    //TODO: maybe export the "2" constant?
 
    $tagGroups = $shortInfo["tags"];
    $tags = array();
 
    $count = 0;
    foreach ($tagGroups as $category => $tagGroup)
    {
      foreach ($tagGroup as $tag)
      {
        if ($count > 2)
          break;
 
        //NOTE: template compatibility. Remove
        $tags[$category] = array($tag);
        $count ++;
      }
    }
 
    return $tags;
  }
 
    // get occupation from tags (profile, instruments)
    // amount 2 "Producer and Musician"
    // amount 1 "Producer"
    public static function getOccupation($uid = null, $lang = "en", $amount = 2) {
        $uid = $uid !== null ? $uid : self::session()->get('profile')['id'];
        $return = "";
        $useThese = [];
 
        $tags = self::getUserTags($uid, 3, false, $lang);
        if (array_key_exists("profile", $tags)) {
            $useThese = array_merge($useThese, $tags['profile']);
        }
        if (array_key_exists("instruments_primary", $tags)) {
            $useThese = array_merge($useThese, $tags['instruments_primary']);
        }
        if (array_key_exists("instruments_secondary", $tags)) {
            $useThese = array_merge($useThese, $tags['instruments_secondary']);
        }
        if ($amount == 2 && count($useThese) > 1) {
            self::translate()->setLanguage($lang);
            $and = self::translate()->get("and");
            $return = $useThese[0] . " " . $and . " " . $useThese[1];
        } else if(count($useThese)) {
            $return = $useThese[0];
        }
        return $return;
    }
 
  /**
   * resendVerificationEmail - Resends an email verification email.
   *
   * @param  {type} User $user The user...
   * @return {type}            description
   */
  public static function resendVerificationEmail (\Models\Users $user)
  {
    if ($user->is_email_verified == 1)
      return false;
 
    $keys = $user->Keys;
 
    if ($keys)
    {
      $keyToSend = $keys->email_verification_key;
    }
    else
    {
      $keyToSend = Cryptography::getToken();
    }
 
    if ($keys)
    {
      $keys->email_verification_key = $keyToSend;
      $result = $keys->save();
    }
    else
    {
      $keys = new Keys();
      $keys->email_verification_key_creation_time = time();
      $keys->email_verification_key = $keyToSend;
      $keys->user_id = $user->id;
      $result = $keys->save();
    }
 
        //Debug::log($keys->email_verification_key);
        if (!$result)
            foreach ($keys->getMessages() as $message)
                Debug::error($message);
 
    // Set cache, so we don't resend it all the time
    \DroobleMemcache()->write($user->id . "send_email_verification", true, Config::mail()->VERIFICATION_RESEND_TIMEOUT);
 
    return MailGenerator::generateVerifyEmail($user);
  }
 
    public static function isEmailValidOrTaken($email) {
    $ret=array('valid'=>false,'exist'=>true);
    if(filter_var($email, FILTER_VALIDATE_EMAIL)){
      $ret['valid']=true;
            $u = \Models\Users::findByEmail($email);
      $ret['exist'] = $u->count() ? true : false;
        } else {
            $ret['exist'] = false;
        }
    return $ret;
    }
 
    public function profileCreate($data,$facebook=false) {
        $start = microtime(true);
 
    //$debug = array();
 
        if(self::session()->has('profile')){
            $badmsg = self::translate()->get("already_logged");
            return [false, $badmsg];
        }
 
    if(!$facebook){ //if !facebook - data is from serialized form
      parse_str($data,$data); //unserialize
    }
    //var_dump($data);
    $err['fname'] = (isset($data['fname']) and $data['fname']!='') ? false : true;
    // $err['lname'] = (isset($data['lname']) and $data['lname']!='') ? false : true;
    $err['email'] = ($facebook && empty($data['email'])) ? false : self::isEmailValidOrTaken($data['email']);
        if (!isset($data['fb_id']) or !$data['fb_id']) {
            $err['password'] = (isset($data['password']) and strlen(trim($data['password']))>4) ? false : true;
        }
    //$err['password_confirm'] = (isset($data['password_confirm']) and $data['password']==$data['password_confirm'] and $data['password_confirm']!='') ? false : true;
 
//        if ( $err['email']['valid'] && $err['email']['exist'] == false && strpos($data['email'], 'drooble.com') ) {
//            // on purpose misleading message
//            $badmsg = self::translate()->get("already_logged");
//            return [false, $badmsg];
//        }
 
        /*
        $country = isset($data['country']) ? $data['country'] : null;
        $city = isset($data['city']) ? $data['city'] : null;
        */
        $city_private = 0;
        $country_private = 0;
        $birthday_day_month_private = 0;
        $birthday_year_private = 0;
        $gender_private = 0;
        $email_private = 1;
 
        // Allowed statuses
        $privacies = [
            'friends' => 2,
            'public' => 0,
            'only-me' => 1
        ];
        if (isset($data['privacy'])) {
            if (isset($data['privacy']['location']) AND isset($privacies[$data['privacy']['location']])) {
                $city_private = $privacies[$data['privacy']['location']];
                $country_private = $privacies[$data['privacy']['location']];
            }
 
            if (isset($data['privacy']['birthday']) AND isset($privacies[$data['privacy']['birthday']])) {
                $birthday_day_month_private = $privacies[$data['privacy']['birthday']];
                $birthday_year_private = $privacies[$data['privacy']['birthday']];
            }
 
            if (isset($data['privacy']['gender']) AND isset($privacies[$data['privacy']['gender']])) {
                $gender_private = $privacies[$data['privacy']['gender']];
            }
 
            if (isset($data['privacy']['email']) AND isset($privacies[$data['privacy']['email']])) {
                $email_private = $privacies[$data['privacy']['email']];
            }
        }
 
    $birthday = isset($data['birthday']) ? $data['birthday'] : '';
    $gender = isset($data['gender']) ? $data['gender'] : '';
    $fb_id = isset($data['fb_id']) ? $data['fb_id'] : '';
    $errno=0;
    $errors=array();
    foreach($err as $k=>$e){
      if(!($e===false)){
        if($k=='email'){
          if(!($e['valid']===true) or !($e['exist']===false)){
            $errno++;
            $err[$k]=true;
          }else{
            $err[$k]=false;
          }
        }else{
          $errno++;
        }
      }
    }
 
    if($errno>0){
      $result=array(true,array("save"=>false,"errors"=>$err));
    }else{
      if(isset($data['username'])) $user=$data['username']; else $user = \Helpers\Users::build_username($data['fname'], $data['lname']);
$log[] = array(__FUNCTION__, "point1", $data['email'], round(microtime(true)-$start,3));
      $new_user = new \Models\Users();
      $new_user->username = $user;
      $new_user->email = $data['email'];
      $new_user->first_name = ucfirst($data['fname']);
      $new_user->last_name = ($data['lname'] ? ucfirst($data['lname']) : '');
      $new_user->password = \Lib\Cryptography::passwordHash( $data['password'] );
      $new_user->lang = self::session()->lang;
            if (isset($data['lang']) and in_array($data['lang'], ['en', 'es'])) {
                $new_user->lang = $data['lang'];
            }
 
      $new_user->gender = $gender;
      $new_user->birthday = $birthday ? $birthday : NULL;
      $new_user->fb_id = $fb_id ? $fb_id : NULL;
      $new_user->is_need_of_reindex = 1;
            $new_user->city_private = $city_private;
            $new_user->country_private = $country_private;
            $new_user->email_private = $email_private;
            $new_user->gender_private = $gender_private;
            $new_user->birthday_day_month_private = $birthday_day_month_private;
            $new_user->birthday_year_private = $birthday_year_private;
            $new_user->lastseen = time();
            $new_user->user_type_id = ((int)$data['user_type_id'] > 0) ? (int)$data['user_type_id'] : 8;
 
            $flags = [
                'email' => (bool) $data['email'],
            ];
            \Helpers\UserValues::set('profile-completion', $flags);
 
            if($data['place_id']){
              $gp = \Models\GooglePlaces::findFirst(array(
          "conditions" => "google_place_id = :google_place_id:",
          "bind"=>array("google_place_id"=>$data['place_id'])
        ));
            }
      if(!$gp){
        if($data['place_id']){// if user set place_id
                    $gp = \Helpers\Basic::addGooglePlace($data['place_id']);
        }
        if(!$gp){ //if user not set, location will be get from IP
          $this->request->ipInfo = \Helpers\IPInfo::getInstance()->getInfoForIp($this->request->getClientAddress());
 
          if($data['place']){
            $g_place = \Helpers\Search::getInstance()->searchCity($data['place']);
          }
 
          if(!$g_place[0]){
            $g_place = \Helpers\Search::getInstance()->searchCity($this->request->ipInfo['city_name']." ".$this->request->ipInfo['country_name']);
          }
 
          if($g_place[0]){
            $gp = \Helpers\Basic::addGooglePlace($g_place[0]['id']);
          }
        }
                if (!$gp) {
                    $gp = new \stdClass();
                }
      }
 
/*
            if($data['place_id']){
              $cached_google_places = \Models\GooglePlaces::findFirst(array(
          "conditions" => "google_place_id = :google_place_id:",
          "bind"=>array("google_place_id"=>$data['place_id'])
        ));
            }
      if(!$cached_google_places){
        if($data['place_id']){// if user seted place_id
              $google_places = new \Lib\GooglePlaces(\Config::googleplaces()->API_KEY);
          $google_places->placeid   = str_replace(" ", "%20", $data['place_id']);
          $place_result = $google_places->details();
        }
        if($place_result['status'] != 'OK'){ //if user not set, location will be get from IP
          $this->request->ipInfo = \Helpers\IPInfo::getInstance()->getInfoForIp($this->request->getClientAddress());
 
          if($data['place']){
            $g_place = \Helpers\Search::getInstance()->searchCity($data['place']);
          }
 
          if(!$g_place[0]){
            $g_place = \Helpers\Search::getInstance()->searchCity($this->request->ipInfo['city_name']." ".$this->request->ipInfo['country_name']);
          }
 
          if($g_place[0]){
            $place_result['status'] = 'OK';
 
                $google_places = new \Lib\GooglePlaces(\Config::googleplaces()->API_KEY);
            $google_places->placeid   = str_replace(" ", "%20", $g_place[0]['id']);
            $place_result = $google_places->details();
 
          }
        }
        $place_description = '';
        if($place_result['status'] == 'OK'){
          $place_id = $place_result['result']['place_id'];
          foreach($place_result['result']['address_components'] as $a_comp){
 
 
            if($a_comp['types'][0]=='locality' AND $a_comp['types'][1]=='political'){
              $place_description .= $a_comp['long_name'];
            }
 
            if($a_comp['types'][0]=='country' AND $a_comp['types'][1]=='political'){
              if($place_description!=''){
                $place_description .= ', ';
              }
              $place_description .= $a_comp['long_name'];
            }
 
          }
 
                    $cached_google_places = \Models\GooglePlaces::findFirst(array(
                        "conditions" => "google_place_id = :google_place_id:",
                        "bind"=>array("google_place_id"=>$place_id)
                    ));
                    if (!$cached_google_places) {
                        $cached_google_places = new \Models\GooglePlaces();
                        $cached_google_places->google_place_id = $place_id;
                        $cached_google_places->google_place_details_en = $place_description;
                        $cached_google_places->save();
                    }
 
        }else{
          $cached_google_places = new \stdClass();
        }
      }*/
$log[] = array(__FUNCTION__, "point2", $data['email'], round(microtime(true)-$start,3));
            $new_user->lat = property_exists($gp, "lat") ? $gp->lat : $this->request->ipInfo['latitude'];
            $new_user->lng = property_exists($gp, "lng") ? $gp->lng : $this->request->ipInfo['longitude'];
 
      if($gp->google_place_id and $gp->google_place_details_en){
        $new_user->city = $gp->google_place_id;
      }
 
 
      if ($new_user->save()===false){
                $errors = [];
 
        foreach ($new_user->getMessages() as $message) {
              \Helpers\Debug::log($message);
                    $errors[] = $message;
          }
          return array(true,"general error 3", $errors);
      } else {
 
          MailGenerator::generateWelcomeTips($new_user); // 2 days timeout
          MailGenerator::generateGetMusicReviewed($new_user); // 5 days timeout
 
                \Helpers\UserValues::set('show_invite_for_karma', true, ['user_id' => $new_user->id]);
 
                // get FB info
 
                if (strlen($new_user->fb_id) > 4) { // avoid crazy literally 'NULL' strings
 
                    $fb = new \Facebook\Facebook([
                        'app_id' => Config::facebook()->API_ID,
                        'app_secret' => Config::facebook()->SECRET,
                        'default_graph_version' => 'v3.3',
                    ]);
 
                    // get FB info
                    $fbApp = new \Facebook\FacebookApp(Config::facebook()->API_ID, Config::facebook()->SECRET);
 
                    $accessToken = $fbApp->getAccessToken();
 
                    $request = new \Facebook\FacebookRequest($fbApp, $accessToken, 'GET', '/' . $new_user->fb_id, ['fields' => 'id,name,first_name,last_name,email,birthday,gender,friends']);
 
                    // Send the request to Graph
                    try {
                        $response = $fb->getClient()->sendRequest($request);
                    } catch (\Facebook\Exceptions\FacebookResponseException $e) {
                        // When Graph returns an error
                        \Helpers\Debug::error('er 3', $e->getMessage());
                        return array(true,"fb error 3");
                    } catch (\Facebook\Exceptions\FacebookSDKException $e) {
                        // When validation fails or other local issues
                        \Helpers\Debug::error('er 4', $e->getMessage());
                        return array(true,"fb error 4");
                    }
 
                    $graphNode = $response->getGraphNode();
 
                    $fbUserData = (array) $graphNode->asArray();
 
                    $savedInfo = new \Models\UsersSavedInfo();
                    $savedInfo->user_id = $new_user->id;
                    $savedInfo->facebook_info = json_encode($fbUserData);
                    if ($savedInfo->save() === false) {
                        foreach ($savedInfo->getMessages() as $message) {
                            \Helpers\Debug::error($message);
                        }
                    }
                }
 
                $di = \Phalcon\DI\FactoryDefault::getDefault();
                $timeZone = \Helpers\Time::getUserTimezone($new_user);
$log[] = array(__FUNCTION__, "point3", $data['email'], round(microtime(true)-$start,3));
                // sorry .. done in a hurry
                // segment/mixpanel info for mobile app
                $router = $di->getRouter();
                $mongoUser = new \Collections\Users();
                $mongoUser->id = (int) $new_user->id;
                $mongoUser->first_name = $new_user->first_name;
                $mongoUser->last_name = $new_user->last_name;
                $mongoUser->username = $new_user->username;
                $mongoUser->email = $new_user->email;
                $mongoUser->lang = $new_user->lang;
        $mongoUser->karma_balance = 0;
        $mongoUser->karma_total = 0;
                $mongoUser->registration_time = (int) $new_user->registration_time;
                $mongoUser->segment = new \stdClass();
                $mongoUser->segment->signup_method = $facebook ? "facebook" : "email";
                if ($router->getClientPlatformType() == 'MA') {
                    $mobileOS = \Helpers\Basic::getInstance()->getMobileAppOS();
 
                    $mongoUser->segment->signup_platform = "Mobile";
                    $mobileTraits = [
                        "name" => $new_user->first_name . " " . $new_user->last_name,
                        "email" => $new_user->email,
                        "signup_platform" => "Mobile",
                        "signup_method" => $facebook ? "facebook" : "email"
                    ];
                    if ($mobileOS != null) {
                        // mobile app
                        $mongoUser->segment->signup_app_os = $mobileOS;
 
                        $mobileTraits['signup_app_os'] = $mobileOS;
 
                        $track = array(
            "userId" => $new_user->id,
            "event" => 'Sign up MobApp',
            "properties" => []
                        );
 
                        \Drooble\FW\DroobleSegment::track($track);
 
                    } else {
                        // mobile browsers
                    }
                    $mongoUser->create();
                    $mobileTraits['createdAt'] = date_format(date_timestamp_set(new \DateTime(), $new_user->registration_time)->setTimezone(new \DateTimeZone($timeZone)), 'c'); //new Date('2009-07-20T22:55:29.000Z')
                    \Drooble\FW\DroobleSegment::identify([
                            "userId" => $new_user->id,
                            "traits" => $mobileTraits
                        ]);
 
                } else {
 
                    $mongoUser->segment->signup_platform = "Desktop";
                    $mongoUser->create();
                    \Drooble\FW\DroobleSegment::identify([
                            "userId" => $new_user->id,
                            "traits" => array(
                                "name" => $new_user->first_name . " " . $new_user->last_name,
                                "email" => $new_user->email,
                                "signup_platform" => "Desktop",
                                "signup_method" => $facebook ? "facebook" : "email",
                                "createdAt" => date_format(date_timestamp_set(new \DateTime(), $new_user->registration_time)->setTimezone(new \DateTimeZone($timeZone)), 'c'), //new Date('2009-07-20T22:55:29.000Z')
                            )
                    ]);
                }
$log[] = array(__FUNCTION__, "point4", $data['email'], round(microtime(true)-$start,3));
                // Check and delete from incomplete registrations
                $check = \Models\IncompleteRegistrations::findFirst([
                    'conditions' => 'email LIKE {email:str}',
                    'bind' => ['email' => $data['email']]
                ]);
                if ($check) {
                    $check->delete();
                    // delete 'did-not-sign-up' reminder emails that were going to be sent
                    $mailNotif = \Models\UserEmailNotifications::find([
                        'conditions' => "type IN ('did-not-sign-up-reminder', 'did-not-sign-up-reminder-follow-up-1', 'did-not-sign-up-reminder-follow-up-2') AND data LIKE {email:str}",
                        'bind' => ['email' => '%'.$data['email'].'%'],
                        'limit' => 3
                    ]);
                    foreach($mailNotif as $notif) {
                        $notif->delete();
                    }
                }
 
                /*set PreSignUpColectedUserData*/
                if($preSignUpColectedUserData = \Collections\PreSignUpColectedUserData::findById($data['presignid'])){
                    $preSignUpColectedUserData->registered=true;
                    $preSignUpColectedUserData->user_id = $new_user->id;
                    $preSignUpColectedUserData->signup_time = time();
                    $preSignUpColectedUserData->save();
                }
 
//                $hooks = $di->getHooks();
//                $hooks::add("onUserRegistered", ['uid' => $new_user->id]);
                \Drooble\Activities\Activities::add("userRegistered", $new_user, $new_user);
$log[] = array(__FUNCTION__, "point5", $data['email'], round(microtime(true)-$start,3));
                // Add want todo stuff
                if (isset($data['want_todo']) AND is_array($data['want_todo']) AND !empty($data['want_todo'])) {
                    $want_todo = array_unique($data['want_todo']);
                    foreach($want_todo as $want) {
                        $user_want_todo = new \Models\UserWantTodo;
                        $user_want_todo->user_id = $new_user->id;
                        if (in_array($want, $user_want_todo->want_list)) {
                            $user_want_todo->want = $want;
                            $user_want_todo->create();
                        }
                    }
                }
 
                $log[] = array(__FUNCTION__, "slowpoint1", $data['email'], round(microtime(true)-$start,3));
 
        $profile_album = new \Models\UserElements();
        $profile_album->type = 'picture_album';
        $profile_album->creator_id = $new_user->id;
        $profile_album->title = "Profile photos";
        $profile_album->deep = 0;
        $profile_album->is_default = 1;
        $profile_album->published_time = time();
        $profile_album->deployed_time = time();
        $profile_album->save();
$log[] = array(__FUNCTION__, "slowpoint2", $data['email'], round(microtime(true)-$start,3));
        $wall_album = new \Models\UserElements();
        $wall_album->type = 'picture_album';
        $wall_album->creator_id = $new_user->id;
        $wall_album->title = "Wall photos";
        $wall_album->is_default = 1;
        $wall_album->published_time = time();
        $wall_album->deployed_time = time();
        $wall_album->save();
$log[] = array(__FUNCTION__, "slowpoint3", $data['email'], round(microtime(true)-$start,3));
        $cover_album = new \Models\UserElements();
        $cover_album->type = 'picture_album';
        $cover_album->creator_id = $new_user->id;
        $cover_album->title = "Cover photos";
        $cover_album->is_default = 1;
        $cover_album->published_time = time();
        $cover_album->deployed_time = time();
        $cover_album->save();
$log[] = array(__FUNCTION__, "slowpoint4", $data['email'], round(microtime(true)-$start,3));
        $video_album = new \Models\UserElements();
        $video_album->type = 'video_album';
        $video_album->creator_id = $new_user->id;
        $video_album->title = "Videos";
        $video_album->is_default = 1;
        $video_album->published_time = time();
        $video_album->deployed_time = time();
        $video_album->save();
$log[] = array(__FUNCTION__, "slowpoint5", $data['email'], round(microtime(true)-$start,3));
                $audio_album = new \Models\UserElements();
                $audio_album->title = "Singles";
                $audio_album->type = "audio_album";
                $audio_album->creator_id = $new_user->id;
                $audio_album->is_default = 1;
                $audio_album->published_time = time();
                $audio_album->deployed_time = time();
                $audio_album->save();
$log[] = array(__FUNCTION__, "slowpoint6", $data['email'], round(microtime(true)-$start,3));
        $new_user->profile_album_id = $profile_album->id;
        $new_user->wall_album_id = $wall_album->id;
        $new_user->cover_album_id = $cover_album->id;
        $new_user->video_album_id = $video_album->id;
                $new_user->audio_album_id = $audio_album->id;
 
        if ($new_user->save()===false){
          foreach ($new_user->getMessages() as $message) {
                \Helpers\Debug::log($message);
            }
            return array(true,"general error 4");
        }
$log[] = array(__FUNCTION__, "slowpoint7", $data['email'], round(microtime(true)-$start,3));
        //add Drooble Support profile to followings (circle: Other - 4)
                $usm = new \Models\UserCircleMembers();
                $usm->user_id = $new_user->id;
                $usm->circle_id = 4;
                $usm->target_user_id = 1;
                if ($usm->save() === FALSE) {
                    foreach ($usm->getMessages() as $message) {
                \Helpers\Debug::log($message);
            }
            return array(true,"general error 5");
                }
$log[] = array(__FUNCTION__, "slowpoint8", $data['email'], round(microtime(true)-$start,3));
                $usmD = new \Models\UserCircleMembers();
                $usmD->user_id = 1;
                $usmD->circle_id = 4;
                $usmD->target_user_id = $new_user->id;
                if ($usmD->save() === FALSE) {
                    foreach ($usmD->getMessages() as $message) {
                \Helpers\Debug::log($message);
            }
            return array(true,"general error 6");
                }
 
                // set following default communities
                \Helpers\UserTags::setDefaultUserCommunities($new_user->id);
 
$log[] = array(__FUNCTION__, "slowpoint9", $data['email'], round(microtime(true)-$start,3));
 
        //get profilepic from facebook
 
 
        /*if ($facebook and !empty($data['fb_picture'])) {
 
          $img_Base64string = base64_encode(file_get_contents($data['fb_picture']['url']));
          //$headers=exif_read_data($img_Base64string);
          //$filename = md5(microtime()).".jpg";
 
          $element = new \Models\UserElements();
          $element->type = 'picture';
          $element->creator_id = $new_user->id;
          $element->album_id = $profile_album->id;
 
          //$img = new \File\Picture($img_Base64string);
          $img = new \Drooble\File\Picture($img_Base64string);
          $img->uid = $new_user->id;
 
          $img->save('originals');
          $img->save('images');
          $img->save('images_tn');
          $img->save('images_comment');
          $img->save('images_comment_tn');
 
          $element->local_picture_filename = $img->filename;
          $element->save();
 
 
          $datae=array(
              "element_id"=> $element->id,
              "type" => 'profile'
            );
          $res = \Helpers\Pictures::createDefault($datae, $new_user->id);
 
          \Helpers\Albums::repairPosition($profile_album->id);
 
        }*/
$log[] = array(__FUNCTION__, "slowpoint10", $data['email'], round(microtime(true)-$start,3));
        if( \Helpers\UserSessions::getInstance()->fillProfileSessionOnLogin($new_user->id, $facebook) ===true){
 
$log[] = array(__FUNCTION__, "slowpoint11", $data['email'], round(microtime(true)-$start,3));
 
          if(isset($data["remember"]) || $facebook){
            \Helpers\UserSessions::getInstance()->keepMeLoggedIn($new_user, $facebook );
                    }
$log[] = array(__FUNCTION__, "slowpoint12", $data['email'], round(microtime(true)-$start,3));
 
//                    \Helpers\UserSessions::getInstance()->updateLastSeen();
                    $result=array(true,array("save"=>true,"account"=>array("id" => $new_user->id ,"user"=>$new_user->username, "lang"=>self::session()->lang)));
        }else{
          $result=array(true,array("save"=>false,"errors"=>array("error"=>"general2")));
        }
$log[] = array(__FUNCTION__, "point6", $data['email'], round(microtime(true)-$start,3));
        //TODO: move this to complete profile
        //
        // NOTE: 02/11/2016: Stopper per Yasen's request. Moved to mixpanel
        // \Lib\MailGenerator::generateGeneralFeedback($new_user);
        // thank you email, 24h after registration
        // \Lib\MailGenerator::generateFounderThankYou($new_user);
 
        // //Check if we need to notify people, who have invited the person
        //
        // $invites = \Models\UserInvitations::find(array(
        //   "conditions"  => "invited_email = :email:",
        //   "bind"      => array(
        //     "email"    => $new_user->email
        //   )
        // ));
        //
        // foreach ($invites as $invite)
        // {
        //   \Lib\MailGenerator::generateFriendsJoinedDrooble($invite->User, $new_user);
        // }
 
                //send email verification, if not facebook
 
        //check for invitation cookie
 
 
        if(isset($_COOKIE['invitestr'])){
          $invitation = \Helpers\Basic::decInviteString($_COOKIE['invitestr']);
          $invitations_realized = new \Models\UserInvitationsRealized();
          $invitations_realized->new_user_id = $new_user->id;
          $invitations_realized->invitor_id = $invitation['uid'];
          $invitations_realized->correspondent = $invitation['correspondent'];
          $invitations_realized->save();
 
//                    $hooks::add("onInvitationRealized", ['uid' => $new_user->id, 'invitor' => $invitation['uid']]);
                    $opParams = ['karmaonInvitationRealized' => ['invitor' => $invitation['uid']]];
                    $invitorO = \Models\Users::findFirst($invitation['uid']);
                    if(isset($_COOKIE['invact'])){
                        $cookieData = \Helpers\Basic::decInviteAction($_COOKIE['invact']);
                        $opParams['action'] = $cookieData['action'];
                        $opParams['intdata'] = $cookieData['intdata'];
                        unset($_COOKIE['invact']);
                        setcookie("invact",'null',time()-60*60*24,"/"); // set cookie to yesterday
                    }
                    \Drooble\Activities\Activities::add("invitationRealized", $new_user, $invitorO, $opParams);
 
          unset($_COOKIE['invitestr']);
          setcookie("invitestr",'null',time()-60*60*24,"/"); // set cookie to yestarday
        }
 
                if (strlen($new_user->fb_id) > 5) {
                    if (isset($preSignUpColectedUserData->avatar->url)) {
                        \Helpers\TemporaryFunctions::createPhotoFromHttp(['profilePicUrl' => $preSignUpColectedUserData->avatar->url], $new_user->id, 'profile');
                    }
//                    \Helpers\Users::sendJoinNotificationsToFacebookFriends($new_user); // looks too obsolete
                }
 
                $new_user->is_email_verified = 0;
                \Helpers\Users::resendVerificationEmail($new_user);
 
        // Assign affiliate program
        if ($this->session->has('affiliate')) {
          $affiliator = \Models\Users::findFirst([
            'conditions' => 'id={id:int}',
            'bind' => ['id' => (int) $this->session->affiliate]
          ]);
 
          if ($affiliator) {
            $check = \Models\UserInvitationsAffiliate::findFirst([
              'conditions' => 'user_id={user:int} AND registered_user_id={registered:int}',
              'bind' => ['user' => $affiliator->id, 'registered' => $new_user->id]
            ]);
 
            if (!$check) {
              $bridge = new \Models\UserInvitationsAffiliate;
              $bridge->user_id = $affiliator->id;
              $bridge->registered_user_id = $new_user->id;
              $bridge->create();
            }
          }
        }
 
$log[] = array(__FUNCTION__, "point7", $data['email'], round(microtime(true)-$start,3));
 
 
//        \Drooble\FW\DroobleSegment::track(array(
//          "userId" => $new_user->id,
//          "event" => "Account Created",
//          "properties" => array(
//            "category" => 'Account',
////            "label" => $facebook ? "Facebook" : "Email"
//          )
//        ));
 
//        \Drooble\FW\DroobleSegment::identify(array(
//          "userId" => $new_user->id,
//          "traits" => array(
//            "email" => $new_user->email,
//            "name" => $new_user->name,
//            // "iam" => {{field value}},
//          // "instruments" => {{field value}},
//          // "genres" => {{field value}},
//          // "influences" => {{field value}},
//          // "equipment" => {{field value}},
//          "birthday" => $new_user->birthday,
//          "gender" => $new_user->gender,
//          "location" => $cached_google_places->google_place_details_en ? $cached_google_places->google_place_details_en : "",
//          "createdAt" => date("c"),//new Date('2009-07-20T22:55:29.000Z'),
//          //"page" => '{{Page URL}}'
//          "affiliate" => $invitation['uid']
//          )
//        ));
 
 
      }
      //DO SEARCH INDEX
            $log[] = array(__FUNCTION__, "profileIndexing start", $data['email'], round(microtime(true)-$start,3));
      \Helpers\Search::profileIndexing($new_user->id);
            $log[] = array(__FUNCTION__, "profileIndexing end", $data['email'], round(microtime(true)-$start,3));
 
            // Signed
            // \Helpers\ActivityBox::add('signed');
    }
    /*
    if ( isset($result[1]) && $result[1]['save'] == true ) {
            $tCode->used = 1;
            if ($tCode->save() === FALSE) {
                foreach($tCode->getMessages() as $message) {
                    \Helpers\Debug::log($message);
                }
            }
    }*/
 
            //\Helpers\Debug::log($log);
 
    return $result;
  }
 
  public static function saveTipsProgress( $data ) {
    if ( isset($data['json_key']) ) {
//      $gs = !is_array($_SESSION['profile']['getting_started']) || $_SESSION['profile']['getting_started']===true ? array() : $_SESSION['profile']['getting_started'] ;
//      $gs[$data['json_key']] = $data['json_val'];
//      $_SESSION['profile']['getting_started'] = $gs;
//      $json = json_encode($gs);
 
            $user = \Models\Users::findFirst("id=".$_SESSION['profile']['id']);
//      $user->getting_started = $json;
            $gs_decoded = json_decode($user->getting_started, true);
            if (!is_array($gs_decoded)) {
                $gs_decoded = array();
            }
            $gs_decoded[$data['json_key']] = $data['json_val'];
            $_SESSION['profile']['getting_started'] = $gs_decoded;
            $user->getting_started = json_encode($gs_decoded);
 
 
      if ($user->save() === FALSE) {
                foreach ($user->getMessages() as $message) {
              \Helpers\Debug::log($message);
          }
                return array(false, 'db problem');
            } else {
                return array(true, $user->getting_started);
            }
 
//      if ( $update !== FALSE ) {
//        $_SESSION['profile']['getting_started'] = $gs;
//
//        return array(true, $json);
//      }
    }
    return array(false, 'problem');
  }
 
  public static function sendJoinNotificationsToFacebookFriends (User $joinedUser)
  {
    $fbInfo = (array)json_decode($joinedUser->UsersSavedInfo->facebook_info);
    $fbFriends = (array)$fbInfo["friends"];
    if (count($fbFriends) == 0)
      return false;
 
    $fbIds = array_map(function ($e) { $e1 = (array)$e; return $e1['id']; }, $fbFriends);
    $users = \Models\Users::query()
      ->inWhere("fb_id", $fbIds)
      ->execute();
 
    foreach ($users as $user)
    {
      MailGenerator::generateFriendsJoinedDrooble($user, $joinedUser);
//      PushNotificationManager::generateInvitedFriendJoined($user, array(
//        "person"   => $joinedUser
//      ));
    }
 
    return true;
  }
 
  /**
   * TODO: implement
   * sendNotificationsAboutJoinedUser - Send notifications that the target user has joined the websites
   *   to all users who have invited them and are not friends with them.
   *
   * @param  {type} User $user description
   * @return {type}            description
   */
  public static function sendNotificationsAboutJoinedUser (User $joinedUser)
  {
    $builder = \Phalcon\Di::getDefault()->getModelsManager()->createBuilder();
    $builder->addFrom("\Models\UserInvitations", "ui");
    $builder->columns("ui.user_id");
    $builder->distinct(true);
    $builder->leftJoin("\Models\UserCircleMembers", "cm.user_id = ui.user_id AND cm.target_user_id = :uid:", "cm");
    $builder->where("ui.invited_email = :email:");
    $builder->andWhere("ui.user_id != :uid:");
    $builder->andWhere("cm.id is NULL");
 
    $results = $builder->getQuery()->execute(array(
      "uid"   => $joinedUser->id,
      "email" => $joinedUser->email
    ))->toArray();
 
    if (!count($results))
    {
      return false;
    }
 
    $results = array_map(function ($e) { return $e['user_id']; }, $results);
    $users = \Models\Users::query()
      ->inWhere("id", $results)
      ->execute();
 
    foreach ($users as $user)
    {
      MailGenerator::generateFriendsJoinedDrooble($user, $joinedUser);
 
//            PushNotificationManager::generateFacebookFriendJoined($user, array(
//                "person"    => $joinedUser
//            ));
 
            /*
      PushNotificationManager::generateInvitedFriendJoined($user, array(
        "person"   => $joinedUser
      ));*/
    }
 
    return true;
  }
 
 
  /**
   * getUserFlags - Gets the current flags for the user. If they don't exist, creates them and returns the object.
   *
   * @param  {type} User $user description
   * @return {type}            description
   */
  public static function getUserFlags (User $user)
  {
    $flags = $user->Flags;
 
    if (!$flags)
    {
      $flags = new \Models\UserFlags();
      $flags->user_id = $user->id;
      $flags->last_like_email_time = 0;
      $flags->last_post_email_time = 0;
      $flags->has_seen_feed = 0;
 
      if (!$flags->save())
        return false;
    }
 
    return $flags;
  }
 
  /**
   * markFeedSeen - Marks the feed seen in UsersFlags and sends invite email confirmation.
   * @return  {Boolean}  Whether the action succeeded.
   */
  public static function markFeedSeen ()
  {
    $user = \Models\Users::findFirst($_SESSION['profile']['id']);
    $flags = self::getUserFlags($user);
 
    if ($flags->has_seen_feed)
      return false;
 
    $_SESSION['profile']['has_gone_to_feed'] = 1;
    $flags->has_seen_feed = 1;
    $flags->save();
 
    self::sendNotificationsAboutJoinedUser($user);
 
    return true;
  }
 
  /**
   * subscribeUserToElement - Adds a UserNotificationSubscription to the target element.
   * @param User   $user    The target user.
   * @param User   $element  The target element.
   * @return {Boolean}    The result of the operation (true/false).
   */
  public static function subscribeUserToElement(User $user, Element $element)
  {
    //Find if previous subscription exists
    $subscription = Subscription::findFirst(array(
      "conditions"  => "user_id = :uid: AND element_id = :eid:",
      "bind"      => array(
        "uid"    => $user->id,
        "eid"    => $element->id
      )
    ));
 
    if ($subscription)
      return false;
 
    $subscription = new Subscription();
    $subscription->user_id = $user->id;
    $subscription->element_id = $element->id;
    $subscription->type = Subscription::TYPE_ALL;
 
    return $subscription->save();
  }
 
  /**
   * Unsubscribes the user from notifications for this element.
   *
   * @param     $user - The user to unsubscribe.
   * @param     $element - THe element to unsubscribe from
   * @return    void
   */
  public static function unsubscribeUser(User $user, Element $element)
  {
    $subscription = Subscriptions::findFirst(array(
      "conditions"  => "user_id = :uid: AND element_id = :eid:",
      "bind"      => array(
        "uid"    => $user->id,
        "eid"    => $element->id
      )
    ));
 
    return $subscription->delete();
  }
 
    public static function getUserStatuses( $id = FALSE ) {
        $statuses = unserialize( USER_STATUS_STATEMENTS );
 
        if ( $id !== FALSE ) {
            return $statuses[$id];
        }
 
        return json_encode( $statuses );
    }
 
    public static function getUserChatStatuses( $id = FALSE ) {
    $statuses = unserialize( CHAT_STATUS_STATEMENTS );
 
    if ( $id !== FALSE ) {
      return $statuses[$id];
    }
 
    return json_encode( $statuses );
  }
 
    public static function getMyInviteUrl($correspondent=false){
    $uid=$_SESSION['profile']['id'];
    if(!$uid) $uid = 1;
    $protocol= explode("/", \Config::defaults()->HOME_URL)[0] ? '' : strtolower(substr($_SERVER["SERVER_PROTOCOL"],0,strpos( $_SERVER["SERVER_PROTOCOL"],'/'))).":";
    return $protocol . \Config::defaults()->HOME_URL . "i/". \Helpers\Basic::encInviteString($uid,$correspondent); //$this->encInviteString($uid,$correspondent);
  }
 
    public static function getUserShortInfoFromSessionToken ($token) {
        $ownId = self::session()->get('profile')['id'];
 
        //Get the info if the id of either of the session matches
        //the token we need to get the relevant entry in the users_pbx
        $sql = "SELECT `short_info_json`, `user_id`
                FROM `search_cache`
                LEFT JOIN `users_pbx`
                ON `search_cache`.`user_id` = `users_pbx`.`side1_user_id`
                    OR `search_cache`.`user_id` = `users_pbx`.`side2_user_id`
                WHERE `users_pbx`.`token` = :token
                ";
        $params = array('token' => $token);
        $res = self::db()->query($sql, $params);
        $res->setFetchMode(\Phalcon\Db::FETCH_ASSOC);
        $r = $res->fetchAll();
 
        //        \Helpers\Debug::log('getUserShortInfoFromSessionToken');
        //        \Helpers\Debug::log($r);
 
        //side 1 in the session
        //we need to get the data for the other person
        $msg = $r[0]['user_id'] == $ownId ? $r[1] : $r[0];
        $msg = json_decode($msg['short_info_json'], true);
 
        return $msg;
    }
 
    public function pageCreate($data, $creator_id = null, $skipPublicity = false, $makeAdminInvitation = false) {
//        $creator_id = $creator_id ? (int) $creator_id : self::session()->get('profile')['id'];
//    $sess_profile = self::session()->get('profile');
//
//    if( !$creator_id || !$sess_profile){
//      return array( false, "user not logged in" );
//    }
//
//    if ( count($sess_profile['user_profiles_ids']) > 5 ) { // 6 - when 5 pages and self
//            $badmsg = self::translate()->get("max_pages_msg");
//            return array(false, $badmsg);
//        }
//
//    $user_input_name = $data['alias'] ? $data['alias'] : $data['name'];
//        $user_input_name = str_replace(['<', '>', '"', "'"], '', $user_input_name);
//
//    if($user_input_name){
//      $exists_user = \Models\Users::findFirst( array( "conditions"=>"username = :name: OR username_old = :name:","bind"=>array( "name" => $user_input_name ) ) );
//      if ($exists_user) {
//                $badmsg = self::translate()->get("username_exists");
//        return array(false, $badmsg);
//      }
//    }else{
//      return array(false,"wrong username");
//    }
//
//    if (preg_match('/^[0-9]+$/', trim($user_input_name))) {
//      return array(false, 'enter a page name');
//    }
//
//        if (strlen($user_input_name) < 6 OR strlen($user_input_name) > 26 OR preg_match('/\s/', $user_input_name) ) {
//            $name = \Helpers\Basic::transliterate($user_input_name, '.');
//            $page_username = \Helpers\Users::build_username($name, false);
//        } else {
//            $page_username = $user_input_name;
//        }
//
//        $page = new \Models\Users();
//    $page->first_name = ucfirst(str_replace(['<', '>', '"', "'"], '', $data['name']));
//    $page->username = $page_username;
//    $page->lang = self::session()->lang;
//    $page->is_page = 1;
//
//        // ugly hardcode - set location for imported pages
//        if (array_key_exists('hardcode_location', $data)) {
//            $page->city = $data['hardcode_location']; //'ChIJ9Xsxy4KGqkARYF6_aRKgAAQ';
//        }
//
//        if (array_key_exists('website', $data)) {
//            $link = str_replace(array('http://', 'https://'), '', $data['website']);
//            if (filter_var("http://" . $link, FILTER_VALIDATE_URL)) {
//                $page->websites = json_encode(array($link));
//            }
//        }
//        if (array_key_exists('email', $data)) {
//            $page->email = $data['email'];
//        }
//        if (array_key_exists('telephone_number', $data)) {
//            $page->telephone_number = $data['telephone_number'];
//        }
//
//            // Set page type (default, prevent some hack)
//        $check = \Models\UserPageTypes::findFirst([
//            'conditions' => 'parent_id IS NOT NULL',
//            'order' => 'id ASC'
//        ]);
//        $page->page_type_id = $check->id;
//
//        // Set custom page type (user select)
//        if (isset($data['page_type'])) {
//            $check = \Models\UserPageTypes::findFirst([
//                'conditions' => 'id={id:int} AND parent_id IS NOT NULL',
//                'bind' => ['id' => (int)$data['page_type']]
//            ]);
//
//            if ($check) {
//                $page->page_type_id = $check->id;
//            }
//        }
//
//    if ($page->save() == false) {
//        foreach ($page->getMessages() as $message) {
//            echo $message, "\n";
//        }
//
//      return false;
//    }
//
//    $page->save();
//    $_SESSION['profile']['user_profiles'][] = array("id" => $page->id, "username" => $page_username, "name" => $data['name'] );
//    $_SESSION['profile']['user_profiles_ids'][] = $page->id;
//
//    $page_rights = new \Models\UserPageRights();
//    $page_rights->user_id = $creator_id;
//    $page_rights->page_id = $page->id;
//    $page_rights->role_id = 1;
//
//    $page_rights->save();
//
//    $profile_album = new \Models\UserElements();
//    $profile_album->title = "Profile photos";
//    $profile_album->type = "picture_album";
//    $profile_album->creator_id = $page->id;
//    $profile_album->is_default = 1;
//    $profile_album->published_time = time();
//    $profile_album->deployed_time = time();
//    $profile_album->save();
//
//    $wall_album = new \Models\UserElements();
//    $wall_album->title = "Wall photos";
//    $wall_album->type = "picture_album";
//    $wall_album->creator_id = $page->id;
//    $wall_album->is_default = 1;
//    $wall_album->published_time = time();
//    $wall_album->deployed_time = time();
//    $wall_album->save();
//
//    $cover_album = new \Models\UserElements();
//    $cover_album->title = "Cover photos";
//    $cover_album->type = "picture_album";
//    $cover_album->creator_id = $page->id;
//    $cover_album->is_default = 1;
//    $cover_album->published_time = time();
//    $cover_album->deployed_time = time();
//    $cover_album->save();
//
//    $audio_album = new \Models\UserElements();
//    $audio_album->title = "Singles";
//    $audio_album->type = "audio_album";
//    $audio_album->creator_id = $page->id;
//    $audio_album->is_default = 1;
//    $audio_album->published_time = time();
//    $audio_album->deployed_time = time();
//    $audio_album->save();
//
//    $video_album = new \Models\UserElements();
//    $video_album->type = 'video_album';
//    $video_album->creator_id = $page->id;
//    $video_album->title = "Videos photos";
//    $video_album->is_default = 1;
//    $video_album->published_time = time();
//    $video_album->deployed_time = time();
//    $video_album->save();
//
//    $page->profile_album_id = $profile_album->id;
//    $page->wall_album_id = $wall_album->id;
//    $page->cover_album_id = $cover_album->id;
//    $page->video_album_id = $video_album->id;
//        $page->audio_album_id = $audio_album->id;
//
//    $page->save();
//
//    //TODO - errors check;
//
//    \Helpers\Search::profileIndexing($page->id);
//        if (!$skipPublicity) { // $skipPublicity is used for FB imported Pages
//           \Drooble\Activities\Activities::add("createdPage", $creator_id, $page);
//           \Helpers\ActivityBox::add('create-band', false, $page);
//
//           $track = array(
//            "userId" => $creator_id,
//            "event" => 'Page created',
//            "properties" => array(
//              "category" => 'Page',
//              "label" => $check ? $check->title : '',
//              "page_category"=> $check ? $check->Parent->title : '',
//              "page_name" => $page->first_name,
//              "Drooble_web_address" => "https://drooble.com/".$page->username
//            )
//          );
//            \Drooble\FW\DroobleSegment::track($track);
//        }
//
//        if ($makeAdminInvitation) {
//            $code = \Lib\Cryptography::encrypt(
//                    serialize(array('action' => 'pai', 'pid' => $page->id))
//                    , \Config::common()->EMAIL_LINK_CRYPTO_KEY);
//            $pai = new \Models\PageAdminInvitations();
//            $pai->code = $code;
//            $pai->page_id = $page->id;
//            $pai->save();
//        }
//
//    return  array( true, array( "save" => true, "page" => array( "id" => $page->id, "name" => $data['name'], "alias" => $page_username )));
 
    }
 
    public static function insertUsersText($data, $uid = null) {
    $uid = $uid !== null ? $uid : self::session()->get('profile')['id'];
        $return = false;
 
    if ($data['pid']) {
      $pid = $data['pid'];
      if (!\Helpers\Basic::checkUserRights($pid, $uid))
                $return = array(false, 'Not an admin user.');
 
      $uid = $pid;
    }
 
        $title = $data['title'];
        $text = $data['text'];
    $scene = $data['scene'];
    $value = array();
    $value['user_id']=$uid;
        $userText = new \Models\UserTexts();
    if (in_array($scene,unserialize(USER_TEXTS_SCENES))) {
            $userText->scene = $scene;
    } else {
            $return = array(false,'scene error');
    }
 
    if ($title) {
            $userText->title = $title;
    }
    if ($text) {
            $userText->text = $text;
    }
        $userText->user_id = $uid;
 
    if ($userText->save()) {
            //            $id = $userText->id;
            // Reindex
            self::db()->update( 'users_profiles',
                array('is_need_of_reindex'),
                array(1),
                array(
                    'conditions' => "id = ?",
                    'bind' => array($uid),
                    'bindTypes' => array(\PDO::PARAM_INT)
                ),
                array(\PDO::PARAM_INT)
            );
 
            $return = array(true, $userText->toArray());
    } else {
            $errrs = $userText->getMessages();
            $return = array(false, $errrs);
        //            $return = array(false, "Can't insert");
    }
        return $return;
    }
 
    public static function forceSegmentIdentify($onceForOldUser = false) {
        $profSess = self::session()->profile;
 
        if ($onceForOldUser) {
            $userFlags = \Models\UserFlags::findFirst([
                        'conditions' => 'user_id={id:int}',
                        'bind' => ['id' => $profSess['id']]
            ]);
            if ($userFlags->olduser_segment_identified) {
                return; // already done
            }
        }
 
        $user = \Models\Users::findFirst($profSess['id']);
        $user_details = self::getDetailsById($profSess['id']);
        $tmp = explode(',', $user_details['place']);
        if ( count($tmp) == 2) {
            $city = trim($tmp[0]);
            $country = trim($tmp[1]);
        }
 
        $timeZone = \Helpers\Time::getUserTimezone($user);
        $traits = array(
            "email" => $user->email,
            "name" => \Helpers\Users::prepareDisplayName( $user->first_name, $user->last_name ),
            "birthday" => $user->birthday,
            "gender" => $user->gender,
            "town" => isset($city) ? $city : '',
            "country" => isset($country) ? $country : '',
            "createdAt" => date_format(date_timestamp_set(new \DateTime(), $user->registration_time)->setTimezone(new \DateTimeZone($timeZone)), 'c'), //new Date('2009-07-20T22:55:29.000Z')
            "email_verified" => $user->is_email_verified ? "yes" : "no"
        );
 
        if ($_SESSION['profile']['teach'] == 1) {
            $traits['teacher'] = 'yes';
        }
        ($profSess['avatar_id'] !== null && $profSess['avatar_id'] > 0) ? $traits['profilePicSet'] = 'yes' : 'no';
        ($profSess['cover_id'] !== null && $profSess['cover_id'] > 0) ? $traits['coverPicSet'] = 'yes' : 'no';
 
        // tag_type => tag label for Segment
        $tags_map = array(
            'profile' => 'Iam',
            'instruments' => 'instrument',
            'genres' => 'genre',
            'influences' => 'influences',
            'equipment' => 'equipment',
        );
        // send first 3 of all tag types
        foreach($tags_map as $tag_type => $tag_label) {
           if (is_array($user_details['tags'][$tag_type])) {
                foreach ($user_details['tags'][$tag_type] as $k => $tag) {
                    if ($k < 3) {
                        $traits[$tag_label . ($k + 1)] = $tag['name'];
                    } else {
                        break;
                    }
                }
            }
        }
 
        $fromInvite = \Models\UserInvitationsRealized::findFirst([
            'conditions' => 'new_user_id={id:int}',
            'bind' => ['id' => $user->id]
        ]);
        if ($fromInvite) {
            $traits['affiliate'] = $fromInvite->invitor_id;
        }
 
        $user_want_todo = \Models\UserWantTodo::find([
            'conditions' => 'user_id={user:int}',
            'bind' => ['user' => $user->id]
        ]);
 
        foreach($user_want_todo as $uwt) {
            switch ($uwt->want) {
                case 'create-page':
                    $traits['create_page'] = true;
                    break;
                case 'find-bandmates':
                    $traits['find_bandmates'] = true;
                    break;
                case 'share':
                    $traits['share_music'] = true;
                    break;
                case 'take-lessons':
                    $traits['take_lessons'] = true;
                    break;
                case 'teach':
                    $traits['teach'] = true;
                    break;
                case 'jam':
                    $traits['jam_online'] = true;
                    break;
                case 'discover':
                    $traits['dis_music_kn'] = true;
                    break;
                case 'sell-something':
                    $traits['sell_some'] = true;
                    break;
                default:
                    break;
            }
        }
 
        $iRes = \Drooble\FW\DroobleSegment::identify(array(
            "userId" => $user->id,
            "traits" => $traits
        ));
 
        if ($onceForOldUser) {
            $userFlags->olduser_segment_identified = 1;
            $userFlags->save();
        }
 
        return;
    }
 
    /* MongoDB read
     * Note: if you need parts ot this data or need it structured in other way -> consider creating UserTags.php helper
     * $returnStruct
     *  when 0 => flat list
     *  when 1 => tree structure ['<legacy type>instruments' => ...]
     *  when 2 - ['ObjectId' => [remapped types]]
     * @detailed - much more data
     */
    public static function getUserTags($uid = null, $returnStruct = 1, $detailed = false, $lang = null, $fillMissingTranslations = true) {
        if (!$uid && !self::session()->isStarted()) {
            throw new \Exception("missing uid in getUserTags");
        }
        $lang = $lang ? $lang : \Helpers\Basic::lang();
        $languages_config = \Config::languages()->language;
        $user_id = $uid ? $uid : self::session()->get('profile')['id'];
        $user = \Collections\Users::findFirst([
            "conditions" => ["id" => (int) $user_id]
        ]);
 
        $return = [];
        $userTags = [];
        $mongoTags = [];
        foreach ($user->tags as $tag) {
            $userTags[(string) $tag->_id] = $tag;
        }
        $objectIds = array_map(function($tag) { return $tag->_id; }, array_values($userTags));
        $mongoTagsRes = \Collections\Tags::find([
            "conditions" => ['_id' => ['$in' => $objectIds]]
        ]);
 
        // loop to make hex map and fill missing translations with default
        foreach ($mongoTagsRes as $tag) {
            if ($fillMissingTranslations) {
                // find existing translation (usually en)
                if (!property_exists($tag->translation, 'en')) {
                    foreach ($tag->translation as $current) {
                        $defaultTo = $current->text; // get first and break
                        break;
                    }
                } else {
                    $defaultTo = $tag->translation->en->text;
                }
                // fill missing translations with existing language
                foreach ($languages_config as $code => $dontneed) {
                    if (!property_exists($tag->translation, $code)) {
                        $tag->translation->{$code} = new \stdClass();
                        $tag->translation->{$code}->text = $defaultTo;
                    }
                }
            }
            $mongoTags[(string) $tag->_id] = $tag; // hex map
        }
 
        $order = 0; // this is not to brake tags (they didn't have order before)
        foreach ($userTags as $tagHex => $userTag) {
            foreach ($userTag->type as $remappedType => $info) {
                if ($info->status && $remappedType != 'community') {
                    $tmp = \Helpers\MongoMigration::$reverseTagTypesRemap[$remappedType];
                    $struct = new \stdClass();
                    $struct->type = $tmp['type'];
                    $struct->subtype = $tmp['subtype'];
                    $struct->translation = $mongoTags[$tagHex]->translation;
                    // tag text in requested language
                    $struct->name = mb_convert_case(stripslashes($mongoTags[$tagHex]->translation->{$lang}->text), MB_CASE_TITLE, 'UTF-8');
 
                    $displayOrder = ((int) $info->order) ? (int) $info->order : $order;
                    $struct->order = $displayOrder;
                    if ($returnStruct == 1) {
                        $return[$struct->type][] = ['order' => $displayOrder, 'data' => $detailed ? $struct : $struct->name];
                    } else if ($returnStruct == 0) {
                        // flat list is ordered outside of this func ... i know .. omg .. not my choice or fault
                        // find in project *keepcalmandorder*
                        $return[] = $detailed ? $struct : $struct->name;
                    } else if ($returnStruct == 2) {
                        $return[$tagHex][] = ['order' => $displayOrder, 'data' => $remappedType];
                    } else if ($returnStruct == 3) {
                        $return[$remappedType][] = ['order' => $displayOrder, 'data' => $detailed ? $struct : $struct->name];
                    }
                    $order++;
                }
            }
        }
 
        // .. this is a patch - not the nicest thing
        if ($returnStruct) {
            foreach ($return as $key => $tags) {
                usort($tags, ['\Helpers\Tags', 'sortTags']);
                $return[$key] = array_map(function($t) { return $t['data'];}, $tags);
            }
 
            if ($returnStruct == 1 || $returnStruct == 3) {
                // sort tag types only when the $key is a type (legacy or remapped)
 
                $tmpReturn = [];
                foreach ($return as $key => $tags) {
                    $tmpReturn[$key] = ['order' => $key, 'data' => $tags];
                }
                uasort($tmpReturn, ['\Helpers\Tags', 'tagTypesSort']);
                $return = [];
                foreach ($tmpReturn as $key => $data) {
                    $return[$key] = $data['data'];
                }
            }
 
        }
 
        return $return;
    }
 
    // TODO in model
    // calculates User languages from their tags and users_profiles.lang field
    // return array lang abbrev e.g. ['en','es']
    public static function getLanguages($uid = null, $userObj = null) {
        $uid = $uid ? (int) $uid : self::session()->get('profile')['id'];
        if(!$uid){
            return ['en'];
        }
        $userTags = \Helpers\Users::getUserTags($uid, 1, false, "en"); // "en" is OK here! trust me, this works for all languages
        $revMap = [];
        foreach (\Config::languages()->language as $code => $info) {
            $revMap[$info->name_in_english] = $code;
        }
 
        $userLangs = [];
        if ($userTags && isset($userTags['languages'])) {
            foreach ($userTags['languages'] as $tagLang) {
                if (isset($revMap[$tagLang])) {
                    $userLangs[] = $revMap[$tagLang];
                }
            }
        }
        if (!count($userLangs)) {
            if (!$userObj) {
                $userObj = \Models\Users::findFirst((int)$uid);
            }
            $userLangs[] = ($userObj && $userObj->lang) ? $userObj->lang : \Helpers\Basic::lang();
        }
        return $userLangs;
    }
 
    public static function getMongoUser($uid = null) {
 
    $uid = $uid ? (int) $uid : (int) self::session()->get("profile")['id'];
 
        $mongoUser = \Collections\Users::findFirst([
            "conditions" => ["id" => $uid]
        ]);
 
        if (!$mongoUser) {
            $details = self::getDetailsById($uid, null, true);
 
            $mongoUser = new \Collections\Users();
            $mongoUser->id = $uid;
            $mongoUser->is_page = (bool) $details['is_page'];
            $mongoUser->email = $details['email'];
            $mongoUser->first_name = $details['firstname'];
            $mongoUser->last_name = $details['lastname'];
            $mongoUser->username = $details['username'];
            $mongoUser->display_name = $details['display_name'];
      $mongoUser->karma_balance = 0;
      $mongoUser->karma_total = 0;
            $mongoUser->save();
        }
        return $mongoUser;
 
    }
 
    public static function hasPresskit($uid) {
        $has = \Collections\Users::findFirst([
            'conditions' => [
                'id' => (int) $uid,
                'presskit' => ['$exists' => true]
            ]
        ]);
        return (bool) $has;
    }
 
    public static function getUnfollowedFacebookFriends($uid = null, $limit = 20) {
        $result = [];
        $limit = (int) $limit;
 
        $uid = $uid ? $uid : self::session()->get('profile')['id'];
        if ($uid) {
            $manager = \Phalcon\Di::getDefault()->getModelsManager();
 
            $user = \Models\Users::findFirst($uid);
 
            if ($user AND $user->fb_id) {
                $like = '%"id":"'.(int)$user->fb_id.'"%';
                $phql = "SELECT u.id FROM \Models\Users u
                    WHERE u.id IN (SELECT user_id FROM \Models\UsersSavedInfo WHERE facebook_info LIKE '".$like."')
                        AND u.id NOT IN (SELECT target_user_id FROM \Models\UserCircleMembers WHERE user_id='".$user->id."')
                        AND u.id != '".$user->id."' AND u.is_disabled=0
                    LIMIT {$limit}
                ";
 
                $friends = $manager->executeQuery($phql);
                if (!empty($friends)) {
                    foreach($friends as $person) {
                        $try = \Helpers\Users::getUserShortInfoFromId($person->id);
                        $result[] = $try;
                    }
                }
            }
        }
 
        return $result;
    }
 
    public static function getFacebookFriends($uid = null, $limit = 0) {
        $result = [];
        $limit = (int) $limit;
 
        $uid = $uid ? $uid : self::session()->get('profile')['id'];
        if ($uid) {
            $manager = \Phalcon\Di::getDefault()->getModelsManager();
 
            $user = \Models\Users::findFirst($uid);
 
            if ($user AND $user->fb_id) {
                $like = '%"id":"'.(int)$user->fb_id.'"%';
                $phql = "SELECT u.id FROM \Models\Users u
                    WHERE u.id IN (SELECT user_id FROM \Models\UsersSavedInfo WHERE facebook_info LIKE '".$like."')
                        AND u.id != '".$user->id."' AND u.is_disabled=0 ";
                if ($limit) {
                    $phql .= " LIMIT {$limit}";
                }
 
                $friends = $manager->executeQuery($phql);
                if (!empty($friends)) {
                    foreach($friends as $person) {
                        $result[] = $person->id;
                    }
                }
            }
        }
 
        return $result;
    }
 
    public static function shortInfoByUsername($username)
    {
        return self::shortInfoById(self::getIdFromUsername($username));
    }
 
    public static function shortInfoById($id)
    {
        if ($id) {
 
            $user = \Models\Users::findFirst($id);
            $avatarEl = \Models\UserElements::findFirst([
                "conditions" => "type = 'picture' AND creator_id = :creator_id: AND album_id = :album_id: AND is_default = '1' AND deleted_time IS NULL AND published_time IS NOT NULL AND deployed_time IS NOT NULL",
                "bind" => ["creator_id" => $user->id, "album_id" => $user->profile_album_id]
            ]);
 
            if ($user) {
 
                $info = new \stdClass();
                $info->open_to_string = self::getOccupation($id);
                $info->display_name = $user->getDisplayName();
                $info->username = $user->username;
                $info->avatar = \Helpers\Pictures::prepareImage($avatarEl->local_picture_filename, $id,'avatar', $avatarEl->modified_time);
                $info->avatar_icon = \Helpers\Pictures::prepareImage($avatarEl->local_picture_filename, $id,'icon', $avatarEl->modified_time);
                if ($user->city) {
                    $place = \Models\GooglePlaces::findFirst([
                        "conditions" => "google_place_id = :google_place_id:",
                        "bind" => ["google_place_id" => $user->city]
                    ]);
 
                    if ($place) {
                        $info->place = $place->google_place_details_en;
                        $info->place_id = $place->google_place_id;
                    }
 
                }
 
                return $info;
 
            }
        }
 
        return false;
    }
 
}
#4Helpers\Users::getUserTags(12268, 3)
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/logic/search.class.php (1660)
<?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));
    }
 
}
 
?>
#5search->viewSearchUsersAndPages(Array([took] => 1, [timed_out] => , [_shards] => Array([total] => 5, [successful] => 5, [failed] => 0), [hits] => Array([total] => 216, [max_score] => 16.053133, [hits] => Array(11))), )
/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/logic/search.class.php (1593)
<?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));
    }
 
}
 
?>
#6search->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));
    }
 
}
 
?>
#7search->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);
?>
#8client->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;
}
 
 
#9include(/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);
#10include(/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
influencesArray ( [0] => Los Cafres )
KeyValue
USERwww-data
HOME/var/www
HTTP_CONNECTIONkeep-alive
HTTP_X_FORWARDED_PROTOhttps
HTTP_X_FORWARDED_PORT443
HTTP_X_FORWARDED_FOR34.239.154.240, 172.70.174.204
HTTP_USER_AGENTCCBot/2.0 (https://commoncrawl.org/faq/)
HTTP_CF_VISITOR{"scheme":"https"}
HTTP_CF_RAY77344f550bd48262-IAD
HTTP_CF_IPCOUNTRYUS
HTTP_CF_CONNECTING_IP34.239.154.240
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_PORT18286
REMOTE_ADDR34.239.154.240
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/?influences%5B%5D=Los+Cafres
SCRIPT_NAME/index.php
CONTENT_LENGTH
CONTENT_TYPE
REQUEST_METHODGET
QUERY_STRINGa=search&b=&c=&d=&e=&influences%5B%5D=Los+Cafres
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_FLOAT1669986700.9749
REQUEST_TIME1669986700
#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/config/apps/languages_by_country_code.php
54/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/lib/mysql.class.php
55/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/lib/client.class.php
56/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/layout.php
57/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/pages_content/0_php_scripting/search_result_page.php
58/var/www/rc/drooble/trunk/projects/drooble_v1/services/web/private/logic/search.class.php
59/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/Search/DroobleSearchManager.php
60/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ClientBuilder.php
61/var/www/rc/drooble/trunk/projects/drooble_v1/config/apps/elasticsearch.php
62/etc/drooble/elasticsearch.php
63/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/NullLogger.php
64/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/AbstractLogger.php
65/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/psr/log/Psr/Log/LoggerInterface.php
66/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/CurlHandler.php
67/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/CurlFactory.php
68/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php
69/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Client/Middleware.php
70/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SmartSerializer.php
71/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Serializers/SerializerInterface.php
72/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactory.php
73/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionFactoryInterface.php
74/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/RoundRobinSelector.php
75/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/Selectors/SelectorInterface.php
76/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/Connection.php
77/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Connections/ConnectionInterface.php
78/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/StaticNoPingConnectionPool.php
79/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/AbstractConnectionPool.php
80/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/ConnectionPool/ConnectionPoolInterface.php
81/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Transport.php
82/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Client.php
83/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IndicesNamespace.php
84/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/AbstractNamespace.php
85/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/ClusterNamespace.php
86/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/NodesNamespace.php
87/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/SnapshotNamespace.php
88/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/CatNamespace.php
89/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/IngestNamespace.php
90/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/TasksNamespace.php
91/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Namespaces/RemoteNamespace.php
92/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/Search/DroobleSearchClient.php
93/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/Search.php
94/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/elasticsearch/elasticsearch/src/Elasticsearch/Endpoints/AbstractEndpoint.php
95/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Core.php
96/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php
97/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php
98/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/FutureInterface.php
99/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/PromiseInterface.php
100/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/PromisorInterface.php
101/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php
102/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/FutureArray.php
103/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php
104/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php
105/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/FulfilledPromise.php
106/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/ExtendedPromiseInterface.php
107/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/react/promise/src/CancellablePromiseInterface.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/services/web/private/controllers/API/ElementsController.php
110/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleAPIController.php
111/var/www/rc/drooble/trunk/projects/drooble_v1/resources/helpers/Users.php
112/var/www/rc/drooble/trunk/projects/drooble_v1/resources/collections/Users.php
113/var/www/rc/drooble/trunk/projects/drooble_v1/resources/library/Drooble/FW/DroobleCollection.php
114/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mongodb/mongodb/src/Client.php
115/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mongodb/mongodb/src/Database.php
116/var/www/rc/drooble/trunk/projects/drooble_v1/resources/lib/mongodb/mongodb/src/Collection.php
Memory
Usage4194304