From d865c29631a10f492218a0ec4b6232b273cee3a9 Mon Sep 17 00:00:00 2001 From: Florent Bruneau Date: Thu, 5 Feb 2009 00:01:06 +0100 Subject: [PATCH] Implements sorting and limits on UserFilter. Start porting xnetgrp to UserFilter. Signed-off-by: Florent Bruneau --- classes/group.php | 42 ++------- classes/user.php | 73 +++------------ classes/userfilter.php | 234 +++++++++++++++++++++++++++++++++++++++++++------ include/common.inc.php | 5 +- modules/events.php | 7 +- modules/xnetgrp.php | 28 +++--- 6 files changed, 251 insertions(+), 138 deletions(-) diff --git a/classes/group.php b/classes/group.php index 62236db..c75191b 100644 --- a/classes/group.php +++ b/classes/group.php @@ -25,9 +25,6 @@ class Group public $shortname; private $data = array(); - private $members = null; - private $admins = null; - private function __construct(array $data) { foreach ($data as $key=>$value) { @@ -55,44 +52,23 @@ class Group return property_exists($this, $name) || isset($this->data[$name]); } - public function getMemberUIDs() - { - if (is_null($this->members)) { - $this->members = XDB::fetchColumn('SELECT uid - FROM groupex.membres - WHERE asso_id = {?}', $this->id); - } - return $this->members; - } - - public function getMembers($sortby = null, $count = null, $offset = null) - { - return User::getBuildUsersWithUIDs($this->getMemberUIDs(), $sortby, $count, $offset); - } - - public function getMemberCount() - { - return count($this->getMemberUIDs()); - } - - public function getAdminUIDs() + private function getUF($admin = false, $extra_cond = null, $sort = null) { - if (is_null($this->admins)) { - $this->admins = XDB::fetchColumn('SELECT uid - FROM groupex.membres - WHERE asso_id = {?} AND perms = \'admin\'', $this->id); + $cond = new UFC_Group($this->id, $admin); + if (!is_null($extra_cond)) { + $cond = new UFC_And($cond, $extra_cond); } - return $this->admins; + return new UserFilter($cond, $sort); } - public function getAdmins($sortby = null, $count = null, $offset = null) + public function getMembers($extra_cond = null, $sort = null) { - return User::getBuildUsersWithUIDs($this->getAdminUIDs(), $sortby, $count, $offset); + return $this->getUF(false, $extra_cond, $sort); } - public function getAdminCount() + public function getAdmins($extra_cond = null, $sort = null) { - return count($this->getAdminUIDs()); + return $this->getUF(true, $extra_cond, $sort); } static public function get($id) diff --git a/classes/user.php b/classes/user.php index cea85c6..17cbd72 100644 --- a/classes/user.php +++ b/classes/user.php @@ -139,58 +139,11 @@ class User extends PlUser throw new UserNotFoundException($res->fetchColumn(1)); } - protected static function loadMainFieldsFromUIDs(array $uids, $sorted = null, $count = null, $offset = null) + protected static function loadMainFieldsFromUIDs(array $uids) { global $globals; $joins = ''; - $orderby = ''; $fields = array(); - if (!is_null($sorted)) { - $order = array(); - $with_ap = false; - $with_pd = false; - foreach (explode(',', $sorted) as $part) { - $desc = ($part[0] == '-'); - if ($desc) { - $part = substr($part, 1); - } - switch ($part) { - case 'promo': - $with_pd = true; - $with_ap = true; - $part = 'IF (pd.promo IS NULL, \'ext\', pd.promo)'; - break; - case 'full_name': - $part = 'a.full_name'; - break; - case 'display_name': - $part = 'a.display_name'; - break; - case 'directory_name': - $part = 'pd.directory_name'; - $with_pd = true; - $with_ap = true; - break; - default: - $part = null; - } - if (!is_null($part)) { - if ($desc) { - $part .= ' DESC'; - } - $order[] = $part; - } - } - if (count($order) > 0) { - if ($with_ap) { - $joins .= "LEFT JOIN account_profiles AS ap ON (ap.uid = a.uid AND FIND_IN_SET('owner', ap.perms))\n"; - } - if ($with_pd) { - $joins .= "LEFT JOIN profile_display AS pd ON (pd.pid = ap.pid)\n"; - } - $orderby = 'ORDER BY ' . implode(', ', $order); - } - } if ($globals->asso('id')) { $joins .= XDB::format("LEFT JOIN groupex.membres AS gpm ON (gpm.uid = a.uid AND gpm.asso_id = {?})\n", $globals->asso('id')); $fields[] = 'gpm.perms AS group_perms'; @@ -201,14 +154,6 @@ class User extends PlUser } else { $fields = ''; } - $limit = ''; - if (!is_null($count)) { - if (!is_null($offset)) { - $limit = ' LIMIT ' . $offset . ', ' . $count; - } else { - $limit = ' LIMIT ' . $count; - } - } $uids = array_map(array('XDB', 'escape'), $uids); return XDB::iterator('SELECT a.uid, a.hruid, a.registration_date, CONCAT(af.alias, \'@' . $globals->mail->domain . '\') AS forlife, @@ -223,9 +168,9 @@ class User extends PlUser INNER JOIN account_types AS at ON (at.type = a.type) LEFT JOIN aliases AS af ON (af.id = a.uid AND af.type = \'a_vie\') LEFT JOIN aliases AS ab ON (ab.id = a.uid AND FIND_IN_SET(\'bestalias\', ab.flags)) - ' . $joins . ' + ' . $joins . ' WHERE a.uid IN (' . implode(', ', $uids) . ') - ' . $orderby . $limit); + GROUP BY a.uid'); } // Implementation of the data loader. @@ -448,12 +393,16 @@ class User extends PlUser } // Fetch a set of users from a list of UIDs - public static function getBuildUsersWithUIDs(array $uids, $sortby = null, $count = null, $offset = null) + public static function getBulkUsersWithUIDs(array $uids) { - $fields = self::loadMainFieldsFromUIDs($uids, $sortby, $count, $offset); - $users = array(); + $fields = self::loadMainFieldsFromUIDs($uids); + $table = array(); while (($list = $fields->next())) { - $users[] = User::getSilentWithValues(null, $list); + $table[$list['uid']] = User::getSilentWithValues(null, $list); + } + $users = array(); + foreach ($uids as $uid) { + $users[] = $table[$uid]; } return $users; } diff --git a/classes/userfilter.php b/classes/userfilter.php index 7a4dd3b..ad9672e 100644 --- a/classes/userfilter.php +++ b/classes/userfilter.php @@ -19,6 +19,11 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ***************************************************************************/ + +/****************** + * CONDITIONS + ******************/ + interface UserFilterCondition { const COND_TRUE = 'TRUE'; @@ -173,14 +178,8 @@ class UFC_Promo implements UserFilterCondition public function buildCondition(UserFilter &$uf) { - // XXX: Definition of promotion for phds and masters might change in near future. - if ($this->grade == UserFilter::GRADE_ING) { - $promo_year = 'entry_year'; - } else { - $promo_year = 'grad_year'; - } $sub = $uf->addEducationFilter(true, $this->grade); - $field = 'pe' . $sub . '.' . $promo_year; + $field = 'pe' . $sub . '.' . UserFilter::promoYear($this->grade); return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo); } } @@ -229,7 +228,7 @@ class UFC_Name implements UserFilterCondition } $cond = $left . $op . $right; $conds = array($this->buildNameQuery($this->type, null, $cond, $uf)); - if (($this->mode & self::VARIANTS) != 0) { + if (($this->mode & self::VARIANTS) != 0 && isset(UserFilter::$name_variants[$this->type])) { foreach (UserFilter::$name_variants[$this->type] as $var) { $conds[] = $this->buildNameQuery($this->type, $var, $cond, $uf); } @@ -313,15 +312,113 @@ class UFC_Group implements UserFilterCondition } } + + +/****************** + * ORDERS + ******************/ + +abstract class UserFilterOrder +{ + protected $desc = false; + + public function buildSort(UserFilter &$uf) + { + $sel = $this->getSortTokens($uf); + if (!is_array($sel)) { + $sel = array($sel); + } + if ($this->desc) { + foreach ($sel as $k=>$s) { + $sel[$k] = $s . ' DESC'; + } + } + return $sel; + } + + abstract protected function getSortTokens(UserFilter &$uf); +} + +class UFO_Promo extends UserFilterOrder +{ + private $grade; + + public function __construct($grade = null, $desc = false) + { + $this->grade = $grade; + $this->desc = $desc; + } + + protected function getSortTokens(UserFilter &$uf) + { + if (UserFilter::isGrade($this->grade)) { + $sub = $uf->addEducationFilter($this->grade); + return 'pe' . $sub . '.' . UserFilter::promoYear($this->grade); + } else { + $sub = $uf->addDisplayFilter(); + return 'pd' . $sub . '.promo'; + } + } +} + +class UFO_Name extends UserFilterOrder +{ + private $type; + private $variant; + private $particle; + + public function __construct($type, $variant = null, $particle = false, $desc = false) + { + $this->type = $type; + $this->variant = $variant; + $this->particle = $particle; + $this->desc = $desc; + } + + protected function getSortTokens(UserFilter &$uf) + { + if (UserFilter::isDisplayName($this->type)) { + $sub = $uf->addDisplayFilter(); + return 'pd' . $sub . '.' . $this->type; + } else { + $sub = $uf->addNameFilter($this->type, $this->variant); + if ($this->particle) { + return 'CONCAT(pn' . $sub . '.particle, \' \', pn' . $sub . '.name)'; + } else { + return 'pn' . $sub . '.name'; + } + } + } +} + +/*********************************** + ********************************* + USER FILTER CLASS + ********************************* + ***********************************/ + class UserFilter { + static private $joinMethods = array(); + private $root; private $sort = array(); private $query = null; private $orderby = null; + private $lastcount = 0; + public function __construct($cond = null, $sort = null) { + if (empty(self::$joinMethods)) { + $class = new ReflectionClass('UserFilter'); + foreach ($class->getMethods() as $method) { + $name = $method->getName(); + if (substr($name, -5) == 'Joins' && $name != 'buildJoins') { + self::$joinMethods[] = $name; + } + } + } if (!is_null($cond)) { if ($cond instanceof UserFilterCondition) { $this->setCondition($cond); @@ -330,12 +427,27 @@ class UserFilter if (!is_null($sort)) { if ($sort instanceof UserFilterOrder) { $this->addSort($sort); + } else if (is_array($sort)) { + foreach ($sort as $s) { + $this->addSort($s); + } } } } private function buildQuery() { + if (is_null($this->orderby)) { + $orders = array(); + foreach ($this->sort as $sort) { + $orders = array_merge($orders, $sort->buildSort($this)); + } + if (count($orders) == 0) { + $this->orderby = ''; + } else { + $this->orderby = 'ORDER BY ' . implode(', ', $orders); + } + } if (is_null($this->query)) { $where = $this->root->buildCondition($this); $joins = $this->buildJoins(); @@ -345,9 +457,6 @@ class UserFilter ' . $joins . ' WHERE (' . $where . ')'; } - if (is_null($this->sortby)) { - $this->sortby = ''; - } } private function formatJoin(array $joins) @@ -374,10 +483,37 @@ class UserFilter private function buildJoins() { - $joins = $this->educationJoins() + $this->nameJoins() + $this->groupJoins(); + $joins = array(); + foreach (self::$joinMethods as $method) { + $joins = array_merge($joins, $this->$method()); + } return $this->formatJoin($joins); } + private function getUIDList($uids = null, $count = null, $offset = null) + { + $this->buildQuery(); + $limit = ''; + if (!is_null($count)) { + if (!is_null($offset)) { + $limit = XDB::format('LIMIT {?}, {?}', $offset, $count); + } else { + $limit = XDB::format('LIMIT {?}', $count); + } + } + $cond = ''; + if (!is_null($uids)) { + $cond = ' AND a.uid IN (' . implode(', ', $uids) . ')'; + } + $fetched = XDB::fetchColumn('SELECT SQL_CALC_FOUND_ROWS a.uid + ' . $this->query . $cond . ' + GROUP BY a.uid + ' . $this->orderby . ' + ' . $limit); + $this->lastcount = (int)XDB::fetchOneCell('SELECT FOUND_ROWS()'); + return $fetched; + } + /** Check that the user match the given rule. */ public function checkUser(PlUser &$user) @@ -390,7 +526,7 @@ class UserFilter /** Filter a list of user to extract the users matching the rule. */ - public function filter(array $users) + public function filter(array $users, $count = null, $offset = null) { $this->buildQuery(); $table = array(); @@ -399,9 +535,7 @@ class UserFilter $uids[] = $user->id(); $table[$user->id()] = $user; } - $fetched = XDB::fetchColumn('SELECT a.uid - ' . $this->query . ' AND a.uid IN (' . implode(', ', $uids) . ') - GROUP BY a.uid'); + $fetched = $this->getUIDList($uids, $count, $offset); $output = array(); foreach ($fetched as $uid) { $output[] = $table[$uid]; @@ -409,17 +543,19 @@ class UserFilter return $output; } - public function getUIDs() + public function getUIDs($count = null, $offset = null) { - $this->buildQuery(); - return XDB::fetchColumn('SELECT a.uid - ' . $this->query . ' - GROUP BY a.uid'); + return $this->getUIDList(null, $count, $offset); + } + + public function getUsers($count = null, $offset = null) + { + return User::getBulkUsersWithUIDs($this->getUIDs($count, $offset)); } - public function getUsers() + public function getTotalCount() { - return User::getBuildUsersWithUIDs($this->getUIDs()); + return $this->lastcount; } public function setCondition(UserFilterCondition &$cond) @@ -430,8 +566,8 @@ class UserFilter public function addSort(UserFilterOrder &$sort) { - $this->sort[] =& $sort; - $this->sortby = null; + $this->sort[] = $sort; + $this->orderby = null; } static public function getLegacy($promo_min, $promo_max) @@ -450,23 +586,51 @@ class UserFilter } + /** DISPLAY + */ + private $pd = false; + public function addDisplayFilter() + { + $this->pd = true; + return ''; + } + + private function displayJoins() + { + if ($this->pd) { + return array('pd' => array('left', 'profile_display', '$ME.pid = $PID')); + } else { + return array(); + } + } + /** NAMES */ + /* name tokens */ const LASTNAME = 'lastname'; const FIRSTNAME = 'firstname'; const NICKNAME = 'nickname'; const PSEUDONYM = 'pseudonym'; const NAME = 'name'; + /* name variants */ const VN_MARITAL = 'marital'; const VN_ORDINARY = 'ordinary'; const VN_OTHER = 'other'; const VN_INI = 'ini'; + /* display names */ + const DN_FULL = 'directory_name'; + const DN_DISPLAY = 'yourself'; + const DN_YOURSELF = 'yourself'; + const DN_DIRECTORY = 'directory_name'; + const DN_PRIVATE = 'private_name'; + const DN_PUBLIC = 'public_name'; + const DN_SHORT = 'short_name'; + const DN_SORT = 'sort_name'; 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()); + self::FIRSTNAME => array(self::VN_ORDINARY, self::VN_INI, self::VN_OTHER) + ); static public function assertName($name) { @@ -475,6 +639,14 @@ class UserFilter } } + static public function isDisplayName($name) + { + return $name == self::DN_FULL || $name == self::DN_DISPLAY + || $name == self::DN_YOURSELF || $name == self::DN_DIRECTORY + || $name == self::DN_PRIVATE || $name == self::DN_PUBLIC + || $name == self::DN_SHORT || $name == self::DN_SORT; + } + private $pn = array(); private $pno = 0; public function addNameFilter($type, $variant = null) @@ -521,6 +693,12 @@ class UserFilter } } + static public function promoYear($grade) + { + // XXX: Definition of promotion for phds and masters might change in near future. + return ($grade == UserFilter::GRADE_ING) ? 'entry_year' : 'grad_year'; + } + private $pepe = array(); private $with_pee = false; private $pe_g = 0; diff --git a/include/common.inc.php b/include/common.inc.php index f524ec8..54802e7 100644 --- a/include/common.inc.php +++ b/include/common.inc.php @@ -23,7 +23,10 @@ function __autoload($cls) { if (!pl_autoload($cls)) { $cls = strtolower($cls); - if (substr($cls, -3, 3) == 'req') { + if (substr($cls, 0, 4) == 'ufc_' || substr($cls, 0, 4) == 'ufo_') { + __autoload('userfilter'); + return; + } else if (substr($cls, -3, 3) == 'req') { @include 'validations.inc.php'; return; } else if (substr($cls, 0, 6) == 'banana') { diff --git a/modules/events.php b/modules/events.php index 967dedf..635d42e 100644 --- a/modules/events.php +++ b/modules/events.php @@ -160,11 +160,14 @@ class EventsModule extends PLModule $uf = new UserFilter(new UFC_And(new UFC_Group('Polytechnique.org'), new UFC_Promo('=', UserFilter::GRADE_ING, 2000), - new UFC_Not(new UFC_Sex(User::GENDER_FEMALE)))); - $users = $uf->getUsers(); + new UFC_Not(new UFC_Sex(User::GENDER_FEMALE))), + array(new UFO_Name(UserFilter::LASTNAME), + new UFO_Name(UserFilter::FIRSTNAME))); + $users = $uf->getUsers(3); foreach ($users as $user) { echo $user->fullName() . '
'; } + echo '... ' . ($uf->getTotalCount() - 3) . ' others
'; function next_event(PlIterator &$it) { diff --git a/modules/xnetgrp.php b/modules/xnetgrp.php index 74c79b8..314a641 100644 --- a/modules/xnetgrp.php +++ b/modules/xnetgrp.php @@ -328,13 +328,22 @@ class XnetGrpModule extends PLModule $ofs = 0; } + $sdesc = $sort{0} == '-'; + $sf = $sdesc ? substr($sort, 1) : $sort; + if ($sf == 'promo') { + $se = new UFO_Promo(null, $sdesc); + } else { + $se = new UFO_Name($sf, null, null, $sdesc); + } + if (Env::b('admin')) { - $users = $globals->asso()->getAdmins($sort, NB_PER_PAGE, $ofs * NB_PER_PAGE); - $count = $globals->asso()->getAdminCount(); + $uf = $globals->asso()->getAdmins(null, $se); } else { - $users = $globals->asso()->getMembers($sort, NB_PER_PAGE, $ofs * NB_PER_PAGE); - $count = $globals->asso()->getMemberCount(); + $uf = $globals->asso()->getMembers(null, $se); } + $users = $uf->getUsers(NB_PER_PAGE, $ofs * NB_PER_PAGE); + $count = $uf->getTotalCount(); + $page->assign('pages', floor(($count + NB_PER_PAGE - 1) / NB_PER_PAGE)); $page->assign('current', $ofs); $page->assign('order', $sort); @@ -356,7 +365,7 @@ class XnetGrpModule extends PLModule { global $globals; $vcard = new VCard($photos == 'photos', 'Membre du groupe ' . $globals->asso('nom')); - $vcard->addUsers($globals->asso()->getMemberUIDs()); + $vcard->addUsers($globals->asso()->getMembers()->getUIDs()); $vcard->show(); } @@ -366,7 +375,7 @@ class XnetGrpModule extends PLModule if (is_null($filename)) { $filename = $globals->asso('diminutif') . '.csv'; } - $users = $globals->asso()->getMembers('directory_name'); + $users = $globals->asso()->getMembers(null, new UFO_Name('directory_name'))->getUsers(); header('Content-Type: text/x-csv; charset=utf-8;'); header('Pragma: '); header('Cache-Control: '); @@ -730,12 +739,7 @@ class XnetGrpModule extends PLModule if ($globals->asso('notif_unsub')) { $mailer = new PlMailer('xnetgrp/unsubscription-notif.mail.tpl'); - $uids = XDB::fetchColumn('SELECT uid - FROM groupex.membres - WHERE perms = \'admin\' AND asso_id = {?}', - $globals->asso('id')); - $users = User::getBuildUsersWithUIDs($uids); - foreach ($users as $user) { + foreach ($globals->asso()->getMembers()->getUsers() as $user) { $mailer->addTo($user); } $mailer->assign('group', $globals->asso('nom')); -- 2.1.4