Phalcon Framework 5.9.3

MongoDB\Driver\Exception\ConnectionTimeoutException: No suitable servers found (`serverSelectionTryOnce` set): [connection refused calling hello on '10.234.61.207:27017']. Topology type: ReplicaSetNoPrimary

/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/functions.php (600)
#0MongoDB\Driver\Manager->selectServer
/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/functions.php (600)
<?php
/*
 * Copyright 2015-present 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
 *
 *   https://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 Exception;
use MongoDB\BSON\Document;
use MongoDB\BSON\PackedArray;
use MongoDB\BSON\Serializable;
use MongoDB\Builder\Type\StageInterface;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\Server;
use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\RuntimeException;
use MongoDB\Operation\ListCollections;
use MongoDB\Operation\WithTransaction;
use Psr\Log\LoggerInterface;
use ReflectionClass;
use ReflectionException;
use stdClass;
 
use function array_is_list;
use function array_key_first;
use function assert;
use function end;
use function get_object_vars;
use function is_array;
use function is_object;
use function is_string;
use function str_ends_with;
use function substr;
 
/**
 * Registers a PSR-3 logger to receive log messages from the driver/library.
 *
 * Calling this method again with a logger that has already been added will have
 * no effect.
 */
function add_logger(LoggerInterface $logger): void
{
    PsrLogAdapter::addLogger($logger);
}
 
/**
 * Unregisters a PSR-3 logger.
 *
 * Calling this method with a logger that has not been added will have no
 * effect.
 */
function remove_logger(LoggerInterface $logger): void
{
    PsrLogAdapter::removeLogger($logger);
}
 
/**
 * Create a new stdClass instance with the provided properties.
 * Use named arguments to specify the property names.
 *     object( property1: value1, property2: value2 )
 *
 * If property names contain a dot or a dollar characters, use array unpacking syntax.
 *     object( ...[ 'author.name' => 1, 'array.$' => 1 ] )
 *
 * @psalm-suppress MoreSpecificReturnType
 * @psalm-suppress LessSpecificReturnStatement
 */
function object(mixed ...$values): stdClass
{
    return (object) $values;
}
 
/**
 * Check whether all servers support executing a write stage on a secondary.
 *
 * @internal
 * @param Server[] $servers
 */
function all_servers_support_write_stage_on_secondary(array $servers): bool
{
    /* Write stages on secondaries are technically supported by FCV 4.4, but the
     * CRUD spec requires all 5.0+ servers since FCV is not tracked by SDAM. */
    static $wireVersionForWriteStageOnSecondary = 13;
 
    foreach ($servers as $server) {
        // We can assume that load balancers only front 5.0+ servers
        if ($server->getType() === Server::TYPE_LOAD_BALANCER) {
            continue;
        }
 
        if (! server_supports_feature($server, $wireVersionForWriteStageOnSecondary)) {
            return false;
        }
    }
 
    return true;
}
 
/**
 * Applies a type map to a document.
 *
 * This function is used by operations where it is not possible to apply a type
 * map to the cursor directly because the root document is a command response
 * (e.g. findAndModify).
 *
 * @internal
 * @param array|object $document Document to which the type map will be applied
 * @param array        $typeMap  Type map for BSON deserialization.
 * @throws InvalidArgumentException
 */
function apply_type_map_to_document(array|object $document, array $typeMap): array|object
{
    if (! is_document($document)) {
        throw InvalidArgumentException::expectedDocumentType('$document', $document);
    }
 
    return Document::fromPHP($document)->toPHP($typeMap);
}
 
/**
 * Converts a document parameter to an array.
 *
 * This is used to facilitate unified access to document fields. It also handles
 * Document, PackedArray, and Serializable objects.
 *
 * This function is not used for type checking. Therefore, it does not reject
 * PackedArray objects or Serializable::bsonSerialize() return values that would
 * encode as BSON arrays.
 *
 * @internal
 * @throws InvalidArgumentException if $document is not an array or object
 */
function document_to_array(array|object $document): array
{
    if ($document instanceof Document || $document instanceof PackedArray) {
        /* Nested documents and arrays are intentionally left as BSON. We avoid
         * iterator_to_array() since Document and PackedArray iteration returns
         * all values as MongoDB\BSON\Value instances. */
 
        /** @psalm-var array */
        return $document->toPHP([
            'array' => 'bson',
            'document' => 'bson',
            'root' => 'array',
        ]);
    } elseif ($document instanceof Serializable) {
        $document = $document->bsonSerialize();
    }
 
    if (is_object($document)) {
        /* Note: this omits all uninitialized properties, whereas BSON encoding
         * includes untyped, uninitialized properties. This is acceptable given
         * document_to_array()'s use cases. */
        $document = get_object_vars($document);
    }
 
    return $document;
}
 
/**
 * Return a collection's encryptedFields from the encryptedFieldsMap
 * autoEncryption driver option (if available).
 *
 * @internal
 * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#collection-encryptedfields-lookup-getencryptedfields
 * @see Collection::drop()
 * @see Database::createCollection()
 * @see Database::dropCollection()
 */
function get_encrypted_fields_from_driver(string $databaseName, string $collectionName, Manager $manager): array|object|null
{
    $encryptedFieldsMap = (array) $manager->getEncryptedFieldsMap();
 
    return $encryptedFieldsMap[$databaseName . '.' . $collectionName] ?? null;
}
 
/**
 * Return a collection's encryptedFields option from the server (if any).
 *
 * @internal
 * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#collection-encryptedfields-lookup-getencryptedfields
 * @see Collection::drop()
 * @see Database::dropCollection()
 */
function get_encrypted_fields_from_server(string $databaseName, string $collectionName, Server $server): array|object|null
{
    $collectionInfoIterator = (new ListCollections($databaseName, ['filter' => ['name' => $collectionName]]))->execute($server);
 
    foreach ($collectionInfoIterator as $collectionInfo) {
        /* Note: ListCollections applies a typeMap that converts BSON documents
         * to PHP arrays. This should not be problematic as encryptedFields here
         * is only used by drop helpers to obtain names of supporting encryption
         * collections. */
        return $collectionInfo['options']['encryptedFields'] ?? null;
    }
 
    return null;
}
 
/**
 * Returns whether a given value is a valid document.
 *
 * This method returns true for any array or object, but specifically excludes
 * BSON PackedArray instances
 *
 * @internal
 */
function is_document(mixed $document): bool
{
    return is_array($document) || (is_object($document) && ! $document instanceof PackedArray);
}
 
/**
 * Return whether the first key in the document starts with a "$" character.
 *
 * This is used for validating aggregation pipeline stages and differentiating
 * update and replacement documents. Since true and false return values may be
 * expected in different contexts, this function intentionally throws if
 * $document has an unexpected type instead of returning false.
 *
 * @internal
 * @throws InvalidArgumentException if $document is not an array or object
 */
function is_first_key_operator(array|object $document): bool
{
    if ($document instanceof PackedArray) {
        return false;
    }
 
    $document = document_to_array($document);
 
    $firstKey = array_key_first($document);
 
    if (! is_string($firstKey)) {
        return false;
    }
 
    return '$' === ($firstKey[0] ?? null);
}
 
/**
 * Returns whether the argument is a valid aggregation or update pipeline.
 *
 * This is primarily used for validating arguments for update and replace
 * operations, but can also be used for validating an aggregation pipeline.
 *
 * The $allowEmpty parameter can be used to control whether an empty array
 * should be considered a valid pipeline. Empty arrays are generally valid for
 * an aggregation pipeline, but the things are more complicated for update
 * pipelines.
 *
 * Update operations must prohibit empty pipelines, since libmongoc may encode
 * an empty pipeline array as an empty replacement document when writing an
 * update command (arrays and documents have the same bson_t representation).
 * For consistency, findOneAndUpdate should also prohibit empty pipelines.
 * Replace operations (e.g. replaceOne, findOneAndReplace) should reject empty
 * and non-empty pipelines alike, since neither is a replacement document.
 *
 * Note: this method may propagate an InvalidArgumentException from
 * document_or_array() if a Serializable object within the pipeline array
 * returns a non-array, non-object value from its bsonSerialize() method.
 *
 * @internal
 * @throws InvalidArgumentException
 */
function is_pipeline(array|object $pipeline, bool $allowEmpty = false): bool
{
    if ($pipeline instanceof PackedArray) {
        /* Nested documents and arrays are intentionally left as BSON. We avoid
         * iterator_to_array() since PackedArray iteration returns all values as
         * MongoDB\BSON\Value instances. */
        /** @psalm-var array */
        $pipeline = $pipeline->toPHP([
            'array' => 'bson',
            'document' => 'bson',
            'root' => 'array',
        ]);
    } elseif ($pipeline instanceof Serializable) {
        $pipeline = $pipeline->bsonSerialize();
    }
 
    if (! is_array($pipeline)) {
        return false;
    }
 
    if ($pipeline === []) {
        return $allowEmpty;
    }
 
    if (! array_is_list($pipeline)) {
        return false;
    }
 
    foreach ($pipeline as $stage) {
        if (! is_document($stage)) {
            return false;
        }
 
        if (! is_first_key_operator($stage)) {
            return false;
        }
    }
 
    return true;
}
 
/**
 * Returns whether the argument is a list that contains at least one
 * {@see StageInterface} object.
 *
 * @internal
 */
function is_builder_pipeline(array $pipeline): bool
{
    if (! $pipeline || ! array_is_list($pipeline)) {
        return false;
    }
 
    foreach ($pipeline as $stage) {
        if (is_object($stage) && $stage instanceof StageInterface) {
            return true;
        }
    }
 
    return false;
}
 
/**
 * Returns whether we are currently in a transaction.
 *
 * @internal
 * @param array $options Command options
 */
function is_in_transaction(array $options): bool
{
    if (isset($options['session']) && $options['session'] instanceof Session && $options['session']->isInTransaction()) {
        return true;
    }
 
    return false;
}
 
/**
 * Return whether the aggregation pipeline ends with an $out or $merge operator.
 *
 * This is used for determining whether the aggregation pipeline must be
 * executed against a primary server.
 *
 * @internal
 * @param array $pipeline Aggregation pipeline
 */
function is_last_pipeline_operator_write(array $pipeline): bool
{
    $lastOp = end($pipeline);
 
    if ($lastOp === false) {
        return false;
    }
 
    if (! is_array($lastOp) && ! is_object($lastOp)) {
        return false;
    }
 
    $key = array_key_first(document_to_array($lastOp));
 
    return $key === '$merge' || $key === '$out';
}
 
/**
 * Return whether the write concern is acknowledged.
 *
 * This function is similar to mongoc_write_concern_is_acknowledged but does not
 * check the fsync option since that was never supported in the PHP driver.
 *
 * @internal
 * @see https://mongodb.com/docs/manual/reference/write-concern/
 */
function is_write_concern_acknowledged(WriteConcern $writeConcern): bool
{
    /* Note: -1 corresponds to MONGOC_WRITE_CONCERN_W_ERRORS_IGNORED, which is
     * deprecated synonym of MONGOC_WRITE_CONCERN_W_UNACKNOWLEDGED and slated
     * for removal in libmongoc 2.0. */
    return ($writeConcern->getW() !== 0 && $writeConcern->getW() !== -1) || $writeConcern->getJournal() === true;
}
 
/**
 * Return whether the server supports a particular feature.
 *
 * @internal
 * @param Server  $server  Server to check
 * @param integer $feature Feature constant (i.e. wire protocol version)
 */
function server_supports_feature(Server $server, int $feature): bool
{
    $info = $server->getInfo();
    $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
    $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;
 
    return $minWireVersion <= $feature && $maxWireVersion >= $feature;
}
 
