Kill calls to auth_user... in module profile.
[platal.git] / classes / userfilter.php
index 7a4dd3b..30a7029 100644 (file)
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
  ***************************************************************************/
 
+
+/******************
+ * CONDITIONS
+ ******************/
+
 interface UserFilterCondition
 {
     const COND_TRUE  = 'TRUE';
@@ -168,20 +173,21 @@ class UFC_Promo implements UserFilterCondition
         $this->grade = $grade;
         $this->comparison = $comparison;
         $this->promo = $promo;
-        UserFilter::assertGrade($this->grade);
+        if ($this->grade != UserFilter::DISPLAY) {
+            UserFilter::assertGrade($this->grade);
+        }
     }
 
     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';
+        if ($this->grade == UserFilter::DISPLAY) {
+            $sub = $uf->addDisplayFilter();
+            return XDB::format('pd' . $sub . '.promo = {?}', $this->promo);
         } else {
-            $promo_year = 'grad_year';
+            $sub = $uf->addEducationFilter(true, $this->grade);
+            $field = 'pe' . $sub . '.' . UserFilter::promoYear($this->grade);
+            return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo);
         }
-        $sub = $uf->addEducationFilter(true, $this->grade);
-        $field = 'pe' . $sub . '.' . $promo_year;
-        return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo);
     }
 }
 
@@ -229,7 +235,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);
             }
@@ -240,37 +246,83 @@ class UFC_Name implements UserFilterCondition
 
 class UFC_Dead implements UserFilterCondition
 {
-    private $dead;
-    public function __construct($dead)
+    private $comparison;
+    private $date;
+
+    public function __construct($comparison = null, $date = null)
     {
-        $this->dead = $dead;
+        $this->comparison = $comparison;
+        $this->date = $date;
     }
 
     public function buildCondition(UserFilter &$uf)
     {
-        if ($this->dead) {
-            return 'p.deathdate IS NOT NULL';
-        } else {
-            return 'p.deathdate IS NULL';
+        $str = 'p.deathdate IS NOT NULL';
+        if (!is_null($this->comparison)) {
+            $str .= ' AND p.deathdate ' . $this->comparison . ' ' . XDB::format('{?}', date('Y-m-d', $this->date));
         }
+        return $str;
     }
 }
 
 class UFC_Registered implements UserFilterCondition
 {
     private $active;
-    public function __construct($active = false)
+    private $comparison;
+    private $date;
+
+    public function __construct($active = false, $comparison = null, $date = null)
     {
         $this->only_active = $active;
+        $this->comparison = $comparison;
+        $this->date = $date;
     }
 
     public function buildCondition(UserFilter &$uf)
     {
         if ($this->active) {
-            return 'a.uid IS NOT NULL AND a.state = \'active\'';
+            $date = 'a.uid IS NOT NULL AND a.state = \'active\'';
         } else {
-            return 'a.uid IS NOT NULL AND a.state != \'pending\'';
+            $date = 'a.uid IS NOT NULL AND a.state != \'pending\'';
+        }
+        if (!is_null($this->comparison)) {
+            $date .= ' AND a.registration_date ' . $this->comparison . ' ' . XDB::format('{?}', date('Y-m-d', $this->date));
         }
+        return $date;
+    }
+}
+
+class UFC_ProfileUpdated implements UserFilterCondition
+{
+    private $comparison;
+    private $date;
+
+    public function __construct($comparison = null, $date = null)
+    {
+        $this->comparison = $comparison;
+        $this->date = $date;
+    }
+
+    public function buildCondition(UserFilter &$uf)
+    {
+        return 'p.last_change ' . $this->comparison . XDB::format(' {?}', date('Y-m-d H:i:s', $this->date));
+    }
+}
+
+class UFC_Birthday implements UserFilterCondition
+{
+    private $comparison;
+    private $date;
+
+    public function __construct($comparison = null, $date = null)
+    {
+        $this->comparison = $comparison;
+        $this->date = $date;
+    }
+
+    public function buildCondition(UserFilter &$uf)
+    {
+        return 'p.next_birthday ' . $this->comparison . XDB::format(' {?}', date('Y-m-d', $this->date));
     }
 }
 
@@ -313,15 +365,282 @@ class UFC_Group implements UserFilterCondition
     }
 }
 
