X-Git-Url: http://git.polytechnique.org/?a=blobdiff_plain;ds=sidebyside;f=classes%2Fuserfilter.php;h=01c67ed7403139d872430558d1acc7bfd93e0922;hb=5dd9d82327c44b3d5ce999c44a088f23e6b8ed8c;hp=7f8259f8d40f9f78953c912ed6931cb9f0c34e9a;hpb=a087cc8da8a4a03f0642f8d5fa7ab61b67675891;p=platal.git diff --git a/classes/userfilter.php b/classes/userfilter.php index 7f8259f..01c67ed 100644 --- a/classes/userfilter.php +++ b/classes/userfilter.php @@ -21,15 +21,25 @@ interface UserFilterCondition { + const COND_TRUE = 'TRUE'; + const COND_FALSE = 'FALSE'; + /** Check that the given user matches the rule. */ - public function check(PlUser &$user); + public function buildCondition(UserFilter &$uf); } abstract class UFC_OneChild implements UserFilterCondition { protected $child; + public function __construct($child = null) + { + if (!is_null($child) && ($child instanceof UserFilterCondition)) { + $this->setChild($child); + } + } + public function setChild(UserFilterCondition &$cond) { $this->child =& $cond; @@ -40,67 +50,114 @@ abstract class UFC_NChildren implements UserFilterCondition { protected $children = array(); + public function __construct() + { + $children = func_get_args(); + foreach ($children as &$child) { + if (!is_null($child) && ($child instanceof UserFilterCondition)) { + $this->addChild($child); + } + } + } + public function addChild(UserFilterCondition &$cond) { $this->children[] =& $cond; } + + protected function catConds(array $cond, $op, $fallback) + { + if (count($cond) == 0) { + return $fallback; + } else if (count($cond) == 1) { + return $cond[0]; + } else { + return '(' . implode(') ' . $op . ' (', $conds) . ')'; + } + } } class UFC_True implements UserFilterCondition { - public function check(PlUser &$user) + public function buildCondition(UserFilter &$uf) { - return true; + return self::COND_TRUE; } } class UFC_False implements UserFilterCondition { - public function check(PlUser &$user) + public function buildCondition(UserFilter &$uf) { - return false; + return self::COND_FALSE; } } class UFC_Not extends UFC_OneChild { - public function check(PlUser &$user) + public function buildCondition(UserFilter &$uf) { - return !$this->child->child($user); + $val = $this->child->buildCondition($uf); + if ($val == self::COND_TRUE) { + return self::COND_FALSE; + } else if ($val == self::COND_FALSE) { + return self::COND_TRUE; + } else { + return 'NOT (' . $val . ')'; + } } } class UFC_And extends UFC_NChildren { - public function check(PlUser &$user) + public function buildCondition(UserFilter &$uf) { - foreach ($this->children as &$cond) { - if (!$cond->check($user)) { - return false; + if (empty($this->children)) { + return self::COND_FALSE; + } else { + $true = self::COND_FALSE; + $conds = array(); + foreach ($this->children as &$child) { + $val = $child->buildCondition($uf); + if ($val == self::COND_TRUE) { + $true = self::COND_TRUE; + } else if ($val == self::COND_FALSE) { + return self::COND_FALSE; + } else { + $conds[] = $val; + } } + return $this->catConds($conds, 'AND', $true); } - return true; } } class UFC_Or extends UFC_NChildren { - public function check(PlUser &$user) + public function buildCondition(UserFilter &$uf) { - foreach ($this->children as &$cond) { - if ($cond->check($user)) { - return true; + if (empty($this->children)) { + return self::COND_TRUE; + } else { + $true = self::COND_TRUE; + $conds = array(); + foreach ($this->children as &$child) { + $val = $child->buildCondition($uf); + if ($val == self::COND_TRUE) { + return self::COND_TRUE; + } else if ($val == self::COND_FALSE) { + $true = self::COND_FALSE; + } else { + $conds[] = $val; + } } + return $this->catConds($conds, 'OR', $true); } - return false; } } class UFC_Promo implements UserFilterCondition { - const GRADE_ING = 'Ing.'; - const GRADE_PHD = 'PhD'; - const GRADE_MST = 'M%'; private $grade; private $promo; @@ -111,38 +168,139 @@ class UFC_Promo implements UserFilterCondition $this->grade = $grade; $this->comparison = $comparison; $this->promo = $promo; + UserFilter::assertGrade($this->grade); } - public function check(PlUser &$user) + public function buildCondition(UserFilter &$uf) { - if (!$user->hasProfile()) { - return false; - } // XXX: Definition of promotion for phds and masters might change in near future. - if ($this->grade == self::GRADE_ING) { + if ($this->grade == UserFilter::GRADE_ING) { $promo_year = 'entry_year'; } else { $promo_year = 'grad_year'; } - $req = XDB::fetchOneCell('SELECT COUNT(pe.id) - FROM profile_education AS pe - INNER JOIN profile_education_degree_enum AS pede ON (pe.degreeid = pede.id AND pede.abbreviation LIKE {?}) - INNER JOIN profile_education_enum AS pee ON (pe.eduid = pee.id AND pee.abbreviation = \'X\') - WHERE pe.' . $promo_year . ' ' . $this->comparison . ' {?} AND pe.uid = {?}', - $this->grade, $this->promo, $user->profile()->id()); - return intval($req) > 0; + $sub = $uf->addEducationFilter(true, $this->grade); + $field = 'pe' . $sub . '.' . $promo_year; + return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo); + } +} + +class UFC_Name implements UserFilterCondition +{ + const PREFIX = 1; + const SUFFIX = 2; + const PARTICLE = 7; + const VARIANTS = 8; + const CONTAINS = 3; + + private $type; + private $text; + private $mode; + + public function __construct($type, $text, $mode) + { + $this->type = $type; + $this->text = $text; + $this->mode = $mode; + } + + private function buildNameQuery($type, $variant, $where, UserFilter &$uf) + { + $sub = $uf->addNameFilter($type, $variant); + return str_replace('$ME', 'pn' . $sub, $where); + } + + public function buildCondition(UserFilter &$uf) + { + $left = '$ME.name'; + $op = ' LIKE '; + if (($this->mode & self::PARTICLE) == self::PARTICLE) { + $left = 'CONCAT($ME.particle, \' \', $ME.name)'; + } + if (($this->mode & self::CONTAINS) == 0) { + $right = XDB::format('{?}', $this->text); + $op = ' = '; + } else if (($this->mode & self::CONTAINS) == self::PREFIX) { + $right = XDB::format('CONCAT({?}, \'%\')', $this->text); + } else if (($this->mode & self::CONTAINS) == self::SUFFIX) { + $right = XDB::format('CONCAT(\'%\', {?})', $this->text); + } else { + $right = XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->text); + } + $cond = $left . $op . $right; + $conds = array($this->buildNameQuery($this->type, null, $cond, $uf)); + if (($this->mode & self::VARIANTS) != 0) { + foreach (UserFilter::$name_variants[$this->type] as $var) { + $conds[] = $this->buildNameQuery($this->type, $var, $cond, $uf); + } + } + return implode(' OR ', $conds); } } class UserFilter { private $root; + private $query = null; + + public function __construct($cond = null) + { + if (!is_null($cond)) { + if ($cond instanceof UserFilterCondition) { + $this->setCondition($cond); + } + } + } + + private function buildQuery() + { + if (is_null($this->query)) { + $where = $this->root->buildCondition($this); + $joins = $this->buildJoins(); + $this->query = 'FROM accounts AS a + INNER JOIN account_profiles AS ap ON (ap.uid = a.uid AND FIND_IN_SET(\'owner\', ap.perms)) + INNER JOIN profiles AS p ON (p.pid = ap.pid) + ' . $joins . ' + WHERE (' . $where . ')'; + } + } + + private function formatJoin(array $joins) + { + $str = ''; + foreach ($joins as $key => $infos) { + $mode = $infos[0]; + $table = $infos[1]; + if ($mode == 'inner') { + $str .= 'INNER JOIN '; + } else if ($mode == 'left') { + $str .= 'LEFT JOIN '; + } else { + Platal::page()->kill("Join mode error"); + } + $str .= $table . ' AS ' . $key; + if (isset($infos[2])) { + $str .= ' ON (' . str_replace(array('$ME', '$PID'), array($key, 'p.pid'), $infos[2]) . ')'; + } + $str .= "\n"; + } + return $str; + } + + private function buildJoins() + { + $joins = $this->educationJoins() + $this->nameJoins(); + return $this->formatJoin($joins); + } /** Check that the user match the given rule. */ public function checkUser(PlUser &$user) { - return $this->root->check($user); + $this->buildQuery(); + $count = (int)XDB::fetchOneCell('SELECT COUNT(*) + ' . $this->query . XDB::format(' AND a.uid = {?}', $user->id())); + return $count == 1; } /** Filter a list of user to extract the users matching the rule. @@ -161,33 +319,133 @@ class UserFilter public function setCondition(UserFilterCondition &$cond) { $this->root =& $cond; + $this->query = null; } - static public function getLegacy($promo_min, $promo_max) { - $min = null; if ($promo_min != 0) { - $min = new UFC_Promo('>=', UFC_Promo::GRADE_ING, intval($promo_min)); + $min = new UFC_Promo('>=', self::GRADE_ING, intval($promo_min)); + } else { + $min = new UFC_True(); } - $max = null; if ($promo_max != 0) { - $max = new UFC_Promo('<=', UFC_Promo::GRADE_ING, intval($promo_max)); - } - $uf = new UserFilter(); - if (is_null($max) && is_null($min)) { - $uf->setCondition(new UFC_True()); - } else if (is_null($max)) { - $uf->setCondition($min); - } else if (is_null($min)) { - $uf->setCondition($max); + $max = new UFC_Promo('<=', self::GRADE_ING, intval($promo_max)); + } else { + $max = new UFC_True(); + } + return new UserFilter(new UFC_And($min, $max)); + } + + + /** NAMES + */ + const LASTNAME = 'lastname'; + const FIRSTNAME = 'firstname'; + const NICKNAME = 'nickname'; + const PSEUDONYM = 'pseudonym'; + const NAME = 'name'; + const VN_MARITAL = 'marital'; + const VN_ORDINARY = 'ordinary'; + const VN_OTHER = 'other'; + const VN_INI = 'ini'; + + static public $name_variants = array( + self::LASTNAME => array(self::VN_MARITAL, self::VN_ORDINARY), + self::FIRSTNAME => array(self::VN_ORDINARY, self::VN_INI, self::VN_OTHER), + self::NICKNAME => array(), self::PSEUDONYM => array(), + self::NAME => array()); + + static public function assertName($name) + { + if (!Profile::getNameTypeId($name)) { + Platal::page()->kill('Invalid name type'); + } + } + + private $pn = array(); + private $pno = 0; + public function addNameFilter($type, $variant = null) + { + if (!is_null($variant)) { + $ft = $type . '_' . $variant; } else { - $cond = new UFC_And(); - $cond->addChild($min); - $cond->addChild($max); - $uf->setCondition($cond); + $ft = $type; + } + $sub = '_' . $ft; + self::assertName($ft); + + if (!is_null($variant) && $variant == 'other') { + $sub .= $this->pno++; + } + $this->pn[$sub] = Profile::getNameTypeId($ft); + return $sub; + } + + private function nameJoins() + { + $joins = array(); + foreach ($this->pn as $sub => $type) { + $joins['pn' . $sub] = array('left', 'profile_name', '$ME.pid = $PID AND $ME.typeid = ' . $type); + } + return $joins; + } + + + /** EDUCATION + */ + const GRADE_ING = 'Ing.'; + const GRADE_PHD = 'PhD'; + const GRADE_MST = 'M%'; + static public function isGrade($grade) + { + return $grade == self::GRADE_ING || self::$grade == GRADE_PHD || self::$grade == GRADE_MST; + } + + static public function assertGrade($grade) + { + if (!self::isGrade($grade)) { + Platal::page()->killError("Diplôme non valide"); + } + } + + private $pepe = array(); + private $with_pee = false; + private $pe_g = 0; + public function addEducationFilter($x = false, $grade = null) + { + if (!$x) { + $index = $this->pe_g; + $sub = $this->pe_g++; + } else { + self::assertGrade($grade); + $index = $grade; + $sub = $grade[0]; + $this->with_pee = true; + } + $sub = '_' . $sub; + $this->pepe[$index] = $sub; + return $sub; + } + + private function educationJoins() + { + $joins = array(); + if ($this->with_pee) { + $joins['pee'] = array('inner', 'profile_education_enum', 'pee.abbreviation = \'X\''); + } + foreach ($this->pepe as $grade => $sub) { + if ($this->isGrade($grade)) { + $joins['pe' . $sub] = array('left', 'profile_education', '$ME.eduid = pee.id AND $ME.uid = $PID'); + $joins['pede' . $sub] = array('inner', 'profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid AND $ME.abbreviation LIKE ' . + XDB::format('{?}', $grade)); + } else { + $joins['pe' . $sub] = array('left', 'profile_education', '$ME.uid = $PID'); + $joins['pee' . $sub] = array('inner', 'profile_education_enum', '$ME.id = pe' . $sub . '.eduid'); + $joins['pede' . $sub] = array('inner', 'profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid'); + } } - return $uf; + return $joins; } }