git subrepo commit (merge) mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "02ae5285"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "649a5c01"
git-subrepo: version: "0.4.3"
origin: "???"
commit: "???"
Change-Id: I870ad468fba026cc5abf3c5699ed1e12ff28b32b
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php
new file mode 100644
index 0000000..d7480a7
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ArrayCacheStore.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use Psr\SimpleCache\CacheInterface;
+
+class ArrayCacheStore implements CacheInterface
+{
+ use InteractsWithTime;
+
+ /**
+ * An array of stored values.
+ *
+ * @var array
+ */
+ protected $storage = [];
+
+ /**
+ * @inheritdoc
+ */
+ public function get($key, $default = null)
+ {
+ if (! isset($this->storage[$key])) {
+ return $default;
+ }
+
+ $item = $this->storage[$key];
+
+ $expiresAt = $item['expiresAt'] ?? 0;
+
+ if ($expiresAt !== 0 && $this->currentTime() > $expiresAt) {
+ $this->delete($key);
+
+ return $default;
+ }
+
+ return $item['value'];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $this->storage[$key] = [
+ 'value' => $value,
+ 'expiresAt' => $this->calculateExpiration($ttl),
+ ];
+
+ return true;
+ }
+
+ /**
+ * Get the expiration time of the key.
+ *
+ * @param int $seconds
+ *
+ * @return int
+ */
+ protected function calculateExpiration($seconds)
+ {
+ return $this->toTimestamp($seconds);
+ }
+
+ /**
+ * Get the UNIX timestamp for the given number of seconds.
+ *
+ * @param int $seconds
+ *
+ * @return int
+ */
+ protected function toTimestamp($seconds)
+ {
+ return $seconds > 0 ? $this->availableAt($seconds) : 0;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function delete($key)
+ {
+ unset($this->storage[$key]);
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function clear()
+ {
+ $this->storage = [];
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ $values = [];
+
+ foreach ($keys as $key) {
+ $values[$key] = $this->get($key, $default);
+ }
+
+ return $values;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value, $ttl);
+ }
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function deleteMultiple($keys)
+ {
+ foreach ($keys as $key) {
+ $this->delete($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function has($key)
+ {
+ return isset($this->storage[$key]);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php
new file mode 100644
index 0000000..c75afa2
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Builder.php
@@ -0,0 +1,1903 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use BadMethodCallException;
+use Closure;
+use DateTimeInterface;
+use InvalidArgumentException;
+use LdapRecord\Connection;
+use LdapRecord\Container;
+use LdapRecord\EscapesValues;
+use LdapRecord\LdapInterface;
+use LdapRecord\LdapRecordException;
+use LdapRecord\Models\Model;
+use LdapRecord\Query\Events\QueryExecuted;
+use LdapRecord\Query\Model\Builder as ModelBuilder;
+use LdapRecord\Query\Pagination\LazyPaginator;
+use LdapRecord\Query\Pagination\Paginator;
+use LdapRecord\Support\Arr;
+use LdapRecord\Utilities;
+
+class Builder
+{
+ use EscapesValues;
+
+ /**
+ * The selected columns to retrieve on the query.
+ *
+ * @var array
+ */
+ public $columns;
+
+ /**
+ * The query filters.
+ *
+ * @var array
+ */
+ public $filters = [
+ 'and' => [],
+ 'or' => [],
+ 'raw' => [],
+ ];
+
+ /**
+ * The LDAP server controls to be sent.
+ *
+ * @var array
+ */
+ public $controls = [];
+
+ /**
+ * The size limit of the query.
+ *
+ * @var int
+ */
+ public $limit = 0;
+
+ /**
+ * Determines whether the current query is paginated.
+ *
+ * @var bool
+ */
+ public $paginated = false;
+
+ /**
+ * The distinguished name to perform searches upon.
+ *
+ * @var string|null
+ */
+ protected $dn;
+
+ /**
+ * The base distinguished name to perform searches inside.
+ *
+ * @var string|null
+ */
+ protected $baseDn;
+
+ /**
+ * The default query type.
+ *
+ * @var string
+ */
+ protected $type = 'search';
+
+ /**
+ * Determines whether the query is nested.
+ *
+ * @var bool
+ */
+ protected $nested = false;
+
+ /**
+ * Determines whether the query should be cached.
+ *
+ * @var bool
+ */
+ protected $caching = false;
+
+ /**
+ * How long the query should be cached until.
+ *
+ * @var DateTimeInterface|null
+ */
+ protected $cacheUntil = null;
+
+ /**
+ * Determines whether the query cache must be flushed.
+ *
+ * @var bool
+ */
+ protected $flushCache = false;
+
+ /**
+ * The current connection instance.
+ *
+ * @var Connection
+ */
+ protected $connection;
+
+ /**
+ * The current grammar instance.
+ *
+ * @var Grammar
+ */
+ protected $grammar;
+
+ /**
+ * The current cache instance.
+ *
+ * @var Cache|null
+ */
+ protected $cache;
+
+ /**
+ * Constructor.
+ *
+ * @param Connection $connection
+ */
+ public function __construct(Connection $connection)
+ {
+ $this->connection = $connection;
+ $this->grammar = new Grammar();
+ }
+
+ /**
+ * Set the current connection.
+ *
+ * @param Connection $connection
+ *
+ * @return $this
+ */
+ public function setConnection(Connection $connection)
+ {
+ $this->connection = $connection;
+
+ return $this;
+ }
+
+ /**
+ * Set the current filter grammar.
+ *
+ * @param Grammar $grammar
+ *
+ * @return $this
+ */
+ public function setGrammar(Grammar $grammar)
+ {
+ $this->grammar = $grammar;
+
+ return $this;
+ }
+
+ /**
+ * Set the cache to store query results.
+ *
+ * @param Cache|null $cache
+ *
+ * @return $this
+ */
+ public function setCache(Cache $cache = null)
+ {
+ $this->cache = $cache;
+
+ return $this;
+ }
+
+ /**
+ * Returns a new Query Builder instance.
+ *
+ * @param string $baseDn
+ *
+ * @return $this
+ */
+ public function newInstance($baseDn = null)
+ {
+ // We'll set the base DN of the new Builder so
+ // developers don't need to do this manually.
+ $dn = is_null($baseDn) ? $this->getDn() : $baseDn;
+
+ return (new static($this->connection))->setDn($dn);
+ }
+
+ /**
+ * Returns a new nested Query Builder instance.
+ *
+ * @param Closure|null $closure
+ *
+ * @return $this
+ */
+ public function newNestedInstance(Closure $closure = null)
+ {
+ $query = $this->newInstance()->nested();
+
+ if ($closure) {
+ $closure($query);
+ }
+
+ return $query;
+ }
+
+ /**
+ * Executes the LDAP query.
+ *
+ * @param string|array $columns
+ *
+ * @return Collection|array
+ */
+ public function get($columns = ['*'])
+ {
+ return $this->onceWithColumns(Arr::wrap($columns), function () {
+ return $this->query($this->getQuery());
+ });
+ }
+
+ /**
+ * Execute the given callback while selecting the given columns.
+ *
+ * After running the callback, the columns are reset to the original value.
+ *
+ * @param array $columns
+ * @param callable $callback
+ *
+ * @return mixed
+ */
+ protected function onceWithColumns($columns, $callback)
+ {
+ $original = $this->columns;
+
+ if (is_null($original)) {
+ $this->columns = $columns;
+ }
+
+ $result = $callback();
+
+ $this->columns = $original;
+
+ return $result;
+ }
+
+ /**
+ * Compiles and returns the current query string.
+ *
+ * @return string
+ */
+ public function getQuery()
+ {
+ // We need to ensure we have at least one filter, as
+ // no query results will be returned otherwise.
+ if (count(array_filter($this->filters)) === 0) {
+ $this->whereHas('objectclass');
+ }
+
+ return $this->grammar->compile($this);
+ }
+
+ /**
+ * Returns the unescaped query.
+ *
+ * @return string
+ */
+ public function getUnescapedQuery()
+ {
+ return Utilities::unescape($this->getQuery());
+ }
+
+ /**
+ * Returns the current Grammar instance.
+ *
+ * @return Grammar
+ */
+ public function getGrammar()
+ {
+ return $this->grammar;
+ }
+
+ /**
+ * Returns the current Cache instance.
+ *
+ * @return Cache|null
+ */
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+ /**
+ * Returns the current Connection instance.
+ *
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+ /**
+ * Returns the query type.
+ *
+ * @return string
+ */
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ /**
+ * Set the base distinguished name of the query.
+ *
+ * @param Model|string $dn
+ *
+ * @return $this
+ */
+ public function setBaseDn($dn)
+ {
+ $this->baseDn = $this->substituteBaseInDn($dn);
+
+ return $this;
+ }
+
+ /**
+ * Get the base distinguished name of the query.
+ *
+ * @return string|null
+ */
+ public function getBaseDn()
+ {
+ return $this->baseDn;
+ }
+
+ /**
+ * Get the distinguished name of the query.
+ *
+ * @return string
+ */
+ public function getDn()
+ {
+ return $this->dn;
+ }
+
+ /**
+ * Set the distinguished name for the query.
+ *
+ * @param string|Model|null $dn
+ *
+ * @return $this
+ */
+ public function setDn($dn = null)
+ {
+ $this->dn = $this->substituteBaseInDn($dn);
+
+ return $this;
+ }
+
+ /**
+ * Substitute the base DN string template for the current base.
+ *
+ * @param Model|string $dn
+ *
+ * @return string
+ */
+ protected function substituteBaseInDn($dn)
+ {
+ return str_replace(
+ '{base}',
+ $this->baseDn,
+ $dn instanceof Model ? $dn->getDn() : $dn
+ );
+ }
+
+ /**
+ * Alias for setting the distinguished name for the query.
+ *
+ * @param string|Model|null $dn
+ *
+ * @return $this
+ */
+ public function in($dn = null)
+ {
+ return $this->setDn($dn);
+ }
+
+ /**
+ * Set the size limit of the current query.
+ *
+ * @param int $limit
+ *
+ * @return $this
+ */
+ public function limit($limit = 0)
+ {
+ $this->limit = $limit;
+
+ return $this;
+ }
+
+ /**
+ * Returns a new query for the given model.
+ *
+ * @param Model $model
+ *
+ * @return ModelBuilder
+ */
+ public function model(Model $model)
+ {
+ return $model->newQueryBuilder($this->connection)
+ ->setCache($this->connection->getCache())
+ ->setBaseDn($this->baseDn)
+ ->setModel($model);
+ }
+
+ /**
+ * Performs the specified query on the current LDAP connection.
+ *
+ * @param string $query
+ *
+ * @return Collection|array
+ */
+ public function query($query)
+ {
+ $start = microtime(true);
+
+ // Here we will create the execution callback. This allows us
+ // to only execute an LDAP request if caching is disabled
+ // or if no cache of the given query exists yet.
+ $callback = function () use ($query) {
+ return $this->parse($this->run($query));
+ };
+
+ $results = $this->getCachedResponse($query, $callback);
+
+ $this->logQuery($this, $this->type, $this->getElapsedTime($start));
+
+ return $this->process($results);
+ }
+
+ /**
+ * Paginates the current LDAP query.
+ *
+ * @param int $pageSize
+ * @param bool $isCritical
+ *
+ * @return Collection|array
+ */
+ public function paginate($pageSize = 1000, $isCritical = false)
+ {
+ $this->paginated = true;
+
+ $start = microtime(true);
+
+ $query = $this->getQuery();
+
+ // Here we will create the pagination callback. This allows us
+ // to only execute an LDAP request if caching is disabled
+ // or if no cache of the given query exists yet.
+ $callback = function () use ($query, $pageSize, $isCritical) {
+ return $this->runPaginate($query, $pageSize, $isCritical);
+ };
+
+ $pages = $this->getCachedResponse($query, $callback);
+
+ $this->logQuery($this, 'paginate', $this->getElapsedTime($start));
+
+ return $this->process($pages);
+ }
+
+ /**
+ * Runs the paginate operation with the given filter.
+ *
+ * @param string $filter
+ * @param int $perPage
+ * @param bool $isCritical
+ *
+ * @return array
+ */
+ protected function runPaginate($filter, $perPage, $isCritical)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($filter, $perPage, $isCritical) {
+ return (new Paginator($this, $filter, $perPage, $isCritical))->execute($ldap);
+ });
+ }
+
+ /**
+ * Chunk the results of a paginated LDAP query.
+ *
+ * @param int $pageSize
+ * @param Closure $callback
+ * @param bool $isCritical
+ *
+ * @return void
+ */
+ public function chunk($pageSize, Closure $callback, $isCritical = false)
+ {
+ $start = microtime(true);
+
+ $query = $this->getQuery();
+
+ foreach ($this->runChunk($query, $pageSize, $isCritical) as $chunk) {
+ $callback($this->process($chunk));
+ }
+
+ $this->logQuery($this, 'chunk', $this->getElapsedTime($start));
+ }
+
+ /**
+ * Runs the chunk operation with the given filter.
+ *
+ * @param string $filter
+ * @param int $perPage
+ * @param bool $isCritical
+ *
+ * @return array
+ */
+ protected function runChunk($filter, $perPage, $isCritical)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($filter, $perPage, $isCritical) {
+ return (new LazyPaginator($this, $filter, $perPage, $isCritical))->execute($ldap);
+ });
+ }
+
+ /**
+ * Processes and converts the given LDAP results into models.
+ *
+ * @param array $results
+ *
+ * @return array
+ */
+ protected function process(array $results)
+ {
+ unset($results['count']);
+
+ return $this->paginated ? $this->flattenPages($results) : $results;
+ }
+
+ /**
+ * Flattens LDAP paged results into a single array.
+ *
+ * @param array $pages
+ *
+ * @return array
+ */
+ protected function flattenPages(array $pages)
+ {
+ $records = [];
+
+ foreach ($pages as $page) {
+ unset($page['count']);
+
+ $records = array_merge($records, $page);
+ }
+
+ return $records;
+ }
+
+ /**
+ * Get the cached response or execute and cache the callback value.
+ *
+ * @param string $query
+ * @param Closure $callback
+ *
+ * @return mixed
+ */
+ protected function getCachedResponse($query, Closure $callback)
+ {
+ // If caching is enabled and we have a cache instance available,
+ // we will try to retrieve the cached results instead.
+ if ($this->caching && $this->cache) {
+ $key = $this->getCacheKey($query);
+
+ if ($this->flushCache) {
+ $this->cache->delete($key);
+ }
+
+ return $this->cache->remember($key, $this->cacheUntil, $callback);
+ }
+
+ // Otherwise, we will simply execute the callback.
+ return $callback();
+ }
+
+ /**
+ * Runs the query operation with the given filter.
+ *
+ * @param string $filter
+ *
+ * @return resource
+ */
+ public function run($filter)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($filter) {
+ // We will avoid setting the controls during any pagination
+ // requests as it will clear the cookie we need to send
+ // to the server upon retrieving every page.
+ if (! $this->paginated) {
+ // Before running the query, we will set the LDAP server controls. This
+ // allows the controls to be automatically reset upon each new query
+ // that is conducted on the same connection during each request.
+ $ldap->setOption(LDAP_OPT_SERVER_CONTROLS, $this->controls);
+ }
+
+ return $ldap->{$this->type}(
+ $this->dn ?? $this->baseDn,
+ $filter,
+ $this->getSelects(),
+ $onlyAttributes = false,
+ $this->limit
+ );
+ });
+ }
+
+ /**
+ * Parses the given LDAP resource by retrieving its entries.
+ *
+ * @param resource $resource
+ *
+ * @return array
+ */
+ public function parse($resource)
+ {
+ if (! $resource) {
+ return [];
+ }
+
+ return $this->connection->run(function (LdapInterface $ldap) use ($resource) {
+ $entries = $ldap->getEntries($resource);
+
+ // Free up memory.
+ if (is_resource($resource)) {
+ $ldap->freeResult($resource);
+ }
+
+ return $entries;
+ });
+ }
+
+ /**
+ * Returns the cache key.
+ *
+ * @param string $query
+ *
+ * @return string
+ */
+ protected function getCacheKey($query)
+ {
+ $host = $this->connection->getLdapConnection()->getHost();
+
+ $key = $host
+ .$this->type
+ .$this->getDn()
+ .$query
+ .implode($this->getSelects())
+ .$this->limit
+ .$this->paginated;
+
+ return md5($key);
+ }
+
+ /**
+ * Returns the first entry in a search result.
+ *
+ * @param array|string $columns
+ *
+ * @return Model|null
+ */
+ public function first($columns = ['*'])
+ {
+ return Arr::get($this->limit(1)->get($columns), 0);
+ }
+
+ /**
+ * Returns the first entry in a search result.
+ *
+ * If no entry is found, an exception is thrown.
+ *
+ * @param array|string $columns
+ *
+ * @throws ObjectNotFoundException
+ *
+ * @return Model|static
+ */
+ public function firstOrFail($columns = ['*'])
+ {
+ if (! $record = $this->first($columns)) {
+ $this->throwNotFoundException($this->getUnescapedQuery(), $this->dn);
+ }
+
+ return $record;
+ }
+
+ /**
+ * Throws a not found exception.
+ *
+ * @param string $query
+ * @param string $dn
+ *
+ * @throws ObjectNotFoundException
+ */
+ protected function throwNotFoundException($query, $dn)
+ {
+ throw ObjectNotFoundException::forQuery($query, $dn);
+ }
+
+ /**
+ * Finds a record by the specified attribute and value.
+ *
+ * @param string $attribute
+ * @param string $value
+ * @param array|string $columns
+ *
+ * @return Model|static|null
+ */
+ public function findBy($attribute, $value, $columns = ['*'])
+ {
+ try {
+ return $this->findByOrFail($attribute, $value, $columns);
+ } catch (ObjectNotFoundException $e) {
+ return;
+ }
+ }
+
+ /**
+ * Finds a record by the specified attribute and value.
+ *
+ * If no record is found an exception is thrown.
+ *
+ * @param string $attribute
+ * @param string $value
+ * @param array|string $columns
+ *
+ * @throws ObjectNotFoundException
+ *
+ * @return Model
+ */
+ public function findByOrFail($attribute, $value, $columns = ['*'])
+ {
+ return $this->whereEquals($attribute, $value)->firstOrFail($columns);
+ }
+
+ /**
+ * Find many records by distinguished name.
+ *
+ * @param array $dns
+ * @param array $columns
+ *
+ * @return array|Collection
+ */
+ public function findMany($dns, $columns = ['*'])
+ {
+ if (empty($dns)) {
+ return $this->process([]);
+ }
+
+ $objects = [];
+
+ foreach ($dns as $dn) {
+ if (! is_null($object = $this->find($dn, $columns))) {
+ $objects[] = $object;
+ }
+ }
+
+ return $this->process($objects);
+ }
+
+ /**
+ * Finds many records by the specified attribute.
+ *
+ * @param string $attribute
+ * @param array $values
+ * @param array $columns
+ *
+ * @return Collection
+ */
+ public function findManyBy($attribute, array $values = [], $columns = ['*'])
+ {
+ $query = $this->select($columns);
+
+ foreach ($values as $value) {
+ $query->orWhere([$attribute => $value]);
+ }
+
+ return $query->get();
+ }
+
+ /**
+ * Finds a record by its distinguished name.
+ *
+ * @param string|array $dn
+ * @param array|string $columns
+ *
+ * @return Model|static|array|Collection|null
+ */
+ public function find($dn, $columns = ['*'])
+ {
+ if (is_array($dn)) {
+ return $this->findMany($dn, $columns);
+ }
+
+ try {
+ return $this->findOrFail($dn, $columns);
+ } catch (ObjectNotFoundException $e) {
+ return;
+ }
+ }
+
+ /**
+ * Finds a record by its distinguished name.
+ *
+ * Fails upon no records returned.
+ *
+ * @param string $dn
+ * @param array|string $columns
+ *
+ * @throws ObjectNotFoundException
+ *
+ * @return Model|static
+ */
+ public function findOrFail($dn, $columns = ['*'])
+ {
+ return $this->setDn($dn)
+ ->read()
+ ->whereHas('objectclass')
+ ->firstOrFail($columns);
+ }
+
+ /**
+ * Adds the inserted fields to query on the current LDAP connection.
+ *
+ * @param array|string $columns
+ *
+ * @return $this
+ */
+ public function select($columns = ['*'])
+ {
+ $columns = is_array($columns) ? $columns : func_get_args();
+
+ if (! empty($columns)) {
+ $this->columns = $columns;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a new select column to the query.
+ *
+ * @param array|mixed $column
+ *
+ * @return $this
+ */
+ public function addSelect($column)
+ {
+ $column = is_array($column) ? $column : func_get_args();
+
+ $this->columns = array_merge((array) $this->columns, $column);
+
+ return $this;
+ }
+
+ /**
+ * Adds a raw filter to the current query.
+ *
+ * @param array|string $filters
+ *
+ * @return $this
+ */
+ public function rawFilter($filters = [])
+ {
+ $filters = is_array($filters) ? $filters : func_get_args();
+
+ foreach ($filters as $filter) {
+ $this->filters['raw'][] = $filter;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a nested 'and' filter to the current query.
+ *
+ * @param Closure $closure
+ *
+ * @return $this
+ */
+ public function andFilter(Closure $closure)
+ {
+ $query = $this->newNestedInstance($closure);
+
+ return $this->rawFilter(
+ $this->grammar->compileAnd($query->getQuery())
+ );
+ }
+
+ /**
+ * Adds a nested 'or' filter to the current query.
+ *
+ * @param Closure $closure
+ *
+ * @return $this
+ */
+ public function orFilter(Closure $closure)
+ {
+ $query = $this->newNestedInstance($closure);
+
+ return $this->rawFilter(
+ $this->grammar->compileOr($query->getQuery())
+ );
+ }
+
+ /**
+ * Adds a nested 'not' filter to the current query.
+ *
+ * @param Closure $closure
+ *
+ * @return $this
+ */
+ public function notFilter(Closure $closure)
+ {
+ $query = $this->newNestedInstance($closure);
+
+ return $this->rawFilter(
+ $this->grammar->compileNot($query->getQuery())
+ );
+ }
+
+ /**
+ * Adds a where clause to the current query.
+ *
+ * @param string|array $field
+ * @param string $operator
+ * @param string $value
+ * @param string $boolean
+ * @param bool $raw
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function where($field, $operator = null, $value = null, $boolean = 'and', $raw = false)
+ {
+ if (is_array($field)) {
+ // If the field is an array, we will assume we have been
+ // provided with an array of key-value pairs and can
+ // add them each as their own seperate where clause.
+ return $this->addArrayOfWheres($field, $boolean, $raw);
+ }
+
+ // If we have been provided with two arguments not a "has" or
+ // "not has" operator, we'll assume the developer is creating
+ // an "equals" clause and set the proper operator in place.
+ if (func_num_args() === 2 && ! in_array($operator, ['*', '!*'])) {
+ [$value, $operator] = [$operator, '='];
+ }
+
+ if (! in_array($operator, $this->grammar->getOperators())) {
+ throw new InvalidArgumentException("Invalid LDAP filter operator [$operator]");
+ }
+
+ // We'll escape the value if raw isn't requested.
+ $value = $this->prepareWhereValue($field, $value, $raw);
+
+ $field = $this->escape($field)->both()->get();
+
+ $this->addFilter($boolean, compact('field', 'operator', 'value'));
+
+ return $this;
+ }
+
+ /**
+ * Prepare the value for being queried.
+ *
+ * @param string $field
+ * @param string $value
+ * @param bool $raw
+ *
+ * @return string
+ */
+ protected function prepareWhereValue($field, $value, $raw = false)
+ {
+ return $raw ? $value : $this->escape($value);
+ }
+
+ /**
+ * Adds a raw where clause to the current query.
+ *
+ * Values given to this method are not escaped.
+ *
+ * @param string|array $field
+ * @param string $operator
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereRaw($field, $operator = null, $value = null)
+ {
+ return $this->where($field, $operator, $value, 'and', true);
+ }
+
+ /**
+ * Adds a 'where equals' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereEquals($field, $value)
+ {
+ return $this->where($field, '=', $value);
+ }
+
+ /**
+ * Adds a 'where not equals' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereNotEquals($field, $value)
+ {
+ return $this->where($field, '!', $value);
+ }
+
+ /**
+ * Adds a 'where approximately equals' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereApproximatelyEquals($field, $value)
+ {
+ return $this->where($field, '~=', $value);
+ }
+
+ /**
+ * Adds a 'where has' clause to the current query.
+ *
+ * @param string $field
+ *
+ * @return $this
+ */
+ public function whereHas($field)
+ {
+ return $this->where($field, '*');
+ }
+
+ /**
+ * Adds a 'where not has' clause to the current query.
+ *
+ * @param string $field
+ *
+ * @return $this
+ */
+ public function whereNotHas($field)
+ {
+ return $this->where($field, '!*');
+ }
+
+ /**
+ * Adds a 'where contains' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereContains($field, $value)
+ {
+ return $this->where($field, 'contains', $value);
+ }
+
+ /**
+ * Adds a 'where contains' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereNotContains($field, $value)
+ {
+ return $this->where($field, 'not_contains', $value);
+ }
+
+ /**
+ * Query for entries that match any of the values provided for the given field.
+ *
+ * @param string $field
+ * @param array $values
+ *
+ * @return $this
+ */
+ public function whereIn($field, array $values)
+ {
+ return $this->orFilter(function (self $query) use ($field, $values) {
+ foreach ($values as $value) {
+ $query->whereEquals($field, $value);
+ }
+ });
+ }
+
+ /**
+ * Adds a 'between' clause to the current query.
+ *
+ * @param string $field
+ * @param array $values
+ *
+ * @return $this
+ */
+ public function whereBetween($field, array $values)
+ {
+ return $this->where([
+ [$field, '>=', $values[0]],
+ [$field, '<=', $values[1]],
+ ]);
+ }
+
+ /**
+ * Adds a 'where starts with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereStartsWith($field, $value)
+ {
+ return $this->where($field, 'starts_with', $value);
+ }
+
+ /**
+ * Adds a 'where *not* starts with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereNotStartsWith($field, $value)
+ {
+ return $this->where($field, 'not_starts_with', $value);
+ }
+
+ /**
+ * Adds a 'where ends with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereEndsWith($field, $value)
+ {
+ return $this->where($field, 'ends_with', $value);
+ }
+
+ /**
+ * Adds a 'where *not* ends with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function whereNotEndsWith($field, $value)
+ {
+ return $this->where($field, 'not_ends_with', $value);
+ }
+
+ /**
+ * Only include deleted models in the results.
+ *
+ * @return $this
+ */
+ public function whereDeleted()
+ {
+ return $this->withDeleted()->whereEquals('isDeleted', 'TRUE');
+ }
+
+ /**
+ * Set the LDAP control option to include deleted LDAP models.
+ *
+ * @return $this
+ */
+ public function withDeleted()
+ {
+ return $this->addControl(LdapInterface::OID_SERVER_SHOW_DELETED, $isCritical = true);
+ }
+
+ /**
+ * Add a server control to the query.
+ *
+ * @param string $oid
+ * @param bool $isCritical
+ * @param mixed $value
+ *
+ * @return $this
+ */
+ public function addControl($oid, $isCritical = false, $value = null)
+ {
+ $this->controls[$oid] = compact('oid', 'isCritical', 'value');
+
+ return $this;
+ }
+
+ /**
+ * Determine if the server control exists on the query.
+ *
+ * @param string $oid
+ *
+ * @return bool
+ */
+ public function hasControl($oid)
+ {
+ return array_key_exists($oid, $this->controls);
+ }
+
+ /**
+ * Adds an 'or where' clause to the current query.
+ *
+ * @param array|string $field
+ * @param string|null $operator
+ * @param string|null $value
+ *
+ * @return $this
+ */
+ public function orWhere($field, $operator = null, $value = null)
+ {
+ return $this->where($field, $operator, $value, 'or');
+ }
+
+ /**
+ * Adds a raw or where clause to the current query.
+ *
+ * Values given to this method are not escaped.
+ *
+ * @param string $field
+ * @param string $operator
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereRaw($field, $operator = null, $value = null)
+ {
+ return $this->where($field, $operator, $value, 'or', true);
+ }
+
+ /**
+ * Adds an 'or where has' clause to the current query.
+ *
+ * @param string $field
+ *
+ * @return $this
+ */
+ public function orWhereHas($field)
+ {
+ return $this->orWhere($field, '*');
+ }
+
+ /**
+ * Adds a 'where not has' clause to the current query.
+ *
+ * @param string $field
+ *
+ * @return $this
+ */
+ public function orWhereNotHas($field)
+ {
+ return $this->orWhere($field, '!*');
+ }
+
+ /**
+ * Adds an 'or where equals' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereEquals($field, $value)
+ {
+ return $this->orWhere($field, '=', $value);
+ }
+
+ /**
+ * Adds an 'or where not equals' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereNotEquals($field, $value)
+ {
+ return $this->orWhere($field, '!', $value);
+ }
+
+ /**
+ * Adds a 'or where approximately equals' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereApproximatelyEquals($field, $value)
+ {
+ return $this->orWhere($field, '~=', $value);
+ }
+
+ /**
+ * Adds an 'or where contains' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereContains($field, $value)
+ {
+ return $this->orWhere($field, 'contains', $value);
+ }
+
+ /**
+ * Adds an 'or where *not* contains' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereNotContains($field, $value)
+ {
+ return $this->orWhere($field, 'not_contains', $value);
+ }
+
+ /**
+ * Adds an 'or where starts with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereStartsWith($field, $value)
+ {
+ return $this->orWhere($field, 'starts_with', $value);
+ }
+
+ /**
+ * Adds an 'or where *not* starts with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereNotStartsWith($field, $value)
+ {
+ return $this->orWhere($field, 'not_starts_with', $value);
+ }
+
+ /**
+ * Adds an 'or where ends with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereEndsWith($field, $value)
+ {
+ return $this->orWhere($field, 'ends_with', $value);
+ }
+
+ /**
+ * Adds an 'or where *not* ends with' clause to the current query.
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function orWhereNotEndsWith($field, $value)
+ {
+ return $this->orWhere($field, 'not_ends_with', $value);
+ }
+
+ /**
+ * Adds a filter binding onto the current query.
+ *
+ * @param string $type The type of filter to add.
+ * @param array $bindings The bindings of the filter.
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return $this
+ */
+ public function addFilter($type, array $bindings)
+ {
+ if (! array_key_exists($type, $this->filters)) {
+ throw new InvalidArgumentException("Filter type: [$type] is invalid.");
+ }
+
+ // Each filter clause require key bindings to be set. We
+ // will validate this here to ensure all of them have
+ // been provided, or throw an exception otherwise.
+ if ($missing = $this->missingBindingKeys($bindings)) {
+ $keys = implode(', ', $missing);
+
+ throw new InvalidArgumentException("Invalid filter bindings. Missing: [$keys] keys.");
+ }
+
+ $this->filters[$type][] = $bindings;
+
+ return $this;
+ }
+
+ /**
+ * Extract any missing required binding keys.
+ *
+ * @param array $bindings
+ *
+ * @return array
+ */
+ protected function missingBindingKeys($bindings)
+ {
+ $required = array_flip(['field', 'operator', 'value']);
+
+ $existing = array_intersect_key($required, $bindings);
+
+ return array_keys(array_diff_key($required, $existing));
+ }
+
+ /**
+ * Get all the filters on the query.
+ *
+ * @return array
+ */
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+
+ /**
+ * Clear the query filters.
+ *
+ * @return $this
+ */
+ public function clearFilters()
+ {
+ foreach (array_keys($this->filters) as $type) {
+ $this->filters[$type] = [];
+ }
+
+ return $this;
+ }
+
+ /**
+ * Determine if the query has attributes selected.
+ *
+ * @return bool
+ */
+ public function hasSelects()
+ {
+ return count($this->columns) > 0;
+ }
+
+ /**
+ * Get the attributes to select on the search.
+ *
+ * @return array
+ */
+ public function getSelects()
+ {
+ $selects = $this->columns ?? ['*'];
+
+ if (in_array('*', $selects)) {
+ return $selects;
+ }
+
+ if (in_array('objectclass', $selects)) {
+ return $selects;
+ }
+
+ // If the * character is not provided in the selected columns,
+ // we need to ensure we always select the object class, as
+ // this is used for constructing models properly.
+ $selects[] = 'objectclass';
+
+ return $selects;
+ }
+
+ /**
+ * Set the query to search on the base distinguished name.
+ *
+ * This will result in one record being returned.
+ *
+ * @return $this
+ */
+ public function read()
+ {
+ $this->type = 'read';
+
+ return $this;
+ }
+
+ /**
+ * Set the query to search one level on the base distinguished name.
+ *
+ * @return $this
+ */
+ public function listing()
+ {
+ $this->type = 'listing';
+
+ return $this;
+ }
+
+ /**
+ * Set the query to search the entire directory on the base distinguished name.
+ *
+ * @return $this
+ */
+ public function recursive()
+ {
+ $this->type = 'search';
+
+ return $this;
+ }
+
+ /**
+ * Whether to mark the current query as nested.
+ *
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function nested($nested = true)
+ {
+ $this->nested = (bool) $nested;
+
+ return $this;
+ }
+
+ /**
+ * Enables caching on the current query until the given date.
+ *
+ * If flushing is enabled, the query cache will be flushed and then re-cached.
+ *
+ * @param DateTimeInterface $until When to expire the query cache.
+ * @param bool $flush Whether to force-flush the query cache.
+ *
+ * @return $this
+ */
+ public function cache(DateTimeInterface $until = null, $flush = false)
+ {
+ $this->caching = true;
+ $this->cacheUntil = $until;
+ $this->flushCache = $flush;
+
+ return $this;
+ }
+
+ /**
+ * Determine if the query is nested.
+ *
+ * @return bool
+ */
+ public function isNested()
+ {
+ return $this->nested === true;
+ }
+
+ /**
+ * Determine whether the query is paginated.
+ *
+ * @return bool
+ */
+ public function isPaginated()
+ {
+ return $this->paginated;
+ }
+
+ /**
+ * Insert an entry into the directory.
+ *
+ * @param string $dn
+ * @param array $attributes
+ *
+ * @throws LdapRecordException
+ *
+ * @return bool
+ */
+ public function insert($dn, array $attributes)
+ {
+ if (empty($dn)) {
+ throw new LdapRecordException('A new LDAP object must have a distinguished name (dn).');
+ }
+
+ if (! array_key_exists('objectclass', $attributes)) {
+ throw new LdapRecordException(
+ 'A new LDAP object must contain at least one object class (objectclass) to be created.'
+ );
+ }
+
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) {
+ return $ldap->add($dn, $attributes);
+ });
+ }
+
+ /**
+ * Create attributes on the entry in the directory.
+ *
+ * @param string $dn
+ * @param array $attributes
+ *
+ * @return bool
+ */
+ public function insertAttributes($dn, array $attributes)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) {
+ return $ldap->modAdd($dn, $attributes);
+ });
+ }
+
+ /**
+ * Update the entry with the given modifications.
+ *
+ * @param string $dn
+ * @param array $modifications
+ *
+ * @return bool
+ */
+ public function update($dn, array $modifications)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn, $modifications) {
+ return $ldap->modifyBatch($dn, $modifications);
+ });
+ }
+
+ /**
+ * Update an entries attribute in the directory.
+ *
+ * @param string $dn
+ * @param array $attributes
+ *
+ * @return bool
+ */
+ public function updateAttributes($dn, array $attributes)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) {
+ return $ldap->modReplace($dn, $attributes);
+ });
+ }
+
+ /**
+ * Delete an entry from the directory.
+ *
+ * @param string $dn
+ *
+ * @return bool
+ */
+ public function delete($dn)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn) {
+ return $ldap->delete($dn);
+ });
+ }
+
+ /**
+ * Delete attributes on the entry in the directory.
+ *
+ * @param string $dn
+ * @param array $attributes
+ *
+ * @return bool
+ */
+ public function deleteAttributes($dn, array $attributes)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn, $attributes) {
+ return $ldap->modDelete($dn, $attributes);
+ });
+ }
+
+ /**
+ * Rename an entry in the directory.
+ *
+ * @param string $dn
+ * @param string $rdn
+ * @param string $newParentDn
+ * @param bool $deleteOldRdn
+ *
+ * @return bool
+ */
+ public function rename($dn, $rdn, $newParentDn, $deleteOldRdn = true)
+ {
+ return $this->connection->run(function (LdapInterface $ldap) use ($dn, $rdn, $newParentDn, $deleteOldRdn) {
+ return $ldap->rename($dn, $rdn, $newParentDn, $deleteOldRdn);
+ });
+ }
+
+ /**
+ * Handle dynamic method calls on the query builder.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @throws BadMethodCallException
+ *
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ // If the beginning of the method being called contains
+ // 'where', we will assume a dynamic 'where' clause is
+ // being performed and pass the parameters to it.
+ if (substr($method, 0, 5) === 'where') {
+ return $this->dynamicWhere($method, $parameters);
+ }
+
+ throw new BadMethodCallException(sprintf(
+ 'Call to undefined method %s::%s()',
+ static::class,
+ $method
+ ));
+ }
+
+ /**
+ * Handles dynamic "where" clauses to the query.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @return $this
+ */
+ public function dynamicWhere($method, $parameters)
+ {
+ $finder = substr($method, 5);
+
+ $segments = preg_split('/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE);
+
+ // The connector variable will determine which connector will be used for the
+ // query condition. We will change it as we come across new boolean values
+ // in the dynamic method strings, which could contain a number of these.
+ $connector = 'and';
+
+ $index = 0;
+
+ foreach ($segments as $segment) {
+ // If the segment is not a boolean connector, we can assume it is a column's name
+ // and we will add it to the query as a new constraint as a where clause, then
+ // we can keep iterating through the dynamic method string's segments again.
+ if ($segment != 'And' && $segment != 'Or') {
+ $this->addDynamic($segment, $connector, $parameters, $index);
+
+ $index++;
+ }
+
+ // Otherwise, we will store the connector so we know how the next where clause we
+ // find in the query should be connected to the previous ones, meaning we will
+ // have the proper boolean connector to connect the next where clause found.
+ else {
+ $connector = $segment;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds an array of wheres to the current query.
+ *
+ * @param array $wheres
+ * @param string $boolean
+ * @param bool $raw
+ *
+ * @return $this
+ */
+ protected function addArrayOfWheres($wheres, $boolean, $raw)
+ {
+ foreach ($wheres as $key => $value) {
+ if (is_numeric($key) && is_array($value)) {
+ // If the key is numeric and the value is an array, we'll
+ // assume we've been given an array with conditionals.
+ [$field, $condition] = $value;
+
+ // Since a value is optional for some conditionals, we will
+ // try and retrieve the third parameter from the array,
+ // but is entirely optional.
+ $value = Arr::get($value, 2);
+
+ $this->where($field, $condition, $value, $boolean);
+ } else {
+ // If the value is not an array, we will assume an equals clause.
+ $this->where($key, '=', $value, $boolean, $raw);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a single dynamic where clause statement to the query.
+ *
+ * @param string $segment
+ * @param string $connector
+ * @param array $parameters
+ * @param int $index
+ *
+ * @return void
+ */
+ protected function addDynamic($segment, $connector, $parameters, $index)
+ {
+ // If no parameters were given to the dynamic where clause,
+ // we can assume a "has" attribute filter is being added.
+ if (count($parameters) === 0) {
+ $this->where(strtolower($segment), '*', null, strtolower($connector));
+ } else {
+ $this->where(strtolower($segment), '=', $parameters[$index], strtolower($connector));
+ }
+ }
+
+ /**
+ * Logs the given executed query information by firing its query event.
+ *
+ * @param Builder $query
+ * @param string $type
+ * @param null|float $time
+ *
+ * @return void
+ */
+ protected function logQuery($query, $type, $time = null)
+ {
+ $args = [$query, $time];
+
+ switch ($type) {
+ case 'listing':
+ $event = new Events\Listing(...$args);
+ break;
+ case 'read':
+ $event = new Events\Read(...$args);
+ break;
+ case 'chunk':
+ $event = new Events\Chunk(...$args);
+ break;
+ case 'paginate':
+ $event = new Events\Paginate(...$args);
+ break;
+ default:
+ $event = new Events\Search(...$args);
+ break;
+ }
+
+ $this->fireQueryEvent($event);
+ }
+
+ /**
+ * Fires the given query event.
+ *
+ * @param QueryExecuted $event
+ *
+ * @return void
+ */
+ protected function fireQueryEvent(QueryExecuted $event)
+ {
+ Container::getInstance()->getEventDispatcher()->fire($event);
+ }
+
+ /**
+ * Get the elapsed time since a given starting point.
+ *
+ * @param int $start
+ *
+ * @return float
+ */
+ protected function getElapsedTime($start)
+ {
+ return round((microtime(true) - $start) * 1000, 2);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php
new file mode 100644
index 0000000..dfbf8cd
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Cache.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use Closure;
+use DateInterval;
+use DateTimeInterface;
+use Psr\SimpleCache\CacheInterface;
+
+class Cache
+{
+ use InteractsWithTime;
+
+ /**
+ * The cache driver.
+ *
+ * @var CacheInterface
+ */
+ protected $store;
+
+ /**
+ * Constructor.
+ *
+ * @param CacheInterface $store
+ */
+ public function __construct(CacheInterface $store)
+ {
+ $this->store = $store;
+ }
+
+ /**
+ * Get an item from the cache.
+ *
+ * @param string $key
+ *
+ * @return mixed
+ */
+ public function get($key)
+ {
+ return $this->store->get($key);
+ }
+
+ /**
+ * Store an item in the cache.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @param DateTimeInterface|DateInterval|int|null $ttl
+ *
+ * @return bool
+ */
+ public function put($key, $value, $ttl = null)
+ {
+ $seconds = $this->secondsUntil($ttl);
+
+ if ($seconds <= 0) {
+ return $this->delete($key);
+ }
+
+ return $this->store->set($key, $value, $seconds);
+ }
+
+ /**
+ * Get an item from the cache, or execute the given Closure and store the result.
+ *
+ * @param string $key
+ * @param DateTimeInterface|DateInterval|int|null $ttl
+ * @param Closure $callback
+ *
+ * @return mixed
+ */
+ public function remember($key, $ttl, Closure $callback)
+ {
+ $value = $this->get($key);
+
+ if (! is_null($value)) {
+ return $value;
+ }
+
+ $this->put($key, $value = $callback(), $ttl);
+
+ return $value;
+ }
+
+ /**
+ * Delete an item from the cache.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ return $this->store->delete($key);
+ }
+
+ /**
+ * Get the underlying cache store.
+ *
+ * @return CacheInterface
+ */
+ public function store()
+ {
+ return $this->store;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php
new file mode 100644
index 0000000..a02146d
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Collection.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use LdapRecord\Models\Model;
+use Tightenco\Collect\Support\Collection as BaseCollection;
+
+class Collection extends BaseCollection
+{
+ /**
+ * @inheritdoc
+ */
+ protected function valueRetriever($value)
+ {
+ if ($this->useAsCallable($value)) {
+ return $value;
+ }
+
+ return function ($item) use ($value) {
+ return $item instanceof Model
+ ? $item->getFirstAttribute($value)
+ : data_get($item, $value);
+ };
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Chunk.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Chunk.php
new file mode 100644
index 0000000..3cd36d8
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Chunk.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace LdapRecord\Query\Events;
+
+class Chunk extends QueryExecuted
+{
+ //
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Listing.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Listing.php
new file mode 100644
index 0000000..2b88ad9
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Listing.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace LdapRecord\Query\Events;
+
+class Listing extends QueryExecuted
+{
+ //
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Paginate.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Paginate.php
new file mode 100644
index 0000000..6f8f262
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Paginate.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace LdapRecord\Query\Events;
+
+class Paginate extends QueryExecuted
+{
+ //
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/QueryExecuted.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/QueryExecuted.php
new file mode 100644
index 0000000..f13ddeb
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/QueryExecuted.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace LdapRecord\Query\Events;
+
+use LdapRecord\Query\Builder;
+
+class QueryExecuted
+{
+ /**
+ * The LDAP filter that was used for the query.
+ *
+ * @var string
+ */
+ protected $query;
+
+ /**
+ * The number of milliseconds it took to execute the query.
+ *
+ * @var float
+ */
+ protected $time;
+
+ /**
+ * Constructor.
+ *
+ * @param Builder $query
+ * @param null|float $time
+ */
+ public function __construct(Builder $query, $time = null)
+ {
+ $this->query = $query;
+ $this->time = $time;
+ }
+
+ /**
+ * Returns the LDAP filter that was used for the query.
+ *
+ * @return Builder
+ */
+ public function getQuery()
+ {
+ return $this->query;
+ }
+
+ /**
+ * Returns the number of milliseconds it took to execute the query.
+ *
+ * @return float|null
+ */
+ public function getTime()
+ {
+ return $this->time;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Read.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Read.php
new file mode 100644
index 0000000..510c4ca
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Read.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace LdapRecord\Query\Events;
+
+class Read extends QueryExecuted
+{
+ //
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Search.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Search.php
new file mode 100644
index 0000000..5132316
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Events/Search.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace LdapRecord\Query\Events;
+
+class Search extends QueryExecuted
+{
+ //
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php
new file mode 100644
index 0000000..3217173
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Grammar.php
@@ -0,0 +1,549 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use UnexpectedValueException;
+
+class Grammar
+{
+ /**
+ * The query operators and their method names.
+ *
+ * @var array
+ */
+ public $operators = [
+ '*' => 'has',
+ '!*' => 'notHas',
+ '=' => 'equals',
+ '!' => 'doesNotEqual',
+ '!=' => 'doesNotEqual',
+ '>=' => 'greaterThanOrEquals',
+ '<=' => 'lessThanOrEquals',
+ '~=' => 'approximatelyEquals',
+ 'starts_with' => 'startsWith',
+ 'not_starts_with' => 'notStartsWith',
+ 'ends_with' => 'endsWith',
+ 'not_ends_with' => 'notEndsWith',
+ 'contains' => 'contains',
+ 'not_contains' => 'notContains',
+ ];
+
+ /**
+ * The query wrapper.
+ *
+ * @var string|null
+ */
+ protected $wrapper;
+
+ /**
+ * Get all the available operators.
+ *
+ * @return array
+ */
+ public function getOperators()
+ {
+ return array_keys($this->operators);
+ }
+
+ /**
+ * Wraps a query string in brackets.
+ *
+ * Produces: (query)
+ *
+ * @param string $query
+ * @param string $prefix
+ * @param string $suffix
+ *
+ * @return string
+ */
+ public function wrap($query, $prefix = '(', $suffix = ')')
+ {
+ return $prefix.$query.$suffix;
+ }
+
+ /**
+ * Compiles the Builder instance into an LDAP query string.
+ *
+ * @param Builder $query
+ *
+ * @return string
+ */
+ public function compile(Builder $query)
+ {
+ if ($this->queryMustBeWrapped($query)) {
+ $this->wrapper = 'and';
+ }
+
+ $filter = $this->compileRaws($query)
+ .$this->compileWheres($query)
+ .$this->compileOrWheres($query);
+
+ switch ($this->wrapper) {
+ case 'and':
+ return $this->compileAnd($filter);
+ case 'or':
+ return $this->compileOr($filter);
+ default:
+ return $filter;
+ }
+ }
+
+ /**
+ * Determine if the query must be wrapped in an encapsulating statement.
+ *
+ * @param Builder $query
+ *
+ * @return bool
+ */
+ protected function queryMustBeWrapped(Builder $query)
+ {
+ return ! $query->isNested() && $this->hasMultipleFilters($query);
+ }
+
+ /**
+ * Assembles all of the "raw" filters on the query.
+ *
+ * @param Builder $builder
+ *
+ * @return string
+ */
+ protected function compileRaws(Builder $builder)
+ {
+ return $this->concatenate($builder->filters['raw']);
+ }
+
+ /**
+ * Assembles all where clauses in the current wheres property.
+ *
+ * @param Builder $builder
+ * @param string $type
+ *
+ * @return string
+ */
+ protected function compileWheres(Builder $builder, $type = 'and')
+ {
+ $filter = '';
+
+ foreach ($builder->filters[$type] as $where) {
+ $filter .= $this->compileWhere($where);
+ }
+
+ return $filter;
+ }
+
+ /**
+ * Assembles all or where clauses in the current orWheres property.
+ *
+ * @param Builder $query
+ *
+ * @return string
+ */
+ protected function compileOrWheres(Builder $query)
+ {
+ $filter = $this->compileWheres($query, 'or');
+
+ if (! $this->hasMultipleFilters($query)) {
+ return $filter;
+ }
+
+ // Here we will detect whether the entire query can be
+ // wrapped inside of an "or" statement by checking
+ // how many filter statements exist for each type.
+ if ($this->queryCanBeWrappedInSingleOrStatement($query)) {
+ $this->wrapper = 'or';
+ } else {
+ $filter = $this->compileOr($filter);
+ }
+
+ return $filter;
+ }
+
+ /**
+ * Determine if the query can be wrapped in a single or statement.
+ *
+ * @param Builder $query
+ *
+ * @return bool
+ */
+ protected function queryCanBeWrappedInSingleOrStatement(Builder $query)
+ {
+ return $this->has($query, 'or', '>=', 1) &&
+ $this->has($query, 'and', '<=', 1) &&
+ $this->has($query, 'raw', '=', 0);
+ }
+
+ /**
+ * Concatenates filters into a single string.
+ *
+ * @param array $bindings
+ *
+ * @return string
+ */
+ public function concatenate(array $bindings = [])
+ {
+ // Filter out empty query segments.
+ return implode(
+ array_filter($bindings, [$this, 'bindingValueIsNotEmpty'])
+ );
+ }
+
+ /**
+ * Determine if the binding value is not empty.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ protected function bindingValueIsNotEmpty($value)
+ {
+ return ! empty($value);
+ }
+
+ /**
+ * Determine if the query is using multiple filters.
+ *
+ * @param Builder $query
+ *
+ * @return bool
+ */
+ protected function hasMultipleFilters(Builder $query)
+ {
+ return $this->has($query, ['and', 'or', 'raw'], '>', 1);
+ }
+
+ /**
+ * Determine if the query contains the given filter statement type.
+ *
+ * @param Builder $query
+ * @param string|array $type
+ * @param string $operator
+ * @param int $count
+ *
+ * @return bool
+ */
+ protected function has(Builder $query, $type, $operator = '>=', $count = 1)
+ {
+ $types = (array) $type;
+
+ $filters = 0;
+
+ foreach ($types as $type) {
+ $filters += count($query->filters[$type]);
+ }
+
+ switch ($operator) {
+ case '>':
+ return $filters > $count;
+ case '>=':
+ return $filters >= $count;
+ case '<':
+ return $filters < $count;
+ case '<=':
+ return $filters <= $count;
+ default:
+ return $filters == $count;
+ }
+ }
+
+ /**
+ * Returns a query string for equals.
+ *
+ * Produces: (field=value)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileEquals($field, $value)
+ {
+ return $this->wrap($field.'='.$value);
+ }
+
+ /**
+ * Returns a query string for does not equal.
+ *
+ * Produces: (!(field=value))
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileDoesNotEqual($field, $value)
+ {
+ return $this->compileNot(
+ $this->compileEquals($field, $value)
+ );
+ }
+
+ /**
+ * Alias for does not equal operator (!=) operator.
+ *
+ * Produces: (!(field=value))
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileDoesNotEqualAlias($field, $value)
+ {
+ return $this->compileDoesNotEqual($field, $value);
+ }
+
+ /**
+ * Returns a query string for greater than or equals.
+ *
+ * Produces: (field>=value)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileGreaterThanOrEquals($field, $value)
+ {
+ return $this->wrap("$field>=$value");
+ }
+
+ /**
+ * Returns a query string for less than or equals.
+ *
+ * Produces: (field<=value)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileLessThanOrEquals($field, $value)
+ {
+ return $this->wrap("$field<=$value");
+ }
+
+ /**
+ * Returns a query string for approximately equals.
+ *
+ * Produces: (field~=value)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileApproximatelyEquals($field, $value)
+ {
+ return $this->wrap("$field~=$value");
+ }
+
+ /**
+ * Returns a query string for starts with.
+ *
+ * Produces: (field=value*)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileStartsWith($field, $value)
+ {
+ return $this->wrap("$field=$value*");
+ }
+
+ /**
+ * Returns a query string for does not start with.
+ *
+ * Produces: (!(field=*value))
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileNotStartsWith($field, $value)
+ {
+ return $this->compileNot(
+ $this->compileStartsWith($field, $value)
+ );
+ }
+
+ /**
+ * Returns a query string for ends with.
+ *
+ * Produces: (field=*value)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileEndsWith($field, $value)
+ {
+ return $this->wrap("$field=*$value");
+ }
+
+ /**
+ * Returns a query string for does not end with.
+ *
+ * Produces: (!(field=value*))
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileNotEndsWith($field, $value)
+ {
+ return $this->compileNot($this->compileEndsWith($field, $value));
+ }
+
+ /**
+ * Returns a query string for contains.
+ *
+ * Produces: (field=*value*)
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileContains($field, $value)
+ {
+ return $this->wrap("$field=*$value*");
+ }
+
+ /**
+ * Returns a query string for does not contain.
+ *
+ * Produces: (!(field=*value*))
+ *
+ * @param string $field
+ * @param string $value
+ *
+ * @return string
+ */
+ public function compileNotContains($field, $value)
+ {
+ return $this->compileNot(
+ $this->compileContains($field, $value)
+ );
+ }
+
+ /**
+ * Returns a query string for a where has.
+ *
+ * Produces: (field=*)
+ *
+ * @param string $field
+ *
+ * @return string
+ */
+ public function compileHas($field)
+ {
+ return $this->wrap("$field=*");
+ }
+
+ /**
+ * Returns a query string for a where does not have.
+ *
+ * Produces: (!(field=*))
+ *
+ * @param string $field
+ *
+ * @return string
+ */
+ public function compileNotHas($field)
+ {
+ return $this->compileNot(
+ $this->compileHas($field)
+ );
+ }
+
+ /**
+ * Wraps the inserted query inside an AND operator.
+ *
+ * Produces: (&query)
+ *
+ * @param string $query
+ *
+ * @return string
+ */
+ public function compileAnd($query)
+ {
+ return $query ? $this->wrap($query, '(&') : '';
+ }
+
+ /**
+ * Wraps the inserted query inside an OR operator.
+ *
+ * Produces: (|query)
+ *
+ * @param string $query
+ *
+ * @return string
+ */
+ public function compileOr($query)
+ {
+ return $query ? $this->wrap($query, '(|') : '';
+ }
+
+ /**
+ * Wraps the inserted query inside an NOT operator.
+ *
+ * @param string $query
+ *
+ * @return string
+ */
+ public function compileNot($query)
+ {
+ return $query ? $this->wrap($query, '(!') : '';
+ }
+
+ /**
+ * Assembles a single where query.
+ *
+ * @param array $where
+ *
+ * @throws UnexpectedValueException
+ *
+ * @return string
+ */
+ protected function compileWhere(array $where)
+ {
+ $method = $this->makeCompileMethod($where['operator']);
+
+ return $this->{$method}($where['field'], $where['value']);
+ }
+
+ /**
+ * Make the compile method name for the operator.
+ *
+ * @param string $operator
+ *
+ * @throws UnexpectedValueException
+ *
+ * @return string
+ */
+ protected function makeCompileMethod($operator)
+ {
+ if (! $this->operatorExists($operator)) {
+ throw new UnexpectedValueException("Invalid LDAP filter operator ['$operator']");
+ }
+
+ return 'compile'.ucfirst($this->operators[$operator]);
+ }
+
+ /**
+ * Determine if the operator exists.
+ *
+ * @param string $operator
+ *
+ * @return bool
+ */
+ protected function operatorExists($operator)
+ {
+ return array_key_exists($operator, $this->operators);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php
new file mode 100644
index 0000000..1562ec0
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/InteractsWithTime.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use Carbon\Carbon;
+use DateInterval;
+use DateTimeInterface;
+
+/**
+ * @author Taylor Otwell
+ *
+ * @see https://laravel.com
+ */
+trait InteractsWithTime
+{
+ /**
+ * Get the number of seconds until the given DateTime.
+ *
+ * @param DateTimeInterface|DateInterval|int $delay
+ *
+ * @return int
+ */
+ protected function secondsUntil($delay)
+ {
+ $delay = $this->parseDateInterval($delay);
+
+ return $delay instanceof DateTimeInterface
+ ? max(0, $delay->getTimestamp() - $this->currentTime())
+ : (int) $delay;
+ }
+
+ /**
+ * Get the "available at" UNIX timestamp.
+ *
+ * @param DateTimeInterface|DateInterval|int $delay
+ *
+ * @return int
+ */
+ protected function availableAt($delay = 0)
+ {
+ $delay = $this->parseDateInterval($delay);
+
+ return $delay instanceof DateTimeInterface
+ ? $delay->getTimestamp()
+ : Carbon::now()->addRealSeconds($delay)->getTimestamp();
+ }
+
+ /**
+ * If the given value is an interval, convert it to a DateTime instance.
+ *
+ * @param DateTimeInterface|DateInterval|int $delay
+ *
+ * @return DateTimeInterface|int
+ */
+ protected function parseDateInterval($delay)
+ {
+ if ($delay instanceof DateInterval) {
+ $delay = Carbon::now()->add($delay);
+ }
+
+ return $delay;
+ }
+
+ /**
+ * Get the current system time as a UNIX timestamp.
+ *
+ * @return int
+ */
+ protected function currentTime()
+ {
+ return Carbon::now()->getTimestamp();
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/ActiveDirectoryBuilder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/ActiveDirectoryBuilder.php
new file mode 100644
index 0000000..8923015
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/ActiveDirectoryBuilder.php
@@ -0,0 +1,247 @@
+<?php
+
+namespace LdapRecord\Query\Model;
+
+use Closure;
+use LdapRecord\LdapInterface;
+use LdapRecord\Models\Attributes\AccountControl;
+use LdapRecord\Models\ModelNotFoundException;
+
+class ActiveDirectoryBuilder extends Builder
+{
+ /**
+ * Finds a record by its Object SID.
+ *
+ * @param string $sid
+ * @param array|string $columns
+ *
+ * @return \LdapRecord\Models\ActiveDirectory\Entry|static|null
+ */
+ public function findBySid($sid, $columns = [])
+ {
+ try {
+ return $this->findBySidOrFail($sid, $columns);
+ } catch (ModelNotFoundException $e) {
+ return;
+ }
+ }
+
+ /**
+ * Finds a record by its Object SID.
+ *
+ * Fails upon no records returned.
+ *
+ * @param string $sid
+ * @param array|string $columns
+ *
+ * @throws ModelNotFoundException
+ *
+ * @return \LdapRecord\Models\ActiveDirectory\Entry|static
+ */
+ public function findBySidOrFail($sid, $columns = [])
+ {
+ return $this->findByOrFail('objectsid', $sid, $columns);
+ }
+
+ /**
+ * Adds a enabled filter to the current query.
+ *
+ * @return $this
+ */
+ public function whereEnabled()
+ {
+ return $this->notFilter(function ($query) {
+ return $query->whereDisabled();
+ });
+ }
+
+ /**
+ * Adds a disabled filter to the current query.
+ *
+ * @return $this
+ */
+ public function whereDisabled()
+ {
+ return $this->rawFilter(
+ (new AccountControl())->accountIsDisabled()->filter()
+ );
+ }
+
+ /**
+ * Adds a 'where member' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function whereMember($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->whereEquals($attribute, $dn);
+ }, 'member', $nested);
+ }
+
+ /**
+ * Adds an 'or where member' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function orWhereMember($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->orWhereEquals($attribute, $dn);
+ }, 'member', $nested);
+ }
+
+ /**
+ * Adds a 'where member of' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function whereMemberOf($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->whereEquals($attribute, $dn);
+ }, 'memberof', $nested);
+ }
+
+ /**
+ * Adds a 'where not member of' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function whereNotMemberof($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->whereNotEquals($attribute, $dn);
+ }, 'memberof', $nested);
+ }
+
+ /**
+ * Adds an 'or where member of' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function orWhereMemberOf($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->orWhereEquals($attribute, $dn);
+ }, 'memberof', $nested);
+ }
+
+ /**
+ * Adds a 'or where not member of' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function orWhereNotMemberof($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->orWhereNotEquals($attribute, $dn);
+ }, 'memberof', $nested);
+ }
+
+ /**
+ * Adds a 'where manager' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function whereManager($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->whereEquals($attribute, $dn);
+ }, 'manager', $nested);
+ }
+
+ /**
+ * Adds a 'where not manager' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function whereNotManager($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->whereNotEquals($attribute, $dn);
+ }, 'manager', $nested);
+ }
+
+ /**
+ * Adds an 'or where manager' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function orWhereManager($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->orWhereEquals($attribute, $dn);
+ }, 'manager', $nested);
+ }
+
+ /**
+ * Adds an 'or where not manager' filter to the current query.
+ *
+ * @param string $dn
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ public function orWhereNotManager($dn, $nested = false)
+ {
+ return $this->nestedMatchQuery(function ($attribute) use ($dn) {
+ return $this->orWhereNotEquals($attribute, $dn);
+ }, 'manager', $nested);
+ }
+
+ /**
+ * Execute the callback with a nested match attribute.
+ *
+ * @param Closure $callback
+ * @param string $attribute
+ * @param bool $nested
+ *
+ * @return $this
+ */
+ protected function nestedMatchQuery(Closure $callback, $attribute, $nested = false)
+ {
+ return $callback(
+ $nested ? $this->makeNestedMatchAttribute($attribute) : $attribute
+ );
+ }
+
+ /**
+ * Make a "nested match" filter attribute for querying descendants.
+ *
+ * @param string $attribute
+ *
+ * @return string
+ */
+ protected function makeNestedMatchAttribute($attribute)
+ {
+ return sprintf('%s:%s:', $attribute, LdapInterface::OID_MATCHING_RULE_IN_CHAIN);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/Builder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/Builder.php
new file mode 100644
index 0000000..eed5e91
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/Builder.php
@@ -0,0 +1,446 @@
+<?php
+
+namespace LdapRecord\Query\Model;
+
+use Closure;
+use DateTime;
+use LdapRecord\Models\Model;
+use LdapRecord\Models\ModelNotFoundException;
+use LdapRecord\Models\Scope;
+use LdapRecord\Models\Types\ActiveDirectory;
+use LdapRecord\Query\Builder as BaseBuilder;
+use LdapRecord\Utilities;
+
+class Builder extends BaseBuilder
+{
+ /**
+ * The model being queried.
+ *
+ * @var Model
+ */
+ protected $model;
+
+ /**
+ * The global scopes to be applied.
+ *
+ * @var array
+ */
+ protected $scopes = [];
+
+ /**
+ * The removed global scopes.
+ *
+ * @var array
+ */
+ protected $removedScopes = [];
+
+ /**
+ * The applied global scopes.
+ *
+ * @var array
+ */
+ protected $appliedScopes = [];
+
+ /**
+ * Dynamically handle calls into the query instance.
+ *
+ * @param string $method
+ * @param array $parameters
+ *
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
+ return $this->callScope([$this->model, $scope], $parameters);
+ }
+
+ return parent::__call($method, $parameters);
+ }
+
+ /**
+ * Apply the given scope on the current builder instance.
+ *
+ * @param callable $scope
+ * @param array $parameters
+ *
+ * @return mixed
+ */
+ protected function callScope(callable $scope, $parameters = [])
+ {
+ array_unshift($parameters, $this);
+
+ return $scope(...array_values($parameters)) ?? $this;
+ }
+
+ /**
+ * Get the attributes to select on the search.
+ *
+ * @return array
+ */
+ public function getSelects()
+ {
+ // Here we will ensure the models GUID attribute is always
+ // selected. In some LDAP directories, the attribute is
+ // virtual and must be requested for specifically.
+ return array_values(array_unique(
+ array_merge([$this->model->getGuidKey()], parent::getSelects())
+ ));
+ }
+
+ /**
+ * Set the model instance for the model being queried.
+ *
+ * @param Model $model
+ *
+ * @return $this
+ */
+ public function setModel(Model $model)
+ {
+ $this->model = $model;
+
+ return $this;
+ }
+
+ /**
+ * Returns the model being queried for.
+ *
+ * @return Model
+ */
+ public function getModel()
+ {
+ return $this->model;
+ }
+
+ /**
+ * Get a new model query builder instance.
+ *
+ * @param string|null $baseDn
+ *
+ * @return static
+ */
+ public function newInstance($baseDn = null)
+ {
+ return parent::newInstance($baseDn)->model($this->model);
+ }
+
+ /**
+ * Finds a model by its distinguished name.
+ *
+ * @param array|string $dn
+ * @param array|string|string[] $columns
+ *
+ * @return Model|\LdapRecord\Query\Collection|static|null
+ */
+ public function find($dn, $columns = ['*'])
+ {
+ return $this->afterScopes(function () use ($dn, $columns) {
+ return parent::find($dn, $columns);
+ });
+ }
+
+ /**
+ * Finds a record using ambiguous name resolution.
+ *
+ * @param string|array $value
+ * @param array|string $columns
+ *
+ * @return Model|\LdapRecord\Query\Collection|static|null
+ */
+ public function findByAnr($value, $columns = ['*'])
+ {
+ if (is_array($value)) {
+ return $this->findManyByAnr($value, $columns);
+ }
+
+ // If the model is not compatible with ANR filters,
+ // we must construct an equivalent filter that
+ // the current LDAP server does support.
+ if (! $this->modelIsCompatibleWithAnr()) {
+ return $this->prepareAnrEquivalentQuery($value)->first($columns);
+ }
+
+ return $this->findBy('anr', $value, $columns);
+ }
+
+ /**
+ * Determine if the current model is compatible with ANR filters.
+ *
+ * @return bool
+ */
+ protected function modelIsCompatibleWithAnr()
+ {
+ return $this->model instanceof ActiveDirectory;
+ }
+
+ /**
+ * Finds a record using ambiguous name resolution.
+ *
+ * If a record is not found, an exception is thrown.
+ *
+ * @param string $value
+ * @param array|string $columns
+ *
+ * @throws ModelNotFoundException
+ *
+ * @return Model
+ */
+ public function findByAnrOrFail($value, $columns = ['*'])
+ {
+ if (! $entry = $this->findByAnr($value, $columns)) {
+ $this->throwNotFoundException($this->getUnescapedQuery(), $this->dn);
+ }
+
+ return $entry;
+ }
+
+ /**
+ * Throws a not found exception.
+ *
+ * @param string $query
+ * @param string $dn
+ *
+ * @throws ModelNotFoundException
+ */
+ protected function throwNotFoundException($query, $dn)
+ {
+ throw ModelNotFoundException::forQuery($query, $dn);
+ }
+
+ /**
+ * Finds multiple records using ambiguous name resolution.
+ *
+ * @param array $values
+ * @param array $columns
+ *
+ * @return \LdapRecord\Query\Collection
+ */
+ public function findManyByAnr(array $values = [], $columns = ['*'])
+ {
+ $this->select($columns);
+
+ if (! $this->modelIsCompatibleWithAnr()) {
+ foreach ($values as $value) {
+ $this->prepareAnrEquivalentQuery($value);
+ }
+
+ return $this->get($columns);
+ }
+
+ return $this->findManyBy('anr', $values);
+ }
+
+ /**
+ * Creates an ANR equivalent query for LDAP distributions that do not support ANR.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ protected function prepareAnrEquivalentQuery($value)
+ {
+ return $this->orFilter(function (self $query) use ($value) {
+ foreach ($this->model->getAnrAttributes() as $attribute) {
+ $query->whereEquals($attribute, $value);
+ }
+ });
+ }
+
+ /**
+ * Finds a record by its string GUID.
+ *
+ * @param string $guid
+ * @param array|string $columns
+ *
+ * @return Model|static|null
+ */
+ public function findByGuid($guid, $columns = ['*'])
+ {
+ try {
+ return $this->findByGuidOrFail($guid, $columns);
+ } catch (ModelNotFoundException $e) {
+ return;
+ }
+ }
+
+ /**
+ * Finds a record by its string GUID.
+ *
+ * Fails upon no records returned.
+ *
+ * @param string $guid
+ * @param array|string $columns
+ *
+ * @throws ModelNotFoundException
+ *
+ * @return Model|static
+ */
+ public function findByGuidOrFail($guid, $columns = ['*'])
+ {
+ if ($this->model instanceof ActiveDirectory) {
+ $guid = Utilities::stringGuidToHex($guid);
+ }
+
+ return $this->whereRaw([
+ $this->model->getGuidKey() => $guid,
+ ])->firstOrFail($columns);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getQuery()
+ {
+ return $this->afterScopes(function () {
+ return parent::getQuery();
+ });
+ }
+
+ /**
+ * Apply the query scopes and execute the callback.
+ *
+ * @param Closure $callback
+ *
+ * @return mixed
+ */
+ protected function afterScopes(Closure $callback)
+ {
+ $this->applyScopes();
+
+ return $callback();
+ }
+
+ /**
+ * Apply the global query scopes.
+ *
+ * @return $this
+ */
+ public function applyScopes()
+ {
+ if (! $this->scopes) {
+ return $this;
+ }
+
+ foreach ($this->scopes as $identifier => $scope) {
+ if (isset($this->appliedScopes[$identifier])) {
+ continue;
+ }
+
+ $scope instanceof Scope
+ ? $scope->apply($this, $this->getModel())
+ : $scope($this);
+
+ $this->appliedScopes[$identifier] = $scope;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Register a new global scope.
+ *
+ * @param string $identifier
+ * @param Scope|\Closure $scope
+ *
+ * @return $this
+ */
+ public function withGlobalScope($identifier, $scope)
+ {
+ $this->scopes[$identifier] = $scope;
+
+ return $this;
+ }
+
+ /**
+ * Remove a registered global scope.
+ *
+ * @param Scope|string $scope
+ *
+ * @return $this
+ */
+ public function withoutGlobalScope($scope)
+ {
+ if (! is_string($scope)) {
+ $scope = get_class($scope);
+ }
+
+ unset($this->scopes[$scope]);
+
+ $this->removedScopes[] = $scope;
+
+ return $this;
+ }
+
+ /**
+ * Remove all or passed registered global scopes.
+ *
+ * @param array|null $scopes
+ *
+ * @return $this
+ */
+ public function withoutGlobalScopes(array $scopes = null)
+ {
+ if (! is_array($scopes)) {
+ $scopes = array_keys($this->scopes);
+ }
+
+ foreach ($scopes as $scope) {
+ $this->withoutGlobalScope($scope);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get an array of global scopes that were removed from the query.
+ *
+ * @return array
+ */
+ public function removedScopes()
+ {
+ return $this->removedScopes;
+ }
+
+ /**
+ * Get an array of the global scopes that were applied to the query.
+ *
+ * @return array
+ */
+ public function appliedScopes()
+ {
+ return $this->appliedScopes;
+ }
+
+ /**
+ * Processes and converts the given LDAP results into models.
+ *
+ * @param array $results
+ *
+ * @return \LdapRecord\Query\Collection
+ */
+ protected function process(array $results)
+ {
+ return $this->model->hydrate(parent::process($results));
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareWhereValue($field, $value, $raw = false)
+ {
+ if ($value instanceof DateTime) {
+ $field = $this->model->normalizeAttributeKey($field);
+
+ if (! $this->model->isDateAttribute($field)) {
+ throw new \UnexpectedValueException(
+ "Cannot convert field [$field] to an LDAP timestamp. You must add this field as a model date."
+ .' Refer to https://ldaprecord.com/docs/model-mutators/#date-mutators'
+ );
+ }
+
+ $value = $this->model->fromDateTime($this->model->getDates()[$field], $value);
+ }
+
+ return parent::prepareWhereValue($field, $value, $raw);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/FreeIpaBuilder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/FreeIpaBuilder.php
new file mode 100644
index 0000000..5e3d43f
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/FreeIpaBuilder.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace LdapRecord\Query\Model;
+
+class FreeIpaBuilder extends Builder
+{
+ /**
+ * Adds a enabled filter to the current query.
+ *
+ * @return $this
+ */
+ public function whereEnabled()
+ {
+ return $this->rawFilter('(!(pwdAccountLockedTime=*))');
+ }
+
+ /**
+ * Adds a disabled filter to the current query.
+ *
+ * @return $this
+ */
+ public function whereDisabled()
+ {
+ return $this->rawFilter('(pwdAccountLockedTime=*)');
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/OpenLdapBuilder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/OpenLdapBuilder.php
new file mode 100644
index 0000000..dd41344
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Model/OpenLdapBuilder.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace LdapRecord\Query\Model;
+
+class OpenLdapBuilder extends Builder
+{
+ /**
+ * Adds a enabled filter to the current query.
+ *
+ * @return $this
+ */
+ public function whereEnabled()
+ {
+ return $this->rawFilter('(!(pwdAccountLockedTime=*))');
+ }
+
+ /**
+ * Adds a disabled filter to the current query.
+ *
+ * @return $this
+ */
+ public function whereDisabled()
+ {
+ return $this->rawFilter('(pwdAccountLockedTime=*)');
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php
new file mode 100644
index 0000000..b2dec28
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/ObjectNotFoundException.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace LdapRecord\Query;
+
+use LdapRecord\LdapRecordException;
+
+class ObjectNotFoundException extends LdapRecordException
+{
+ /**
+ * The query filter that was used.
+ *
+ * @var string
+ */
+ protected $query;
+
+ /**
+ * The base DN of the query that was used.
+ *
+ * @var string
+ */
+ protected $baseDn;
+
+ /**
+ * Create a new exception for the executed filter.
+ *
+ * @param string $query
+ * @param null $baseDn
+ *
+ * @return static
+ */
+ public static function forQuery($query, $baseDn = null)
+ {
+ return (new static())->setQuery($query, $baseDn);
+ }
+
+ /**
+ * Set the query that was used.
+ *
+ * @param string $query
+ * @param string|null $baseDn
+ *
+ * @return $this
+ */
+ public function setQuery($query, $baseDn = null)
+ {
+ $this->query = $query;
+ $this->baseDn = $baseDn;
+ $this->message = "No LDAP query results for filter: [$query] in: [$baseDn]";
+
+ return $this;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php
new file mode 100644
index 0000000..3dfd3f1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/AbstractPaginator.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace LdapRecord\Query\Pagination;
+
+use LdapRecord\LdapInterface;
+use LdapRecord\Query\Builder;
+
+abstract class AbstractPaginator
+{
+ /**
+ * The query builder instance.
+ *
+ * @var Builder
+ */
+ protected $query;
+
+ /**
+ * The filter to execute.
+ *
+ * @var string
+ */
+ protected $filter;
+
+ /**
+ * The amount of objects to fetch per page.
+ *
+ * @var int
+ */
+ protected $perPage;
+
+ /**
+ * Whether the operation is critical.
+ *
+ * @var bool
+ */
+ protected $isCritical;
+
+ /**
+ * Constructor.
+ *
+ * @param Builder $query
+ */
+ public function __construct(Builder $query, $filter, $perPage, $isCritical)
+ {
+ $this->query = $query;
+ $this->filter = $filter;
+ $this->perPage = $perPage;
+ $this->isCritical = $isCritical;
+ }
+
+ /**
+ * Execute the pagination request.
+ *
+ * @param LdapInterface $ldap
+ *
+ * @return array
+ */
+ public function execute(LdapInterface $ldap)
+ {
+ $pages = [];
+
+ $this->prepareServerControls();
+
+ do {
+ $this->applyServerControls($ldap);
+
+ if (! $resource = $this->query->run($this->filter)) {
+ break;
+ }
+
+ $this->updateServerControls($ldap, $resource);
+
+ $pages[] = $this->query->parse($resource);
+ } while (! empty($this->fetchCookie()));
+
+ $this->resetServerControls($ldap);
+
+ return $pages;
+ }
+
+ /**
+ * Fetch the pagination cookie.
+ *
+ * @return string
+ */
+ abstract protected function fetchCookie();
+
+ /**
+ * Prepare the server controls before executing the pagination request.
+ *
+ * @return void
+ */
+ abstract protected function prepareServerControls();
+
+ /**
+ * Apply the server controls.
+ *
+ * @param LdapInterface $ldap
+ *
+ * @return void
+ */
+ abstract protected function applyServerControls(LdapInterface $ldap);
+
+ /**
+ * Reset the server controls.
+ *
+ * @param LdapInterface $ldap
+ *
+ * @return mixed
+ */
+ abstract protected function resetServerControls(LdapInterface $ldap);
+
+ /**
+ * Update the server controls.
+ *
+ * @param LdapInterface $ldap
+ * @param resource $resource
+ *
+ * @return void
+ */
+ abstract protected function updateServerControls(LdapInterface $ldap, $resource);
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/DeprecatedPaginator.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/DeprecatedPaginator.php
new file mode 100644
index 0000000..b4a7f8d
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/DeprecatedPaginator.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace LdapRecord\Query\Pagination;
+
+use LdapRecord\LdapInterface;
+
+/**
+ * @deprecated since v2.5.0
+ */
+class DeprecatedPaginator extends AbstractPaginator
+{
+ /**
+ * The pagination cookie.
+ *
+ * @var string
+ */
+ protected $cookie = '';
+
+ /**
+ * @inheritdoc
+ */
+ protected function fetchCookie()
+ {
+ return $this->cookie;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareServerControls()
+ {
+ $this->cookie = '';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function applyServerControls(LdapInterface $ldap)
+ {
+ $ldap->controlPagedResult($this->perPage, $this->isCritical, $this->cookie);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function updateServerControls(LdapInterface $ldap, $resource)
+ {
+ $ldap->controlPagedResultResponse($resource, $this->cookie);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function resetServerControls(LdapInterface $ldap)
+ {
+ $ldap->controlPagedResult();
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/LazyPaginator.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/LazyPaginator.php
new file mode 100644
index 0000000..2974b8f
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/LazyPaginator.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace LdapRecord\Query\Pagination;
+
+use LdapRecord\LdapInterface;
+
+class LazyPaginator extends Paginator
+{
+ /**
+ * Execute the pagination request.
+ *
+ * @param LdapInterface $ldap
+ *
+ * @return Generator
+ */
+ public function execute(LdapInterface $ldap)
+ {
+ $this->prepareServerControls();
+
+ do {
+ $this->applyServerControls($ldap);
+
+ if (! $resource = $this->query->run($this->filter)) {
+ break;
+ }
+
+ $this->updateServerControls($ldap, $resource);
+
+ yield $this->query->parse($resource);
+ } while (! empty($this->fetchCookie()));
+
+ $this->resetServerControls($ldap);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/Paginator.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/Paginator.php
new file mode 100644
index 0000000..9ab6e67
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Query/Pagination/Paginator.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace LdapRecord\Query\Pagination;
+
+use LdapRecord\LdapInterface;
+
+class Paginator extends AbstractPaginator
+{
+ /**
+ * @inheritdoc
+ */
+ protected function fetchCookie()
+ {
+ return $this->query->controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareServerControls()
+ {
+ $this->query->addControl(LDAP_CONTROL_PAGEDRESULTS, $this->isCritical, [
+ 'size' => $this->perPage, 'cookie' => '',
+ ]);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function applyServerControls(LdapInterface $ldap)
+ {
+ $ldap->setOption(LDAP_OPT_SERVER_CONTROLS, $this->query->controls);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function updateServerControls(LdapInterface $ldap, $resource)
+ {
+ $errorCode = $dn = $errorMessage = $refs = null;
+
+ $ldap->parseResult(
+ $resource,
+ $errorCode,
+ $dn,
+ $errorMessage,
+ $refs,
+ $this->query->controls
+ );
+
+ $this->resetPageSize();
+ }
+
+ /**
+ * Reset the page control page size.
+ *
+ * @return void
+ */
+ protected function resetPageSize()
+ {
+ $this->query->controls[LDAP_CONTROL_PAGEDRESULTS]['value']['size'] = $this->perPage;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function resetServerControls(LdapInterface $ldap)
+ {
+ $this->query->controls = [];
+ }
+}