| <?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); |
| } |
| } |