Implements sorting and limits on UserFilter. Start porting xnetgrp to UserFilter.
authorFlorent Bruneau <florent.bruneau@polytechnique.org>
Wed, 4 Feb 2009 23:01:06 +0000 (00:01 +0100)
committerFlorent Bruneau <florent.bruneau@polytechnique.org>
Wed, 4 Feb 2009 23:01:06 +0000 (00:01 +0100)
Signed-off-by: Florent Bruneau <florent.bruneau@polytechnique.org>
classes/group.php
classes/user.php
classes/userfilter.php
include/common.inc.php
modules/events.php
modules/xnetgrp.php

index 62236db..c75191b 100644 (file)
@@ -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)
index cea85c6..17cbd72 100644 (file)
@@ -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;
     }
index 7a4dd3b..ad9672e 100644 (file)
  *  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;
index f524ec8..54802e7 100644 (file)
@@ -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') {
index 967dedf..635d42e 100644 (file)
@@ -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() . '<br />';
         }
+        echo '... ' . ($uf->getTotalCount() - 3) . ' others<br />';
 
         function next_event(PlIterator &$it)
         {
index 74c79b8..314a641 100644 (file)
@@ -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'));