+class UFC_Email implements UserFilterCondition
+{
+    private $email;
+    public function __construct($email)
+    {
+        $this->email = $email;
+    }
+
+    public function buildCondition(UserFilter &$uf)
+    {
+        if (User::isForeignEmailAddress($this->email)) {
+            $sub = $uf->addEmailRedirectFilter($this->email);
+            return XDB::format('e' . $sub . '.email IS NOT NULL OR a.email = {?}', $this->email);
+        } else if (User::isVirtualEmailAddress($this->email)) {
+            $sub = $uf->addVirtualEmailFilter($this->email);
+            return 'vr' . $sub . '.redirect IS NOT NULL';
+        } else {
+            @list($user, $domain) = explode('@', $this->email);
+            $sub = $uf->addAliasFilter($user);
+            return 'al' . $sub . '.alias IS NOT NULL';
+        }
+    }
+}
+
+class UFC_EmailList implements UserFilterCondition
+{
+    private $emails;
+    public function __construct($emails)
+    {
+        $this->emails = $emails;
+    }
+
+    public function buildCondition(UserFilter &$uf)
+    {
+        $email   = null;
+        $virtual = null;
+        $alias   = null;
+        $cond = array();
+
+        if (count($this->emails) == 0) {
+            return UserFilterCondition::COND_TRUE;
+        }
+
+        foreach ($this->emails as $entry) {
+            if (User::isForeignEmailAddress($entry)) {
+                if (is_null($email)) {
+                    $email = $uf->addEmailRedirectFilter();
+                }
+                $cond[] = XDB::format('e' . $email . '.email = {?} OR a.email = {?}', $entry, $entry);
+            } else if (User::isVirtualEmailAddress($entry)) {
+                if (is_null($virtual)) {
+                    $virtual = $uf->addVirtualEmailFilter();
+                }
+                $cond[] = XDB::format('vr' . $virtual . '.redirect IS NOT NULL AND v' . $virtual . '.alias = {?}', $entry);
+            } else {
+                if (is_null($alias)) {
+                    $alias = $uf->addAliasFilter();
+                }
+                @list($user, $domain) = explode('@', $entry);
+                $cond[] = XDB::format('al' . $alias . '.alias = {?}', $user);
+            }
+        }
+        return '(' . implode(') OR (', $cond) . ')';
+    }
+}
+
+abstract class UFC_UserRelated implements UserFilterCondition
+{
+    protected $user;
+    public function __construct(PlUser &$user)
+    {
+        $this->user =& $user;
+    }
+}
+
+class UFC_Contact extends UFC_UserRelated
+{
+    public function buildCondition(UserFilter &$uf)
+    {
+        $sub = $uf->addContactFilter($this->user->id());
+        return 'c' . $sub . '.contact IS NOT NULL';
+    }
+}
+
+class UFC_WatchRegistration extends UFC_UserRelated
+{
+    public function buildCondition(UserFilter &$uf)
+    {
+        if (!$this->user->watch('registration')) {
+            return UserFilterCondition::COND_FALSE;
+        }
+        $uids = $this->user->watchUsers();
+        if (count($uids) == 0) {
+            return UserFilterCondition::COND_FALSE;
+        } else {
+            return '$UID IN ' . XDB::formatArray($uids);
+        }
+    }
+}
+
+class UFC_WatchPromo extends UFC_UserRelated
+{
+    private $grade;
+    public function __construct(PlUser &$user, $grade = UserFilter::GRADE_ING)
+    {
+        parent::__construct($user);
+        $this->grade = $grade;
+    }
+
+    public function buildCondition(UserFilter &$uf)
+    {
+        $promos = $this->user->watchPromos();
+        if (count($promos) == 0) {
+            return UserFilterCondition::COND_FALSE;
+        } else {
+            $sube = $uf->addEducationFilter(true, $this->grade);
+            $field = 'pe' . $sube . '.' . UserFilter::promoYear($this->grade);
+            return $field . ' IN ' . XDB::formatArray($promos);
+        }
+    }
+}
+
+class UFC_WatchContact extends UFC_Contact
+{
+    public function buildCondition(UserFilter &$uf)
+    {
+        if (!$this->user->watchContacts()) {
+            return UserFilterCondition::COND_FALSE;
+        }
+        return parent::buildCondition($uf);
+    }
+}
+
+
+/******************
+ * ORDERS
+ ******************/
+
+abstract class UserFilterOrder
+{
+    protected $desc = false;
+    public function __construct($desc = false)
+    {
+        $this->desc = $desc;
+    }
+
+    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)
+    {
+        parent::__construct($desc);
+        $this->grade = $grade;
+    }
+
+    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)
+    {
+        parent::__construct($desc);
+        $this->type = $type;
+        $this->variant = $variant;
+        $this->particle = $particle;
+    }
+
+    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';
+            }
+        }
+    }
+}
+
+class UFO_Registration extends UserFilterOrder
+{
+    protected function getSortTokens(UserFilter &$uf)
+    {
+        return 'a.registration_date';
+    }
+}
+
+class UFO_Birthday extends UserFilterOrder
+{
+    protected function getSortTokens(UserFilter &$uf)
+    {
+        return 'p.next_birthday';
+    }
+}
+
+class UFO_ProfileUpdate extends UserFilterOrder
+{
+    protected function getSortTokens(UserFilter &$uf)
+    {
+        return 'p.last_change';
+    }
+}
+
+class UFO_Death extends UserFilterOrder
+{
+    protected function getSortTokens(UserFilter &$uf)
+    {
+        return 'p.deathdate';
+    }
+}
+
+
+/***********************************
+  *********************************
+          USER FILTER CLASS
+  *********************************
+ ***********************************/
+
 class UserFilter
 {
+    static private $joinMethods = array();
+
     private $root;
     private $sort = array();
     private $query = null;
     private $orderby = null;
 
+    private $lastcount = null;
+
     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 +649,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 +679,6 @@ class UserFilter
                                ' . $joins . '
                            WHERE  (' . $where . ')';
         }