/**
 * Return whether the input is an array of strings.
 *
 * @internal
 */
function is_string_array(mixed $input): bool
{
    if (! is_array($input)) {
        return false;
    }
 
    foreach ($input as $item) {
        if (! is_string($item)) {
            return false;
        }
    }
 
    return true;
}
 
/**
 * Performs a deep copy of a value.
 *
 * This function will clone objects and recursively copy values within arrays.
 *
 * @internal
 * @see https://bugs.php.net/bug.php?id=49664
 * @param mixed $element Value to be copied
 * @throws ReflectionException
 */
function recursive_copy(mixed $element): mixed
{
    if (is_array($element)) {
        foreach ($element as $key => $value) {
            $element[$key] = recursive_copy($value);
        }
 
        return $element;
    }
 
    if (! is_object($element)) {
        return $element;
    }
 
    if (! (new ReflectionClass($element))->isCloneable()) {
        return $element;
    }
 
    return clone $element;
}
 
/**
 * Creates a type map to apply to a field type
 *
 * This is used in the Aggregate, Distinct, and FindAndModify operations to
 * apply the root-level type map to the document that will be returned. It also
 * replaces the root type with object for consistency within these operations
 *
 * An existing type map for the given field path will not be overwritten
 *
 * @internal
 * @param array  $typeMap   The existing typeMap
 * @param string $fieldPath The field path to apply the root type to
 */
function create_field_path_type_map(array $typeMap, string $fieldPath): array
{
    // If some field paths already exist, we prefix them with the field path we are assuming as the new root
    if (isset($typeMap['fieldPaths']) && is_array($typeMap['fieldPaths'])) {
        $fieldPaths = $typeMap['fieldPaths'];
 
        $typeMap['fieldPaths'] = [];
        foreach ($fieldPaths as $existingFieldPath => $type) {
            $typeMap['fieldPaths'][$fieldPath . '.' . $existingFieldPath] = $type;
        }
    }
 
    // If a root typemap was set, apply this to the field object
    if (isset($typeMap['root'])) {
        $typeMap['fieldPaths'][$fieldPath] = $typeMap['root'];
    }
 
    /* Special case if we want to convert an array, in which case we need to
     * ensure that the field containing the array is exposed as an array,
     * instead of the type given in the type map's array key. */
    if (str_ends_with($fieldPath, '.$')) {
        $typeMap['fieldPaths'][substr($fieldPath, 0, -2)] = 'array';
    }
 
    $typeMap['root'] = 'object';
 
    return $typeMap;
}
 
/**
 * Execute a callback within a transaction in the given session
 *
 * This helper takes care of retrying the commit operation or the entire
 * transaction if an error occurs.
 *
 * If the commit fails because of an UnknownTransactionCommitResult error, the
 * commit is retried without re-invoking the callback.
 * If the commit fails because of a TransientTransactionError, the entire
 * transaction will be retried. In this case, the callback will be invoked
 * again. It is important that the logic inside the callback is idempotent.
 *
 * In case of failures, the commit or transaction are retried until 120 seconds
 * from the initial call have elapsed. After that, no retries will happen and
 * the helper will throw the last exception received from the driver.
 *
 * @see Client::startSession()
 * @see Session::startTransaction() for supported transaction options
 *
 * @param Session  $session            A session object as retrieved by Client::startSession
 * @param callable $callback           A callback that will be invoked within the transaction
 * @param array    $transactionOptions Additional options that are passed to Session::startTransaction
 * @throws RuntimeException for driver errors while committing the transaction
 * @throws Exception for any other errors, including those thrown in the callback
 */
function with_transaction(Session $session, callable $callback, array $transactionOptions = []): void
{
    $operation = new WithTransaction($callback, $transactionOptions);
    $operation->execute($session);
}
 
/**
 * Returns the session option if it is set and valid.
 *
 * @internal
 */
function extract_session_from_options(array $options): ?Session
{
    if (isset($options['session']) && $options['session'] instanceof Session) {
        return $options['session'];
    }
 
    return null;
}
 
/**
 * Returns the readPreference option if it is set and valid.
 *
 * @internal
 */
function extract_read_preference_from_options(array $options): ?ReadPreference
{
    if (isset($options['readPreference']) && $options['readPreference'] instanceof ReadPreference) {
        return $options['readPreference'];
    }
 
    return null;
}
 
/**
 * Performs server selection, respecting the readPreference and session options.
 *
 * The pinned server for an active transaction takes priority, followed by an
 * operation-level read preference, followed by an active transaction's read
 * preference, followed by a primary read preference.
 *
 * @internal
 */
function select_server(Manager $manager, array $options): Server
{
    $session = extract_session_from_options($options);
    $server = $session instanceof Session ? $session->getServer() : null;
 
    // Pinned server for an active transaction takes priority
    if ($server !== null) {
        return $server;
    }
 
    // Operation read preference takes priority
    $readPreference = extract_read_preference_from_options($options);
 
    // Read preference for an active transaction takes priority
    if ($readPreference === null && $session instanceof Session && $session->isInTransaction()) {
        /* Session::getTransactionOptions() should always return an array if the
         * session is in a transaction, but we can be defensive. */
        $readPreference = extract_read_preference_from_options($session->getTransactionOptions() ?? []);
    }
 
    // Manager::selectServer() defaults to a primary read preference
    return $manager->selectServer($readPreference);
}
 
/**
 * Performs server selection for an aggregate operation with a write stage. The
 * $options parameter may be modified by reference if a primary read preference
 * must be forced due to the existence of pre-5.0 servers in the topology.
 *
 * @internal
 * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#aggregation-pipelines-with-write-stages
 */
function select_server_for_aggregate_write_stage(Manager $manager, array &$options): Server
{
    $readPreference = extract_read_preference_from_options($options);
 
    /* If there is either no read preference or a primary read preference, there
     * is no special server selection logic to apply.
     *
     * Note: an alternative read preference could still be inherited from an
     * active transaction's options, but we can rely on libmongoc to raise a
     * "read preference in a transaction must be primary" error if necessary. */
    if ($readPreference === null || $readPreference->getModeString() === ReadPreference::PRIMARY) {
        return select_server($manager, $options);
    }
 
    $server = null;
    $serverSelectionError = null;
 
    try {
        $server = select_server($manager, $options);
    } catch (DriverRuntimeException $serverSelectionError) {
    }
 
    /* If any pre-5.0 servers exist in the topology, force a primary read
     * preference and repeat server selection if it previously failed or
     * selected a secondary. */
    if (! all_servers_support_write_stage_on_secondary($manager->getServers())) {
        $options['readPreference'] = new ReadPreference(ReadPreference::PRIMARY);
 
        if ($server === null || $server->isSecondary()) {
            return select_server($manager, $options);
        }
    }
 
    /* If the topology only contains 5.0+ servers, we should either return the
     * previously selected server or propagate the server selection error. */
    if ($serverSelectionError !== null) {
        throw $serverSelectionError;
    }
 
    assert($server instanceof Server);
 
    return $server;
}
 
/**
 * Performs server selection for a write operation.
 *
 * The pinned server for an active transaction takes priority, followed by an
 * operation-level read preference, followed by a primary read preference. This
 * is similar to select_server() except that it ignores a read preference from
 * an active transaction's options.
 *
 * @internal
 */
