├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── Builder.php ├── Exceptions │ └── GrammarException.php ├── Filter │ ├── Filter.php │ └── FilterFactory.php └── Group │ ├── Group.php │ └── GroupFactory.php └── tests ├── BuilderTest.php ├── Filter ├── FilterFactoryTest.php └── FilterTest.php ├── Group ├── GroupFactoryTest.php └── GroupTest.php └── UnitTestCase.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | 7 | before_install: echo "extension=ldap.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install 12 | 13 | script: phpunit -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Avadanei Danut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LDAP Query Builder 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/avadaneidanut/ldapquery/v/stable)](https://packagist.org/packages/avadaneidanut/ldapquery) [![Total Downloads](https://poser.pugx.org/avadaneidanut/ldapquery/downloads)](https://packagist.org/packages/avadaneidanut/ldapquery) [![Latest Unstable Version](https://poser.pugx.org/avadaneidanut/ldapquery/v/unstable)](https://packagist.org/packages/avadaneidanut/ldapquery) [![License](https://poser.pugx.org/avadaneidanut/ldapquery/license)](https://packagist.org/packages/avadaneidanut/ldapquery) [![Build Status](https://travis-ci.org/avadaneidanut/ldapquery.svg?branch=master)](https://travis-ci.org/avadaneidanut/ldapquery) 4 | 5 | LDAP Query Builder is simple tool for easily generate queries for LDAP filtering. Quick example: 6 | 7 | ```php 8 | $query = \LdapQuery\Builder::create()->where('attrBar', 'value') 9 | ->where('attrFoo', '<>' 'value2') 10 | ->orWhere('attrBaz', [1, 2, 3, 4, 5, 6, 7, 8, 9]) 11 | ->where(function($builder) { 12 | $builder->where('bla', 'bla2') 13 | ->orWhere('bla3', 'bla1'); 14 | }) 15 | ->stringify() 16 | ; 17 | ``` 18 | 19 | Output: 20 | ``` 21 | (&(|(&(attrBar=value)(!(attrFoo=value2)))(|(attrBaz=1)(attrBaz=2)(attrBaz=3)(attrBaz=4)(attrBaz=5)(attrBaz=6)(attrBaz=7)(attrBaz=8)(attrBaz=9)))(|(bla=bla2)(bla3=bla1))) 22 | ``` 23 | 24 | If you want to examine queries generated and don't manually separate groups just: 25 | 26 | ```php 27 | $builder = new \LdapQuery\Builder; 28 | $builder->where('attrBar', 'value') 29 | ->where('attrFoo', '<>' 'value2') 30 | ->orWhere('attrBaz', [1, 2, 3, 4, 5, 6, 7, 8, 9]) 31 | ->where(function($builder) { 32 | $builder->where('bla', 'bla2') 33 | ->orWhere('bla3', 'bla1'); 34 | }); 35 | 36 | $builder->toArray(); # will generate a nice output 37 | ``` 38 | 39 | Output: 40 | ``` 41 | (& 42 | (| 43 | (& 44 | (attrBar=value) 45 | (! 46 | (attrFoo=value2) 47 | ) 48 | ) 49 | (| 50 | (attrBaz=1) 51 | (attrBaz=2) 52 | (attrBaz=3) 53 | (attrBaz=4) 54 | (attrBaz=5) 55 | (attrBaz=6) 56 | (attrBaz=7) 57 | (attrBaz=8) 58 | (attrBaz=9) 59 | ) 60 | ) 61 | (| 62 | (bla=bla2) 63 | (bla3=bla1) 64 | ) 65 | ) 66 | ``` 67 | 68 | Usage with sympfony ldap component: 69 | ```php 70 | use LdapQuery\Builder; 71 | use Symfony\Component\Ldap\LdapClient; 72 | 73 | $client = new LdapClient('ldap.example.com'); 74 | $client->bind('uid=AB1234,ou=people,o=world', 'secretpassword'); 75 | 76 | $builder = new Builder; 77 | 78 | $details = $client->find( 79 | 'ou=people,o=world', 80 | (string)$builder->where('uid', 'AB123*')->where('cn', '~=','*Danut*'), 81 | ['uid','cn','mail','office','mobile'] 82 | ); 83 | ``` 84 | 85 | ### Available methods on Builder class 86 | 87 | ```php 88 | /** 89 | * Add a where clause to the LDAP Query. Defaulted to & logical, acts like a andWhere. 90 | * 91 | * @param string|Closure $attribute 92 | * @param string|array|null $operator 93 | * @param string|array|null $value 94 | * @param string|null $wildcard 95 | * @param bool $escape 96 | * @param string $logical 97 | * 98 | * @return $this 99 | * 100 | * @throws GrammarException 101 | */ 102 | public function where($attribute, $operator = null, $value = null, $wildcard = null, $escape = true, $negation = false, $logical = '&'); 103 | 104 | /** 105 | * Add a or where clause to the LDAP Query. 106 | * 107 | * @param string|Closure $attribute 108 | * @param string|array|null $operator 109 | * @param string|array|null $value 110 | * @param string|null $wildcard 111 | * @param bool $escape 112 | * 113 | * @return $this 114 | * 115 | * @throws GrammarException 116 | */ 117 | public function orWhere($attribute, $operator = null, $value = null, $wildcard = null, $escape = true, $negation = false); 118 | 119 | /** 120 | * Convert Group object to a string, LDAP valid query group. 121 | * 122 | * @return string 123 | */ 124 | public function stringify(); 125 | 126 | /** 127 | * Convert Builder object to array. 128 | * 129 | * @return array 130 | */ 131 | public function toArray(); 132 | ``` 133 | 134 | ### Dynamic where clauses 135 | LdapQuery\Builder class has plenty dynamic "where clauses" that are transformed automatically calls to "where" or "orWhere" methods with arguments translatted from method name. This can be used as any other method example: 136 | ```php 137 | $builder->orWhereBegins('attribute', 'value'); will be translated in 138 | $builder->orWhere('attribute', 'value', null, 'begins', true); 139 | ``` 140 | Available Dynamic where clauses 141 | ```php 142 | // whereRaw - where attribute unescaped value 143 | $builder->whereRaw('foo', 'bar*'); 144 | print $builder; // (foo=bar*) 145 | 146 | // orWhereRaw - or where attribute unescaped value 147 | $builder->where('foo', 'bar') 148 | ->orWhereRaw('foo', 'baz*'); 149 | print $builder; // (|(foo=bar)(foo=baz*)) 150 | ``` 151 | ```php 152 | // whereNot - where attribute not value 153 | $builder->whereNot('foo', 'bar'); 154 | print $builder; // (!(foo=bar)) 155 | 156 | // whereNotRaw - where attribute not unescaped value 157 | $builder->whereNotRaw('foo', 'bar*'); 158 | print $builder; // (!(foo=bar*)) 159 | 160 | // orWhereNotRaw - or where attribute not unescaped value 161 | $builder->where('foo', 'bar') 162 | ->orWhereNotRaw('foo', 'baz*'); 163 | print $builder; // (|(foo=bar)(!(foo=baz*))) 164 | ``` 165 | 166 | ```php 167 | // whereBegins - where attribute begins with value 168 | $buidler->whereBegins('foo', 'b'); 169 | print $builder; // (foo=b*) 170 | 171 | // whereBeginsRaw - where attribute begins with unescaped value 172 | $builder->whereBeginsRaw('foo', 'b)'); 173 | print $builder; // (foo=b)*) 174 | 175 | // whereBeginsNot - where attribute not begins with value 176 | $builder->whereNotBegins('foo', 'b'); 177 | print $builder; // (!(foo=b*)) 178 | 179 | // whereBeginsNotRaw - where attribute not begins with unescaped value 180 | $builder->whereNotBeginsRaw('foo', 'b()'); 181 | print $builder; // (!(foo=b()*)) 182 | 183 | // orWhereBegins - or where attribute begins with value 184 | $builder->where('foo', 'bar') 185 | ->orWhereBegins('foo', 'b'); 186 | print $builder; // (|(foo=bar)(foo=b*)) 187 | 188 | // orWhereBeginsRaw - or where attribute begins with unescaped value 189 | $builder->where('foo', 'bar') 190 | ->orWhereBeginsRaw('foo', 'b()'); 191 | print $builder; // (|(foo=bar)(foo=b()*)) 192 | 193 | // orWhereBeginsNot - or where attribute not begins with value 194 | $builder->where('foo', 'bar') 195 | ->orWhereBeginsNot('foo', 'b'); 196 | print $builder; // (|(foo=bar)(!(foo=b*))) 197 | 198 | // orWhereBeginsNotRaw - or where attribute not begins with unescaped value 199 | $builder->where('foo', 'bar') 200 | ->orWhereBeginsNotRaw('foo', 'b()'); 201 | print $builder; // (|(foo=bar)(!(foo=b()*))) 202 | ``` 203 | 204 | ```php 205 | // whereEnds - where attribute ends with value 206 | $buidler->whereEnds('foo', 'b'); 207 | print $builder; // (foo=*b) 208 | 209 | // whereEndsRaw - where attribute ends with unescaped value 210 | $builder->whereEndsRaw('foo', 'b)'); 211 | print $builder; // (foo=*b)) 212 | 213 | // whereEndsNot - where attribute not ends with value 214 | $builder->whereNotEnds('foo', 'b'); 215 | print $builder; // (!(foo=*b)) 216 | 217 | // whereEndsNotRaw - where attribute not ends with unescaped value 218 | $builder->whereNotEndsRaw('foo', 'b()'); 219 | print $builder; // (!(foo=*b())) 220 | 221 | // orWhereEnds - or where attribute ends with value 222 | $builder->where('foo', 'bar') 223 | ->orWhereEnds('foo', 'b'); 224 | print $builder; // (|(foo=bar)(foo=*b)) 225 | 226 | // orWhereEndsRaw - or where attribute ends with unescaped value 227 | $builder->where('foo', 'bar') 228 | ->orWhereEndsRaw('foo', 'b()'); 229 | print $builder; // (|(foo=bar)(foo=*b())) 230 | 231 | // orWhereEndsNot - or where attribute not ends with value 232 | $builder->where('foo', 'bar') 233 | ->orWhereEndsNot('foo', 'b'); 234 | print $builder; // (|(foo=bar)(!(foo=*b))) 235 | 236 | // orWhereEndsNotRaw - or where attribute not ends with unescaped value 237 | $builder->where('foo', 'bar') 238 | ->orWhereEndsNotRaw('foo', 'b()'); 239 | print $builder; // (|(foo=bar)(!(foo=*b()))) 240 | ``` 241 | 242 | ```php 243 | // whereLike - where attribute like value 244 | $buidler->whereLike('foo', 'b'); 245 | print $builder; // (foo=*b*) 246 | 247 | // whereLikeRaw - where attribute like unescaped value 248 | $builder->whereLikeRaw('foo', 'b)'); 249 | print $builder; // (foo=*b)*) 250 | 251 | // whereLikeNot - where attribute not like value 252 | $builder->whereNotLike('foo', 'b'); 253 | print $builder; // (!(foo=*b*)) 254 | 255 | // whereLikeNotRaw - where attribute not like unescaped value 256 | $builder->whereNotLikeRaw('foo', 'b()'); 257 | print $builder; // (!(foo=*b()*)) 258 | 259 | // orWhereLike - or where attribute like value 260 | $builder->where('foo', 'bar') 261 | ->orWhereLike('foo', 'b'); 262 | print $builder; // (|(foo=bar)(foo=*b*)) 263 | 264 | // orWhereLikeRaw - or where attribute like unescaped value 265 | $builder->where('foo', 'bar') 266 | ->orWhereLikeRaw('foo', 'b()'); 267 | print $builder; // (|(foo=bar)(foo=*b()*)) 268 | 269 | // orWhereLikeNot - or where attribute not like value 270 | $builder->where('foo', 'bar') 271 | ->orWhereLikeNot('foo', 'b'); 272 | print $builder; // (|(foo=bar)(!(foo=*b*))) 273 | 274 | // orWhereLikeNotRaw - or where attribute not like unescaped value 275 | $builder->where('foo', 'bar') 276 | ->orWhereLikeNotRaw('foo', 'b()'); 277 | print $builder; // (|(foo=bar)(!(foo=*b()*))) 278 | ``` 279 | 280 | ### Installation 281 | 282 | LdapQuery requires composer to install 283 | 284 | ```sh 285 | $ composer require avadaneidanut/ldapquery 286 | ``` 287 | 288 | ### Development 289 | 290 | Want to contribute? Great! Can't wait to hear from you! 291 | 292 | License 293 | ---- 294 | 295 | MIT 296 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "avadaneidanut/ldapquery", 3 | "type": "library", 4 | "description": "A light weight package for easily building LDAP advanced filter queries.", 5 | "keywords": [ 6 | "active directory", 7 | "ldap", 8 | "ldapquery", 9 | "ldap query", 10 | "windows", 11 | "windows server", 12 | "ldap filters" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Avadanei Danut", 17 | "email": "danut@worldit.info" 18 | } 19 | ], 20 | "license": "MIT", 21 | "require": { 22 | "php": ">=5.6", 23 | "illuminate/support": "^5.2" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^5.3", 27 | "psy/psysh": "^0.7.2", 28 | "symfony/ldap": "^3.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "LdapQuery\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "LdapQuery\\Tests\\": "tests/" 38 | } 39 | }, 40 | "repositories":[ 41 | { 42 | "packagist": false 43 | }, 44 | { 45 | "type": "composer", 46 | "url": "https://packagist.org" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | tests/Filter/FilterTest.php 13 | tests/Filter/FilterFactoryTest.php 14 | 15 | 16 | tests/Group/GroupTest.php 17 | tests/Group/GroupFactoryTest.php 18 | 19 | 20 | tests/BuilderTest.php 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Builder.php: -------------------------------------------------------------------------------- 1 | group = GroupFactory::build(); 72 | } 73 | 74 | /** 75 | * Convert Group object to a string, LDAP valid query group. 76 | * 77 | * @return string 78 | */ 79 | public function stringify() 80 | { 81 | return $this->group->stringify(); 82 | } 83 | 84 | /** 85 | * Convert Builder object to array. 86 | * 87 | * @return array 88 | */ 89 | public function toArray() 90 | { 91 | return $this->group->toArray(); 92 | } 93 | 94 | /** 95 | * Add a where clause to the LDAP Query. Defaulted to & logical, acts like a andWhere. 96 | * 97 | * @param string|Closure $attribute 98 | * @param string|array|null $operator 99 | * @param string|array|null $value 100 | * @param string|null $wildcard 101 | * @param bool $escape 102 | * @param string $logical 103 | * 104 | * @return $this 105 | * 106 | * @throws GrammarException 107 | */ 108 | public function where($attribute, $operator = null, $value = null, $wildcard = null, $escape = true, $negation = false, $logical = '&') 109 | { 110 | // If value is null, we assume that value is allocated to $operator 111 | // and the we want default operator (=) 112 | if ($value === null) { 113 | $value = $operator; 114 | $operator = '='; 115 | } 116 | 117 | // If value is an array, use a closure to generate a or group with all the 118 | // values. 119 | if (is_array($value)) { 120 | return $this->where(function($builder) use ($attribute, $value, $wildcard, $escape){ 121 | foreach ($value as $v) { 122 | $builder->orWhere($attribute, '=', $v, $wildcard, $escape); 123 | } 124 | }, null, null, null, null, $negation, $logical); 125 | } 126 | 127 | // Solve context accordingly to the rules. 128 | $group = $this->solveContext($logical, $negation); 129 | 130 | // If the attribute is a closure then we assume that we want to create a new context 131 | // and push it into the current group 132 | if ($attribute instanceof Closure) { 133 | $closureBuilder = new self(); 134 | $attribute($closureBuilder); 135 | $group->push($closureBuilder->group); 136 | return $this; 137 | } 138 | 139 | $group->push(FilterFactory::build($attribute, $value, $operator, $wildcard, $escape)); 140 | return $this; 141 | } 142 | 143 | /** 144 | * Add a or where clause to the LDAP Query. 145 | * 146 | * @param string|Closure $attribute 147 | * @param string|array|null $operator 148 | * @param string|array|null $value 149 | * @param string|null $wildcard 150 | * @param bool $escape 151 | * 152 | * @return $this 153 | * 154 | * @throws GrammarException 155 | */ 156 | public function orWhere($attribute, $operator = null, $value = null, $wildcard = null, $escape = true, $negation = false) 157 | { 158 | return $this->where($attribute, $operator, $value, $wildcard, $escape, $negation, '|'); 159 | } 160 | 161 | /** 162 | * Handle dynamic where clauses to the LDAP Query. 163 | * 164 | * @param string $method 165 | * @param string|Closure $attribute 166 | * @param string|array|null $operator 167 | * @param string|array|null $value 168 | * 169 | * @return $this 170 | * 171 | * @throws GrammarException 172 | * @throws InvalidArgumentException 173 | */ 174 | protected function dynamicWhere($method, $attribute, $operator = null, $value = null) 175 | { 176 | $options = explode('_', snake_case($method)); 177 | 178 | if (Str::startsWith($method, 'where')) { 179 | $method = 'where'; 180 | } else { 181 | $method = 'orWhere'; 182 | } 183 | 184 | // Add escape option. 185 | $escape = !in_array('raw', $options); 186 | 187 | // Add wildcard option. 188 | if (in_array('begins', $options)) { 189 | $wildcard = 'begins'; 190 | } elseif (in_array('ends', $options)) { 191 | $wildcard = 'ends'; 192 | } elseif (in_array('like', $options)) { 193 | $wildcard = 'like'; 194 | } else { 195 | $wildcard = null; 196 | } 197 | 198 | if ($wildcard !== null && $attribute instanceof Closure) { 199 | throw new InvalidArgumentException("Dynamic where clauses don't support wildcard operator if attribute is a Closure"); 200 | } 201 | 202 | $negation = in_array('not', $options); 203 | 204 | return $this->{$method}($attribute, $operator, $value, $wildcard, $escape, $negation); 205 | } 206 | 207 | /** 208 | * Solve the group context. 209 | * 210 | * @param string $logical 211 | * @param bool $negation 212 | * 213 | * @return Builder 214 | */ 215 | protected function solveContext($logical, $negation) 216 | { 217 | if ($this->group->getOperator() !== $logical){ 218 | // If the current group operator is different from the requested 219 | // spawn a new group and nest the current one 220 | $group = GroupFactory::build($logical); 221 | $this->group->nest($group); 222 | $this->group = $group; 223 | } 224 | 225 | if ($negation) { 226 | // Create a new ! group, push it without clone and return to 227 | // build query on it. 228 | $context = GroupFactory::build('!'); 229 | $group = GroupFactory::build(); 230 | $context->push($group, false); 231 | $this->group->push($context, false); 232 | return $group; 233 | } 234 | return $this->group; 235 | } 236 | 237 | /** 238 | * Dynamically cast the object to string. 239 | * 240 | * @return string 241 | */ 242 | public function __toString() 243 | { 244 | return $this->stringify(); 245 | } 246 | 247 | /** 248 | * Handle dynamic method calls into the method. 249 | * 250 | * @param string $method 251 | * @param array $parameters 252 | * 253 | * @return mixed 254 | * 255 | * @throws BadMethodCallException 256 | */ 257 | public function __call($method, $parameters) 258 | { 259 | if (in_array($method, $this->dynamicWheres)) { 260 | return $this->dynamicWhere($method, ...$parameters); 261 | } 262 | 263 | $className = static::class; 264 | 265 | throw new BadMethodCallException("Call to undefined method {$className}::{$method}()"); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/Exceptions/GrammarException.php: -------------------------------------------------------------------------------- 1 | =', // Lexicographically greater than or equal to 19 | ]; 20 | 21 | /** 22 | * Accepted wildcards keywords 23 | * 24 | * @var array 25 | */ 26 | protected $wildcards = [ 27 | null, // nothing 28 | 'begins', // attribute=value* 29 | 'ends', // attribute=*value 30 | 'like', // attribute=*value* 31 | ]; 32 | 33 | /** 34 | * Logical operator 35 | * 36 | * @var string 37 | */ 38 | protected $operator; 39 | 40 | /** 41 | * LDAP Attribute 42 | * 43 | * @var string 44 | */ 45 | protected $attribute; 46 | 47 | /** 48 | * Value to be compared 49 | * 50 | * @var mixed 51 | */ 52 | protected $value; 53 | 54 | /** 55 | * ldap_escape value 56 | * 57 | * @var bool 58 | */ 59 | protected $escape; 60 | 61 | /** 62 | * Value wildcard (*) 63 | * 64 | * @var string 65 | */ 66 | protected $wildcard; 67 | 68 | /** 69 | * Create a new instance of the object. 70 | * 71 | * @param string $attribute 72 | * @param string $value 73 | * @param string $operator 74 | * @param string|null $wildcard 75 | * @param bool $escape 76 | * 77 | * @return void 78 | * 79 | * @throws GrammarException 80 | */ 81 | public function __construct($attribute, $value, $operator = '=', $wildcard = null, $escape = true) 82 | { 83 | // Check if the operator is valid. 84 | if (!$this->isOperatorValid($operator)) { 85 | throw new GrammarException( 86 | "Invalid filter operator. Accepted operators: " . implode(', ', $this->operators) 87 | ); 88 | } 89 | 90 | // Check if the wildcard is valid. 91 | if (!$this->isWildcardValid($wildcard)) { 92 | throw new GrammarException( 93 | "Invalid wildcard keyword. Accepted keywords: " . implode(', ', $this->wildcards) 94 | ); 95 | } 96 | 97 | // Store arguments to coresponding properties. 98 | $this->attribute = $attribute; 99 | $this->operator = $operator; 100 | $this->value = $value; 101 | $this->escape = $escape; 102 | $this->wildcard = $wildcard; 103 | 104 | // Escape value. 105 | $this->escape(); 106 | } 107 | 108 | /** 109 | * Dynamically cast the object to string. 110 | * 111 | * @return string 112 | */ 113 | public function __toString() 114 | { 115 | return $this->stringify(); 116 | } 117 | 118 | /** 119 | * Convert Filter object to a string, LDAP valid query group. 120 | * 121 | * @return string 122 | */ 123 | public function stringify() 124 | { 125 | return sprintf( 126 | '(%s%s%s)', 127 | $this->attribute, 128 | $this->operator, 129 | $this->value 130 | ); 131 | } 132 | 133 | /** 134 | * Convert Filter object to array. 135 | * 136 | * @return array 137 | */ 138 | public function toArray($tab = '') 139 | { 140 | return [ 141 | $tab . '(', 142 | $tab . ' ' . $this->attribute . $this->operator . $this->value, 143 | $tab . ')' 144 | ]; 145 | } 146 | 147 | /** 148 | * LDAP Escape value and apply wildcards, if set. 149 | * 150 | * @return void 151 | */ 152 | private function escape() 153 | { 154 | if ($this->escape) { 155 | $this->value = ldap_escape($this->value, null, LDAP_ESCAPE_FILTER); 156 | } 157 | $this->wildcardify(); 158 | } 159 | 160 | /** 161 | * Apply wildcards if provided. 162 | * 163 | * @return void 164 | */ 165 | private function wildcardify() 166 | { 167 | if ($this->wildcard === null) { 168 | return; 169 | } elseif ($this->wildcard === 'begins') { 170 | $this->value .= '*'; 171 | } elseif ($this->wildcard === 'ends') { 172 | $this->value = '*' . $this->value; 173 | } elseif ($this->wildcard === 'like') { 174 | $this->value = '*' . $this->value .'*'; 175 | } 176 | } 177 | 178 | /** 179 | * Check if the operator is valid. 180 | * 181 | * @param string $operator 182 | * 183 | * @return boolean 184 | */ 185 | private function isOperatorValid($operator) 186 | { 187 | return in_array($operator, $this->operators); 188 | } 189 | 190 | /** 191 | * Check if the wildcard is valid. 192 | * 193 | * @param string $wildcard 194 | * 195 | * @return boolean 196 | */ 197 | private function isWildcardValid($wildcard) 198 | { 199 | return in_array($wildcard, $this->wildcards); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Filter/FilterFactory.php: -------------------------------------------------------------------------------- 1 | isOperatorValid($operator)) { 47 | throw new GrammarException( 48 | "Invalid group operator. Accepted operators: " . implode(', ', $this->operators) 49 | ); 50 | } 51 | 52 | $this->operator = $operator; 53 | } 54 | 55 | /** 56 | * Return logical operator 57 | * 58 | * @return string 59 | */ 60 | public function getOperator() 61 | { 62 | return $this->operator; 63 | } 64 | 65 | /** 66 | * Check if the group is empty. 67 | * 68 | * @return boolean 69 | */ 70 | public function isEmpty() 71 | { 72 | return count($this->content) === 0; 73 | } 74 | 75 | /** 76 | * Nest this Group to provided parent. 77 | * 78 | * @param Group $parent 79 | * 80 | * @return void 81 | */ 82 | public function nest(Group $parent) 83 | { 84 | $parent->push($this); 85 | } 86 | 87 | /** 88 | * Push a new entry in the content stack. 89 | * 90 | * @param Filter|Group $entry 91 | * @param bool $clone 92 | * 93 | * @return void 94 | * 95 | * @throws InvalidArgumentException 96 | * @throws GrammarException 97 | */ 98 | public function push($entry, $clone = true) 99 | { 100 | if (!$entry instanceof Filter && !$entry instanceof Group) { 101 | throw new InvalidArgumentException("push function only accepts Filter or Group. Input was: " . get_class($entry)); 102 | } elseif ($this->operator === '!' && count($this->content)) { 103 | throw new GrammarException("A 'not' group can contain only one Filter or one Group"); 104 | } 105 | 106 | // By cloning the entry we avoid creating an infinite loop if somehow 107 | // somebody pushs $this object 108 | $this->content[] = $clone ? clone $entry : $entry; 109 | } 110 | 111 | /** 112 | * Convert Group object to a string, LDAP valid query group. 113 | * 114 | * @return string 115 | */ 116 | public function stringify() 117 | { 118 | // Early exit if no content. 119 | if (!count($this->content)) { 120 | return ''; 121 | } 122 | $query = $this->beginning(); 123 | 124 | array_map(function($entry) use (&$query){ 125 | $query .= $entry; 126 | }, $this->content); 127 | 128 | return $query . $this->end(); 129 | } 130 | 131 | /** 132 | * Convert Group object to array. 133 | * 134 | * @return array 135 | */ 136 | public function toArray($tab = '') 137 | { 138 | // Early exit if no content. 139 | if (!count($this->content)) { 140 | return []; 141 | } 142 | 143 | $query = []; 144 | $endTab = $tab; 145 | 146 | if ( ($beginning = $this->beginning()) !== ''){ 147 | $query = [$tab . $beginning]; 148 | // Increment tab 149 | $tab .= ' '; 150 | } 151 | 152 | array_map(function($entry) use (&$query, $tab){ 153 | $query = array_merge($query, $entry->toArray($tab)); 154 | }, $this->content); 155 | 156 | if ( ($end = $this->end()) !== ''){ 157 | $query[] = $endTab . $this->end(); 158 | } 159 | 160 | return $query; 161 | } 162 | 163 | 164 | /** 165 | * Generate query beginning. 166 | * 167 | * @return string 168 | */ 169 | private function beginning() 170 | { 171 | if (count($this->content) > 1) { 172 | return '(' . $this->operator; 173 | } elseif (count($this->content) === 1 && $this->operator === '!') { 174 | return '(!'; 175 | } 176 | return ''; 177 | } 178 | 179 | /** 180 | * Generate query end. 181 | * 182 | * @return string 183 | */ 184 | private function end() 185 | { 186 | if ((count($this->content) > 1) || (count($this->content) === 1 && $this->operator === '!')) { 187 | return ')'; 188 | } 189 | return ''; 190 | } 191 | 192 | /** 193 | * Check if the provided operator is valid. 194 | * 195 | * @param string $operator 196 | * 197 | * @return boolean 198 | */ 199 | private function isOperatorValid($operator) 200 | { 201 | return in_array($operator, $this->operators); 202 | } 203 | 204 | /** 205 | * Dynamically cast the object to string. 206 | * 207 | * @return string 208 | */ 209 | public function __toString() 210 | { 211 | return $this->stringify(); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Group/GroupFactory.php: -------------------------------------------------------------------------------- 1 | getBuilder(); 20 | 21 | $builder->whereBegins(function($query){}); 22 | } 23 | 24 | public function testWhere() 25 | { 26 | $builder = $this->getBuilder(); 27 | 28 | $builder->where('foo', 'bar') 29 | ->where('baz', 'foo') 30 | ->where('foo', 'baz') 31 | ->where('baz', 'foo') 32 | ->where('bar', '*foo*') 33 | ->where('baz', '(foo)'); 34 | 35 | $this->assertEquals('(&(foo=bar)(baz=foo)(foo=baz)(baz=foo)(bar=\2afoo\2a)(baz=\28foo\29))', $builder->stringify()); 36 | } 37 | 38 | public function testWhereRaw() 39 | { 40 | $builder = $this->getBuilder(); 41 | 42 | $builder->whereRaw('bar', '*foo*') 43 | ->whereRaw('baz', '(foo)'); 44 | 45 | $this->assertEquals('(&(bar=*foo*)(baz=(foo)))', $builder->stringify()); 46 | } 47 | 48 | public function testOrWhere() 49 | { 50 | $builder = $this->getBuilder(); 51 | 52 | $builder->where('foo', 'bar') 53 | ->orWhere('baz', 'foo') 54 | ->orWhere('foo', 'baz') 55 | ->orWhere('baz', 'foo') 56 | ->orWhere('bar', '*foo*') 57 | ->orWhere('baz', '(foo)'); 58 | 59 | $this->assertEquals('(|(foo=bar)(baz=foo)(foo=baz)(baz=foo)(bar=\2afoo\2a)(baz=\28foo\29))', $builder->stringify()); 60 | } 61 | 62 | public function testOrWhereRaw() 63 | { 64 | $builder = $this->getBuilder(); 65 | 66 | $builder->whereRaw('bar', '*foo*') 67 | ->orWhereRaw('baz', '(foo)'); 68 | 69 | $this->assertEquals('(|(bar=*foo*)(baz=(foo)))', $builder->stringify()); 70 | } 71 | 72 | public function testWhereClosure() 73 | { 74 | $builder = $this->getBuilder(); 75 | 76 | $builder->where(function($builder){ 77 | $builder->where('foo', 'bar') 78 | ->where('baz', 'foo'); 79 | }) 80 | ->orWhere(function($builder){ 81 | $builder->where('baz', 'foo') 82 | ->orWhere('foo', 'baz'); 83 | }); 84 | 85 | $this->assertEquals('(|(&(foo=bar)(baz=foo))(|(baz=foo)(foo=baz)))', $builder->stringify()); 86 | } 87 | 88 | public function testWhereArray() 89 | { 90 | $builder = $this->getBuilder(); 91 | 92 | $builder->where('foo', [1, 2, 3, 4, 5, 6, 7]) 93 | ->where('baz', ['foo', 'bar']) 94 | ->orWhere('foo', 'baz'); 95 | 96 | $this->assertEquals('(|(&(|(foo=1)(foo=2)(foo=3)(foo=4)(foo=5)(foo=6)(foo=7))(|(baz=foo)(baz=bar)))(foo=baz))', $builder->stringify()); 97 | } 98 | 99 | public function testWhereNot() 100 | { 101 | $builder = $this->getBuilder(); 102 | 103 | $builder->whereNot('foo', 'bar') 104 | ->orWhereNot('bar', 'foo'); 105 | 106 | $this->assertEquals('(|(!(foo=bar))(!(bar=foo)))', $builder->stringify()); 107 | 108 | $builder = $this->getBuilder(); 109 | 110 | $builder->whereNot('foo', [1, 2, 3, 4, 5]) 111 | ->where('baz', 'foo') 112 | ->orWhere('foobar', '**1**'); 113 | 114 | $this->assertEquals('(|(&(!(|(foo=1)(foo=2)(foo=3)(foo=4)(foo=5)))(baz=foo))(foobar=\2a\2a1\2a\2a))', $builder->stringify()); 115 | } 116 | 117 | public function testWhereNotRaw() 118 | { 119 | $builder = $this->getBuilder(); 120 | 121 | $builder->whereNotRaw('foo', '(bar)') 122 | ->orWhereNotRaw('bar', '*foo*') 123 | ->whereNotRaw('bar', ['*1', '2*', '*2*']); 124 | 125 | $this->assertEquals('(&(|(!(foo=(bar)))(!(bar=*foo*)))(!(|(bar=*1)(bar=2*)(bar=*2*))))', $builder->stringify()); 126 | } 127 | 128 | public function testWhereBegins() 129 | { 130 | $builder = $this->getBuilder(); 131 | 132 | $builder->whereBegins('foo', 'bar*') 133 | ->orWhereBegins('foo', '**bar**'); 134 | 135 | $this->assertEquals('(|(foo=bar\2a*)(foo=\2a\2abar\2a\2a*))', $builder->stringify()); 136 | } 137 | 138 | public function testWhereBeginsArray() 139 | { 140 | $builder = $this->getBuilder(); 141 | 142 | $builder->whereBegins('foo', ['bar', 'baz', 'foo']); 143 | 144 | $this->assertEquals('(|(foo=bar*)(foo=baz*)(foo=foo*))', $builder->stringify()); 145 | } 146 | 147 | public function testWhereEnds() 148 | { 149 | $builder = $this->getBuilder(); 150 | 151 | $builder->whereEnds('foo', 'bar*') 152 | ->orWhereEnds('foo', '**bar**'); 153 | 154 | $this->assertEquals('(|(foo=*bar\2a)(foo=*\2a\2abar\2a\2a))', $builder->stringify()); 155 | } 156 | 157 | public function testWhereLike() 158 | { 159 | $builder = $this->getBuilder(); 160 | 161 | $builder->whereLike('foo', 'bar*') 162 | ->orWhereLike('foo', '**bar**'); 163 | 164 | $this->assertEquals('(|(foo=*bar\2a*)(foo=*\2a\2abar\2a\2a*))', $builder->stringify()); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/Filter/FilterFactoryTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(new Filter('attribute', 'value'), FilterFactory::build('attribute', 'value')); 14 | } 15 | 16 | public function testBuildRaw() 17 | { 18 | $this->assertEquals(new Filter('attribute', 'value', '=', null, false), FilterFactory::buildRaw('attribute', 'value')); 19 | } 20 | 21 | public function testOutput() 22 | { 23 | $this->assertEquals((new Filter('attribute', 'value'))->stringify(), FilterFactory::build('attribute', 'value')->stringify()); 24 | $this->assertEquals((new Filter('attribute', '*value*'))->stringify(), FilterFactory::build('attribute', '*value*')->stringify()); 25 | } 26 | 27 | public function testOutputRaw() 28 | { 29 | $this->assertEquals((new Filter('attribute', 'value', '=', null, false))->stringify(), FilterFactory::buildRaw('attribute', 'value')->stringify()); 30 | $this->assertEquals((new Filter('attribute', '*value*', '=', null, false))->stringify(), FilterFactory::buildRaw('attribute', '*value*')->stringify()); 31 | } 32 | 33 | /** 34 | * @expectedException \LdapQuery\Exceptions\GrammarException 35 | */ 36 | public function testInValidOperator() 37 | { 38 | $f = FilterFactory::build('attribute', 'value', '<>'); 39 | } 40 | 41 | /** 42 | * @expectedException \LdapQuery\Exceptions\GrammarException 43 | */ 44 | public function testInValidOperatorRaw() 45 | { 46 | $f = FilterFactory::buildRaw('attribute', 'value', '<>'); 47 | } 48 | 49 | /** 50 | * @expectedException \LdapQuery\Exceptions\GrammarException 51 | */ 52 | public function testInValidWildcard() 53 | { 54 | $f = FilterFactory::build('attribute', 'value', '=', 'something'); 55 | } 56 | 57 | /** 58 | * @expectedException \LdapQuery\Exceptions\GrammarException 59 | */ 60 | public function testInValidWildcardRaw() 61 | { 62 | $f = FilterFactory::buildRaw('attribute', 'value', '=', 'something'); 63 | } 64 | } -------------------------------------------------------------------------------- /tests/Filter/FilterTest.php: -------------------------------------------------------------------------------- 1 | newFilter('attribute', 'value', '<>'); 21 | } 22 | 23 | /** 24 | * @expectedException \LdapQuery\Exceptions\GrammarException 25 | */ 26 | public function testInValidWildcard() 27 | { 28 | $this->newFilter('attribute', 'value', '=', 'something'); 29 | } 30 | 31 | public function testEscapedValue() 32 | { 33 | $this->assertEquals('(attribute=\2avalue\2a)', $this->newFilter('attribute', '*value*')->stringify()); 34 | $this->assertEquals('(attribute=\28value\29\28\2a\2a\29)', $this->newFilter('attribute', '(value)(**)')->stringify()); 35 | $this->assertEquals('(attribute=/\5c\2a\28\29value\5c\2a/)', $this->newFilter('attribute', '/\*()value\\*/')->stringify()); 36 | $this->assertEquals('(attribute=\2a\2a\2a\2a\2a)', $this->newFilter('attribute', '*****')->stringify()); 37 | } 38 | 39 | public function testUnescapedValue() 40 | { 41 | $this->assertEquals('(attribute=*value*)', $this->newFilter('attribute', '*value*', '=', null, false)->stringify()); 42 | $this->assertEquals('(attribute=(value)(**))', $this->newFilter('attribute', '(value)(**)' , '=', null, false)->stringify()); 43 | $this->assertEquals('(attribute=/\*()value\\*/)', $this->newFilter('attribute', '/\*()value\\*/', '=', null, false)->stringify()); 44 | $this->assertEquals('(attribute=*****)', $this->newFilter('attribute', '*****', '=', null, false)->stringify()); 45 | } 46 | 47 | public function testWildcardBegins() 48 | { 49 | $this->assertEquals('(attribute=value*)', $this->newFilter('attribute', 'value', '=', 'begins')->stringify()); 50 | $this->assertEquals('(attribute=value\2a*)', $this->newFilter('attribute', 'value*', '=', 'begins')->stringify()); 51 | } 52 | 53 | public function testWildcardEnds() 54 | { 55 | $this->assertEquals('(attribute=*value)', $this->newFilter('attribute', 'value', '=', 'ends')->stringify()); 56 | $this->assertEquals('(attribute=*\2avalue)', $this->newFilter('attribute', '*value', '=', 'ends')->stringify()); 57 | } 58 | public function testWildcardLike() 59 | { 60 | $this->assertEquals('(attribute=*value*)', $this->newFilter('attribute', 'value', '=', 'like')->stringify()); 61 | $this->assertEquals('(attribute=*\2avalue\2a*)', $this->newFilter('attribute', '*value*', '=', 'like')->stringify()); 62 | } 63 | 64 | public function testToArray() 65 | { 66 | $expected = [ 67 | "(", 68 | " attribute=value", 69 | ")", 70 | ]; 71 | 72 | $this->assertEquals($expected, $this->newFilter('attribute', 'value')->toArray()); 73 | } 74 | 75 | public function testLogicalOperator() 76 | { 77 | $this->assertEquals('(attribute=value)', $this->newFilter('attribute', 'value')->stringify()); 78 | $this->assertEquals('(attribute=value)', $this->newFilter('attribute', 'value', '=')->stringify()); 79 | $this->assertEquals('(attribute~=value)', $this->newFilter('attribute', 'value', '~=')->stringify()); 80 | $this->assertEquals('(attribute<=value)', $this->newFilter('attribute', 'value', '<=')->stringify()); 81 | $this->assertEquals('(attribute>=value)', $this->newFilter('attribute', 'value', '>=')->stringify()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/Group/GroupFactoryTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Group::class, GroupFactory::build('&')); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Group/GroupTest.php: -------------------------------------------------------------------------------- 1 | newGroup(); 19 | 20 | $this->assertEmpty($g->stringify()); 21 | $this->assertEquals([], $g->toArray()); 22 | } 23 | 24 | /** 25 | * @expectedException \LdapQuery\Exceptions\GrammarException 26 | */ 27 | public function testInValidOperator() 28 | { 29 | $this->newGroup('&&'); 30 | } 31 | 32 | /** 33 | * @expectedException \LdapQuery\Exceptions\GrammarException 34 | */ 35 | public function testMultipleEntriesOnNotGroup() 36 | { 37 | $g = $this->newGroup('!'); 38 | $g->push($this->newGroup()); 39 | $g->push($this->newGroup()); 40 | } 41 | 42 | public function testValidOperator() 43 | { 44 | $this->newGroup('&'); 45 | $this->newGroup('|'); 46 | $this->newGroup('!'); 47 | } 48 | 49 | public function testIsEmpty() 50 | { 51 | $this->assertTrue($this->newGroup()->isEmpty()); 52 | } 53 | 54 | /** 55 | * @expectedException \InvalidArgumentException 56 | */ 57 | public function testPushInvalidArgument() 58 | { 59 | $this->newGroup()->push(new \StdClass); 60 | } 61 | 62 | public function testPush() 63 | { 64 | $g = $this->newGroup(); 65 | foreach ($this->getGroups(100, '&|') as $group) { 66 | foreach ($this->getFilters(10) as $filter) { 67 | $group->push($filter); 68 | } 69 | $g->push($group); 70 | } 71 | } 72 | 73 | public function testNest() 74 | { 75 | $group = $this->newGroup('|'); 76 | $groupToNest = $this->newGroup(); 77 | 78 | $groupToNest->push(FilterFactory::build('foo', 'bar')); 79 | $groupToNest->push(FilterFactory::build('foo', 'bar')); 80 | $groupToNest->nest($group); 81 | 82 | $group->push(FilterFactory::build('foo', 'bar')); 83 | 84 | $this->assertEquals('(|(&(foo=bar)(foo=bar))(foo=bar))', $group->stringify()); 85 | } 86 | 87 | public function testStringify() 88 | { 89 | $g = $this->newGroup(); 90 | $g->push(FilterFactory::build('attribute', 'value')); 91 | 92 | $this->assertEquals('(attribute=value)', $g->stringify()); 93 | $this->assertEquals((string)$g, $g->stringify()); 94 | 95 | $g->push(FilterFactory::build('attribute', '**value**')); 96 | 97 | $this->assertEquals('(&(attribute=value)(attribute=\2a\2avalue\2a\2a))', $g->stringify()); 98 | 99 | $gg = $this->newGroup('!'); 100 | $g->push($gg); 101 | 102 | $this->assertEquals('(&(attribute=value)(attribute=\2a\2avalue\2a\2a))', $g->stringify()); 103 | 104 | $gg->push(FilterFactory::build('foo', 'bar')); 105 | $g->push($gg); 106 | 107 | $this->assertEquals('(&(attribute=value)(attribute=\2a\2avalue\2a\2a)(!(foo=bar)))', $g->stringify()); 108 | 109 | $ggg = $this->newGroup('|'); 110 | $ggg->push($g); 111 | $ggg->push(FilterFactory::build('foo', 'baz', '~=', 'begins')); 112 | 113 | $this->assertEquals('(|(&(attribute=value)(attribute=\2a\2avalue\2a\2a)(!(foo=bar)))(foo~=baz*))', $ggg->stringify()); 114 | } 115 | 116 | public function testToArray() 117 | { 118 | $g = $this->newGroup(); 119 | 120 | $this->assertEquals([], $g->toArray()); 121 | $this->assertEquals([], $g->toArray(' ')); 122 | 123 | $g = $this->newGroup(); 124 | $g->push(FilterFactory::build('foo', 'bar')); 125 | 126 | $this->assertEquals( 127 | [ 128 | '(', 129 | ' foo=bar', 130 | ')' 131 | ], 132 | $g->toArray() 133 | ); 134 | 135 | $g = $this->newGroup('!'); 136 | $g->push(FilterFactory::build('foo', 'bar')); 137 | 138 | $this->assertEquals( 139 | [ 140 | '(!', 141 | ' (', 142 | ' foo=bar', 143 | ' )', 144 | ')' 145 | ], 146 | $g->toArray() 147 | ); 148 | 149 | $g = $this->newGroup(); 150 | $g->push(FilterFactory::build('foo', 'bar')); 151 | $g->push($g); 152 | 153 | $this->assertEquals( 154 | [ 155 | '(&', 156 | ' (', 157 | ' foo=bar', 158 | ' )', 159 | ' (', 160 | ' foo=bar', 161 | ' )', 162 | ')' 163 | ], 164 | $g->toArray() 165 | ); 166 | 167 | $g = $this->newGroup(); 168 | $g->push(FilterFactory::build('foo', 'bar')); 169 | $g->push(FilterFactory::build('foo', 'bar')); 170 | $g->push($g); 171 | 172 | $this->assertEquals( 173 | [ 174 | '(&', 175 | ' (', 176 | ' foo=bar', 177 | ' )', 178 | ' (', 179 | ' foo=bar', 180 | ' )', 181 | ' (&', 182 | ' (', 183 | ' foo=bar', 184 | ' )', 185 | ' (', 186 | ' foo=bar', 187 | ' )', 188 | ' )', 189 | ')' 190 | ], 191 | $g->toArray() 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /tests/UnitTestCase.php: -------------------------------------------------------------------------------- 1 |