-        if (is_null($this->sortby)) {
-            $this->sortby = '';
-        }
     }
 
     private function formatJoin(array $joins)
@@ -374,10 +705,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 ' . XDB::formatArray($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,18 +748,21 @@ 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();
         $uids  = array();
         foreach ($users as $user) {
-            $uids[] = $user->id();
-            $table[$user->id()] = $user;
+            if ($user instanceof PlUser) {
+                $uid = $user->id();
+            } else {
+                $uid = $user;
+            }
+            $uids[] = $uid;
+            $table[$uid] = $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 +770,25 @@ 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());
+        if (is_null($this->lastcount)) {
+            $this->buildQuery();
+            return (int)XDB::fetchOneCell('SELECT  COUNT(DISTINCT a.uid)
+                                          ' . $this->query);
+        } else {
+            return $this->lastcount;
+        }
     }
 
     public function setCondition(UserFilterCondition &$cond)
@@ -430,8 +799,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)
@@ -449,24 +818,83 @@ class UserFilter
         return new UserFilter(new UFC_And($min, $max));
     }
 
+    static public function sortByName()
+    {
+        return array(new UFO_Name(self::LASTNAME), new UFO_Name(self::FIRSTNAME));
+    }
+
+    static public function sortByPromo()
+    {
+        return array(new UFO_Promo(), new UFO_Name(self::LASTNAME), new UFO_Name(self::FIRSTNAME));
+    }
+
+    static private function getDBSuffix($string)
+    {
+        return preg_replace('/[^a-z0-9]/i', '', $string);
+    }
+
+
+    private $option = 0;
+    private function register_optional(array &$table, $val)
+    {
+        if (is_null($val)) {
+            $sub   = $this->option++;
+            $index = null;
+        } else {
+            $sub   = self::getDBSuffix($val);
+            $index = $val;
+        }
+        $sub = '_' . $sub;
+        $table[$sub] = $index;
+        return $sub;
+    }
+
+    /** DISPLAY
+     */
+    const DISPLAY = '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,8 +903,15 @@ 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)
     {
         if (!is_null($variant)) {
@@ -488,7 +923,7 @@ class UserFilter
         self::assertName($ft);
 
         if (!is_null($variant) && $variant == 'other') {
-            $sub .= $this->pno++;
+            $sub .= $this->option++;
         }
         $this->pn[$sub] = Profile::getNameTypeId($ft);
         return $sub;
@@ -511,7 +946,7 @@ class UserFilter
     const GRADE_MST = 'M%';
     static public function isGrade($grade)
     {
-        return $grade == self::GRADE_ING || self::$grade == GRADE_PHD || self::$grade == GRADE_MST;
+        return $grade == self::GRADE_ING || $grade == self::GRADE_PHD || $grade == self::GRADE_MST;
     }
 
     static public function assertGrade($grade)
@@ -521,14 +956,19 @@ 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;
     public function addEducationFilter($x = false, $grade = null)
     {
         if (!$x) {
-            $index = $this->pe_g;
-            $sub   = $this->pe_g++;
+            $index = $this->option;
+            $sub   = $this->option++;
         } else {
             self::assertGrade($grade);
             $index = $grade;
@@ -564,7 +1004,6 @@ class UserFilter
     /** GROUPS
      */
     private $gpm = array();
-    private $gpm_o = 0;
     public function addGroupFilter($group = null)
     {
         if (!is_null($group)) {
@@ -572,10 +1011,10 @@ class UserFilter
                 $index = $sub = $group;
             } else {
                 $index = $group;
-                $sub   = preg_replace('/[^a-z0-9]/i', '', $group);
+                $sub   = self::getDBSuffix($group);
             }
         } else {
-            $sub = 'group_' . $this->gpm_o++;
+            $sub = 'group_' . $this->option++;
             $index = null;
         }
         $sub = '_' . $sub;
@@ -599,7 +1038,145 @@ class UserFilter
         }
         return $joins;
     }
+
+    /** EMAILS
+     */
+    private $e = array();
+    public function addEmailRedirectFilter($email = null)
+    {
+        return $this->register_optional($this->e, $email);
+    }
+
+    private $ve = array();
+    public function addVirtualEmailFilter($email = null)
+    {
+        $this->addAliasFilter(self::ALIAS_FORLIFE);
+        return $this->register_optional($this->ve, $email);
+    }
+
+    const ALIAS_BEST    = 'bestalias';
+    const ALIAS_FORLIFE = 'forlife';
+    private $al = array();
+    public function addAliasFilter($alias = null)
+    {
+        return $this->register_optional($this->al, $alias);
+    }
+
+    private function emailJoins()
+    {
+        global $globals;
+        $joins = array();
+        foreach ($this->e as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['e' . $sub] = array('left', 'emails', '$ME.uid = $UID AND $ME.flags != \'filter\'');
+            } else {
+                $joins['e' . $sub] = array('left', 'emails', XDB::format('$ME.uid = $UID AND $ME.flags != \'filter\' AND $ME.email = {?}', $key));
+            }
+        }
+        foreach ($this->al as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['al' . $sub] = array('left', 'aliases', '$ME.id = $UID AND $ME.type IN (\'alias\', \'a_vie\')');
+            } else if ($key == self::ALIAS_BEST) {
+                $joins['al' . $sub] = array('left', 'aliases', '$ME.id = $UID AND $ME.type IN (\'alias\', \'a_vie\') AND  FIND_IN_SET(\'bestalias\', $ME.flags)');
+            } else if ($key == self::ALIAS_FORLIFE) {
+                $joins['al' . $sub] = array('left', 'aliases', '$ME.id = $UID AND $ME.type = \'a_vie\'');
+            } else {
+                $joins['al' . $sub] = array('left', 'aliases', XDB::format('$ME.id = $UID AND $ME.type IN (\'alias\', \'a_vie\') AND $ME.alias = {?}', $key));
+            }
+        }
+        foreach ($this->ve as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['v' . $sub] = array('left', 'virtual', '$ME.type = \'user\'');
+            } else {
+                $joins['v' . $sub] = array('left', 'virtual', XDB::format('$ME.type = \'user\' AND $ME.alias = {?}', $key));
+            }
+            $joins['vr' . $sub] = array('left', 'virtual_redirect', XDB::format('$ME.vid = v' . $sub . '.vid
+                                                                                 AND ($ME.redirect IN (CONCAT(al_forlife.alias, \'@\', {?}),
+                                                                                                       CONCAT(al_forlife.alias, \'@\', {?}),
+                                                                                                       a.email))',
+                                                                                $globals->mail->domain, $globals->mail->domain2));
+        }
+        return $joins;
+    }
+
+
+    /** CONTACTS
+     */
+    private $cts = array();
+    public function addContactFilter($uid = null)
+    {
+        return $this->register_optional($this->cts, is_null($uid) ? null : 'user_' . $uid);
+    }
+
+    private function contactJoins()
+    {
+        $joins = array();
+        foreach ($this->cts as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['c' . $sub] = array('left', 'contacts', '$ME.contact = $UID');
+            } else {
+                $joins['c' . $sub] = array('left', 'contacts', XDB::format('$ME.uid = {?} AND $ME.contact = $UID', substr($key, 5)));
+            }
+        }
+        return $joins;
+    }
+
+
+    /** CARNET
+     */
+    private $wn = array();
+    public function addWatchRegistrationFilter($uid = null)
+    {
+        return $this->register_optional($this->wn, is_null($uid) ? null : 'user_' . $uid);
+    }
+
+    private $wp = array();
+    public function addWatchPromoFilter($uid = null)
+    {
+        return $this->register_optional($this->wp, is_null($uid) ? null : 'user_' . $uid);
+    }
+
+    private $w = array();
+    public function addWatchFilter($uid = null)
+    {
+        return $this->register_optional($this->w, is_null($uid) ? null : 'user_' . $uid);
+    }
+
+    private function watchJoins()
+    {
+        $joins = array();
+        foreach ($this->w as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['w' . $sub] = array('left', 'watch');
+            } else {
+                $joins['w' . $sub] = array('left', 'watch', XDB::format('$ME.uid = {?}', substr($key, 5)));
+            }
+        }
+        foreach ($this->wn as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['wn' . $sub] = array('left', 'watch_nonins', '$ME.ni_id = $UID');
+            } else {
+                $joins['wn' . $sub] = array('left', 'watch_nonins', XDB::format('$ME.uid = {?} AND $ME.ni_id = $UID', substr($key, 5)));
+            }
+        }
+        foreach ($this->wn as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['wn' . $sub] = array('left', 'watch_nonins', '$ME.ni_id = $UID');
+            } else {
+                $joins['wn' . $sub] = array('left', 'watch_nonins', XDB::format('$ME.uid = {?} AND $ME.ni_id = $UID', substr($key, 5)));
+            }
+        }
+        foreach ($this->wp as $sub=>$key) {
+            if (is_null($key)) {
+                $joins['wp' . $sub] = array('left', 'watch_promo');
+            } else {
+                $joins['wp' . $sub] = array('left', 'watch_promo', XDB::format('$ME.uid = {?}', substr($key, 5)));
+            }
+        }
+        return $joins;
+    }
 }
 
+
 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
 ?>