function select_server_for_write(Manager $manager, array $options): Server
{
    return select_server($manager, $options + ['readPreference' => new ReadPreference(ReadPreference::PRIMARY)]);
}
#1MongoDB\select_server
/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Collection.php (302)
<?php
/*
 * Copyright 2015-present 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
 *
 *   https://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 Countable;
use Iterator;
use MongoDB\BSON\Document;
use MongoDB\BSON\PackedArray;
use MongoDB\Builder\BuilderEncoder;
use MongoDB\Builder\Pipeline;
use MongoDB\Codec\DocumentCodec;
use MongoDB\Codec\Encoder;
use MongoDB\Driver\CursorInterface;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Driver\Manager;
use MongoDB\Driver\ReadConcern;
use MongoDB\Driver\ReadPreference;
use MongoDB\Driver\WriteConcern;
use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnexpectedValueException;
use MongoDB\Exception\UnsupportedException;
use MongoDB\Model\BSONArray;
use MongoDB\Model\BSONDocument;
use MongoDB\Model\IndexInfo;
use MongoDB\Operation\Aggregate;
use MongoDB\Operation\BulkWrite;
use MongoDB\Operation\Count;
use MongoDB\Operation\CountDocuments;
use MongoDB\Operation\CreateIndexes;
use MongoDB\Operation\CreateSearchIndexes;
use MongoDB\Operation\DeleteMany;
use MongoDB\Operation\DeleteOne;
use MongoDB\Operation\Distinct;
use MongoDB\Operation\DropCollection;
use MongoDB\Operation\DropEncryptedCollection;
use MongoDB\Operation\DropIndexes;
use MongoDB\Operation\DropSearchIndex;
use MongoDB\Operation\EstimatedDocumentCount;
use MongoDB\Operation\Explain;
use MongoDB\Operation\Explainable;
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\ListSearchIndexes;
use MongoDB\Operation\RenameCollection;
use MongoDB\Operation\ReplaceOne;
use MongoDB\Operation\UpdateMany;
use MongoDB\Operation\UpdateOne;
use MongoDB\Operation\UpdateSearchIndex;
use MongoDB\Operation\Watch;
use stdClass;
 
use function array_diff_key;
use function array_intersect_key;
use function array_key_exists;
use function current;
use function is_array;
use function is_bool;
use function strlen;
 
class Collection
{
    private const DEFAULT_TYPE_MAP = [
        'array' => BSONArray::class,
        'document' => BSONDocument::class,
        'root' => BSONDocument::class,
    ];
 
    private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8;
 
    /** @psalm-var Encoder<array|stdClass|Document|PackedArray, mixed> */
    private readonly Encoder $builderEncoder;
 
    private ?DocumentCodec $codec = null;
 
    private ReadConcern $readConcern;
 
    private ReadPreference $readPreference;
 
    private array $typeMap;
 
    private WriteConcern $writeConcern;
 
    private bool $autoEncryptionEnabled;
 
    /**
     * 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:
     *
     *  * builderEncoder (MongoDB\Codec\Encoder): Encoder for query and
     *    aggregation builders. If not given, the default encoder will be used.
     *
     *  * codec (MongoDB\Codec\DocumentCodec): Codec used to decode documents
     *    from BSON to PHP objects.
     *
     *  * 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(private Manager $manager, private string $databaseName, private string $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['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) {
            throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class);
        }
 
        if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) {
            throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class);
        }
 
        if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) {
            throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class);
        }
 
        if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
            throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class);
        }
 
        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'], WriteConcern::class);
        }
 
        if (isset($options['autoEncryptionEnabled']) && ! is_bool($options['autoEncryptionEnabled'])) {
            throw InvalidArgumentException::invalidType('"autoEncryptionEnabled" option', $options['autoEncryptionEnabled'], 'boolean');
        }
 
        $this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder();
        $this->codec = $options['codec'] ?? null;
        $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern();
        $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference();
        $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP;
        $this->writeConcern = $options['writeConcern'] ?? $this->manager->getWriteConcern();
        $this->autoEncryptionEnabled = $options['autoEncryptionEnabled'] ?? false;
    }
 
    /**
     * Return internal properties for debugging purposes.
     *
     * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo
     */
    public function __debugInfo(): array
    {
        return [
            'builderEncoder' => $this->builderEncoder,
            'codec' => $this->codec,
            '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://mongodb.com/docs/manual/core/databases-and-collections/
     */
    public function __toString(): string
    {
        return $this->databaseName . '.' . $this->collectionName;
    }
 
    /**
     * Executes an aggregation framework pipeline on the collection.
     *
     * @see Aggregate::__construct() for supported options
     * @param array|Pipeline $pipeline Aggregation pipeline
     * @param array          $options  Command options
     * @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 $pipeline, array $options = []): CursorInterface
    {
        if (is_array($pipeline) && is_builder_pipeline($pipeline)) {
            $pipeline = new Pipeline(...$pipeline);
        }
 
        $pipeline = $this->builderEncoder->encodeIfSupported($pipeline);
 
        $hasWriteStage = is_last_pipeline_operator_write($pipeline);
 
        $options = $this->inheritReadPreference($options);
 
        $server = $hasWriteStage
            ? select_server_for_aggregate_write_stage($this->manager, $options)
            : select_server($this->manager, $options);
 
        /* MongoDB 4.2 and later supports a read concern when an $out stage is
         * being used, but earlier versions do not.
         */
        if (! $hasWriteStage || server_supports_feature($server, self::WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE)) {
            $options = $this->inheritReadConcern($options);
        }
 
        $options = $this->inheritCodecOrTypeMap($options);
 
        if ($hasWriteStage) {
            $options = $this->inheritWriteOptions($options);
        }
 
        $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
     * @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 = []): BulkWriteResult
    {
        $options = $this->inheritBuilderEncoder($options);
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodec($options);
 
        $operation = new BulkWrite($this->databaseName, $this->collectionName, $operations, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * 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
     * @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)
     *
     * @deprecated 1.4
     */
    public function count(array|object $filter = [], array $options = []): int
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritReadOptions($options);
 
        $operation = new Count($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Gets the number of documents matching the filter.
     *
     * @see CountDocuments::__construct() for supported options
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
     * @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 countDocuments(array|object $filter = [], array $options = []): int
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritReadOptions($options);
 
        $operation = new CountDocuments($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * 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(array|object $key, array $options = []): string
    {
        $operationOptionKeys = ['comment' => 1, 'commitQuorum' => 1, 'maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
        $indexOptions = array_diff_key($options, $operationOptionKeys);
        $operationOptions = array_intersect_key($options, $operationOptionKeys);
 
        return current($this->createIndexes([['key' => $key] + $indexOptions], $operationOptions));
    }
 
    /**
     * 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 https://mongodb.com/docs/manual/reference/command/createIndexes/
     * @see https://mongodb.com/docs/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 = []): array
    {
        $options = $this->inheritWriteOptions($options);
 
        $operation = new CreateIndexes($this->databaseName, $this->collectionName, $indexes, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Create an Atlas Search index for the collection.
     * Only available when used against a 7.0+ Atlas cluster.
     *
     * @see https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/
     * @see https://mongodb.com/docs/manual/reference/method/db.collection.createSearchIndex/
     * @param array|object                                         $definition Atlas Search index mapping definition
     * @param array{comment?: mixed, name?: string, type?: string} $options    Index and command options
     * @return string The name of the created search 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 createSearchIndex(array|object $definition, array $options = []): string
    {
        $indexOptionKeys = ['name' => 1, 'type' => 1];
        /** @psalm-var array{name?: string, type?: string} */
        $indexOptions = array_intersect_key($options, $indexOptionKeys);
        /** @psalm-var array{comment?: mixed} */
        $operationOptions = array_diff_key($options, $indexOptionKeys);
 
        $names = $this->createSearchIndexes([['definition' => $definition] + $indexOptions], $operationOptions);
 
        return current($names);
    }
 
    /**
     * Create one or more Atlas Search indexes for the collection.
     * Only available when used against a 7.0+ Atlas cluster.
     *
     * Each element in the $indexes array must have "definition" document and they may have a "name" string.
     * The name can be omitted for a single index, in which case a name will be the default.
     * For example:
     *
     *     $indexes = [
     *         // Create a search index with the default name on a single field
     *         ['definition' => ['mappings' => ['dynamic' => false, 'fields' => ['title' => ['type' => 'string']]]]],
     *         // Create a named search index on all fields
     *         ['name' => 'search_all', 'definition' => ['mappings' => ['dynamic' => true]]],
     *     ];
     *
     * @see https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/
     * @see https://mongodb.com/docs/manual/reference/method/db.collection.createSearchIndex/
     * @param list<array{definition: array|object, name?: string, type?: string}> $indexes List of search index specifications
     * @param array{comment?: mixed}                                              $options Command options
     * @return string[] The names of the created search 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 createSearchIndexes(array $indexes, array $options = []): array
    {
        $operation = new CreateSearchIndexes($this->databaseName, $this->collectionName, $indexes, $options);
        $server = select_server_for_write($this->manager, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Deletes all documents matching the filter.
     *
     * @see DeleteMany::__construct() for supported options
     * @see https://mongodb.com/docs/manual/reference/command/delete/
     * @param array|object $filter  Query by which to delete documents
     * @param array        $options Command options
     * @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(array|object $filter, array $options = []): DeleteResult
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritWriteOptions($options);
 
        $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Deletes at most one document matching the filter.
     *
     * @see DeleteOne::__construct() for supported options
     * @see https://mongodb.com/docs/manual/reference/command/delete/
     * @param array|object $filter  Query by which to delete documents
     * @param array        $options Command options
     * @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(array|object $filter, array $options = []): DeleteResult
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritWriteOptions($options);
 
        $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * 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
     * @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(string $fieldName, array|object $filter = [], array $options = []): array
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritReadOptions($options);
        $options = $this->inheritTypeMap($options);
 
        $operation = new Distinct($this->databaseName, $this->collectionName, $fieldName, $filter, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Drop this collection.
     *
     * @see DropCollection::__construct() for supported options
     * @param array $options Additional options
     * @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 = []): void
    {
        $options = $this->inheritWriteOptions($options);
 
        $server = select_server_for_write($this->manager, $options);
 
        if ($this->autoEncryptionEnabled && ! isset($options['encryptedFields'])) {
            $options['encryptedFields'] = get_encrypted_fields_from_driver($this->databaseName, $this->collectionName, $this->manager)
                ?? get_encrypted_fields_from_server($this->databaseName, $this->collectionName, $server);
        }
 
        $operation = isset($options['encryptedFields'])
            ? new DropEncryptedCollection($this->databaseName, $this->collectionName, $options)
            : new DropCollection($this->databaseName, $this->collectionName, $options);
 
        $operation->execute($server);
    }
 
    /**
     * Drop a single index in the collection.
     *
     * @see DropIndexes::__construct() for supported options
     * @param string|IndexInfo $indexName Index name or model object
     * @param array            $options   Additional options
     * @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(string|IndexInfo $indexName, array $options = []): void
    {
        $indexName = (string) $indexName;
 
        if ($indexName === '*') {
            throw new InvalidArgumentException('dropIndexes() must be used to drop multiple indexes');
        }
 
        $options = $this->inheritWriteOptions($options);
 
        $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options);
 
        $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Drop all indexes in the collection.
     *
     * @see DropIndexes::__construct() for supported options
     * @param array $options Additional options
     * @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 = []): void
    {
        $options = $this->inheritWriteOptions($options);
 
        $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options);
 
        $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Drop a single Atlas Search index in the collection.
     * Only available when used against a 7.0+ Atlas cluster.
     *
     * @param string                 $name    Search index name
     * @param array{comment?: mixed} $options Additional options
     * @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 dropSearchIndex(string $name, array $options = []): void
    {
        $operation = new DropSearchIndex($this->databaseName, $this->collectionName, $name);
        $server = select_server_for_write($this->manager, $options);
 
        $operation->execute($server);
    }
 
    /**
     * Gets an estimated number of documents in the collection using the collection metadata.
     *
     * @see EstimatedDocumentCount::__construct() for supported options
     * @param array $options Command options
     * @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 estimatedDocumentCount(array $options = []): int
    {
        $options = $this->inheritReadOptions($options);
 
        $operation = new EstimatedDocumentCount($this->databaseName, $this->collectionName, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Explains explainable commands.
     *
     * @see Explain::__construct() for supported options
     * @see https://mongodb.com/docs/manual/reference/command/explain/
     * @param Explainable $explainable Command on which to run explain
     * @param array       $options     Additional options
     * @throws UnsupportedException if explainable or 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 explain(Explainable $explainable, array $options = []): array|object
    {
        $options = $this->inheritReadPreference($options);
        $options = $this->inheritTypeMap($options);
 
        $operation = new Explain($this->databaseName, $explainable, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Finds documents matching the query.
     *
     * @see Find::__construct() for supported options
     * @see https://mongodb.com/docs/manual/crud/#read-operations
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
     * @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(array|object $filter = [], array $options = []): CursorInterface
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritReadOptions($options);
        $options = $this->inheritCodecOrTypeMap($options);
 
        $operation = new Find($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Finds a single document matching the query.
     *
     * @see FindOne::__construct() for supported options
     * @see https://mongodb.com/docs/manual/crud/#read-operations
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Additional options
     * @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(array|object $filter = [], array $options = []): array|object|null
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritReadOptions($options);
        $options = $this->inheritCodecOrTypeMap($options);
 
        $operation = new FindOne($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * 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 https://mongodb.com/docs/manual/reference/command/findAndModify/
     * @param array|object $filter  Query by which to filter documents
     * @param array        $options Command options
     * @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(array|object $filter, array $options = []): array|object|null
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodecOrTypeMap($options);
 
        $operation = new FindOneAndDelete($this->databaseName, $this->collectionName, $filter, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * 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 https://mongodb.com/docs/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
     * @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(array|object $filter, array|object $replacement, array $options = []): array|object|null
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodecOrTypeMap($options);
 
        $operation = new FindOneAndReplace($this->databaseName, $this->collectionName, $filter, $replacement, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * 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 https://mongodb.com/docs/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
     * @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(array|object $filter, array|object $update, array $options = []): array|object|null
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodecOrTypeMap($options);
 
        $operation = new FindOneAndUpdate($this->databaseName, $this->collectionName, $filter, $update, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /** @psalm-return Encoder<array|stdClass|Document|PackedArray, mixed> */
    public function getBuilderEncoder(): Encoder
    {
        return $this->builderEncoder;
    }
 
    public function getCodec(): ?DocumentCodec
    {
        return $this->codec;
    }
 
    /**
     * Return the collection name.
     */
    public function getCollectionName(): string
    {
        return $this->collectionName;
    }
 
    /**
     * Return the database name.
     */
    public function getDatabaseName(): string
    {
        return $this->databaseName;
    }
 
    /**
     * Return the Manager.
     */
    public function getManager(): Manager
    {
        return $this->manager;
    }
 
    /**
     * Return the collection namespace.
     *
     * @see https://mongodb.com/docs/manual/reference/glossary/#term-namespace
     */
    public function getNamespace(): string
    {
        return $this->databaseName . '.' . $this->collectionName;
    }
 
    /**
     * Return the read concern for this collection.
     *
     * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php
     */
    public function getReadConcern(): ReadConcern
    {
        return $this->readConcern;
    }
 
    /**
     * Return the read preference for this collection.
     */
    public function getReadPreference(): ReadPreference
    {
        return $this->readPreference;
    }
 
    /**
     * Return the type map for this collection.
     */
    public function getTypeMap(): array
    {
        return $this->typeMap;
    }
 
    /**
     * Return the write concern for this collection.
     *
     * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php
     */
    public function getWriteConcern(): WriteConcern
    {
        return $this->writeConcern;
    }
 
    /**
     * Inserts multiple documents.
     *
     * @see InsertMany::__construct() for supported options
     * @see https://mongodb.com/docs/manual/reference/command/insert/
     * @param list<object|array> $documents The documents to insert
     * @param array              $options   Command options
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function insertMany(array $documents, array $options = []): InsertManyResult
    {
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodec($options);
 
        $operation = new InsertMany($this->databaseName, $this->collectionName, $documents, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Inserts one document.
     *
     * @see InsertOne::__construct() for supported options
     * @see https://mongodb.com/docs/manual/reference/command/insert/
     * @param array|object $document The document to insert
     * @param array        $options  Command options
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function insertOne(array|object $document, array $options = []): InsertOneResult
    {
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodec($options);
 
        $operation = new InsertOne($this->databaseName, $this->collectionName, $document, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Returns information for all indexes for the collection.
     *
     * @see ListIndexes::__construct() for supported options
     * @return Iterator<int, IndexInfo>
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function listIndexes(array $options = []): Iterator
    {
        $operation = new ListIndexes($this->databaseName, $this->collectionName, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Returns information for all Atlas Search indexes for the collection.
     * Only available when used against a 7.0+ Atlas cluster.
     *
     * @param array $options Command options
     * @return Countable&Iterator
     * @throws InvalidArgumentException for parameter/option parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     * @see ListSearchIndexes::__construct() for supported options
     */
    public function listSearchIndexes(array $options = []): Iterator
    {
        $options = $this->inheritTypeMap($options);
 
        $operation = new ListSearchIndexes($this->databaseName, $this->collectionName, $options);
        $server = select_server($this->manager, $options);
 
        return $operation->execute($server);
    }
 
    /**
     * Renames the collection.
     *
     * @see RenameCollection::__construct() for supported options
     * @param string      $toCollectionName New name of the collection
     * @param string|null $toDatabaseName   New database name of the collection. Defaults to the original database.
     * @param array       $options          Additional options
     * @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 rename(string $toCollectionName, ?string $toDatabaseName = null, array $options = []): void
    {
        if (! isset($toDatabaseName)) {
            $toDatabaseName = $this->databaseName;
        }
 
        $options = $this->inheritWriteOptions($options);
 
        $operation = new RenameCollection($this->databaseName, $this->collectionName, $toDatabaseName, $toCollectionName, $options);
 
        $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Replaces at most one document matching the filter.
     *
     * @see ReplaceOne::__construct() for supported options
     * @see https://mongodb.com/docs/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
     * @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(array|object $filter, array|object $replacement, array $options = []): UpdateResult
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $options = $this->inheritWriteOptions($options);
        $options = $this->inheritCodec($options);
 
        $operation = new ReplaceOne($this->databaseName, $this->collectionName, $filter, $replacement, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Updates all documents matching the filter.
     *
     * @see UpdateMany::__construct() for supported options
     * @see https://mongodb.com/docs/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
     * @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(array|object $filter, array|object $update, array $options = []): UpdateResult
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $update = $this->builderEncoder->encodeIfSupported($update);
        $options = $this->inheritWriteOptions($options);
 
        $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Updates at most one document matching the filter.
     *
     * @see UpdateOne::__construct() for supported options
     * @see https://mongodb.com/docs/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
     * @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(array|object $filter, array|object $update, array $options = []): UpdateResult
    {
        $filter = $this->builderEncoder->encodeIfSupported($filter);
        $update = $this->builderEncoder->encodeIfSupported($update);
        $options = $this->inheritWriteOptions($options);
 
        $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options);
 
        return $operation->execute(select_server_for_write($this->manager, $options));
    }
 
    /**
     * Update a single Atlas Search index in the collection.
     * Only available when used against a 7.0+ Atlas cluster.
     *
     * @param string                 $name       Search index name
     * @param array|object           $definition Atlas Search index definition
     * @param array{comment?: mixed} $options    Command options
     * @throws UnsupportedException if options are not supported by the selected server
     * @throws InvalidArgumentException for parameter parsing errors
     * @throws DriverRuntimeException for other driver errors (e.g. connection errors)
     */
    public function updateSearchIndex(string $name, array|object $definition, array $options = []): void
    {
        $operation = new UpdateSearchIndex($this->databaseName, $this->collectionName, $name, $definition, $options);
        $server = select_server_for_write($this->manager, $options);
 
        $operation->execute($server);
    }
 
    /**
     * Create a change stream for watching changes to the collection.
     *
     * @see Watch::__construct() for supported options
     * @param array|Pipeline $pipeline Aggregation pipeline
     * @param array          $options  Command options
     * @throws InvalidArgumentException for parameter/option parsing errors
     */
    public function watch(array|Pipeline $pipeline = [], array $options = []): ChangeStream
    {
        if (is_array($pipeline) && is_builder_pipeline($pipeline)) {
            $pipeline = new Pipeline(...$pipeline);
        }
 
        $pipeline = $this->builderEncoder->encodeIfSupported($pipeline);
 
        $options = $this->inheritReadOptions($options);
        $options = $this->inheritCodecOrTypeMap($options);
 
        $operation = new Watch($this->manager, $this->databaseName, $this->collectionName, $pipeline, $options);
 
        return $operation->execute(select_server($this->manager, $options));
    }
 
    /**
     * Get a clone of this collection with different options.
     *
     * @see Collection::__construct() for supported options
     * @param array $options Collection constructor options
     * @throws InvalidArgumentException for parameter/option parsing errors
     */
    public function withOptions(array $options = []): Collection
    {
        $options += [
            'autoEncryptionEnabled' => $this->autoEncryptionEnabled,
            'builderEncoder' => $this->builderEncoder,
            'codec' => $this->codec,
            'readConcern' => $this->readConcern,
            'readPreference' => $this->readPreference,
            'typeMap' => $this->typeMap,
            'writeConcern' => $this->writeConcern,
        ];
 
        return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
    }
 
    private function inheritBuilderEncoder(array $options): array
    {
        return ['builderEncoder' => $this->builderEncoder] + $options;
    }
 
    private function inheritCodec(array $options): array
    {
        // If the options contain a type map, don't inherit anything
        if (isset($options['typeMap'])) {
            return $options;
        }
 
        if (! array_key_exists('codec', $options)) {
            $options['codec'] = $this->codec;
        }
 
        return $options;
    }
 
    private function inheritCodecOrTypeMap(array $options): array
    {
        // If the options contain a type map, don't inherit anything
        if (isset($options['typeMap'])) {
            return $options;
        }
 
        // If this collection does not use a codec, or if a codec was explicitly
        // defined in the options, only inherit the type map (if possible)
        if (! $this->codec || array_key_exists('codec', $options)) {
            return $this->inheritTypeMap($options);
        }
 
        // At this point, we know that we use a codec and the options array did
        // not explicitly contain a codec, so we can inherit ours
        $options['codec'] = $this->codec;
 
        return $options;
    }
 
    private function inheritReadConcern(array $options): array
    {
        // ReadConcern and ReadPreference may not change within a transaction
        if (! isset($options['readConcern']) && ! is_in_transaction($options)) {
            $options['readConcern'] = $this->readConcern;
        }
 
        return $options;
    }
 
    private function inheritReadOptions(array $options): array
    {
        $options = $this->inheritReadConcern($options);
 
        return $this->inheritReadPreference($options);
    }
 
    private function inheritReadPreference(array $options): array
    {
        // ReadConcern and ReadPreference may not change within a transaction
        if (! isset($options['readPreference']) && ! is_in_transaction($options)) {
            $options['readPreference'] = $this->readPreference;
        }
 
        return $options;
    }
 
    private function inheritTypeMap(array $options): array
    {
        // Only inherit the type map if no codec is used
        if (! isset($options['typeMap']) && ! isset($options['codec'])) {
            $options['typeMap'] = $this->typeMap;
        }
 
        return $options;
    }
 
    private function inheritWriteOptions(array $options): array
    {
        // WriteConcern may not change within a transaction
        if (! is_in_transaction($options)) {
            if (! isset($options['writeConcern'])) {
                $options['writeConcern'] = $this->writeConcern;
            }
        }
 
        return $options;
    }
}
#2MongoDB\Collection->count
/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Models/BaseCollection.php (103)
<?php
 
namespace ChatHispanoEngine\Core\Models;
 
/**
 * Class that managers MongoDB database
 *
 * @author iwalkalone
 */
class BaseCollection extends \Phalcon\Di\Injectable
{
    public $_id;
 
    protected const MONGOMODELCLASS = "Phalcon\Db\Adapter\MongoDB\Model";
 
    public function getId()
    {
        return $this->_id;
    }
 
    protected static function getConnection()
    {
        $class = get_called_class();
        $o = new $class();
        return $o->getDI()->get('mongo');
    }
 
    protected static function getInstance()
    {
        $class = get_called_class();
        $o = new $class();
        return $o;
    }
 
    protected static function getCollection()
    {
        $class = get_called_class();
        $o = new $class();
        return $o->getSource();
    }
 
    public static function findFirst(array $data = [])
    {
        $database = self::getConnection();
        $collection = self::getCollection();
        if (isset($data[0])) {
            $filters = $data[0];
            unset($data[0]);
        } else {
            $filters = [];
        }
        $options = $data;
        $doc = $database->$collection->findOne($filters, $options);
        if (!$doc) {
            return false;
        }
        $o = self::getInstance();
        $o->fromArray($doc->getArrayCopy());
        return $o;
    }
 
    public static function findById($id)
    {
        return self::findFirst([
            [
                '_id' => new \MongoDB\BSON\ObjectId($id),
            ],
        ]);
    }
 
    public static function find(array $data = [])
    {
        $database = self::getConnection();
        $collection = self::getCollection();
        if (isset($data[0])) {
            $filters = $data[0];
            unset($data[0]);
        } else {
            $filters = [];
        }
        $options = $data;
        $result = $database->$collection->find($filters, $options);
        $list = [];
        foreach ($result as $doc) {
            $o = self::getInstance();
            $o->fromArray($doc->getArrayCopy());
            $list[] = $o;
        }
        return $list;
    }
 
    public static function count(array $data = [])
    {
        $database = self::getConnection();
        $collection = self::getCollection();
        if (isset($data[0])) {
            $filters = $data[0];
            unset($data[0]);
        } else {
            $filters = [];
        }
        $options = $data;
        return $database->$collection->count($filters, $options); // Will be deprecated?
        //return $database->$collection->countDocuments($filters, $options);
    }
 
    public function create()
    {
        $database = self::getConnection();
        $collection = self::getCollection();
        $data = get_object_vars($this);
        $data['_id'] = new \MongoDB\BSON\ObjectId();
        $res = $database->$collection->insertOne($data);
        if ($res->getInsertedCount() > 0) {
            $this->_id = $res->getInsertedId();
            return $this->_id;
        } else {
            return false;
        }
    }
 
    public function save()
    {
        $database = self::getConnection();
        $collection = self::getCollection();
        $data = get_object_vars($this);
        if (isset($data['_id'])) {
            $id = $data['_id'];
            unset($data['_id']);
            $res = $database->$collection->updateOne([
                '_id' => $id,
            ], [
                '$set' => $data,
            ]);
            return $res->getModifiedCount() > 0;
        } else {
            return $this->create();
        }
    }
 
    public function delete()
    {
        $database = self::getConnection();
        $collection = self::getCollection();
        $res = $database->$collection->deleteOne([
            '_id' => $this->_id,
        ]);
        return $res->getDeletedCount() > 0;
    }
 
    protected function fromArrayInternal(array $data)
    {
        $a = [];
        foreach ($data as $key => $val) {
            if (is_object($val)) {
                $class = get_class($val);
                if (substr($class, 0, strlen(self::MONGOMODELCLASS)) == self::MONGOMODELCLASS) {
                    $a[$key] = $this->fromArrayInternal($val->getArrayCopy());
                } else {
                    $a[$key] = $val;
                }
            } else {
                $a[$key] = $val;
            }
        }
        return $a;
    }
 
    public function fromArray(array $data)
    {
        foreach ($data as $key => $val) {
            if (is_object($val)) {
                $class = get_class($val);
                if (substr($class, 0, strlen(self::MONGOMODELCLASS)) == self::MONGOMODELCLASS) {
                    $this->$key = $this->fromArrayInternal($val->getArrayCopy());
                } else {
                    $this->$key = $val;
                }
            } else {
                $this->$key = $val;
            }
        }
    }
 
    public function toArray($columns = null): array
    {
        $data = get_object_vars($this);
        if ($columns && is_array($columns)) {
            $data = array_diff_assoc($data, $columns);
        }
        unset($data['_id']);
        return $data;
    }
 
    // Dummy function to make it compat with Phalcon 3 code
    public function getMessages(): array
    {
        return [];
    }
}
#3ChatHispanoEngine\Core\Models\BaseCollection::count
/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Managers/InspIRCd/ChannelLoggerManager.php (112)
<?php
 
namespace ChatHispanoEngine\Core\Managers\InspIRCd;
 
use ChatHispanoEngine\Core\Exception\Exception;
use ChatHispanoEngine\Core\Exception\ErrorCodes;
use ChatHispanoEngine\Core\Library\Faker;
use ChatHispanoEngine\Core\Library\Util;
use ChatHispanoEngine\Core\Models\InspIRCd\Channel\Log;
use ChatHispanoEngine\Core\Models\InspIRCd\Channel\Story;
 
class ChannelLoggerManager extends \Phalcon\Mvc\Model\Manager
{
    public const PAGE_SIZE = 10;
 
    public function listByChannel($channel, $date = '', $asc = false)
    {
        if ($date == '') {
            $date = date('Y-m-d');
        }
 
        // Get timestamps of start and end of day to retrieve only data of specified day
        $start = strtotime($date);
        $end = strtotime($date.' +1 day');
 
        return Log::find(array(
            array(
                'channel' => mb_strtolower($channel),
                'date' => [
                    '$gte' => new \MongoDB\BSON\UTCDateTime($start * 1000),
                    '$lte' => new \MongoDB\BSON\UTCDateTime($end * 1000),
                ],
            ),
            'sort' => [
                'date' => $asc === true ? 1 : -1,
            ],
        ));
    }
 
    public function create($ts, $channel, $event, $sender, $reason = null, $kicker = null, $new_nick = null)
    {
        $o = new Log();
        $o->date = new \MongoDB\BSON\UTCDateTime($ts * 1000);
        $o->channel = mb_strtolower($channel);
        $o->event = $event;
        $o->sender = $sender;
        $o->reason = $reason;
        $o->kicker = $kicker;
        $o->new_nick = $new_nick;
        if (false === $o->save()) {
            throw new Exception(null, ErrorCodes::ERR_CHANNEL_LOG_CREATE);
        }
 
        return $o;
    }
 
    public function countStories()
    {
        return Story::count();
    }
 
    public function listStories($offset = 0, $limit = 50)
    {
        return Story::find([
            [],
            'sort' => [
                'date' => -1,
            ],
            'limit' => $limit,
            'skip' => $offset,
        ]);
    }
 
    public function listActiveStories($channel = null, $date = null, array $sort, $page = 1)
    {
        $query = [
            'status' => Story::STATUS_ACTIVE,
        ];
        if ($channel) {
            $query['channel'] = '#'.$channel;
        }
        if ($date) {
            $query['date'] = [
                '$gte' => new \MongoDB\BSON\UTCDateTime(strtotime($date) * 1000),
                '$lt' => new \MongoDB\BSON\UTCDateTime(strtotime($date.' +1 day') * 1000),
            ];
        }
 
        return Story::find(array(
            $query,
            'sort' => $sort,
            'limit' => self::PAGE_SIZE,
            'skip' => ($page - 1) * self::PAGE_SIZE,
        ));
    }
 
    public function countActiveStories($channel = null, $date = null)
    {
        $query = [
            'status' => Story::STATUS_ACTIVE,
        ];
        if ($channel) {
            $query['channel'] = '#'.$channel;
        }
        if ($date) {
            $query['date'] = [
                '$gte' => new \MongoDB\BSON\UTCDateTime(strtotime($date) * 1000),
                '$lt' => new \MongoDB\BSON\UTCDateTime(strtotime($date.' +1 day') * 1000),
            ];
        }
 
        return Story::count(array(
            $query,
        ));
    }
 
    public function getStoriesPages($cnt)
    {
        return ceil($cnt / self::PAGE_SIZE);
    }
 
    public function getStory($id)
    {
        $o = Story::findFirst([
            [
                '_id' => new \MongoDB\BSON\ObjectId($id),
            ],
        ]);
 
        if ($o === false || $o === null) {
            throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_NOT_FOUND);
        }
 
        return $o;
    }
 
    public function getLastActiveStoryFromChannel($channel)
    {
        return Story::findFirst([
            [
                'channel' => $channel,
            ],
            'sort' => [
                'date' => -1,
            ],
        ]);
    }
 
 
    public function getPreviousStory(Story $s, $list, $hasDate = 0, $hasChannel = 0)
    {
        $query = [
            'status' => Story::STATUS_ACTIVE,
        ];
        if ($hasChannel) {
            $query['channel'] = $s->channel;
        }
        if ($hasDate) {
            $query['date'] = [
                '$gte' => new \MongoDB\BSON\UTCDateTime(strtotime($s->getDate('Y-m-d')) * 1000),
                '$lt' => $s->date,
            ];
        } else {
            $query['date'] = [
                '$lt' => $s->date,
            ];
        }
        switch ($list) {
            case 'viewed':
                $sort = [
                    'views' => -1,
                    'date' => -1,
                ];
                break;
            case 'liked':
                $sort = [
                    'positive' => -1,
                    'negative' => 1,
                    'date' => -1,
                ];
                break;
            case 'disliked':
                $sort = [
                    'negative' => -1,
                    'positive' => 1,
                    'date' => -1,
                ];
                break;
            default:
                $sort = [
                    'date' => -1,
                ];
                break;
        }
 
        return Story::findFirst(array(
            $query,
            'sort' => $sort,
        ));
    }
 
    public function getNextStory(Story $s, $list, $hasDate = 0, $hasChannel = 0)
    {
        $query = [
            'status' => Story::STATUS_ACTIVE,
        ];
        if ($hasChannel) {
            $query['channel'] = $s->channel;
        }
        if ($hasDate) {
            $query['date'] = [
                '$gt' => $s->date,
                '$lt' => new \MongoDB\BSON\UTCDateTime(strtotime($s->getDate('Y-m-d').' +1 day') * 1000),
            ];
        } else {
            $query['date'] = [
                '$gt' => $s->date,
            ];
        }
        switch ($list) {
            case 'viewed':
                $sort = [
                    'views' => -1,
                    'date' => 1,
                ];
                break;
            case 'liked':
                $sort = [
                    'positive' => -1,
                    'negative' => 1,
                    'date' => 1,
                ];
                break;
            case 'disliked':
                $sort = [
                    'negative' => -1,
                    'positive' => 1,
                    'date' => 1,
                ];
                break;
            default:
                $sort = [
                    'date' => 1,
                ];
                break;
        }
 
        return Story::findFirst(array(
            $query,
            'sort' => $sort,
        ));
    }
 
    public function createStory($ts, $channel, $messages = [])
    {
        $o = new Story();
        $o->createIndexes();
        $o->date = new \MongoDB\BSON\UTCDateTime($ts * 1000);
        $o->channel = mb_strtolower($channel);
        $o->status = Story::STATUS_ACTIVE;
        $o->views = 0;
        $o->positive = 0;
        $o->negative = 0;
        $o->positive_emails = [];
        $o->negative_emails = [];
        $o->messages = [];
        $anonimized_nick = [];
        $nick_color = [];
        $nick_color_dec = [];
        foreach ($messages as $msg) {
            if (!isset($anonimized_nick[$msg->sender])) {
                $anonimized_nick[$msg->sender] = Faker::getNick();
                $round = 0;
                while (true) {
                    $r = rand(0, 130);
                    $g = rand(0, 130);
                    $b = rand(0, 130);
                    $v = true;
                    foreach ($nick_color_dec as $c) {
                        $d = abs($r - $c[0]) + abs($g - $c[1]) + abs($b - $c[2]);
                        if ($d < 50) {
                            $v = false;
                            break;
                        }
                    }
                    if ($v === true || $round >= 10) {
                        break;
                    }
                    $round++;
                }
                $nick_color[$msg->sender] = '#'.str_pad(dechex($r), 2, "0").str_pad(dechex($g), 2, "0").str_pad(dechex($b), 2, "0");
                $nick_color_dec[$msg->sender] = [$r, $g, $b];
            }
        }
        foreach ($messages as $msg) {
            $o->messages[] = [
                'date' => $msg->date->toDateTime()->getTimestamp(),
                'nick' => $msg->sender,
                'anonimized_nick' => $anonimized_nick[$msg->sender],
                'nick_color' => $nick_color[$msg->sender],
                'message' => str_replace(array_keys($anonimized_nick), array_values($anonimized_nick), Util::stripColor($msg->reason)),
            ];
        }
        $o->lines = count($o->messages);
        $o->users = count($anonimized_nick);
        if (false === $o->save()) {
            throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_CREATE);
        }
 
        return $o;
    }
 
    public function enableStory($id)
    {
        $o = $this->getStory($id);
        $o->status = Story::STATUS_ACTIVE;
        if (false === $o->save()) {
            throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
        }
 
        return $o;
    }
 
    public function disableStory($id)
    {
        $o = $this->getStory($id);
        $o->status = Story::STATUS_DISABLED;
        if (false === $o->save()) {
            throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
        }
 
        return $o;
    }
 
    public function addStoryView(Story $o)
    {
        $o->views = $o->views + 1;
        if (false === $o->save()) {
            throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
        }
 
        return $o;
    }
 
    public function likeStory(Story $o, $email)
    {
        $ppos = get_class($o->positive_emails) == 'MongoDB\Model\BSONArray' ? array_search(mb_strtolower($email), $o->positive_emails->getArrayCopy()) : false;
        $npos = get_class($o->negative_emails) == 'MongoDB\Model\BSONArray' ? array_search(mb_strtolower($email), $o->negative_emails->getArrayCopy()) : false;
        if (false !== $npos) {
            // We remove from the negative list
            $o->negative_emails->offsetUnset($npos);
            $o->negative = $o->negative - 1;
            if (false === $o->save()) {
                throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
            }
        } elseif (false === $ppos) {
            // We add to the positive list
            if (get_class($o->positive_emails) != 'MongoDB\Model\BSONArray') {
                $o->positive_emails = new \MongoDB\Model\BSONArray();
            }
            $o->positive_emails->append($email);
            $o->positive = $o->positive + 1;
            if (false === $o->save()) {
                throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
            }
        } else {
            // We ignore it, as it is already in positive list
        }
 
        return $o;
    }
 
    public function likeStorySession(Story $o)
    {
        if ($this->getDI()->get('session')->has('story_like_'.$o->_id->__toString())) {
            // It exists, so we ignore it
        } else {
            $removeNegative = false;
            if ($this->getDI()->get('session')->has('story_dislike_'.$o->_id->__toString())) {
                $removeNegative = true;
                $o->negative--;
            }
            $o->positive++;
            if (false === $o->save()) {
                throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
            }
            if ($removeNegative) {
                $this->getDI()->get('session')->remove('story_dislike_'.$o->_id->__toString());
            }
            $this->getDI()->get('session')->set('story_like_'.$o->_id->__toString(), 1);
        }
 
        return $o;
    }
 
    public function dislikeStory(Story $o, $email)
    {
        $ppos = get_class($o->positive_emails) == 'MongoDB\Model\BSONArray' ? array_search(mb_strtolower($email), $o->positive_emails->getArrayCopy()) : false;
        $npos = get_class($o->negative_emails) == 'MongoDB\Model\BSONArray' ? array_search(mb_strtolower($email), $o->negative_emails->getArrayCopy()) : false;
        if (false !== $ppos) {
            // We remove from the positive list
            array_splice($o->positive_emails, $ppos, 1);
            $o->positive_emails->offsetUnset($ppos);
            $o->positive = $o->positive - 1;
            if (false === $o->save()) {
                throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
            }
        } elseif (false === $npos) {
            // We add to the negative list
            if (get_class($o->negative_emails) != 'MongoDB\Model\BSONArray') {
                $o->negative_emails = new \MongoDB\Model\BSONArray();
            }
            $o->negative_emails->append($email);
            $o->negative = $o->negative + 1;
            if (false === $o->save()) {
                throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
            }
        } else {
            // We ignore it, as it is already in negative list
        }
 
        return $o;
    }
 
    public function dislikeStorySession(Story $o)
    {
        if ($this->getDI()->get('session')->has('story_dislike_'.$o->_id->__toString())) {
            // It exists, so we ignore it
        } else {
            $removePositive = false;
            if ($this->getDI()->get('session')->has('story_like_'.$o->_id->__toString())) {
                $removePositive = true;
                $o->positive--;
            }
            $o->negative++;
            if (false === $o->save()) {
                throw new Exception(null, ErrorCodes::ERR_CHANNEL_STORY_UPDATE);
            }
            if ($removePositive === true) {
                $this->getDI()->get('session')->remove('story_like_'.$o->_id->__toString());
            }
            $this->getDI()->get('session')->set('story_dislike_'.$o->_id->__toString(), 1);
        }
 
        return $o;
    }
}
#4ChatHispanoEngine\Core\Managers\InspIRCd\ChannelLoggerManager->countActiveStories
/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/Controllers/StoriesController.php (38)
<?php
 
namespace ChatHispanoEngine\Web\Controllers;
 
use ChatHispanoEngine\Core\Library\MobileDetect;
 
class StoriesController extends BaseController
{
    public function indexAction($channel = null, $date = null)
    {
        $this->view->channel = $channel;
        $this->view->date = $date;
        if ($date) {
            if ($channel) {
                $this->view->stories_title = $this->_t('Stories from %name% at %date%', [
                    'name' => '#'.$channel,
                    'date' => date('d/m/Y', strtotime($date)),
                ]);
            } else {
                $this->view->stories_title = $this->_t('Stories at %date%', [
                    'date' => date('d/m/Y', strtotime($date)),
                ]);
            }
        } elseif ($channel) {
            $this->view->stories_title = $this->_t('Stories from %name%', [
                'name' => '#'.$channel,
            ]);
        } else {
            $this->view->stories_title = $this->_t('Stories', []);
        }
        $this->setHeadTitle($this->view->stories_title);
        $this->view->hasDate = $date ? 1 : 0;
        $this->view->hasChannel = $channel ? 1 : 0;
 
        $page = $this->request->getQuery('page', 'int', 1);
        $channel_logger_manager = $this->getDI()->get('channel_logger_manager');
        $this->view->page = $page;
        $this->view->total_stories = $channel_logger_manager->countActiveStories($channel, $date);
        $this->view->total_pages = $channel_logger_manager->getStoriesPages($this->view->total_stories);
        $this->view->uri_stories = $this->request->get('_url');
        if ($page > $this->view->total_pages) {
            $this->flashSession->error($this->_t('Page does not exist'));
            $this->response->redirect('/historias');
            return;
        }
        $this->view->stories = [
            'last' => $channel_logger_manager->listActiveStories($channel, $date, [
                'date' => -1,
            ], $page),
            'viewed' => $channel_logger_manager->listActiveStories($channel, $date, [
                'views' => -1,
                'date' => -1,
            ], $page),
            'liked' => $channel_logger_manager->listActiveStories($channel, $date, [
                'positive' => -1,
                'negative' => 1,
                'date' => -1,
            ], $page),
            'disliked' => $channel_logger_manager->listActiveStories($channel, $date, [
                'negative' => -1,
                'positive' => 1,
                'date' => -1,
            ], $page),
        ];
 
        // Navigation
        $nav = [
            ['name' => $this->_t('Home', []), 'url' => '/', ],
            ['name' => $this->_t('Stories', []), 'url' => '/historias', ],
        ];
        if ($channel) {
            $nav[] = ['name' => $this->_t('Channel %name%', ['name' => '#'.$channel]), 'url' => '/historias/'.$channel, ];
        }
        if ($date) {
            $nav[] = ['name' => $date, 'url' => '/historias/'.$channel.'/'.$date, ];
        }
        $this->view->navigation = $nav;
    }
 
    public function viewAction($channel, $date, $id)
    {
        $list = $this->request->getQuery('l', 'string', 'last');
        $hasDate = $this->request->getQuery('d', 'string', 0);
        $hasChannel = $this->request->getQuery('c', 'string', 0);
 
        $config = $this->getDI()->get('config');
        $channel_logger_manager = $this->getDI()->get('channel_logger_manager');
 
        try {
            $story = $channel_logger_manager->getStory($id);
        } catch (\Exception $e) {
            $this->flashSession->error($this->_t($e->getMessage()));
            $this->response->redirect('/historias');
            return;
        }
 
        if ($story->channel != '#'.$channel || $story->getDate('Y-m-d') != $date) {
            $this->flashSession->error($this->_t('Unexpected error'));
            $this->response->redirect('/historias');
            return;
        }
 
        try {
            $detect = new MobileDetect();
            $detect->setUserAgent($this->request->getUserAgent());
            if (false === $detect->is('Bot') && false === $detect->is('MobileBot')) {
                $v = $this->session->get('story_view_'.$story->_id->__toString());
                if (!$v || $v != 1) {
                    $story = $channel_logger_manager->addStoryView($story);
                    $this->session->set('story_view_'.$story->_id->__toString(), 1);
                }
            }
        } catch (\Exception $e) {
        }
 
        $this->view->story = $story;
        $this->view->list = $list;
        $this->view->hasDate = $hasDate;
        $this->view->hasChannel = $hasChannel;
        $this->view->previousStory = $channel_logger_manager->getPreviousStory($story, $list, $hasDate, $hasChannel);
        $this->view->nextStory = $channel_logger_manager->getNextStory($story, $list, $hasDate, $hasChannel);
 
        // Navigation
        $this->view->navigation = [
            ['name' => $this->_t('Home', []), 'url' => '/', ],
            ['name' => $this->_t('Stories', []), 'url' => '/historias', ],
            ['name' => $this->_t('Channel %name%', ['name' => $story->channel]), 'url' => '/historias/'.$channel, ],
            ['name' => $date, 'url' => '/historias/'.$channel.'/'.$date, ],
            ['name' => $id, 'url' => '/historias/'.$channel.'/'.$date.'/'.$id, ],
        ];
    }
 
    public function likeAction($channel, $date, $id)
    {
        $this->response->setHeader('X-Robots-Tag', 'noindex');
        $this->view->disable();
 
        try {
            $channel_logger_manager = $this->getDI()->get('channel_logger_manager');
            $story = $channel_logger_manager->getStory($id);
        } catch (\Exception $e) {
            $this->flashSession->error($this->_t($e->getMessage()));
            $this->response->redirect('/historias');
            return;
        }
 
        if ($story->channel != '#'.$channel || $story->getDate('Y-m-d') != $date) {
            $this->flashSession->error($this->_t('Unexpected error'));
            $this->response->redirect('/historias');
            return;
        }
 
        try {
            $detect = new MobileDetect();
            $detect->setUserAgent($this->request->getUserAgent());
            if (false === $detect->is('Bot') && false === $detect->is('MobileBot')) {
                if ($this->auth->isUserSignedIn()) {
                    $story = $channel_logger_manager->likeStory($story, $this->auth->getIdentity()['email']);
                } else {
                    $story = $channel_logger_manager->likeStorySession($story);
                }
            }
        } catch (\Exception $e) {
            $this->flashSession->error($this->_t($e->getMessage()));
        }
 
        $this->response->redirect($this->view->backLink);
    }
 
    public function dislikeAction($channel, $date, $id)
    {
        $this->response->setHeader('X-Robots-Tag', 'noindex');
        $this->view->disable();
 
        try {
            $channel_logger_manager = $this->getDI()->get('channel_logger_manager');
            $story = $channel_logger_manager->getStory($id);
        } catch (\Exception $e) {
            $this->flashSession->error($this->_t($e->getMessage()));
            $this->response->redirect('/historias');
            return;
        }
 
        if ($story->channel != '#'.$channel || $story->getDate('Y-m-d') != $date) {
            $this->flashSession->error($this->_t('Unexpected error'));
            $this->response->redirect('/historias');
            return;
        }
 
        try {
            $detect = new MobileDetect();
            $detect->setUserAgent($this->request->getUserAgent());
            if (false === $detect->is('Bot') && false === $detect->is('MobileBot')) {
                if ($this->auth->isUserSignedIn()) {
                    $story = $channel_logger_manager->dislikeStory($story, $this->auth->getIdentity()['email']);
                } else {
                    $story = $channel_logger_manager->dislikeStorySession($story);
                }
            }
        } catch (\Exception $e) {
            $this->flashSession->error($this->_t($e->getMessage()));
        }
 
        $this->response->redirect($this->view->backLink);
    }
}
#5ChatHispanoEngine\Web\Controllers\StoriesController->indexAction
#6Phalcon\Dispatcher\AbstractDispatcher->callActionMethod
#7Phalcon\Dispatcher\AbstractDispatcher->dispatch
#8Phalcon\Mvc\Application->handle
/srv/ChatHispanoEngine/releases/20250927141530/apps/Application.php (150)
<?php
 
// namespace ChatHispanoEngine;
 
/**
 * Application driver class to initialize Phalcon and
 * other resources.
 */
class Application extends \Phalcon\Mvc\Application
{
    private static $mode = 'dev';
 
    private static $DEFAULT_MODULE = 'api';
 
    public const MODE_PRODUCTION = 'prod';
    public const MODE_STAGING = 'staging';
    public const MODE_TEST = 'test';
    public const MODE_DEVELOPMENT = 'dev';
 
    /**
     * Set application mode and error reporting level.
     */
    public function __construct($defaultModule, $env = 'dev')
    {
        $this->modules = array(
            'core' => array(
                'className' => 'ChatHispanoEngine\Core\Module',
                'path' => __DIR__.'/Core/Module.php',
            ),
            'api' => array(
                'className' => 'ChatHispanoEngine\Api\Module',
                'path' => __DIR__.'/Api/Module.php',
            ),
            'login' => array(
                'className' => 'ChatHispanoEngine\Login\Module',
                'path' => __DIR__.'/Login/Module.php',
            ),
            'oidc' => array(
                'className' => 'ChatHispanoEngine\Oidc\Module',
                'path' => __DIR__.'/Oidc/Module.php',
            ),
            'web' => array(
                'className' => 'ChatHispanoEngine\Web\Module',
                'path' => __DIR__.'/Web/Module.php',
            ),
            'backoffice' => array(
                'className' => 'ChatHispanoEngine\Backoffice\Module',
                'path' => __DIR__.'/Backoffice/Module.php',
            ),
            'movil' => array(
                'className' => 'ChatHispanoEngine\Movil\Module',
                'path' => __DIR__.'/Movil/Module.php',
            ),
            'regwebexternal' => array(
                'className' => 'ChatHispanoEngine\RegWebExternal\Module',
                'path' => __DIR__.'/Regwebexternal/Module.php',
            ),
            'cdn' => array(
                'className' => 'ChatHispanoEngine\Cdn\Module',
                'path' => __DIR__.'/Cdn/Module.php',
            ),
            'shorten' => array(
                'className' => 'ChatHispanoEngine\Shorten\Module',
                'path' => __DIR__.'/Shorten/Module.php',
            ),
        );
 
        static::$DEFAULT_MODULE = $defaultModule;
        self::$mode = $env;
 
        self::$mode = trim(file_get_contents(__DIR__.'/../config/environment.txt'));
        define('ENVIRONMENT', self::$mode);
 
        if (!defined('PHALCON_MODE')) {
            $mode = getenv('PHALCON_MODE');
            $mode = $mode ? $mode : self::$mode;
            define('PHALCON_MODE', $mode);
        }
 
        switch (self::getMode()) {
            case self::MODE_PRODUCTION:
            case self::MODE_STAGING:
                error_reporting(0);
                break;
            case self::MODE_TEST:
            case self::MODE_DEVELOPMENT:
                ini_set('display_errors', 'On');
                error_reporting(E_ALL);
                break;
        }
    }
 
    /**
     * Register the services here to make them general or register in
     * the ModuleDefinition to make them module-specific.
     */
    protected function _registerServices()
    {
        $defaultModule = self::$DEFAULT_MODULE;
        $modules = $this->modules;
        $config = include __DIR__.'/../config/config.php';
        $env_config = include __DIR__.'/../config/config_'.ENVIRONMENT.'.php';
        $config->merge($env_config);
 
        $di = new \Phalcon\DI\FactoryDefault();
 
        include __DIR__.'/../config/loader.php';
        include __DIR__.'/../config/services.php';
        include __DIR__.'/../config/routing.php';
 
        $this->setDI($di);
    }
 
    /**
     * Run the application.
     */
    public function main()
    {
        if (static::MODE_PRODUCTION === static::getMode()) {
            $this->mainProd();
        } else {
            $this->mainDev();
        }
    }
 
    private function getRequestUri()
    {
        if (!isset($_SERVER)) {
            return "/";
        }
        if (!is_array($_SERVER)) {
            return "/";
        }
        if (!isset($_SERVER['REQUEST_URI'])) {
            return "/";
        }
        return $_SERVER['REQUEST_URI'];
    }
 
    /**
     * Run the development environment.
     */
    private function mainDev()
    {
        (new \Phalcon\Support\Debug())->listen();
 
        $this->_registerServices();
        $this->registerModules($this->modules);
 
        $response = $this->handle($this->getRequestUri());
        $response->send();
    }
 
    /**
     * Run the production environment.
     */
    private function mainProd()
    {
        try {
            $this->registerModules($this->modules);
            $this->_registerServices();
 
            $response = $this->handle($this->getRequestUri());
            $response->send();
        } catch (\Exception $e) {
            $logger = new \Phalcon\Logger\Adapter\Stream(__DIR__.'/../logs/'.date('Y-m-d').'.log');
            $msg = "[".$_SERVER['SERVER_NAME']."] [".$_SERVER['REQUEST_URI']."] [".$e->getCode()."] ".$e->getMessage()." at ".$e->getFile()." (".$e->getLine().")";
            $msg .= "\n".$e->getTraceAsString();
            $logger->process(new \Phalcon\Logger\Item($msg, "error", 100));
            $logger->close();
 
            // remove view contents from buffer
            ob_clean();
 
            $errorCode = 500;
            $errorView = __DIR__.'/../public/errors/error.html';
 
            if (401 === $e->getCode()) {
                // 401 UNAUTHORIZED
                $errorCode = 401;
            } elseif (403 === $e->getCode()) {
                // 403 FORBIDDEN
                $errorCode = 403;
            } elseif (404 === $e->getCode()
                || $e instanceof Phalcon\Mvc\View\Exception
                || $e instanceof Phalcon\Mvc\Dispatcher\Exception) {
                // 404 NOT FOUND
                $errorCode = 404;
            }
 
            // Get error view contents. Since we are including the view
            // file here you can use PHP and local vars inside the error view.
            ob_start();
            include_once $errorView;
            $contents = ob_get_contents();
            ob_end_clean();
 
            // send view to header
            $response = $this->getDI()->getShared('response');
            $response->resetHeaders()
                ->setStatusCode($errorCode, null)
                ->setContent($contents)
                ->send()
            ;
 
            /**
             * We try to register in MongoDB the error to be able to
             * track it in backoffice and/or receive emails
             */
            try {
                $system_log_manager = $this->getDI()->get('system_log_manager');
                if ($errorCode == 500) {
                    $system_log_manager->createError([
                        'ip' => $_SERVER['SERVER_ADDR'],
                        'host' => $_SERVER['SERVER_NAME'],
                        'process' => 'php-fpm',
                        'message' => $e->getMessage(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                    ]);
                } else {
                    $system_log_manager->createWarning([
                        'ip' => $_SERVER['SERVER_ADDR'],
                        'host' => $_SERVER['SERVER_NAME'],
                        'process' => 'php-fpm',
                        'message' => $e->getMessage(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                    ]);
                }
            } catch (\Exception $e) {
            }
        }
    }
 
    public function slowLog($t)
    {
        $config = $this->getDI()->get('config');
        $irc_manager = $this->getDI()->get('inspircd_irc_manager');
        $server = gethostname() ? gethostname() : 'unknown';
        $uri = "https://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
        if (substr($_SERVER['REQUEST_URI'], 0, 10) == '/historias') {
            return;
        }
        $msg = "\x02[SLOWLOG] [".str_pad($server, 6, " ", STR_PAD_LEFT)."] [".str_pad(round($t, 1), 5, " ", STR_PAD_LEFT)."s] PATH=\x02 ".$_SERVER['REQUEST_URI']
            ."\x02 TYPE=\x02 ".$_SERVER['REQUEST_METHOD']."\x02 URI=\x02 ".$uri;
        $irc_manager->privmsgOrderEnqueue('AAAAAI', $config->irc->debug_channel, $msg);
    }
 
    /**
     * Get the current mode.
     *
     * @return string
     */
    public static function getMode()
    {
        return self::$mode;
    }
}
#9Application->mainDev
/srv/ChatHispanoEngine/releases/20250927141530/apps/Application.php (122)
<?php
 
// namespace ChatHispanoEngine;
 
/**
 * Application driver class to initialize Phalcon and
 * other resources.
 */
class Application extends \Phalcon\Mvc\Application
{
    private static $mode = 'dev';
 
    private static $DEFAULT_MODULE = 'api';
 
    public const MODE_PRODUCTION = 'prod';
    public const MODE_STAGING = 'staging';
    public const MODE_TEST = 'test';
    public const MODE_DEVELOPMENT = 'dev';
 
    /**
     * Set application mode and error reporting level.
     */
    public function __construct($defaultModule, $env = 'dev')
    {
        $this->modules = array(
            'core' => array(
                'className' => 'ChatHispanoEngine\Core\Module',
                'path' => __DIR__.'/Core/Module.php',
            ),
            'api' => array(
                'className' => 'ChatHispanoEngine\Api\Module',
                'path' => __DIR__.'/Api/Module.php',
            ),
            'login' => array(
                'className' => 'ChatHispanoEngine\Login\Module',
                'path' => __DIR__.'/Login/Module.php',
            ),
            'oidc' => array(
                'className' => 'ChatHispanoEngine\Oidc\Module',
                'path' => __DIR__.'/Oidc/Module.php',
            ),
            'web' => array(
                'className' => 'ChatHispanoEngine\Web\Module',
                'path' => __DIR__.'/Web/Module.php',
            ),
            'backoffice' => array(
                'className' => 'ChatHispanoEngine\Backoffice\Module',
                'path' => __DIR__.'/Backoffice/Module.php',
            ),
            'movil' => array(
                'className' => 'ChatHispanoEngine\Movil\Module',
                'path' => __DIR__.'/Movil/Module.php',
            ),
            'regwebexternal' => array(
                'className' => 'ChatHispanoEngine\RegWebExternal\Module',
                'path' => __DIR__.'/Regwebexternal/Module.php',
            ),
            'cdn' => array(
                'className' => 'ChatHispanoEngine\Cdn\Module',
                'path' => __DIR__.'/Cdn/Module.php',
            ),
            'shorten' => array(
                'className' => 'ChatHispanoEngine\Shorten\Module',
                'path' => __DIR__.'/Shorten/Module.php',
            ),
        );
 
        static::$DEFAULT_MODULE = $defaultModule;
        self::$mode = $env;
 
        self::$mode = trim(file_get_contents(__DIR__.'/../config/environment.txt'));
        define('ENVIRONMENT', self::$mode);
 
        if (!defined('PHALCON_MODE')) {
            $mode = getenv('PHALCON_MODE');
            $mode = $mode ? $mode : self::$mode;
            define('PHALCON_MODE', $mode);
        }
 
        switch (self::getMode()) {
            case self::MODE_PRODUCTION:
            case self::MODE_STAGING:
                error_reporting(0);
                break;
            case self::MODE_TEST:
            case self::MODE_DEVELOPMENT:
                ini_set('display_errors', 'On');
                error_reporting(E_ALL);
                break;
        }
    }
 
    /**
     * Register the services here to make them general or register in
     * the ModuleDefinition to make them module-specific.
     */
    protected function _registerServices()
    {
        $defaultModule = self::$DEFAULT_MODULE;
        $modules = $this->modules;
        $config = include __DIR__.'/../config/config.php';
        $env_config = include __DIR__.'/../config/config_'.ENVIRONMENT.'.php';
        $config->merge($env_config);
 
        $di = new \Phalcon\DI\FactoryDefault();
 
        include __DIR__.'/../config/loader.php';
        include __DIR__.'/../config/services.php';
        include __DIR__.'/../config/routing.php';
 
        $this->setDI($di);
    }
 
    /**
     * Run the application.
     */
    public function main()
    {
        if (static::MODE_PRODUCTION === static::getMode()) {
            $this->mainProd();
        } else {
            $this->mainDev();
        }
    }
 
    private function getRequestUri()
    {
        if (!isset($_SERVER)) {
            return "/";
        }
        if (!is_array($_SERVER)) {
            return "/";
        }
        if (!isset($_SERVER['REQUEST_URI'])) {
            return "/";
        }
        return $_SERVER['REQUEST_URI'];
    }
 
    /**
     * Run the development environment.
     */
    private function mainDev()
    {
        (new \Phalcon\Support\Debug())->listen();
 
        $this->_registerServices();
        $this->registerModules($this->modules);
 
        $response = $this->handle($this->getRequestUri());
        $response->send();
    }
 
    /**
     * Run the production environment.
     */
    private function mainProd()
    {
        try {
            $this->registerModules($this->modules);
            $this->_registerServices();
 
            $response = $this->handle($this->getRequestUri());
            $response->send();
        } catch (\Exception $e) {
            $logger = new \Phalcon\Logger\Adapter\Stream(__DIR__.'/../logs/'.date('Y-m-d').'.log');
            $msg = "[".$_SERVER['SERVER_NAME']."] [".$_SERVER['REQUEST_URI']."] [".$e->getCode()."] ".$e->getMessage()." at ".$e->getFile()." (".$e->getLine().")";
            $msg .= "\n".$e->getTraceAsString();
            $logger->process(new \Phalcon\Logger\Item($msg, "error", 100));
            $logger->close();
 
            // remove view contents from buffer
            ob_clean();
 
            $errorCode = 500;
            $errorView = __DIR__.'/../public/errors/error.html';
 
            if (401 === $e->getCode()) {
                // 401 UNAUTHORIZED
                $errorCode = 401;
            } elseif (403 === $e->getCode()) {
                // 403 FORBIDDEN
                $errorCode = 403;
            } elseif (404 === $e->getCode()
                || $e instanceof Phalcon\Mvc\View\Exception
                || $e instanceof Phalcon\Mvc\Dispatcher\Exception) {
                // 404 NOT FOUND
                $errorCode = 404;
            }
 
            // Get error view contents. Since we are including the view
            // file here you can use PHP and local vars inside the error view.
            ob_start();
            include_once $errorView;
            $contents = ob_get_contents();
            ob_end_clean();
 
            // send view to header
            $response = $this->getDI()->getShared('response');
            $response->resetHeaders()
                ->setStatusCode($errorCode, null)
                ->setContent($contents)
                ->send()
            ;
 
            /**
             * We try to register in MongoDB the error to be able to
             * track it in backoffice and/or receive emails
             */
            try {
                $system_log_manager = $this->getDI()->get('system_log_manager');
                if ($errorCode == 500) {
                    $system_log_manager->createError([
                        'ip' => $_SERVER['SERVER_ADDR'],
                        'host' => $_SERVER['SERVER_NAME'],
                        'process' => 'php-fpm',
                        'message' => $e->getMessage(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                    ]);
                } else {
                    $system_log_manager->createWarning([
                        'ip' => $_SERVER['SERVER_ADDR'],
                        'host' => $_SERVER['SERVER_NAME'],
                        'process' => 'php-fpm',
                        'message' => $e->getMessage(),
                        'file' => $e->getFile(),
                        'line' => $e->getLine(),
                    ]);
                }
            } catch (\Exception $e) {
            }
        }
    }
 
    public function slowLog($t)
    {
        $config = $this->getDI()->get('config');
        $irc_manager = $this->getDI()->get('inspircd_irc_manager');
        $server = gethostname() ? gethostname() : 'unknown';
        $uri = "https://".$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
        if (substr($_SERVER['REQUEST_URI'], 0, 10) == '/historias') {
            return;
        }
        $msg = "\x02[SLOWLOG] [".str_pad($server, 6, " ", STR_PAD_LEFT)."] [".str_pad(round($t, 1), 5, " ", STR_PAD_LEFT)."s] PATH=\x02 ".$_SERVER['REQUEST_URI']
            ."\x02 TYPE=\x02 ".$_SERVER['REQUEST_METHOD']."\x02 URI=\x02 ".$uri;
        $irc_manager->privmsgOrderEnqueue('AAAAAI', $config->irc->debug_channel, $msg);
    }
 
    /**
     * Get the current mode.
     *
     * @return string
     */
    public static function getMode()
    {
        return self::$mode;
    }
}
#10Application->main
/srv/ChatHispanoEngine/releases/20250927141530/public/index.php (13)
<?php
 
date_default_timezone_set('Europe/Madrid');
 
require_once __DIR__.'/../vendor/autoload.php';
require_once __DIR__.'/../apps/Application.php';
 
if (isset($_SERVER['PHALCON_APP'])) {
    $app = new Application($_SERVER['PHALCON_APP']);
} else {
    $app = new Application('web');
}
$app->main();
 
try {
    $t = microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
    if ($t > 6) {
        $app->slowLog($t);
    }
} catch (\Exception $e) {
}
KeyValue
_url/historias
KeyValue
USERwww-data
HOME/var/www
HTTP_CONNECTIONclose
HTTP_X_FORWARDED_FOR216.73.217.19
HTTP_X_FORWARDED_PROTOhttp
HTTP_COOKIEPHPSESSID=746f49f3d9573681655006483137ee69
HTTP_REFERERhttps://test.chathispano.com/historias/les_maduras/2024-05-24/66512b62527d1d7fbb0b89c1/like
HTTP_ACCEPT_ENCODINGgzip, br, zstd, deflate
HTTP_USER_AGENTMozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)
HTTP_ACCEPT*/*
DISABLE_ANTEVENIO0
DISABLE_MOBUSI0
DISABLE_MASSARIUS0
DISABLE_RELATEDCONTENT0
DISABLE_ADSENSE0
ADS_DEFER0
PHALCON_APPweb
CHATHISPANOENGINE_REVISIONdevelopment
CHATHISPANOENGINE_INSTANCEvirtualbox
SCRIPT_FILENAME/srv/ChatHispanoEngine/releases/20250927141530/public/index.php
PATH_TRANSLATED/srv/ChatHispanoEngine/current/public
PATH_INFO
HTTP_HOSTtest.chathispano.com
REDIRECT_STATUS200
SERVER_NAMEtest.chathispano.com
SERVER_PORT80
SERVER_ADDR10.234.61.101
REMOTE_USER
REMOTE_PORT36460
REMOTE_ADDR10.234.61.153
SERVER_SOFTWAREnginx/1.26.3
GATEWAY_INTERFACECGI/1.1
REQUEST_SCHEMEhttp
SERVER_PROTOCOLHTTP/1.1
DOCUMENT_ROOT/srv/ChatHispanoEngine/releases/20250927141530/public
DOCUMENT_URI/index.php
REQUEST_URI/historias
SCRIPT_NAME/index.php
CONTENT_LENGTH
CONTENT_TYPE
REQUEST_METHODGET
QUERY_STRING_url=/historias
FCGI_ROLERESPONDER
PHP_SELF/index.php
REQUEST_TIME_FLOAT1781542643.0192
REQUEST_TIME1781542643
#Path
0/srv/ChatHispanoEngine/releases/20250927141530/public/index.php
1/srv/ChatHispanoEngine/releases/20250927141530/vendor/autoload.php
2/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/autoload_real.php
3/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/platform_check.php
4/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/ClassLoader.php
5/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/include_paths.php
6/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/autoload_static.php
7/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-mbstring/bootstrap.php
8/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-mbstring/bootstrap80.php
9/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/deprecation-contracts/function.php
10/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/amp/lib/functions.php
11/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/amp/lib/Internal/functions.php
12/srv/ChatHispanoEngine/releases/20250927141530/vendor/react/promise/src/functions_include.php
13/srv/ChatHispanoEngine/releases/20250927141530/vendor/react/promise/src/functions.php
14/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-ctype/bootstrap.php
15/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-ctype/bootstrap80.php
16/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-intl-normalizer/bootstrap.php
17/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-intl-normalizer/bootstrap80.php
18/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-php85/bootstrap.php
19/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-php85/bootstrap80.php
20/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-php84/bootstrap.php
21/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-intl-grapheme/bootstrap.php
22/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-intl-grapheme/bootstrap80.php
23/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/byte-stream/lib/functions.php
24/srv/ChatHispanoEngine/releases/20250927141530/vendor/illuminate/collections/functions.php
25/srv/ChatHispanoEngine/releases/20250927141530/vendor/illuminate/collections/helpers.php
26/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/string/Resources/functions.php
27/srv/ChatHispanoEngine/releases/20250927141530/vendor/myclabs/deep-copy/src/DeepCopy/deep_copy.php
28/srv/ChatHispanoEngine/releases/20250927141530/vendor/ralouphie/getallheaders/src/getallheaders.php
29/srv/ChatHispanoEngine/releases/20250927141530/vendor/phpunit/phpunit/src/Framework/Assert/Functions.php
30/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/clock/Resources/now.php
31/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-intl-idn/bootstrap.php
32/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/translation/Resources/functions.php
33/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/var-dumper/Resources/functions/dump.php
34/srv/ChatHispanoEngine/releases/20250927141530/vendor/guzzlehttp/guzzle/src/functions_include.php
35/srv/ChatHispanoEngine/releases/20250927141530/vendor/guzzlehttp/guzzle/src/functions.php
36/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-php80/bootstrap.php
37/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/process/lib/functions.php
38/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/serialization/src/functions.php
39/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/sync/src/functions.php
40/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/sync/src/ConcurrentIterator/functions.php
41/srv/ChatHispanoEngine/releases/20250927141530/vendor/illuminate/reflection/helpers.php
42/srv/ChatHispanoEngine/releases/20250927141530/vendor/psy/psysh/src/functions.php
43/srv/ChatHispanoEngine/releases/20250927141530/vendor/daverandom/libdns/src/functions.php
44/srv/ChatHispanoEngine/releases/20250927141530/vendor/illuminate/support/functions.php
45/srv/ChatHispanoEngine/releases/20250927141530/vendor/illuminate/support/helpers.php
46/srv/ChatHispanoEngine/releases/20250927141530/vendor/symfony/polyfill-php81/bootstrap.php
47/srv/ChatHispanoEngine/releases/20250927141530/vendor/amphp/dns/lib/functions.php
48/srv/ChatHispanoEngine/releases/20250927141530/vendor/codeception/codeception/functions.php
49/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/functions.php
50/srv/ChatHispanoEngine/releases/20250927141530/apps/Application.php
51/srv/ChatHispanoEngine/releases/20250927141530/config/config.php
52/srv/ChatHispanoEngine/releases/20250927141530/config/config_staging.php
53/srv/ChatHispanoEngine/releases/20250927141530/config/loader.php
54/srv/ChatHispanoEngine/releases/20250927141530/config/services.php
55/srv/ChatHispanoEngine/releases/20250927141530/config/managers.php
56/srv/ChatHispanoEngine/releases/20250927141530/config/routing.php
57/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/config/routing.php
58/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/Module.php
59/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/config/config.php
60/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/config/services.php
61/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/config/managers.php
62/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/Controllers/StoriesController.php
63/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/Controllers/BaseController.php
64/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Controllers/BaseController.php
65/srv/ChatHispanoEngine/releases/20250927141530/vendor/iwalkalone/translator/src/Translator.php
66/srv/ChatHispanoEngine/releases/20250927141530/apps/Web/Auth/Auth.php
67/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Managers/InspIRCd/GlineManager.php
68/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Models/InspIRCd/Gline.php
69/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Models/BaseCollection.php
70/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Client.php
71/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/InstalledVersions.php
72/srv/ChatHispanoEngine/releases/20250927141530/vendor/composer/installed.php
73/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/BuilderEncoder.php
74/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Codec/EncodeIfSupported.php
75/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Codec/Encoder.php
76/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/PipelineEncoder.php
77/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/RecursiveEncode.php
78/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/VariableEncoder.php
79/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/DictionaryEncoder.php
80/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/FieldPathEncoder.php
81/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/CombinedFieldQueryEncoder.php
82/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/QueryEncoder.php
83/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/OutputWindowEncoder.php
84/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/OperatorEncoder.php
85/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Builder/Encoder/DateTimeEncoder.php
86/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Database.php
87/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Collection.php
88/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Operation/FindOne.php
89/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Operation/Explainable.php
90/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Operation/Find.php
91/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Managers/InspIRCd/ChannelManager.php
92/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Models/Channel/Datasheet.php
93/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Managers/VivelavitaManager.php
94/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Managers/InspIRCd/ChannelLoggerManager.php
95/srv/ChatHispanoEngine/releases/20250927141530/apps/Core/Models/InspIRCd/Channel/Story.php
96/srv/ChatHispanoEngine/releases/20250927141530/vendor/mongodb/mongodb/src/Operation/Count.php
Memory
Usage4194304