UserFilter build a SQL query. Add filtering on names.
[platal.git] / classes / userfilter.php
CommitLineData
a087cc8d
FB
1<?php
2/***************************************************************************
3 * Copyright (C) 2003-2009 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22interface UserFilterCondition
23{
24 /** Check that the given user matches the rule.
25 */
784745ce 26 public function buildCondition(UserFilter &$uf);
a087cc8d
FB
27}
28
29abstract class UFC_OneChild implements UserFilterCondition
30{
31 protected $child;
32
33 public function setChild(UserFilterCondition &$cond)
34 {
35 $this->child =& $cond;
36 }
37}
38
39abstract class UFC_NChildren implements UserFilterCondition
40{
41 protected $children = array();
42
43 public function addChild(UserFilterCondition &$cond)
44 {
45 $this->children[] =& $cond;
46 }
47}
48
49class UFC_True implements UserFilterCondition
50{
784745ce 51 public function buildCondition(UserFilter &$uf)
a087cc8d 52 {
784745ce 53 return 'TRUE';
a087cc8d
FB
54 }
55}
56
57class UFC_False implements UserFilterCondition
58{
784745ce 59 public function buildCondition(UserFilter &$uf)
a087cc8d 60 {
784745ce 61 return 'FALSE';
a087cc8d
FB
62 }
63}
64
65class UFC_Not extends UFC_OneChild
66{
784745ce 67 public function buildCondition(UserFilter &$uf)
a087cc8d 68 {
784745ce 69 return 'NOT (' . $this->child->buildCondition($uf) . ')';
a087cc8d
FB
70 }
71}
72
73class UFC_And extends UFC_NChildren
74{
784745ce 75 public function buildCondition(UserFilter &$uf)
a087cc8d 76 {
784745ce
FB
77 if (empty($this->children)) {
78 return 'FALSE';
79 } else {
80 $conds = array();
81 foreach ($this->children as &$child) {
82 $conds[] = $child->buildCondition($uf);
a087cc8d 83 }
784745ce 84 return '(' . implode (') AND (', $conds) . ')';
a087cc8d 85 }
a087cc8d
FB
86 }
87}
88
89class UFC_Or extends UFC_NChildren
90{
784745ce 91 public function buildCondition(UserFilter &$uf)
a087cc8d 92 {
784745ce
FB
93 if (empty($this->children)) {
94 return 'TRUE';
95 } else {
96 $conds = array();
97 foreach ($this->children as &$child) {
98 $conds[] = $child->buildCondition($uf);
a087cc8d 99 }
784745ce 100 return '(' . implode (') OR (', $conds) . ')';
a087cc8d 101 }
a087cc8d
FB
102 }
103}
104
105class UFC_Promo implements UserFilterCondition
106{
a087cc8d
FB
107
108 private $grade;
109 private $promo;
110 private $comparison;
111
112 public function __construct($comparison, $grade, $promo)
113 {
114 $this->grade = $grade;
115 $this->comparison = $comparison;
116 $this->promo = $promo;
784745ce 117 UserFilter::assertGrade($this->grade);
a087cc8d
FB
118 }
119
784745ce 120 public function buildCondition(UserFilter &$uf)
a087cc8d 121 {
a087cc8d 122 // XXX: Definition of promotion for phds and masters might change in near future.
784745ce 123 if ($this->grade == UserFilter::GRADE_ING) {
a087cc8d
FB
124 $promo_year = 'entry_year';
125 } else {
126 $promo_year = 'grad_year';
127 }
784745ce
FB
128 $sub = $uf->addEducationFilter(true, $this->grade);
129 $field = 'pe' . $sub . '.' . $promo_year;
130 return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo);
131 }
132}
133
134class UFC_Name implements UserFilterCondition
135{
136 const PREFIX = 1;
137 const SUFFIX = 2;
138 const PARTICLE = 7;
139 const VARIANTS = 8;
140 const CONTAINS = 3;
141
142 private $type;
143 private $text;
144 private $mode;
145
146 public function __construct($type, $text, $mode)
147 {
148 $this->type = $type;
149 $this->text = $text;
150 $this->mode = $mode;
151 }
152
153 private function buildNameQuery($type, $variant, $where, UserFilter &$uf)
154 {
155 $sub = $uf->addNameFilter($type, $variant);
156 return str_replace('$ME', 'pn' . $sub, $where);
157 }
158
159 public function buildCondition(UserFilter &$uf)
160 {
161 $left = '$ME.name';
162 $op = ' LIKE ';
163 if (($this->mode & self::PARTICLE) == self::PARTICLE) {
164 $left = 'CONCAT($ME.particle, \' \', $ME.name)';
165 }
166 if (($this->mode & self::CONTAINS) == 0) {
167 $right = XDB::format('{?}', $this->text);
168 $op = ' = ';
169 } else if (($this->mode & self::CONTAINS) == self::PREFIX) {
170 $right = XDB::format('CONCAT({?}, \'%\')', $this->text);
171 } else if (($this->mode & self::CONTAINS) == self::SUFFIX) {
172 $right = XDB::format('CONCAT(\'%\', {?})', $this->text);
173 } else {
174 $right = XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->text);
175 }
176 $cond = $left . $op . $right;
177 $conds = array($this->buildNameQuery($this->type, null, $cond, $uf));
178 if (($this->mode & self::VARIANTS) != 0) {
179 foreach (UserFilter::$name_variants[$this->type] as $var) {
180 $conds[] = $this->buildNameQuery($this->type, $var, $cond, $uf);
181 }
182 }
183 return implode(' OR ', $conds);
a087cc8d
FB
184 }
185}
186
187class UserFilter
188{
189 private $root;
784745ce
FB
190 private $query = null;
191
192 private function buildQuery()
193 {
194 if (is_null($this->query)) {
195 $where = $this->root->buildCondition($this);
196 $joins = $this->buildJoins();
197 $this->query = 'FROM accounts AS a
198 INNER JOIN account_profiles AS ap ON (ap.uid = a.uid AND FIND_IN_SET(\'owner\', ap.perms))
199 INNER JOIN profiles AS p ON (p.pid = ap.pid)
200 ' . $joins . '
201 WHERE (' . $where . ')';
202 }
203 }
204
205 private function formatJoin(array $joins)
206 {
207 $str = '';
208 foreach ($joins as $key => $infos) {
209 $mode = $infos[0];
210 $table = $infos[1];
211 if ($mode == 'inner') {
212 $str .= 'INNER JOIN ';
213 } else if ($mode == 'left') {
214 $str .= 'LEFT JOIN ';
215 } else {
216 Platal::page()->kill("Join mode error");
217 }
218 $str .= $table . ' AS ' . $key;
219 if (isset($infos[2])) {
220 $str .= ' ON (' . str_replace(array('$ME', '$PID'), array($key, 'p.pid'), $infos[2]) . ')';
221 }
222 $str .= "\n";
223 }
224 return $str;
225 }
226
227 private function buildJoins()
228 {
229 $joins = $this->educationJoins() + $this->nameJoins();
230 return $this->formatJoin($joins);
231 }
a087cc8d
FB
232
233 /** Check that the user match the given rule.
234 */
235 public function checkUser(PlUser &$user)
236 {
784745ce
FB
237 $this->buildQuery();
238 $count = (int)XDB::fetchOneCell('SELECT COUNT(*)
239 ' . $this->query . XDB::format(' AND a.uid = {?}', $user->id()));
240 return $count == 1;
a087cc8d
FB
241 }
242
243 /** Filter a list of user to extract the users matching the rule.
244 */
245 public function filter(array $users)
246 {
247 $output = array();
248 foreach ($users as &$user) {
249 if ($this->checkUser($user)) {
250 $output[] = $user;
251 }
252 }
253 return $output;
254 }
255
256 public function setCondition(UserFilterCondition &$cond)
257 {
258 $this->root =& $cond;
784745ce 259 $this->query = null;
a087cc8d
FB
260 }
261
a087cc8d
FB
262 static public function getLegacy($promo_min, $promo_max)
263 {
264 $min = null;
265 if ($promo_min != 0) {
784745ce 266 $min = new UFC_Promo('>=', self::GRADE_ING, intval($promo_min));
a087cc8d
FB
267 }
268 $max = null;
269 if ($promo_max != 0) {
784745ce 270 $max = new UFC_Promo('<=', self::GRADE_ING, intval($promo_max));
a087cc8d
FB
271 }
272 $uf = new UserFilter();
273 if (is_null($max) && is_null($min)) {
274 $uf->setCondition(new UFC_True());
275 } else if (is_null($max)) {
276 $uf->setCondition($min);
277 } else if (is_null($min)) {
278 $uf->setCondition($max);
279 } else {
280 $cond = new UFC_And();
281 $cond->addChild($min);
282 $cond->addChild($max);
283 $uf->setCondition($cond);
284 }
285 return $uf;
286 }
784745ce
FB
287
288
289 /** NAMES
290 */
291 const LASTNAME = 'lastname';
292 const FIRSTNAME = 'firstname';
293 const NICKNAME = 'nickname';
294 const PSEUDONYM = 'pseudonym';
295 const NAME = 'name';
296 const VN_MARITAL = 'marital';
297 const VN_ORDINARY = 'ordinary';
298 const VN_OTHER = 'other';
299 const VN_INI = 'ini';
300
301 static public $name_variants = array(
302 self::LASTNAME => array(self::VN_MARITAL, self::VN_ORDINARY),
303 self::FIRSTNAME => array(self::VN_ORDINARY, self::VN_INI, self::VN_OTHER),
304 self::NICKNAME => array(), self::PSEUDONYM => array(),
305 self::NAME => array());
306
307 static public function assertName($name)
308 {
309 if (!Profile::getNameTypeId($name)) {
310 Platal::page()->kill('Invalid name type');
311 }
312 }
313
314 private $pn = array();
315 private $pno = 0;
316 public function addNameFilter($type, $variant = null)
317 {
318 if (!is_null($variant)) {
319 $ft = $type . '_' . $variant;
320 } else {
321 $ft = $type;
322 }
323 $sub = '_' . $ft;
324 self::assertName($ft);
325
326 if (!is_null($variant) && $variant == 'other') {
327 $sub .= $this->pno++;
328 }
329 $this->pn[$sub] = Profile::getNameTypeId($ft);
330 return $sub;
331 }
332
333 private function nameJoins()
334 {
335 $joins = array();
336 foreach ($this->pn as $sub => $type) {
337 $joins['pn' . $sub] = array('left', 'profile_name', '$ME.pid = $PID AND $ME.typeid = ' . $type);
338 }
339 return $joins;
340 }
341
342
343 /** EDUCATION
344 */
345 const GRADE_ING = 'Ing.';
346 const GRADE_PHD = 'PhD';
347 const GRADE_MST = 'M%';
348 static public function isGrade($grade)
349 {
350 return $grade == self::GRADE_ING || self::$grade == GRADE_PHD || self::$grade == GRADE_MST;
351 }
352
353 static public function assertGrade($grade)
354 {
355 if (!self::isGrade($grade)) {
356 Platal::page()->killError("DiplĂ´me non valide");
357 }
358 }
359
360 private $pepe = array();
361 private $with_pee = false;
362 private $pe_g = 0;
363 public function addEducationFilter($x = false, $grade = null)
364 {
365 if (!$x) {
366 $index = $this->pe_g;
367 $sub = $this->pe_g++;
368 } else {
369 self::assertGrade($grade);
370 $index = $grade;
371 $sub = $grade[0];
372 $this->with_pee = true;
373 }
374 $sub = '_' . $sub;
375 $this->pepe[$index] = $sub;
376 return $sub;
377 }
378
379 private function educationJoins()
380 {
381 $joins = array();
382 if ($this->with_pee) {
383 $joins['pee'] = array('inner', 'profile_education_enum', 'pee.abbreviation = \'X\'');
384 }
385 foreach ($this->pepe as $grade => $sub) {
386 if ($this->isGrade($grade)) {
387 $joins['pe' . $sub] = array('left', 'profile_education', '$ME.eduid = pee.id AND $ME.uid = $PID');
388 $joins['pede' . $sub] = array('inner', 'profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid AND $ME.abbreviation LIKE ' .
389 XDB::format('{?}', $grade));
390 } else {
391 $joins['pe' . $sub] = array('left', 'profile_education', '$ME.uid = $PID');
392 $joins['pee' . $sub] = array('inner', 'profile_education_enum', '$ME.id = pe' . $sub . '.eduid');
393 $joins['pede' . $sub] = array('inner', 'profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid');
394 }
395 }
396 return $joins;
397 }
a087cc8d
FB
398}
399
400// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
401?>