X-Git-Url: http://git.polytechnique.org/?a=blobdiff_plain;f=classes%2Fuserfilter.php;h=23149158fd5a0e3603b2d7e82dfce0247922e1f8;hb=22771578c2d44fe4488546116a84e1561f083ed7;hp=98ef33c1fa85aa4430fca1a832f37c6b4c88bedc;hpb=a9ef52c96594b408aae8b084f13d872a211ea086;p=platal.git diff --git a/classes/userfilter.php b/classes/userfilter.php index 98ef33c..2314915 100644 --- a/classes/userfilter.php +++ b/classes/userfilter.php @@ -1,6 +1,6 @@ addWheteverFilter so that the UserFilter makes - * adequate joins. It must return the 'WHERE' condition to use - * with the filter. - */ -interface UserFilterCondition extends PlFilterCondition -{ -} -// }}} - -// {{{ class UFC_HasProfile -/** Filters users who have a profile - */ -class UFC_HasProfile implements UserFilterCondition -{ - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - return '$PID IS NOT NULL'; - } -} -// }}} - -// {{{ class UFC_Hruid -/** Filters users based on their hruid - * @param $val Either an hruid, or a list of those - */ -class UFC_Hruid implements UserFilterCondition -{ - private $hruids; - - public function __construct() - { - $this->hruids = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireAccounts(); - return XDB::format('a.hruid IN {?}', $this->hruids); - } -} -// }}} - -// {{{ class UFC_Hrpid -/** Filters users based on the hrpid of their profiles - * @param $val Either an hrpid, or a list of those - */ -class UFC_Hrpid implements UserFilterCondition -{ - private $hrpids; - - public function __construct() - { - $this->hrpids = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - return XDB::format('p.hrpid IN {?}', $this->hrpids); - } -} -// }}} - -// {{{ class UFC_Ip -/** Filters users based on one of their last IPs - * @param $ip IP from which connection are checked - */ -class UFC_Ip implements UserFilterCondition -{ - private $ip; - - public function __construct($ip) - { - $this->ip = $ip; - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addLoggerFilter(); - $ip = ip_to_uint($this->ip); - return XDB::format($sub . '.ip = {?} OR ' . $sub . '.forward_ip = {?}', $ip, $ip); - } -} -// }}} - -// {{{ class UFC_Comment -class UFC_Comment implements UserFilterCondition -{ - private $text; - - public function __construct($text) - { - $this->text = $text; - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.freetext ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->text); - } -} -// }}} - -// {{{ class UFC_Promo -/** Filters users based on promotion - * @param $comparison Comparison operator (>, =, ...) - * @param $grade Formation on which to restrict, UserFilter::DISPLAY for "any formation" - * @param $promo Promotion on which the filter is based - */ -class UFC_Promo implements UserFilterCondition -{ - - private $grade; - private $promo; - private $comparison; - - public function __construct($comparison, $grade, $promo) - { - $this->grade = $grade; - $this->comparison = $comparison; - $this->promo = $promo; - if ($this->grade != UserFilter::DISPLAY) { - UserFilter::assertGrade($this->grade); - } - if ($this->grade == UserFilter::DISPLAY && $this->comparison != '=') { - // XXX: we might try to guess the grade from the first char of the promo and forbid only '<= 2004', but allow '<= X2004' - Platal::page()->killError("Il n'est pas possible d'appliquer la comparaison '" . $this->comparison . "' aux promotions sans spécifier de formation (X/M/D)"); - } - } - - public function buildCondition(PlFilter &$uf) - { - if ($this->grade == UserFilter::DISPLAY) { - $sub = $uf->addDisplayFilter(); - return XDB::format('pd' . $sub . '.promo ' . $this->comparison . ' {?}', $this->promo); - } else { - $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); - } - } -} -// }}} - -// {{{ class UFC_SchoolId -/** Filters users based on their shoold identifier - * @param type Parameter type (Xorg, AX, School) - * @param value School id value - */ -class UFC_SchoolId implements UserFilterCondition -{ - const AX = 'ax'; - const Xorg = 'xorg'; - const School = 'school'; - - private $type; - private $id; - - static public function assertType($type) - { - if ($type != self::AX && $type != self::Xorg && $type != self::School) { - Platal::page()->killError("Type de matricule invalide: $type"); - } - } - - public function __construct($type, $id) - { - $this->type = $type; - $this->id = $id; - self::assertType($type); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - $id = $this->id; - $type = $this->type; - if ($type == self::School) { - $type = self::Xorg; - $id = Profile::getXorgId($id); - } - return XDB::format('p.' . $type . '_id = {?}', $id); - } -} -// }}} - -// {{{ class UFC_EducationSchool -/** Filters users by formation - * @param $val The formation to search (either ID or array of IDs) - */ -class UFC_EducationSchool implements UserFilterCondition -{ - private $val; - - public function __construct() - { - $this->val = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addEducationFilter(); - return XDB::format('pe' . $sub . '.eduid IN {?}', $this->val); - } -} -// }}} - -// {{{ class UFC_EducationDegree -class UFC_EducationDegree implements UserFilterCondition -{ - private $diploma; - - public function __construct() - { - $this->diploma = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addEducationFilter(); - return XDB::format('pe' . $sub . '.degreeid IN {?}', $this->diploma); - } -} -// }}} - -// {{{ class UFC_EducationField -class UFC_EducationField implements UserFilterCondition -{ - private $val; - - public function __construct() - { - $this->val = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addEducationFilter(); - return XDB::format('pe' . $sub . '.fieldid IN {?}', $this->val); - } -} -// }}} - -// {{{ class UFC_Name -/** Filters users based on name - * @param $type Type of name field on which filtering is done (firstname, lastname...) - * @param $text Text on which to filter - * @param $mode Flag indicating search type (prefix, suffix, with particule...) - */ -class UFC_Name implements UserFilterCondition -{ - const EXACT = XDB::WILDCARD_EXACT; // 0x000 - const PREFIX = XDB::WILDCARD_PREFIX; // 0x001 - const SUFFIX = XDB::WILDCARD_SUFFIX; // 0x002 - const CONTAINS = XDB::WILDCARD_CONTAINS; // 0x003 - const PARTICLE = 0x004; - const VARIANTS = 0x008; - - 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(PlFilter &$uf) - { - $left = '$ME.name'; - if (($this->mode & self::PARTICLE) == self::PARTICLE) { - $left = 'CONCAT($ME.particle, \' \', $ME.name)'; - } - $right = XDB::formatWildcards($this->mode & self::CONTAINS, $this->text); - - $cond = $left . $right; - $conds = array($this->buildNameQuery($this->type, null, $cond, $uf)); - if (($this->mode & self::VARIANTS) != 0 && isset(Profile::$name_variants[$this->type])) { - foreach (Profile::$name_variants[$this->type] as $var) { - $conds[] = $this->buildNameQuery($this->type, $var, $cond, $uf); - } - } - return implode(' OR ', $conds); - } -} -// }}} - -// {{{ class UFC_NameTokens -/** Selects users based on tokens in their name (for quicksearch) - * @param $tokens An array of tokens to search - * @param $flags Flags the tokens must have (e.g 'public' for public search) - * @param $soundex (bool) Whether those tokens are fulltext or soundex - */ -class UFC_NameTokens implements UserFilterCondition -{ - /* Flags */ - const FLAG_PUBLIC = 'public'; - - private $tokens; - private $flags; - private $soundex; - private $exact; - - public function __construct($tokens, $flags = array(), $soundex = false, $exact = false) - { - if (is_array($tokens)) { - $this->tokens = $tokens; - } else { - $this->tokens = array($tokens); - } - if (is_array($flags)) { - $this->flags = $flags; - } else { - $this->flags = array($flags); - } - $this->soundex = $soundex; - $this->exact = $exact; - } - - public function buildCondition(PlFilter &$uf) - { - $conds = array(); - foreach ($this->tokens as $i => $token) { - $sub = $uf->addNameTokensFilter($token); - if ($this->soundex) { - $c = XDB::format($sub . '.soundex = {?}', $token); - } else if ($this->exact) { - $c = XDB::format($sub . '.token = {?}', $token); - } else { - $c = $sub . '.token ' . XDB::formatWildcards(XDB::WILDCARD_PREFIX, $token); - } - if ($this->flags != null) { - $c .= XDB::format(' AND ' . $sub . '.flags IN {?}', $this->flags); - } - $conds[] = $c; - } - - return implode(' AND ', $conds); - } -} -// }}} - -// {{{ class UFC_Nationality -class UFC_Nationality implements UserFilterCondition -{ - private $val; - - public function __construct() - { - $this->val = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - $nat = XDB::formatArray($this->val); - $conds = array( - 'p.nationality1 IN ' . $nat, - 'p.nationality2 IN ' . $nat, - 'p.nationality3 IN ' . $nat, - ); - return implode(' OR ', $conds); - } -} -// }}} - -// {{{ class UFC_Dead -/** Filters users based on death date - * @param $comparison Comparison operator - * @param $date Date to which death date should be compared (DateTime object, string or timestamp) - */ -class UFC_Dead implements UserFilterCondition -{ - private $comparison; - private $date; - - public function __construct($comparison = null, $date = null) - { - $this->comparison = $comparison; - $this->date = make_datetime($date); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - $str = 'p.deathdate IS NOT NULL'; - if (!is_null($this->comparison)) { - $str .= ' AND p.deathdate ' . $this->comparison . ' ' . XDB::format('{?}', $this->date->format('Y-m-d')); - } - return $str; - } -} -// }}} - -// {{{ class UFC_Registered -/** Filters users based on registration state - * @param $active Whether we want to use only "active" users (i.e with a valid redirection) - * @param $comparison Comparison operator - * @param $date Date to which users registration date should be compared - */ -class UFC_Registered implements UserFilterCondition -{ - private $active; - private $comparison; - private $date; - - public function __construct($active = false, $comparison = null, $date = null) - { - $this->active = $active; - $this->comparison = $comparison; - $this->date = make_datetime($date); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireAccounts(); - if ($this->active) { - $date = '$UID IS NOT NULL AND a.state = \'active\''; - } else { - $date = '$UID IS NOT NULL AND a.state != \'pending\''; - } - if (!is_null($this->comparison)) { - $date .= ' AND a.registration_date != \'0000-00-00 00:00:00\' AND a.registration_date ' . $this->comparison . ' ' . XDB::format('{?}', $this->date->format('Y-m-d')); - } - return $date; - } -} -// }}} - -// {{{ class UFC_ProfileUpdated -/** Filters users based on profile update date - * @param $comparison Comparison operator - * @param $date Date to which profile update date must be compared - */ -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(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.last_change ' . $this->comparison . XDB::format(' {?}', date('Y-m-d H:i:s', $this->date)); - } -} -// }}} - -// {{{ class UFC_Birthday -/** Filters users based on next birthday date - * @param $comparison Comparison operator - * @param $date Date to which users next birthday date should be compared - */ -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(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.next_birthday ' . $this->comparison . XDB::format(' {?}', date('Y-m-d', $this->date)); - } -} -// }}} - -// {{{ class UFC_Sex -/** Filters users based on sex - * @parm $sex One of User::GENDER_MALE or User::GENDER_FEMALE, for selecting users - */ -class UFC_Sex implements UserFilterCondition -{ - private $sex; - public function __construct($sex) - { - $this->sex = $sex; - } - - public function buildCondition(PlFilter &$uf) - { - if ($this->sex != User::GENDER_MALE && $this->sex != User::GENDER_FEMALE) { - return self::COND_FALSE; - } else { - $uf->requireProfiles(); - return XDB::format('p.sex = {?}', $this->sex == User::GENDER_FEMALE ? 'female' : 'male'); - } - } -} -// }}} - -// {{{ class UFC_Group -/** Filters users based on group membership - * @param $group Group whose members we are selecting - * @param $anim Whether to restrict selection to animators of that group - */ -class UFC_Group implements UserFilterCondition -{ - private $group; - private $anim; - public function __construct($group, $anim = false) - { - $this->group = $group; - $this->anim = $anim; - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addGroupFilter($this->group); - $where = 'gpm' . $sub . '.perms IS NOT NULL'; - if ($this->anim) { - $where .= ' AND gpm' . $sub . '.perms = \'admin\''; - } - return $where; - } -} -// }}} - -// {{{ class UFC_Binet -/** Selects users based on their belonging to a given (list of) binet - * @param $binet either a binet_id or an array of binet_ids - */ -class UFC_Binet implements UserFilterCondition -{ - private $val; - - public function __construct() - { - $this->val = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addBinetsFilter(); - return XDB::format($sub . '.binet_id IN {?}', $this->val); - } -} -// }}} - -// {{{ class UFC_Section -/** Selects users based on section - * @param $section ID of the section - */ -class UFC_Section implements UserFilterCondition -{ - private $section; - - public function __construct() - { - $this->section = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $uf->requireProfiles(); - return XDB::format('p.section IN {?}', $this->section); - } -} -// }}} - -// {{{ class UFC_Email -/** Filters users based on an email or a list of emails - * @param $emails List of emails whose owner must be selected - */ -class UFC_Email implements UserFilterCondition -{ - private $emails; - public function __construct() - { - $this->emails = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $foreign = array(); - $virtual = array(); - $aliases = array(); - $cond = array(); - - if (count($this->emails) == 0) { - return PlFilterCondition::COND_TRUE; - } - - foreach ($this->emails as $entry) { - if (User::isForeignEmailAddress($entry)) { - $foreign[] = $entry; - } else if (User::isVirtualEmailAddress($entry)) { - $virtual[] = $entry; - } else { - @list($user, $domain) = explode('@', $entry); - $aliases[] = $user; - } - } - - if (count($foreign) > 0) { - $sub = $uf->addEmailRedirectFilter($foreign); - $cond[] = XDB::format('e' . $sub . '.email IS NOT NULL OR a.email IN {?}', $foreign); - } - if (count($virtual) > 0) { - $sub = $uf->addVirtualEmailFilter($virtual); - $cond[] = 'vr' . $sub . '.redirect IS NOT NULL'; - } - if (count($aliases) > 0) { - $sub = $uf->addAliasFilter($aliases); - $cond[] = 'al' . $sub . '.alias IS NOT NULL'; - } - return '(' . implode(') OR (', $cond) . ')'; - } -} -// }}} - -// {{{ class UFC_Address -abstract class UFC_Address implements UserFilterCondition -{ - /** Valid address type ('hq' is reserved for company addresses) - */ - const TYPE_HOME = 1; - const TYPE_PRO = 2; - const TYPE_ANY = 3; - - /** Text for these types - */ - protected static $typetexts = array( - self::TYPE_HOME => 'home', - self::TYPE_PRO => 'pro', - ); - - protected $type; - - /** Flags for addresses - */ - const FLAG_CURRENT = 0x0001; - const FLAG_TEMP = 0x0002; - const FLAG_SECOND = 0x0004; - const FLAG_MAIL = 0x0008; - const FLAG_CEDEX = 0x0010; - - // Binary OR of those flags - const FLAG_ANY = 0x001F; - - /** Text of these flags - */ - protected static $flagtexts = array( - self::FLAG_CURRENT => 'current', - self::FLAG_TEMP => 'temporary', - self::FLAG_SECOND => 'secondary', - self::FLAG_MAIL => 'mail', - self::FLAG_CEDEX => 'cedex', - ); - - protected $flags; - - public function __construct($type = null, $flags = null) - { - $this->type = $type; - $this->flags = $flags; - } - - protected function initConds($sub) - { - $conds = array(); - $types = array(); - foreach (self::$typetexts as $flag => $type) { - if ($flag & $this->type) { - $types[] = $type; - } - } - if (count($types)) { - $conds[] = XDB::format($sub . '.type IN {?}', $types); - } - - if ($this->flags != self::FLAG_ANY) { - foreach(self::$flagtexts as $flag => $text) { - if ($flag & $this->flags) { - $conds[] = 'FIND_IN_SET(' . XDB::format('{?}', $text) . ', ' . $sub . '.flags)'; - } - } - } - return $conds; - } - -} -// }}} - -// {{{ class UFC_AddressText -/** Select users based on their address, using full text search - * @param $text Text for filter in fulltext search - * @param $textSearchMode Mode for search (one of XDB::WILDCARD_*) - * @param $type Filter on address type - * @param $flags Filter on address flags - * @param $country Filter on address country - * @param $locality Filter on address locality - */ -class UFC_AddressText extends UFC_Address -{ - - private $text; - private $textSearchMode; - - public function __construct($text = null, $textSearchMode = XDB::WILDCARD_CONTAINS, - $type = null, $flags = self::FLAG_ANY, $country = null, $locality = null) - { - parent::__construct($type, $flags); - $this->text = $text; - $this->textSearchMode = $textSearchMode; - $this->country = $country; - $this->locality = $locality; - } - - private function mkMatch($txt) - { - return XDB::formatWildcards($this->textSearchMode, $txt); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addAddressFilter(); - $conds = $this->initConds($sub); - if ($this->text != null) { - $conds[] = $sub . '.text' . $this->mkMatch($this->text); - } - - if ($this->country != null) { - $subc = $uf->addAddressCountryFilter(); - $subconds = array(); - $subconds[] = $subc . '.country' . $this->mkMatch($this->country); - $subconds[] = $subc . '.countryFR' . $this->mkMatch($this->country); - $conds[] = implode(' OR ', $subconds); - } - - if ($this->locality != null) { - $subl = $uf->addAddressLocalityFilter(); - $conds[] = $subl . '.name' . $this->mkMatch($this->locality); - } - - return implode(' AND ', $conds); - } -} -// }}} - -// {{{ class UFC_AddressField -/** Filters users based on their address, - * @param $val Either a code for one of the fields, or an array of such codes - * @param $fieldtype The type of field to look for - * @param $type Filter on address type - * @param $flags Filter on address flags - */ -class UFC_AddressField extends UFC_Address -{ - const FIELD_COUNTRY = 1; - const FIELD_ADMAREA = 2; - const FIELD_SUBADMAREA = 3; - const FIELD_LOCALITY = 4; - const FIELD_ZIPCODE = 5; - - /** Data of the filter - */ - private $val; - private $fieldtype; - - public function __construct($val, $fieldtype, $type = null, $flags = self::FLAG_ANY) - { - parent::__construct($type, $flags); - - if (!is_array($val)) { - $val = array($val); - } - $this->val = $val; - $this->fieldtype = $fieldtype; - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addAddressFilter(); - $conds = $this->initConds($sub); - - switch ($this->fieldtype) { - case self::FIELD_COUNTRY: - $field = 'countryId'; - break; - case self::FIELD_ADMAREA: - $field = 'administrativeAreaId'; - break; - case self::FIELD_SUBADMAREA: - $field = 'subAdministrativeAreaId'; - break; - case self::FIELD_LOCALITY: - $field = 'localityId'; - break; - case self::FIELD_ZIPCODE: - $field = 'postalCode'; - break; - default: - Platal::page()->killError('Invalid address field type: ' . $this->fieldtype); - } - $conds[] = XDB::format($sub . '.' . $field . ' IN {?}', $this->val); - - return implode(' AND ', $conds); - } -} -// }}} - -// {{{ class UFC_Corps -/** Filters users based on the corps they belong to - * @param $corps Corps we are looking for (abbreviation) - * @param $type Whether we search for original or current corps - */ -class UFC_Corps implements UserFilterCondition -{ - const CURRENT = 1; - const ORIGIN = 2; - - private $corps; - private $type; - - public function __construct($corps, $type = self::CURRENT) - { - $this->corps = $corps; - $this->type = $type; - } - - public function buildCondition(PlFilter &$uf) - { - /** Tables shortcuts: - * pc for profile_corps, - * pceo for profile_corps_enum - orginal - * pcec for profile_corps_enum - current - */ - $sub = $uf->addCorpsFilter($this->type); - $cond = $sub . '.abbreviation = ' . $corps; - return $cond; - } -} -// }}} - -// {{{ class UFC_Corps_Rank -/** Filters users based on their rank in the corps - * @param $rank Rank we are looking for (abbreviation) - */ -class UFC_Corps_Rank implements UserFilterCondition -{ - private $rank; - public function __construct($rank) - { - $this->rank = $rank; - } - - public function buildCondition(PlFilter &$uf) - { - /** Tables shortcuts: - * pcr for profile_corps_rank - */ - $sub = $uf->addCorpsRankFilter(); - $cond = $sub . '.abbreviation = ' . $rank; - return $cond; - } -} -// }}} - -// {{{ class UFC_Job_Company -/** Filters users based on the company they belong to - * @param $type The field being searched (self::JOBID, self::JOBNAME or self::JOBACRONYM) - * @param $value The searched value - */ -class UFC_Job_Company implements UserFilterCondition -{ - const JOBID = 'id'; - const JOBNAME = 'name'; - const JOBACRONYM = 'acronym'; - - private $type; - private $value; - - public function __construct($type, $value) - { - $this->assertType($type); - $this->type = $type; - $this->value = $value; - } - - private function assertType($type) - { - if ($type != self::JOBID && $type != self::JOBNAME && $type != self::JOBACRONYM) { - Platal::page()->killError("Type de recherche non valide."); - } - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addJobCompanyFilter(); - $cond = $sub . '.' . $this->type . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->value); - return $cond; - } -} -// }}} - -// {{{ class UFC_Job_Sectorization -/** Filters users based on the ((sub)sub)sector they work in - * @param $val The ID of the sector, or an array of such IDs - * @param $type The kind of search (subsubsector/subsector/sector) - */ -class UFC_Job_Sectorization implements UserFilterCondition -{ - private $val; - private $type; - - public function __construct($val, $type = UserFilter::JOB_SECTOR) - { - self::assertType($type); - if (!is_array($val)) { - $val = array($val); - } - $this->val = $val; - $this->type = $type; - } - - private static function assertType($type) - { - if ($type != UserFilter::JOB_SECTOR && $type != UserFilter::JOB_SUBSECTOR && $type != UserFilter::JOB_SUBSUBSECTOR) { - Platal::page()->killError("Type de secteur non valide."); - } - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addJobSectorizationFilter($this->type); - return $sub . '.id = ' . XDB::format('{?}', $this->val); - } -} -// }}} - -// {{{ class UFC_Job_Description -/** Filters users based on their job description - * @param $description The text being searched for - * @param $fields The fields to search for (user-defined, ((sub|)sub|)sector) - */ -class UFC_Job_Description implements UserFilterCondition -{ - - private $description; - private $fields; - - public function __construct($description, $fields) - { - $this->fields = $fields; - $this->description = $description; - } - - public function buildCondition(PlFilter &$uf) - { - $conds = array(); - if ($this->fields & UserFilter::JOB_USERDEFINED) { - $sub = $uf->addJobFilter(); - $conds[] = $sub . '.description ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description); - } - if ($this->fields & UserFilter::JOB_CV) { - $uf->requireProfiles(); - $conds[] = 'p.cv ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description); - } - if ($this->fields & UserFilter::JOB_SECTOR) { - $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SECTOR); - $conds[] = $sub . '.name ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description); - } - if ($this->fields & UserFilter::JOB_SUBSECTOR) { - $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SUBSECTOR); - $conds[] = $sub . '.name ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description); - } - if ($this->fields & UserFilter::JOB_SUBSUBSECTOR) { - $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SUBSUBSECTOR); - $conds[] = $sub . '.name ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description); - $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_ALTERNATES); - $conds[] = $sub . '.name ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description); - } - return implode(' OR ', $conds); - } -} -// }}} - -// {{{ class UFC_Networking -/** Filters users based on network identity (IRC, ...) - * @param $type Type of network (-1 for any) - * @param $value Value to search - */ -class UFC_Networking implements UserFilterCondition -{ - private $type; - private $value; - - public function __construct($type, $value) - { - $this->type = $type; - $this->value = $value; - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addNetworkingFilter(); - $conds = array(); - $conds[] = $sub . '.address ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->value); - if ($this->type != -1) { - $conds[] = $sub . '.nwid = ' . XDB::format('{?}', $this->type); - } - return implode(' AND ', $conds); - } -} -// }}} - -// {{{ class UFC_Phone -/** Filters users based on their phone number - * @param $num_type Type of number (pro/user/home) - * @param $phone_type Type of phone (fixed/mobile/fax) - * @param $number Phone number - */ -class UFC_Phone implements UserFilterCondition -{ - const NUM_PRO = 'pro'; - const NUM_USER = 'user'; - const NUM_HOME = 'address'; - const NUM_ANY = 'any'; - - const PHONE_FIXED = 'fixed'; - const PHONE_MOBILE = 'mobile'; - const PHONE_FAX = 'fax'; - const PHONE_ANY = 'any'; - - private $num_type; - private $phone_type; - private $number; - - public function __construct($number, $num_type = self::NUM_ANY, $phone_type = self::PHONE_ANY) - { - require_once('profil.func.inc.php'); - $this->number = $number; - $this->num_type = $num_type; - $this->phone_type = format_phone_number($phone_type); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addPhoneFilter(); - $conds = array(); - $conds[] = $sub . '.search_tel = ' . XDB::format('{?}', $this->number); - if ($this->num_type != self::NUM_ANY) { - $conds[] = $sub . '.link_type = ' . XDB::format('{?}', $this->num_type); - } - if ($this->phone_type != self::PHONE_ANY) { - $conds[] = $sub . '.tel_type = ' . XDB::format('{?}', $this->phone_type); - } - return implode(' AND ', $conds); - } -} -// }}} - -// {{{ class UFC_Medal -/** Filters users based on their medals - * @param $medal ID of the medal - * @param $grade Grade of the medal (null for 'any') - */ -class UFC_Medal implements UserFilterCondition -{ - private $medal; - private $grade; - - public function __construct($medal, $grade = null) - { - $this->medal = $medal; - $this->grade = $grade; - } - - public function buildCondition(PlFilter &$uf) - { - $conds = array(); - $sub = $uf->addMedalFilter(); - $conds[] = $sub . '.mid = ' . XDB::format('{?}', $this->medal); - if ($this->grade != null) { - $conds[] = $sub . '.gid = ' . XDB::format('{?}', $this->grade); - } - return implode(' AND ', $conds); - } -} -// }}} - -// {{{ class UFC_Photo -/** Filters profiles with photo - */ -class UFC_Photo implements UserFilterCondition -{ - public function buildCondition(PlFilter &$uf) - { - $uf->addPhotoFilter(); - return 'photo.attach IS NOT NULL'; - } -} -// }}} - -// {{{ class UFC_Mentor_Expertise -/** Filters users by mentoring expertise - * @param $expertise Domain of expertise - */ -class UFC_Mentor_Expertise implements UserFilterCondition -{ - private $expertise; - - public function __construct($expertise) - { - $this->expertise = $expertise; - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addMentorFilter(UserFilter::MENTOR_EXPERTISE); - return $sub . '.expertise ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->expertise); - } -} -// }}} - -// {{{ class UFC_Mentor_Country -/** Filters users by mentoring country - * @param $country Two-letters code of country being searched - */ -class UFC_Mentor_Country implements UserFilterCondition -{ - private $country; - - public function __construct() - { - $this->country = pl_flatten(func_get_args()); - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addMentorFilter(UserFilter::MENTOR_COUNTRY); - return $sub . '.country IN ' . XDB::format('{?}', $this->country); - } -} -// }}} - -// {{{ class UFC_Mentor_Sectorization -/** Filters users based on mentoring (sub|)sector - * @param $sector ID of (sub)sector - * @param $type Whether we are looking for a sector or a subsector - */ -class UFC_Mentor_Sectorization implements UserFilterCondition -{ - const SECTOR = 1; - const SUBSECTOR = 2; - private $sector; - private $type; - - public function __construct($sector, $type = self::SECTOR) - { - $this->sector = $sector; - $this->type = $type; - } - - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addMentorFilter(UserFilter::MENTOR_SECTOR); - if ($this->type == self::SECTOR) { - $field = 'sectorid'; - } else { - $field = 'subsectorid'; - } - return $sub . '.' . $field . ' = ' . XDB::format('{?}', $this->sector); - } -} -// }}} - -// {{{ class UFC_UserRelated -/** Filters users based on a relation toward a user - * @param $user User to which searched users are related - */ -abstract class UFC_UserRelated implements UserFilterCondition -{ - protected $user; - public function __construct(PlUser &$user) - { - $this->user =& $user; - } -} -// }}} - -// {{{ class UFC_Contact -/** Filters users who belong to selected user's contacts - */ -class UFC_Contact extends UFC_UserRelated -{ - public function buildCondition(PlFilter &$uf) - { - $sub = $uf->addContactFilter($this->user->id()); - return 'c' . $sub . '.contact IS NOT NULL'; - } -} -// }}} - -// {{{ class UFC_WatchRegistration -/** Filters users being watched by selected user - */ -class UFC_WatchRegistration extends UFC_UserRelated -{ - public function buildCondition(PlFilter &$uf) - { - if (!$this->user->watchType('registration')) { - return PlFilterCondition::COND_FALSE; - } - $uids = $this->user->watchUsers(); - if (count($uids) == 0) { - return PlFilterCondition::COND_FALSE; - } else { - return XDB::format('$UID IN {?}', $uids); - } - } -} -// }}} - -// {{{ class UFC_WatchPromo -/** Filters users belonging to a promo watched by selected user - * @param $user Selected user (the one watching promo) - * @param $grade Formation the user is watching - */ -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(PlFilter &$uf) - { - $promos = $this->user->watchPromos(); - if (count($promos) == 0) { - return PlFilterCondition::COND_FALSE; - } else { - $sube = $uf->addEducationFilter(true, $this->grade); - $field = 'pe' . $sube . '.' . UserFilter::promoYear($this->grade); - return XDB::format($field . ' IN {?}', $promos); - } - } -} -// }}} - -// {{{ class UFC_WatchContact -/** Filters users watched by selected user - */ -class UFC_WatchContact extends UFC_Contact -{ - public function buildCondition(PlFilter &$uf) - { - if (!$this->user->watchContacts()) { - return PlFilterCondition::COND_FALSE; - } - return parent::buildCondition($uf); - } -} -// }}} - -// {{{ class UFC_MarketingHash -/** Filters users using the hash generated - * to send marketing emails to him. - */ -class UFC_MarketingHash implements UserFilterCondition -{ - private $hash; - - public function __construct($hash) - { - $this->hash = $hash; - } - - public function buildCondition(PlFilter &$uf) - { - $table = $uf->addMarketingHash(); - return XDB::format('rm.hash = {?}', $this->hash); - } -} -// }}} - -/****************** - * ORDERS - ******************/ - -// {{{ class UserFilterOrder -/** Base class for ordering results of a query. - * Parameters for the ordering must be given to the constructor ($desc for a - * descending order). - * The getSortTokens function is used to get actual ordering part of the query. - */ -abstract class UserFilterOrder extends PlFilterOrder -{ - /** This function must return the tokens to use for ordering - * @param &$uf The UserFilter whose results must be ordered - * @return The name of the field to use for ordering results - */ -// abstract protected function getSortTokens(UserFilter &$uf); -} -// }}} - -// {{{ class UFO_Promo -/** Orders users by promotion - * @param $grade Formation whose promotion users should be sorted by (restricts results to users of that formation) - * @param $desc Whether sort is descending - */ -class UFO_Promo extends UserFilterOrder -{ - private $grade; - - public function __construct($grade = null, $desc = false) - { - parent::__construct($desc); - $this->grade = $grade; - } - - protected function getSortTokens(PlFilter &$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 -/** Sorts users by name - * @param $type Type of name on which to sort (firstname...) - * @param $variant Variant of that name to use (marital, ordinary...) - * @param $particle Set to true if particles should be included in the sorting order - * @param $desc If sort order should be descending - */ -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(PlFilter &$uf) - { - if (Profile::isDisplayName($this->type)) { - $sub = $uf->addDisplayFilter(); - $token = 'pd' . $sub . '.' . $this->type; - if ($uf->accountsRequired()) { - $account_token = Profile::getAccountEquivalentName($this->type); - return 'IFNULL(' . $token . ', a.' . $account_token . ')'; - } else { - return $token; - } - } 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_Score -class UFO_Score extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $toks = $uf->getNameTokens(); - $scores = array(); - - // If there weren't any sort tokens, we shouldn't sort by score, sort by NULL instead - if (count($toks) == 0) { - return 'NULL'; - } - - foreach ($toks as $sub => $token) { - $scores[] = XDB::format('SUM(' . $sub . '.score + IF (' . $sub . '.token = {?}, 5, 0) )', $token); - } - return implode(' + ', $scores); - } -} -// }}} - -// {{{ class UFO_Registration -/** Sorts users based on registration date - */ -class UFO_Registration extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireAccounts(); - return 'a.registration_date'; - } -} -// }}} - -// {{{ class UFO_Birthday -/** Sorts users based on next birthday date - */ -class UFO_Birthday extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.next_birthday'; - } -} -// }}} - -// {{{ class UFO_ProfileUpdate -/** Sorts users based on last profile update - */ -class UFO_ProfileUpdate extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.last_change'; - } -} -// }}} - -// {{{ class UFO_Death -/** Sorts users based on death date - */ -class UFO_Death extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.deathdate'; - } -} -// }}} - -// {{{ class UFO_Uid -/** Sorts users based on their uid - */ -class UFO_Uid extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireAccounts(); - return '$UID'; - } -} -// }}} - -// {{{ class UFO_Hruid -/** Sorts users based on their hruid - */ -class UFO_Hruid extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireAccounts(); - return 'a.hruid'; - } -} -// }}} - -// {{{ class UFO_Pid -/** Sorts users based on their pid - */ -class UFO_Pid extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireProfiles(); - return '$PID'; - } -} -// }}} - -// {{{ class UFO_Hrpid -/** Sorts users based on their hrpid - */ -class UFO_Hrpid extends UserFilterOrder -{ - protected function getSortTokens(PlFilter &$uf) - { - $uf->requireProfiles(); - return 'p.hrpid'; - } -} -// }}} - +require_once dirname(__FILE__) . '/userfilter/conditions.inc.php'; +require_once dirname(__FILE__) . '/userfilter/orders.inc.php'; /*********************************** ********************************* @@ -1584,7 +60,7 @@ class UFO_Hrpid extends UserFilterOrder * when referring to the joined table. * * For example, if data from profile_job must be available to filter results, - * the UFC object will call $uf-addJobFilter(), which will set the 'with_pj' var and + * the UFC object will call $uf-addJobFilter(), which will set the 'with_pj' var and * return 'pj', the short name to use when referring to profile_job; when building * the query, calling the jobJoins function will return an array containing a single * row: @@ -1604,9 +80,15 @@ class UserFilter extends PlFilter private $root; private $sort = array(); + private $grouper = null; private $query = null; private $orderby = null; + // Store the current 'search' visibility. + private $visibility = null; + // If the 'search' visibility should be based on a DB field instead. + private $visibility_field = null; + private $lastusercount = null; private $lastprofilecount = null; @@ -1627,7 +109,7 @@ class UserFilter extends PlFilter } } if (!is_null($sort)) { - if ($sort instanceof UserFilterOrder) { + if ($sort instanceof PlFilterOrder) { $this->addSort($sort); } else if (is_array($sort)) { foreach ($sort as $s) { @@ -1635,6 +117,52 @@ class UserFilter extends PlFilter } } } + + // This will set the visibility to the default correct level. + $this->visibility = Visibility::defaultForRead(); + } + + /** Get the SQL condition to filter by visibility level for a field. + * This will return a SQL condition which evaluates to True if the given + * field display level is available from the current access level. + * @param $field Name of the field holding a display level + * @return string SQL condition, properly escaped, for that field. + */ + public function getVisibilityConditionForField($field) + { + if ($this->visibility_field != null) { + // Use enum 'bit' arithmetic. + // Display levels are ordered as 'hidden, private, ax, public' + // Thus ax > private. + // The $sub.display_level cell will contain the 'most private' display + // level available based on $field. If it is 'ax' and $field is + // 'private','ax' <= 'private' is false. + $sub = $this->addVisibilityFieldFilter($this->visibility_field); + return $sub . '.best_display_level + 0 <= 0 + ' . $field; + } else { + $sub = $this->addVisibilityAbsoluteFilter($this->visibility->level()); + return $sub . '.best_display_level + 0 <= 0 + ' . $field; + } + } + + /** Get the SQL condition to filter by a given visibility level. + * @param $level One of Visibility::EXPORT_* + * @return string A SQL condition, properly escaped, which evaluates to 'true' if the $level can be viewed with the current access level. + */ + public function getVisibilityConditionAbsolute($level) + { + if ($this->visibility_field != null) { + // The $sub.display_levels cell will contain allowed display levels + // for an access level of $this->visibility_field. + $sub = $this->addVisibilityFieldFilter($this->visibility_field); + return XDB::format('FIND_IN_SET({?}, ' . $sub . '.display_levels', $level); + } else { + if ($this->visibility->isVisible($level)) { + return 'TRUE'; + } else { + return 'FALSE'; + } + } } private function buildQuery() @@ -1675,7 +203,43 @@ class UserFilter extends PlFilter } } - private function getUIDList($uids = null, PlLimit &$limit) + public function hasGroups() + { + return $this->grouper != null; + } + + public function getGroups() + { + return $this->getUIDGroups(); + } + + public function getUIDGroups() + { + $this->requireAccounts(); + $this->buildQuery(); + $token = $this->grouper->getGroupToken($this); + + $groups = XDB::rawFetchAllRow('SELECT ' . $token . ', COUNT(a.uid) + ' . $this->query . ' + GROUP BY ' . $token, + 0); + return $groups; + } + + public function getPIDGroups() + { + $this->requireProfiles(); + $this->buildQuery(); + $token = $this->grouper->getGroupToken($this); + + $groups = XDB::rawFetchAllRow('SELECT ' . $token . ', COUNT(p.pid) + ' . $this->query . ' + GROUP BY ' . $token, + 0); + return $groups; + } + + private function getUIDList($uids = null, PlLimit $limit) { $this->requireAccounts(); $this->buildQuery(); @@ -1684,16 +248,16 @@ class UserFilter extends PlFilter if (!empty($uids)) { $cond = XDB::format(' AND a.uid IN {?}', $uids); } - $fetched = XDB::fetchColumn('SELECT SQL_CALC_FOUND_ROWS a.uid - ' . $this->query . $cond . ' - GROUP BY a.uid - ' . $this->orderby . ' - ' . $lim); + $fetched = XDB::rawFetchColumn('SELECT SQL_CALC_FOUND_ROWS a.uid + ' . $this->query . $cond . ' + GROUP BY a.uid + ' . $this->orderby . ' + ' . $lim); $this->lastusercount = (int)XDB::fetchOneCell('SELECT FOUND_ROWS()'); return $fetched; } - private function getPIDList($pids = null, PlLimit &$limit) + private function getPIDList($pids = null, PlLimit $limit) { $this->requireProfiles(); $this->buildQuery(); @@ -1702,11 +266,11 @@ class UserFilter extends PlFilter if (!is_null($pids)) { $cond = XDB::format(' AND p.pid IN {?}', $pids); } - $fetched = XDB::fetchColumn('SELECT SQL_CALC_FOUND_ROWS p.pid - ' . $this->query . $cond . ' - GROUP BY p.pid - ' . $this->orderby . ' - ' . $lim); + $fetched = XDB::rawFetchColumn('SELECT SQL_CALC_FOUND_ROWS p.pid + ' . $this->query . $cond . ' + GROUP BY p.pid + ' . $this->orderby . ' + ' . $lim); $this->lastprofilecount = (int)XDB::fetchOneCell('SELECT FOUND_ROWS()'); return $fetched; } @@ -1721,23 +285,25 @@ class UserFilter extends PlFilter /** Check that the user match the given rule. */ - public function checkUser(PlUser &$user) + public function checkUser(PlUser $user) { $this->requireAccounts(); $this->buildQuery(); - $count = (int)XDB::fetchOneCell('SELECT COUNT(*) - ' . $this->query . XDB::format(' AND a.uid = {?}', $user->id())); + $count = (int)XDB::rawFetchOneCell('SELECT COUNT(*) + ' . $this->query + . XDB::format(' AND a.uid = {?}', $user->id())); return $count == 1; } /** Check that the profile match the given rule. */ - public function checkProfile(Profile &$profile) + public function checkProfile(Profile $profile) { $this->requireProfiles(); $this->buildQuery(); - $count = (int)XDB::fetchOneCell('SELECT COUNT(*) - ' . $this->query . XDB::format(' AND p.pid = {?}', $profile->id())); + $count = (int)XDB::rawFetchOneCell('SELECT COUNT(*) + ' . $this->query + . XDB::format(' AND p.pid = {?}', $profile->id())); return $count == 1; } @@ -1877,6 +443,10 @@ class UserFilter extends PlFilter return $this->getUsers($limit); } + public function getIds($limit = null) + { + return $this->getUIDs(); + } public function getTotalCount() { @@ -1888,7 +458,7 @@ class UserFilter extends PlFilter if (is_null($this->lastusercount)) { $this->requireAccounts(); $this->buildQuery(); - return (int)XDB::fetchOneCell('SELECT COUNT(DISTINCT a.uid) + return (int)XDB::rawFetchOneCell('SELECT COUNT(DISTINCT a.uid) ' . $this->query); } else { return $this->lastusercount; @@ -1900,25 +470,87 @@ class UserFilter extends PlFilter if (is_null($this->lastprofilecount)) { $this->requireProfiles(); $this->buildQuery(); - return (int)XDB::fetchOneCell('SELECT COUNT(DISTINCT p.pid) + return (int)XDB::rawFetchOneCell('SELECT COUNT(DISTINCT p.pid) ' . $this->query); } else { return $this->lastprofilecount; } } - public function setCondition(PlFilterCondition &$cond) + public function setCondition(PlFilterCondition $cond) { $this->root =& $cond; $this->query = null; } - public function addSort(PlFilterOrder &$sort) + public function addSort(PlFilterOrder $sort) { + if (count($this->sort) == 0 && $sort instanceof PlFilterGroupableOrder) + { + $this->grouper = $sort; + } $this->sort[] = $sort; $this->orderby = null; } + public function export() + { + $export = array('conditions' => $this->root->export()); + if (!empty($this->sort)) { + $export['sorts'] = array(); + foreach ($this->sort as $sort) { + $export['sorts'][] = $sort->export(); + } + } + return $export; + } + + public function exportConditions() + { + return $this->root->export(); + } + + public static function fromExport(array $export) + { + $export = new PlDict($export); + if (!$export->has('conditions')) { + throw new Exception("Cannot build a user filter without conditions"); + } + $cond = UserFilterCondition::fromExport($export->v('conditions')); + $sorts = null; + if ($export->has('sorts')) { + $sorts = array(); + foreach ($export->v('sorts') as $sort) { + $sorts[] = UserFilterOrder::fromExport($sort); + } + } + return new UserFilter($cond, $sorts); + } + + public static function fromJSon($json) + { + $export = json_decode($json, true); + if (is_null($export)) { + throw new Exception("Invalid json: $json"); + } + return self::fromExport($json); + } + + public static function fromExportedConditions(array $export) + { + $cond = UserFilterCondition::fromExport($export); + return new UserFilter($cond); + } + + public static function fromJSonConditions($json) + { + $export = json_decode($json, true); + if (is_null($export)) { + throw new Exception("Invalid json: $json"); + } + return self::fromExportedConditions($json); + } + static public function getLegacy($promo_min, $promo_max) { if ($promo_min != 0) { @@ -1936,12 +568,12 @@ class UserFilter extends PlFilter static public function sortByName() { - return array(new UFO_Name(Profile::LASTNAME), new UFO_Name(Profile::FIRSTNAME)); + return array(new UFO_Name()); } static public function sortByPromo() { - return array(new UFO_Promo(), new UFO_Name(Profile::LASTNAME), new UFO_Name(Profile::FIRSTNAME)); + return array(new UFO_Promo(), new UFO_Name()); } static private function getDBSuffix($string) @@ -2011,6 +643,55 @@ class UserFilter extends PlFilter return $joins; } + /** VISIBILITY + */ + private $vlevels = array(); + private $vfields = array(); + public function addVisibilityAbsoluteFilter($level) + { + $sub = 'pvel_' . $level; + $this->vlevels[$level] = $sub; + return $sub; + } + + public function addVisibilityFieldFilter($field) + { + $sub = 'pvef_' . self::getDBSuffix($field); + $this->vfields[$field] = $sub; + return $sub; + } + + protected function visibilityJoins() + { + $joins = array(); + foreach ($this->vlevels as $level => $sub) { + $joins[$sub] = PlSqlJoin::inner('profile_visibility_enum', '$ME.access_level = {?}', $level); + } + foreach ($this->vfields as $field => $sub) { + $joins[$sub] = PlSqlJoin::inner('profile_visibility_enum', '$ME.access_level = ' . $field); + } + return $joins; + } + + /** PERMISSIONS + */ + private $at = false; + public function requirePerms() + { + $this->requireAccounts(); + $this->at = true; + return 'at'; + } + + protected function permJoins() + { + if ($this->at) { + return array('at' => PlSqlJoin::left('account_types', '$ME.type = a.type')); + } else { + return array(); + } + } + /** DISPLAY */ const DISPLAY = 'display'; @@ -2050,44 +731,6 @@ class UserFilter extends PlFilter return $joins; } - /** NAMES - */ - - static public function assertName($name) - { - if (!DirEnum::getID(DirEnum::NAMETYPES, $name)) { - Platal::page()->kill('Invalid name type: ' . $name); - } - } - - private $pn = array(); - public function addNameFilter($type, $variant = null) - { - $this->requireProfiles(); - if (!is_null($variant)) { - $ft = $type . '_' . $variant; - } else { - $ft = $type; - } - $sub = '_' . $ft; - self::assertName($ft); - - if (!is_null($variant) && $variant == 'other') { - $sub .= $this->option++; - } - $this->pn[$sub] = DirEnum::getID(DirEnum::NAMETYPES, $ft); - return $sub; - } - - protected function nameJoins() - { - $joins = array(); - foreach ($this->pn as $sub => $type) { - $joins['pn' . $sub] = PlSqlJoin::left('profile_name', '$ME.pid = $PID AND $ME.typeid = {?}', $type); - } - return $joins; - } - /** NAMETOKENS */ private $name_tokens = array(); @@ -2138,9 +781,9 @@ class UserFilter extends PlFilter /** EDUCATION */ - const GRADE_ING = 'Ing.'; - const GRADE_PHD = 'PhD'; - const GRADE_MST = 'M%'; + const GRADE_ING = Profile::DEGREE_X; + const GRADE_PHD = Profile::DEGREE_D; + const GRADE_MST = Profile::DEGREE_M; static public function isGrade($grade) { return ($grade !== 0) && ($grade == self::GRADE_ING || $grade == self::GRADE_PHD || $grade == self::GRADE_MST); @@ -2187,7 +830,7 @@ class UserFilter extends PlFilter foreach ($this->pepe as $grade => $sub) { if ($this->isGrade($grade)) { $joins['pe' . $sub] = PlSqlJoin::left('profile_education', '$ME.eduid = pee.id AND $ME.pid = $PID'); - $joins['pede' . $sub] = PlSqlJoin::inner('profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid AND $ME.abbreviation LIKE {?}', $grade); + $joins['pede' . $sub] = PlSqlJoin::inner('profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid AND $ME.degree LIKE {?}', $grade); } else { $joins['pe' . $sub] = PlSqlJoin::left('profile_education', '$ME.pid = $PID'); $joins['pee' . $sub] = PlSqlJoin::inner('profile_education_enum', '$ME.id = pe' . $sub . '.eduid'); @@ -2220,6 +863,15 @@ class UserFilter extends PlFilter return $sub; } + private $gpfm = array(); + public function addGroupFormerMemberFilter() + { + $this->requireAccounts(); + $sub = '_' . $this->option++; + $this->gpfm[] = $sub; + return $sub; + } + protected function groupJoins() { $joins = array(); @@ -2234,6 +886,29 @@ class UserFilter extends PlFilter $joins['gpm' . $sub] = PlSqlJoin::left('group_members', '$ME.uid = $UID AND $ME.asso_id = gpa' . $sub . '.id'); } } + foreach ($this->gpfm as $sub) { + $joins['gpfm' . $sub] = PlSqlJoin::left('group_former_members', '$ME.uid = $UID'); + } + return $joins; + } + + /** NLS + */ + private $nls = array(); + public function addNewsLetterFilter($nlid) + { + $this->requireAccounts(); + $sub = 'nl_' . $nlid; + $this->nls[$nlid] = $sub; + return $sub; + } + + protected function newsLetterJoins() + { + $joins = array(); + foreach ($this->nls as $key => $sub) { + $joins[$sub] = PlSqlJoin::left('newsletter_ins', '$ME.nlid = {?} AND $ME.uid = $UID', $key); + } return $joins; } @@ -2268,74 +943,78 @@ class UserFilter extends PlFilter /** EMAILS */ - private $e = array(); + private $ra = array(); + /** Allows filtering by redirection. + * @param $email If null, enable a left join on the email redirection table + * (email_redirect_account); otherwise, perform a left join on users having + * that email as a redirection. + * @return Suffix to use to access the adequate table. + */ public function addEmailRedirectFilter($email = null) { $this->requireAccounts(); - return $this->register_optional($this->e, $email); - } - - private $ve = array(); - public function addVirtualEmailFilter($email = null) + return $this->register_optional($this->ra, $email); + } + + const ALIAS_BEST = 'bestalias'; + const ALIAS_FORLIFE = 'forlife'; + const ALIAS_AUXILIARY = 'alias_aux'; + private $sa = array(); + /** Allows filtering by source email. + * @param $email If null, enable a left join on the email source table + * (email_source_account); otherwise, perform a left join on users having + * that email as a source email. + * @return Suffix to use to access the adequate table. + */ + public function addAliasFilter($email = null) { - $this->addAliasFilter(self::ALIAS_FORLIFE); - return $this->register_optional($this->ve, $email); + $this->requireAccounts(); + return $this->register_optional($this->sa, $email); } - const ALIAS_BEST = 'bestalias'; - const ALIAS_FORLIFE = 'forlife'; - private $al = array(); - public function addAliasFilter($alias = null) + private $with_rf = false; + /** Allows filtering by active redirection. + * @return Suffix to use to access the adequate table. + */ + public function addActiveEmailRedirectFilter($email = null) { $this->requireAccounts(); - return $this->register_optional($this->al, $alias); + $this->with_rf = true; } protected function emailJoins() { global $globals; $joins = array(); - foreach ($this->e as $sub=>$key) { - if (is_null($key)) { - $joins['e' . $sub] = PlSqlJoin::left('emails', '$ME.uid = $UID AND $ME.flags != \'filter\''); + foreach ($this->ra as $sub => $redirections) { + if (is_null($redirections)) { + $joins['ra' . $sub] = PlSqlJoin::left('email_redirect_account', '$ME.uid = $UID AND $ME.type != \'imap\''); } else { - if (!is_array($key)) { - $key = array($key); + if (!is_array($redirections)) { + $key = array($redirections); } - $joins['e' . $sub] = PlSqlJoin::left('emails', '$ME.uid = $UID AND $ME.flags != \'filter\' - AND $ME.email IN {?}', $key); + $joins['ra' . $sub] = PlSqlJoin::left('email_redirect_account', '$ME.uid = $UID AND $ME.type != \'imap\' + AND $ME.redirect IN {?}', $redirections); } } - foreach ($this->al as $sub=>$key) { - if (is_null($key)) { - $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type IN (\'alias\', \'a_vie\')'); - } else if ($key == self::ALIAS_BEST) { - $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type IN (\'alias\', \'a_vie\') AND FIND_IN_SET(\'bestalias\', $ME.flags)'); - } else if ($key == self::ALIAS_FORLIFE) { - $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type = \'a_vie\''); + foreach ($this->sa as $sub => $emails) { + if (is_null($emails)) { + $joins['sa' . $sub] = PlSqlJoin::left('email_source_account', '$ME.uid = $UID'); + } else if ($sub == self::ALIAS_BEST) { + $joins['sa' . $sub] = PlSqlJoin::left('email_source_account', '$ME.uid = $UID AND FIND_IN_SET(\'bestalias\', $ME.flags)'); + } else if ($sub == self::ALIAS_FORLIFE) { + $joins['sa' . $sub] = PlSqlJoin::left('email_source_account', '$ME.uid = $UID AND $ME.type = \'forlife\''); + } else if ($sub == self::ALIAS_AUXILIARY) { + $joins['sa' . $sub] = PlSqlJoin::left('email_source_account', '$ME.uid = $UID AND $ME.type = \'alias_aux\''); } else { - if (!is_array($key)) { - $key = array($key); + if (!is_array($emails)) { + $key = array($emails); } - $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type IN (\'alias\', \'a_vie\') - AND $ME.alias IN {?}', $key); + $joins['sa' . $sub] = PlSqlJoin::left('email_source_account', '$ME.uid = $UID AND $ME.email IN {?}', $emails); } } - foreach ($this->ve as $sub=>$key) { - if (is_null($key)) { - $joins['v' . $sub] = PlSqlJoin::left('virtual', '$ME.type = \'user\''); - } else { - if (!is_array($key)) { - $key = array($key); - } - $joins['v' . $sub] = PlSqlJoin::left('virtual', '$ME.type = \'user\' AND $ME.alias IN {?}', $key); - } - $joins['vr' . $sub] = PlSqlJoin::left('virtual_redirect', - '$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); + if ($this->with_rf) { + $joins['rf'] = PlSqlJoin::left('email_redirect_account', '$ME.uid = $UID AND $ME.type != \'imap\' AND $ME.flags = \'active\'');; } return $joins; } @@ -2343,44 +1022,28 @@ class UserFilter extends PlFilter /** ADDRESSES */ - private $with_pa = false; - public function addAddressFilter() + private $types = array(); + public function addAddressFilter($type) { $this->requireProfiles(); $this->with_pa = true; - return 'pa'; - } - - private $with_pac = false; - public function addAddressCountryFilter() - { - $this->requireProfiles(); - $this->addAddressFilter(); - $this->with_pac = true; - return 'gc'; - } - private $with_pal = false; - public function addAddressLocalityFilter() - { - $this->requireProfiles(); - $this->addAddressFilter(); - $this->with_pal = true; - return 'gl'; + $sub = '_' . $this->option++; + $this->types[$type] = $sub; + return $sub; } protected function addressJoins() { $joins = array(); - if ($this->with_pa) { - $joins['pa'] = PlSqlJoin::left('profile_addresses', '$ME.pid = $PID'); - } - if ($this->with_pac) { - $joins['gc'] = PlSqlJoin::left('geoloc_countries', '$ME.iso_3166_1_a2 = pa.countryID'); - } - if ($this->with_pal) { - $joins['gl'] = PlSqlJoin::left('geoloc_localities', '$ME.id = pa.localityID'); + foreach ($this->types as $type => $sub) { + $joins['pa' . $sub] = PlSqlJoin::inner('profile_addresses', '$ME.pid = $PID'); + $joins['pac' . $sub] = PlSqlJoin::inner('profile_addresses_components', + '$ME.pid = pa' . $sub . '.pid AND $ME.jobid = pa' . $sub . '.jobid AND $ME.groupid = pa' . $sub . '.groupid AND $ME.type = pa' . $sub . '.type AND $ME.id = pa' . $sub . '.id'); + $joins['pace' . $sub] = PlSqlJoin::inner('profile_addresses_components_enum', + '$ME.id = pac' . $sub . '.component_id AND FIND_IN_SET({?}, $ME.types)', $type); } + return $joins; } @@ -2396,10 +1059,10 @@ class UserFilter extends PlFilter $this->requireProfiles(); $this->pc = true; if ($type == UFC_Corps::CURRENT) { - $pce['pcec'] = 'current_corpsid'; + $this->pce['pcec'] = 'current_corpsid'; return 'pcec'; } else if ($type == UFC_Corps::ORIGIN) { - $pce['pceo'] = 'original_corpsid'; + $this->pce['pceo'] = 'original_corpsid'; return 'pceo'; } } @@ -2430,30 +1093,18 @@ class UserFilter extends PlFilter /** JOBS */ - const JOB_SECTOR = 0x0001; - const JOB_SUBSECTOR = 0x0002; - const JOB_SUBSUBSECTOR = 0x0004; - const JOB_ALTERNATES = 0x0008; - const JOB_USERDEFINED = 0x0010; - const JOB_CV = 0x0020; - - const JOB_SECTORIZATION = 0x000F; - const JOB_ANY = 0x003F; + const JOB_USERDEFINED = 0x0001; + const JOB_CV = 0x0002; + const JOB_ANY = 0x0003; /** Joins : * pj => profile_job * pje => profile_job_enum - * pjse => profile_job_sector_enum - * pjsse => profile_job_subsector_enum - * pjssse => profile_job_subsubsector_enum - * pja => profile_job_alternates + * pjt => profile_job_terms */ - private $with_pj = false; + private $with_pj = false; private $with_pje = false; - private $with_pjse = false; - private $with_pjsse = false; - private $with_pjssse = false; - private $with_pja = false; + private $with_pjt = 0; public function addJobFilter() { @@ -2469,22 +1120,19 @@ class UserFilter extends PlFilter return 'pje'; } - public function addJobSectorizationFilter($type) + /** + * Adds a filter on job terms of profile. + * @param $nb the number of job terms to use + * @return an array of the fields to filter (one for each term). + */ + public function addJobTermsFilter($nb = 1) { - $this->addJobFilter(); - if ($type == self::JOB_SECTOR) { - $this->with_pjse = true; - return 'pjse'; - } else if ($type == self::JOB_SUBSECTOR) { - $this->with_pjsse = true; - return 'pjsse'; - } else if ($type == self::JOB_SUBSUBSECTOR) { - $this->with_pjssse = true; - return 'pjssse'; - } else if ($type == self::JOB_ALTERNATES) { - $this->with_pja = true; - return 'pja'; + $this->with_pjt = $nb; + $jobtermstable = array(); + for ($i = 1; $i <= $nb; ++$i) { + $jobtermstable[] = 'pjtr_'.$i; } + return $jobtermstable; } protected function jobJoins() @@ -2496,17 +1144,11 @@ class UserFilter extends PlFilter if ($this->with_pje) { $joins['pje'] = PlSqlJoin::left('profile_job_enum', '$ME.id = pj.jobid'); } - if ($this->with_pjse) { - $joins['pjse'] = PlSqlJoin::left('profile_job_sector_enum', '$ME.id = pj.sectorid'); - } - if ($this->with_pjsse) { - $joins['pjsse'] = PlSqlJoin::left('profile_job_subsector_enum', '$ME.id = pj.subsectorid'); - } - if ($this->with_pjssse) { - $joins['pjssse'] = PlSqlJoin::left('profile_job_subsubsector_enum', '$ME.id = pj.subsubsectorid'); - } - if ($this->with_pja) { - $joins['pja'] = PlSqlJoin::left('profile_job_alternates', '$ME.subsubsectorid = pj.subsubsectorid'); + if ($this->with_pjt > 0) { + for ($i = 1; $i <= $this->with_pjt; ++$i) { + $joins['pjt_'.$i] = PlSqlJoin::left('profile_job_term', '$ME.pid = $PID'); + $joins['pjtr_'.$i] = PlSqlJoin::left('profile_job_term_relation', '$ME.jtid_2 = pjt_'.$i.'.jtid'); + } } return $joins; } @@ -2572,27 +1214,64 @@ class UserFilter extends PlFilter return $joins; } + /** DELTATEN + */ + private $dts = array(); + const DELTATEN = 1; + const DELTATEN_MESSAGE = 2; + // TODO: terms + + public function addDeltaTenFilter($type) + { + $this->requireProfiles(); + switch ($type) { + case self::DELTATEN: + $this->dts['pdt'] = 'profile_deltaten'; + return 'pdt'; + case self::DELTATEN_MESSAGE: + $this->dts['pdtm'] = 'profile_deltaten'; + return 'pdtm'; + default: + Platal::page()->killError("Undefined DeltaTen filter."); + } + } + + protected function deltatenJoins() + { + $joins = array(); + foreach ($this->dts as $sub => $tab) { + $joins[$sub] = PlSqlJoin::left($tab, '$ME.pid = $PID'); + } + return $joins; + } + /** MENTORING */ private $pms = array(); - const MENTOR_EXPERTISE = 1; - const MENTOR_COUNTRY = 2; - const MENTOR_SECTOR = 3; + private $mjtr = false; + const MENTOR = 1; + const MENTOR_EXPERTISE = 2; + const MENTOR_COUNTRY = 3; + const MENTOR_TERM = 4; public function addMentorFilter($type) { - $this->requireAccounts(); + $this->requireProfiles(); switch($type) { + case self::MENTOR: + $this->pms['pm'] = 'profile_mentor'; + return 'pm'; case self::MENTOR_EXPERTISE: $this->pms['pme'] = 'profile_mentor'; return 'pme'; case self::MENTOR_COUNTRY: $this->pms['pmc'] = 'profile_mentor_country'; return 'pmc'; - case self::MENTOR_SECTOR: - $this->pms['pms'] = 'profile_mentor_sector'; - return 'pms'; + case self::MENTOR_TERM: + $this->pms['pmt'] = 'profile_mentor_term'; + $this->mjtr = true; + return 'mjtr'; default: Platal::page()->killError("Undefined mentor filter."); } @@ -2604,6 +1283,9 @@ class UserFilter extends PlFilter foreach ($this->pms as $sub => $tab) { $joins[$sub] = PlSqlJoin::left($tab, '$ME.pid = $PID'); } + if ($this->mjtr) { + $joins['mjtr'] = PlSqlJoin::left('profile_job_term_relation', '$ME.jtid_2 = pmt.jtid'); + } return $joins; } @@ -2695,6 +1377,7 @@ class UserFilter extends PlFilter { $this->requireProfiles(); $this->with_photo = true; + return 'photo'; } protected function photoJoins() @@ -2726,7 +1409,6 @@ class UserFilter extends PlFilter } } // }}} - // {{{ class ProfileFilter class ProfileFilter extends UserFilter { @@ -2735,6 +1417,11 @@ class ProfileFilter extends UserFilter return $this->getProfiles($limit); } + public function getIds($limit = null) + { + return $this->getPIDs(); + } + public function filter(array $profiles, $limit = null) { return $this->filterProfiles($profiles, self::defaultLimit($limit)); @@ -2744,6 +1431,11 @@ class ProfileFilter extends UserFilter { return $this->getTotalProfileCount(); } + + public function getGroups() + { + return $this->getPIDGroups(); + } } // }}}