Merge remote branch 'origin/xorg/f/geocoding' into xorg/master
authorStéphane Jacob <sj@m4x.org>
Wed, 1 Jun 2011 16:12:40 +0000 (18:12 +0200)
committerStéphane Jacob <sj@m4x.org>
Wed, 1 Jun 2011 16:12:40 +0000 (18:12 +0200)
1  2 
bin/export_sql.bash
classes/direnum.php
classes/profile.php
classes/userfilter.php
classes/userfilter/conditions.inc.php
htdocs/javascript/profile.js
include/userset.inc.php
include/webservices/manageurs.server.inc.php
modules/profile.php
upgrade/1.1.2/README

diff --combined bin/export_sql.bash
@@@ -7,10 -7,8 +7,8 @@@
  # Developers: list 'public' tables here.
  SHARED_TABLES="account_types \
  forums \
- geoloc_administrativeareas \
  geoloc_countries \
- geoloc_localities \
- geoloc_subadministrativeareas \
+ geoloc_languages \
  log_actions \
  newsletter_cat \
  profile_binet_enum \
@@@ -27,6 -25,7 +25,6 @@@ profile_job_term_relation 
  profile_langskill_enum \
  profile_medal_enum \
  profile_medal_grade_enum \
 -profile_name_enum \
  profile_networking_enum \
  profile_section_enum \
  profile_skill_enum \
diff --combined classes/direnum.php
@@@ -29,6 -29,9 +29,6 @@@ class DirEnu
       * Each of these consts contains the basename of the class (its full name
       * being DE_$basename).
       */
 -    const NAMETYPES      = 'nametypes';
 -    const NAMES          = 'names';
 -
      const BINETS         = 'binets';
      const GROUPESX       = 'groupesx';
      const SECTIONS       = 'sections';
      const ORIGINCORPS    = 'origincorps';
      const CORPSRANKS     = 'corpsranks';
  
-     const NATIONALITIES  = 'nationalities';
-     const COUNTRIES      = 'countries';
-     const ADMINAREAS     = 'adminareas';
-     const SUBADMINAREAS  = 'subadminareas';
-     const LOCALITIES     = 'localities';
+     const NATIONALITIES       = 'nationalities';
+     const SUBLOCALITIES       = 'sublocalities';
+     const LOCALITIES          = 'localities';
+     const ADMNISTRATIVEAREAS3 = 'admnistrativeareas3';
+     const ADMNISTRATIVEAREAS2 = 'admnistrativeareas2';
+     const ADMNISTRATIVEAREAS1 = 'admnistrativeareas1';
+     const COUNTRIES           = 'countries';
  
      const COMPANIES      = 'companies';
      const JOBDESCRIPTION = 'jobdescription';
@@@ -430,6 -435,29 +432,6 @@@ abstract class DE_WithSuboption extend
  }
  // }}}
  
 -// {{{ class DE_NameTypes
 -// returns 'system' names ('lastname', 'lastname_marital', ...)
 -class DE_NameTypes extends DirEnumeration
 -{
 -    public $capabilities = 0x005; // self::HAS_OPTIONS | self::SAVE_IN_SESSION;
 -
 -    protected $from     = 'profile_name_enum';
 -    protected $valfield = 'type';
 -}
 -// }}}
 -
 -// {{{ class DE_Names
 -// returns 'system' names ('lastname', 'lastname_marital', ...)
 -class DE_Names extends DirEnumeration
 -{
 -    public $capabilities = 0x005; // self::HAS_OPTIONS | self::SAVE_IN_SESSION;
 -
 -    protected $from     = 'profile_name_enum';
 -    protected $idfield  = 'type';
 -    protected $valfield = 'name';
 -}
 -// }}}
 -
  /** GROUPS
   */
  // {{{ class DE_Binets
@@@ -565,56 -593,54 +567,54 @@@ class DE_Nationalities extends DirEnume
  }
  // }}}
  
- // {{{ class DE_Countries
- class DE_Countries extends DirEnumeration
+ // {{{ class DE_AddressesComponents
+ class DE_AddressesComponents extends DirEnumeration
  {
-     protected $idfield   = 'geoloc_countries.iso_3166_1_a2';
-     protected $valfield  = 'geoloc_countries.country';
-     protected $valfield2 = 'geoloc_countries.countryEn';
-     protected $from      = 'geoloc_countries';
+     protected $idfield   = 'profile_addresses_components_enum.id';
+     protected $valfield  = 'profile_addresses_components_enum.long_name';
+     protected $from      = 'profile_addresses_components_enum';
  
-     protected $ac_join   = 'INNER JOIN profile_addresses ON (geoloc_countries.iso_3166_1_a2 = profile_addresses.countryId)';
-     protected $ac_unique = 'profile_addresses.pid';
-     protected $ac_where  = 'profile_addresses.type = \'home\'';
+     protected $ac_join   = 'INNER JOIN profile_addresses_components ON (profile_addresses_components.component_id = profile_addresses_components_enum.id)';
+     protected $ac_unique = 'profile_addresses_components.pid';
  }
  // }}}
- // {{{ class DE_AdminAreas
- class DE_AdminAreas extends DE_WithSuboption
+ // {{{ class DE_AddressesComponents extensions
+ class DE_Countries extends DE_AddressesComponents
  {
-     protected $idfield   = 'geoloc_administrativeareas.id';
-     protected $optfield  = 'geoloc_administrativeareas.country';
-     protected $valfield  = 'geoloc_administrativeareas.name';
-     protected $from      = 'geoloc_administrativeareas';
+     protected $where = 'WHERE  FIND_IN_SET(\'country\', profile_addresses_components_enum.types)';
+     protected $ac_where  = 'profile_addresses_components.type = \'home\' AND FIND_IN_SET(\'country\', profile_addresses_components_enum.types)';
+ }
  
-     protected $ac_join   = 'INNER JOIN profile_addresses ON (profile_addresses.administrativeAreaId = geoloc_administrativeareas.id)';
-     protected $ac_unique = 'profile_addresses.pid';
+ class DE_Admnistrativeareas1 extends DE_AddressesComponents
+ {
+     protected $where = 'WHERE  FIND_IN_SET(\'admnistrative_area_1\', profile_addresses_components_enum.types)';
+     protected $ac_where  = 'profile_addresses_components.type = \'home\' AND FIND_IN_SET(\'admnistrative_area_1\', profile_addresses_components_enum.types)';
  }
- // }}}
  
- // {{{ class DE_SubAdminAreas
- class DE_SubAdminAreas extends DE_WithSuboption
+ class DE_Admnistrativeareas2 extends DE_AddressesComponents
  {
-     protected $idfield   = 'geoloc_subadministrativeareas.id';
-     protected $optfield  = 'geoloc_subadministrativeareas.administrativearea';
-     protected $valfield  = 'geoloc_subadministrativeareas.name';
-     protected $from      = 'geoloc_subadministrativeareas';
+     protected $where = 'WHERE  FIND_IN_SET(\'admnistrative_area_2\', profile_addresses_components_enum.types)';
+     protected $ac_where  = 'profile_addresses_components.type = \'home\' AND FIND_IN_SET(\'admnistrative_area_2\', profile_addresses_components_enum.types)';
+ }
  
-     protected $ac_join   = 'INNER JOIN profile_addresses ON (profile_addresses.subadministrativeAreaId = geoloc_subadministrativeareas.id)';
-     protected $ac_unique = 'profile_addresses.pid';
+ class DE_Admnistrativeareas3 extends DE_AddressesComponents
+ {
+     protected $where = 'WHERE  FIND_IN_SET(\'admnistrative_area_3\', profile_addresses_components_enum.types)';
+     protected $ac_where  = 'profile_addresses_components.type = \'home\' AND FIND_IN_SET(\'admnistrative_area_3\', profile_addresses_components_enum.types)';
  }
- // }}}
  
- // {{{ class DE_Localities
- class DE_Localities extends DirEnumeration
+ class DE_Localities extends DE_AddressesComponents
  {
-     protected $idfield   = 'geoloc_localities.id';
-     protected $valfield  = 'geoloc_localities.name';
-     protected $from      = 'geoloc_localities';
+     protected $where = 'WHERE  FIND_IN_SET(\'locality\', profile_addresses_components_enum.types)';
+     protected $ac_where  = 'profile_addresses_components.type = \'home\' AND FIND_IN_SET(\'locality\', profile_addresses_components_enum.types)';
+ }
  
-     protected $ac_join   = 'INNER JOIN profile_addresses ON (profile_addresses.localityID = geoloc_localities.id)';
-     protected $ac_unique = 'profile_addresses.pid';
+ class DE_Sublocalities extends DE_AddressesComponents
+ {
+     protected $where = 'WHERE  FIND_IN_SET(\'sublocality\', profile_addresses_components_enum.types)';
+     protected $ac_where  = 'profile_addresses_components.type = \'home\' AND FIND_IN_SET(\'sublocality\', profile_addresses_components_enum.types)';
  }
  // }}}
  
  /** JOBS
diff --combined classes/profile.php
@@@ -273,20 -273,6 +273,20 @@@ class Profile implements PlExportabl
          }
      }
  
 +    public static function educationDuration($education)
 +    {
 +        switch ($education) {
 +          case self::DEGREE_X:
 +            return 3;
 +          case self::DEGREE_M:
 +            return 2;
 +          case self::DEGREE_D:
 +            return 3;
 +          default:
 +            return 0;
 +        }
 +    }
 +
      /** Number of years between the promotion year until the
       * graduation year. In standard schools it's 0, but for
       * Polytechnique the promo year is the entry year.
          return 0;
      }
  
+     // Returns the profile's color.
+     public function promoColor()
+     {
+         switch ($this->mainEducation()) {
+           case 'X':
+             if (($this->yearpromo() % 2) === 0) {
+                 return 'red';
+             } else {
+                 return 'yellow';
+             }
+           case 'M':
+             return 'green';
+           case 'D':
+             return 'blue';
+           default:
+             return 'gray';
+         }
+     }
      /** Print a name with the given formatting:
       * %s = • for women
       * %f = firstname
      public function nationalities()
      {
          $nats = array();
-         $countries = DirEnum::getOptions(DirEnum::COUNTRIES);
+         $nationalities = DirEnum::getOptions(DirEnum::NATIONALITIES);
          if ($this->nationality1) {
-             $nats[$this->nationality1] = $countries[$this->nationality1];
+             $nats[$this->nationality1] = $nationalities[$this->nationality1];
          }
          if ($this->nationality2) {
-             $nats[$this->nationality2] = $countries[$this->nationality2];
+             $nats[$this->nationality2] = $nationalities[$this->nationality2];
          }
          if ($this->nationality3) {
-             $nats[$this->nationality3] = $countries[$this->nationality3];
+             $nats[$this->nationality3] = $nationalities[$this->nationality3];
          }
          return $nats;
      }
       *      profile_job, profile_langskills, profile_mentor, profile_networking,
       *      profile_phones, profile_skills, watch_profile
       *  *always keeps in: profile_corps, profile_display, profile_education,
 -     *      profile_medals, profile_name, profile_photos, search_name
 +     *      profile_medals, profile_*_names, profile_photos, search_name
       *  *modifies: profiles
       */
      public function clear()
                                       IF (p.freetext_pub IN {?}, p.freetext, NULL) AS freetext,
                                       pe.entry_year, pe.grad_year, pe.promo_year, pe.program, pe.fieldid,
                                       IF ({?}, pse.text, NULL) AS section,
 -                                     pn_f.name AS firstname, pn_l.name AS lastname,
 -                                     IF ({?}, pn_n.name, NULL) AS nickname,
 -                                     IF (pn_uf.name IS NULL, pn_f.name, pn_uf.name) AS firstname_ordinary,
 -                                     IF (pn_ul.name IS NULL, pn_l.name, pn_ul.name) AS lastname_ordinary,
 +                                     ppn.firstname_main AS firstname, ppn.lastname_main AS lastname, IF ({?}, pn.name, NULL) AS nickname,
 +                                     IF (ppn.firstname_ordinary = \'\', ppn.firstname_main, ppn.firstname_ordinary) AS firstname_ordinary,
 +                                     IF (ppn.lastname_ordinary = \'\', ppn.firstname_main, ppn.lastname_ordinary) AS lastname_ordinary,
                                       pd.yourself, pd.promo, pd.short_name, pd.public_name AS full_name,
                                       pd.directory_name, pd.public_name, pd.private_name,
                                       IF (pp.pub IN {?}, pp.display_tel, NULL) AS mobile,
                           INNER JOIN  profile_display AS pd ON (pd.pid = p.pid)
                           INNER JOIN  profile_education AS pe ON (pe.pid = p.pid AND FIND_IN_SET(\'primary\', pe.flags))
                            LEFT JOIN  profile_section_enum AS pse ON (pse.id = p.section)
 -                         INNER JOIN  profile_name AS pn_f ON (pn_f.pid = p.pid
 -                                                              AND pn_f.typeid = ' . self::getNameTypeId('firstname', true) . ')
 -                         INNER JOIN  profile_name AS pn_l ON (pn_l.pid = p.pid
 -                                                              AND pn_l.typeid = ' . self::getNameTypeId('lastname', true) . ')
 -                          LEFT JOIN  profile_name AS pn_uf ON (pn_uf.pid = p.pid
 -                                                               AND pn_uf.typeid = ' . self::getNameTypeId('firstname_ordinary', true) . ')
 -                          LEFT JOIN  profile_name AS pn_ul ON (pn_ul.pid = p.pid
 -                                                               AND pn_ul.typeid = ' . self::getNameTypeId('lastname_ordinary', true) . ')
 -                          LEFT JOIN  profile_name AS pn_n ON (pn_n.pid = p.pid
 -                                                              AND pn_n.typeid = ' . self::getNameTypeId('nickname', true) . ')
 +                         INNER JOIN  profile_public_names AS ppn ON (ppn.pid = p.pid)
 +                          LEFT JOIN  profile_private_names AS pn ON (pn.pid = p.pid AND type = \'nickname\')
                            LEFT JOIN  profile_phones AS pp ON (pp.pid = p.pid AND pp.link_type = \'user\' AND tel_type = \'mobile\')
                            LEFT JOIN  profile_photos AS ph ON (ph.pid = p.pid)
                            LEFT JOIN  profile_mentor AS pm ON (pm.pid = p.pid)
          }
      }
  
 -    public static function getNameTypeId($type, $for_sql = false)
 -    {
 -        if (!S::has('name_types')) {
 -            $table = XDB::fetchAllAssoc('type', 'SELECT  id, type
 -                                                   FROM  profile_name_enum');
 -            S::set('name_types', $table);
 -        } else {
 -            $table = S::v('name_types');
 -        }
 -        if ($for_sql) {
 -            return XDB::escape($table[$type]);
 -        } else {
 -            return $table[$type];
 -        }
 -    }
 -
      public static function rebuildSearchTokens($pids, $transaction = true)
      {
 +        require_once 'name.func.inc.php';
          if (!is_array($pids)) {
              $pids = array($pids);
          }
 -        $keys = XDB::iterator("(SELECT  n.pid AS pid, n.name AS name, e.score AS score, e.general_type,
 -                                        IF(FIND_IN_SET('public', e.flags), 'public', '') AS public
 -                                  FROM  profile_name      AS n
 -                            INNER JOIN  profile_name_enum AS e ON (n.typeid = e.id)
 -                                 WHERE  n.pid IN {?} AND NOT FIND_IN_SET('not_displayed', e.flags))
 -                                 UNION
 -                                (SELECT  n.pid AS pid, n.particle AS name, 0 AS score, e.general_type,
 -                                         IF(FIND_IN_SET('public', e.flags), 'public', '') AS public
 -                                   FROM  profile_name      AS n
 -                             INNER JOIN  profile_name_enum AS e ON (n.typeid = e.id)
 -                                  WHERE  n.pid IN {?} AND NOT FIND_IN_SET('not_displayed', e.flags))
 -                               ",
 -                              $pids, $pids);
 +        $keys = XDB::iterator("(SELECT  pid, name, type, IF(type = 'nickname', 2, 1) AS score, '' AS public
 +                                  FROM  profile_private_names
 +                                 WHERE  pid IN {?})
 +                                UNION
 +                               (SELECT  pid, lastname_main, 'lastname' AS type, 10 AS score, 'public' AS public
 +                                  FROM  profile_public_names
 +                                 WHERE  lastname_main != '' AND pid IN {?})
 +                                UNION
 +                               (SELECT  pid, lastname_marital, 'lastname' AS type, 10 AS score, 'public' AS public
 +                                  FROM  profile_public_names
 +                                 WHERE  lastname_marital != '' AND pid IN {?})
 +                                UNION
 +                               (SELECT  pid, lastname_ordinary, 'lastname' AS type, 10 AS score, 'public' AS public
 +                                  FROM  profile_public_names
 +                                 WHERE  lastname_ordinary != '' AND pid IN {?})
 +                                UNION
 +                               (SELECT  pid, firstname_main, 'firstname' AS type, 10 AS score, 'public' AS public
 +                                  FROM  profile_public_names
 +                                 WHERE  firstname_main != '' AND pid IN {?})
 +                                UNION
 +                               (SELECT  pid, firstname_ordinary, 'firstname' AS type, 10 AS score, 'public' AS public
 +                                  FROM  profile_public_names
 +                                 WHERE  firstname_ordinary != '' AND pid IN {?})
 +                                UNION
 +                               (SELECT  pid, pseudonym, 'nickname' AS type, 10 AS score, 'public' AS public
 +                                  FROM  profile_public_names
 +                                 WHERE  pseudonym != '' AND pid IN {?})",
 +                              $pids, $pids, $pids, $pids, $pids, $pids, $pids);
          $names = array();
          while ($key = $keys->next()) {
              if ($key['name'] == '') {
                  continue;
              }
 -            $pid   = $key['pid'];
 -            require_once 'name.func.inc.php';
 +            $pid  = $key['pid'];
              $toks = split_name_for_search($key['name']);
              $toks = array_reverse($toks);
  
                  $token = $tok . $token;
                  $names["$pid-$token"] = XDB::format('({?}, {?}, {?}, {?}, {?}, {?})',
                                                      $token, $pid, soundex_fr($token),
 -                                                    $eltScore, $key['public'], $key['general_type']);
 +                                                    $eltScore, $key['public'], $key['type']);
              }
          }
          if ($transaction) {
diff --combined classes/userfilter.php
@@@ -543,12 -543,12 +543,12 @@@ class UserFilter extends PlFilte
  
      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)
          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();
  
      /** 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;
      }
  
@@@ -583,28 -583,67 +583,28 @@@ class UFC_EducationField extends UserFi
      }
  }
  // }}}
 -// {{{ 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_NameInitial
 +/** Filters users based on sort_name
 + * @param $initial Initial on which to filter
   */
 -class UFC_Name extends UserFilterCondition
 +class UFC_NameInitial extends 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 $initial;
  
 -    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)
 +    public function __construct($initial)
      {
 -        $sub = $uf->addNameFilter($type, $variant);
 -        return str_replace('$ME', 'pn' . $sub, $where);
 +        $this->initial = $initial;
      }
  
      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);
 +        $sub = $uf->addDisplayFilter();
 +        return 'SUBSTRING(pd.sort_name, 1, 1) ' . XDB::formatWildcards(XDB::WILDCARD_PREFIX, $this->initial);
      }
  
      public function export()
      {
 -        $export = $this->buildExport($this->type);
 -        if ($this->mode & self::VARIANTS) {
 -            $export['search_in_variants'] = true;
 -        }
 -        if ($this->mode & self::PARTICLE) {
 -            $export['search_in_particle'] = true;
 -        }
 -        $export['comparison'] = self::comparisonFromXDBWildcard($this->mode & 0x3);
 -        $export['text'] = $this->text;
 +        $export = $this->buildExport($this->initial);
          return $export;
      }
  }
@@@ -991,17 -1030,20 +991,20 @@@ class UFC_Email extends UserFilterCondi
  // {{{ class UFC_Address
  abstract class UFC_Address extends UserFilterCondition
  {
-     /** Valid address type ('hq' is reserved for company addresses)
+     /** Valid address type
       */
-     const TYPE_HOME = 1;
-     const TYPE_PRO  = 2;
-     const TYPE_ANY  = 3;
+     const TYPE_HOME     = 1;
+     const TYPE_PRO      = 2;
+     const TYPE_NON_HQ   = 3;
+     const TYPE_HQ       = 4;
+     const TYPE_ANY      = 7;
  
      /** Text for these types
       */
      protected static $typetexts = array(
          self::TYPE_HOME => 'home',
          self::TYPE_PRO  => 'pro',
+         self::TYPE_HQ   => 'hq',
      );
  
      protected $type;
              }
          }
          if (count($types)) {
-             $conds[] = XDB::format($sub . '.type IN {?}', $types);
+             $conds[] = XDB::format('pa' . $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)';
+                     $conds[] = 'FIND_IN_SET(' . XDB::format('{?}', $text) . ', pa' . $sub . '.flags)';
                  }
              }
          }
  
  }
  // }}}
- // {{{ 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, $uf->getVisibilityCondition($sub . '.pub'));
-         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 $type Filter on address type
   * @param $flags Filter on address flags
   */
- class UFC_AddressField extends UFC_Address
+ class UFC_AddressComponent extends UFC_Address
  {
-     const FIELD_COUNTRY    = 1;
-     const FIELD_ADMAREA    = 2;
-     const FIELD_SUBADMAREA = 3;
-     const FIELD_LOCALITY   = 4;
-     const FIELD_ZIPCODE    = 5;
+     static $components = array('sublocality', 'locality', 'administrative_area_level_3', 'administrative_area_level_2', 'administrative_area_level_1', 'country');
  
      /** Data of the filter
       */
      private $val;
      private $fieldtype;
+     private $exact;
  
-     public function __construct($val, $fieldtype, $type = null, $flags = self::FLAG_ANY)
+     public function __construct($val, $fieldtype, $exact = true, $type = null, $flags = self::FLAG_ANY)
      {
-         parent::__construct($type, $flags);
+         if (!in_array($fieldtype, self::$components)) {
+             Platal::page()->killError('Invalid address field type: ' . $this->fieldtype);
+         }
  
+         parent::__construct($type, $flags);
          if (!is_array($val)) {
              $val = array($val);
          }
          $this->val       = $val;
          $this->fieldtype = $fieldtype;
+         $this->exact     = $exact;
      }
  
      public function buildCondition(PlFilter $uf)
      {
-         $sub = $uf->addAddressFilter();
-         $conds = $this->initConds($sub, $uf->getVisibilityCondition($sub . '.pub'));
-         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);
+         $sub = $uf->addAddressFilter($this->fieldtype);
+         $conds = $this->initConds($sub, $uf->getVisibilityCondition('pa' . $sub . '.pub'));
+         $conds[] = XDB::format('pace' . $sub . '.id IN {?}', $this->val);
  
          return implode(' AND ', $conds);
      }
@@@ -77,10 -77,12 +77,10 @@@ function addSearchName(isFemale
      while ($('#search_name_' + i).length != 0) {
          i++;
      }
 -    $('#search_name_' + i)
 -        .updateHtml('profile/ajax/searchname/' + i + '/' + isFemale,
 -                    function(data) {
 -                        $('#searchname').before(data);
 -                        changeNameFlag(i);
 -                    });
 +    $('#search_name_' + i).updateHtml('profile/ajax/searchname/' + i + '/' + isFemale,
 +        function(data) {
 +            $('#searchname').before(data);
 +    });
  }
  
  function removeSearchName(i, isFemale)
      updateNameDisplay(isFemale);
  }
  
 -function changeNameFlag(i)
 -{
 -    $('#flag_' + i).remove();
 -    var typeid = $('#search_name_' + i).find('select').val();
 -    var type   = $('#search_name_' + i).find('select :selected').text();
 -    if ($('[name=sn_type_' + typeid + '_' + i + ']').val() > 0) {
 -        $('#flag_cb_' + i).after('<span id="flag_' + i + '">&nbsp;' +
 -            '<img src="images/icons/flag_green.gif" alt="site public" title="site public" />' +
 -            '<input type="hidden" name="search_names[' + i + '][pub]" value="1"/>' +
 -            '<input type="hidden" name="search_names[' + i + '][typeid]" value="' + typeid + '"/>' +
 -            '<input type="hidden" name="search_names[' + i + '][type]" value="' + type + '"/></span>');
 -    } else {
 -        $('#flag_cb_' + i).after('<span id="flag_' + i + '">&nbsp;' +
 -            '<img src="images/icons/flag_red.gif" alt="site privé" title="site privé" />' +
 -            '<input type="hidden" name="search_names[' + i + '][typeid]" value="' + typeid + '"/>' +
 -            '<input type="hidden" name="search_names[' + i + '][type]" value="' + type + '"/></span>');
 -    }
 -}
 -
  function updateNameDisplay(isFemale)
  {
 +    var lastnames = new Array('lastname_main', 'lastname_ordinary', 'lastname_marital', 'pseudonym');
 +    var firstnames = new Array('firstname_main', 'firstname_ordinary');
      var searchnames = '';
 -    for (var i = 0; i < 10; i++) {
 +
 +    for (var i = 0; i < 4; ++i) {
 +        searchnames += $('.names_advanced').find('[name*=' + lastnames[i] + ']').val() + ';';
 +    }
 +    searchnames += '-;'
 +    for (var i = 0; i < 2; ++i) {
 +        searchnames += $('.names_advanced').find('[name*=' + firstnames[i] + ']').val() + ';';
 +    }
 +    searchnames += '-';
 +
 +    var has_private = false;
 +    for (var i = 0; i < 10; ++i) {
          if ($('#search_name_' + i).find(':text').val()) {
 -            searchnames += $('#search_name_' + i).find('[name*=typeid]').val() + ';';
 -            searchnames += $('#search_name_' + i).find(':text').val() + ';;';
 +            searchnames += ';' + $('#search_name_' + i).find('[name*=type]').val() + ';' + $('#search_name_' + i).find(':text').val();
 +            has_private = true;
          }
      }
 +    searchnames += (has_private ? '' : ';');
      $.xget('profile/ajax/buildnames/' + searchnames + '/' + isFemale,
             function(data){
                 var name = data.split(';');
             });
  }
  
 -function toggleParticle(id)
 -{
 -    if ($('#search_name_' + id).find("[name*='[particle]']").val() == '') {
 -        $('#search_name_' + id).find("[name*='[particle]']").val(1);
 -    } else {
 -        $('#search_name_' + id).find("[name*='[particle]']").val('');
 -    }
 -}
 -
  // Promotions {{{1
  
  function togglePromotionEdition()
@@@ -301,34 -317,41 +301,41 @@@ function checkCurrentAddress(id
      }
  }
  
- function addAddress()
+ function addAddress(pid)
  {
      var i = 0;
      while ($('#addresses_' + i + '_cont').length != 0) {
          i++;
      }
      $('#add_address').before('<div id="addresses_' + i + '_cont"></div>');
-     $('#addresses_' + i + '_cont').updateHtml('profile/ajax/address/' + i,
+     $('#addresses_' + i + '_cont').updateHtml('profile/ajax/address/' + i + '/' + pid,
                                                checkCurrentAddress());
  }
  
- function addressChanged(prefid)
+ function addressChanged(prefid, color)
  {
+     var text = $('#' + prefid + '_cont').find("[name*='[text]']").val();
      $('#' + prefid + '_cont').find('[name*=changed]').val("1");
+     $.xpost('map_url/', { text:text, color:color }, function(data) {
+         $('#' + prefid + '_static_map_url').show();
+         $('#' + prefid + '_static_map_url').find('img').attr('src', data);
+     });
  }
  
- function validGeoloc(prefid, id, geoloc)
+ function deleteGeocoding(prefid)
  {
-     if (geoloc == 1) {
-         $('#' + prefid + '_cont').find('[name*=text]').val($('#' + prefid + '_cont').find('[name*=geocodedText]').val());
-         $('#' + prefid + '_cont').find('[name*=postalText]').val('');
-     }
-     if (geoloc > 0) {
-         $('#' + prefid + '_cont').find("[name*='[geocodedText]']").remove();
+     if($('#' + prefid + '_geocoding_removal').find('[name*=request]:checkbox:checked').length == 0) {
+         return true;
      }
-     $('#' + prefid + '_cont').find('[name*=text]').removeClass('error');
-     $('#' + prefid + '_cont').find('[name*=geocodeChosen]').val(geoloc);
-     $('.' + prefid + '_geoloc').remove();
+     return confirm(
+         "La localisation de l'adresse sert à deux choses : te placer dans "
+         + "le planisphère et te faire apparaître dans la recherche avancée par "
+         + "pays, région, département, ville... La supprimer t'en fera disparaître. "
+         + "\nIl ne faut le faire que si cette localisation "
+         + "est réellement erronée. Avant de supprimer cette localisation, l'équipe de "
+         + "Polytechnique.org tentera de la réparer.\n\nConfirmes-tu ta "
+         + "demande de suppression de cette localisation ?");
  }
  
  // {{{1 Phones
diff --combined include/userset.inc.php
@@@ -245,7 -245,8 +245,7 @@@ class MinificheView extends MixedVie
              && $this->params['starts_with'] != null) {
  
              $this->set->addCond(
 -                new UFC_Name(Profile::LASTNAME,
 -                    $this->params['starts_with'], UFC_Name::PREFIX)
 +                new UFC_NameInitial($this->params['starts_with'])
              );
          }
          return parent::apply($page);
@@@ -358,6 -359,37 +358,37 @@@ class TrombiView extends MixedVie
      }
  }
  
+ class MapView implements PlView
+ {
+     private $set;
+     public function __construct(PlSet $set, array $params)
+     {
+         $this->set = $set;
+     }
+     public function apply(PlPage $page)
+     {
+         Platal::load('geoloc');
+         if (Get::b('ajax')) {
+             $pids = $this->set->getIds(new PlLimit());
+             GeolocModule::assign_json_to_map($page, $pids);
+             $page->runJSON();
+             exit;
+         } else {
+             $this->set->getIds(new PlLimit());
+             GeolocModule::prepare_map($page);
+             return 'geoloc/index.tpl';
+         }
+     }
+     public function args()
+     {
+         return $this->set->args();
+     }
+ }
  class GadgetView implements PlView
  {
      public function __construct(PlSet $set, array $params)
@@@ -38,15 -38,20 +38,20 @@@ function get_annuaire_infos($method, $p
                                  $params[1]);
              $array = $res->next();
          } else {
-             $res = XDB::iterRow("SELECT  p.birthdate, pa.text, pa.postalCode
-                                          gl.name, pa.countryId, p.pid, pa.id
-                                    FROM  profiles          AS p
-                               LEFT JOIN  profile_addresses AS pa ON (pa.pid = p.pid)
-                               LEFT JOIN  geoloc_localities AS gl ON (pl.id = pa.localityId)
+             $res = XDB::iterRow("SELECT  p.birthdate, pa.text, GROUP_CONCAT(pace3.short_name), GROUP_CONCAT(pace2.short_name),
+                                          GROUP_CONCAT(pace1.short_name), p.pid, pa.id
+                                    FROM  profiles                          AS p
+                               LEFT JOIN  profile_addresses                 AS pa    ON (pa.pid = p.pid)
+                               LEFT JOIN  profile_addresses_components      AS pc    ON (pa.pid = pc.pid AND pa.jobid = pc.jobid AND pa.groupid = pc.groupid
+                                                                                         AND pa.type = pc.type AND pa.id = pc.id)
+                               LEFT JOIN  profile_addresses_components_enum AS pace1 ON (FIND_IN_SET(\'country\', pace1.types) AND pace1.id = pc.component_id)
+                               LEFT JOIN  profile_addresses_components_enum AS pace2 ON (FIND_IN_SET(\'locality\', pace2.types) AND pace2.id = pc.component_id)
+                               LEFT JOIN  profile_addresses_components_enum AS pace3 ON (FIND_IN_SET(\'postal_code\', pace3.types) AND pace3.id = pc.component_id)
                                    WHERE  p.xorg_id = {?} AND NOT FIND_IN_SET('job', pa.flags)
                                 ORDER BY  NOT FIND_IN_SET('current', pa.flags),
                                           FIND_IN_SET('secondary', pa.flags),
-                                          NOT FIND_IN_SET('mail', pa.flags)",
+                                          NOT FIND_IN_SET('mail', pa.flags)
+                                GROUP BY  pa.pid, pa.jobid, pa.groupid, pa.id, pa.type",
                                  $params[1]);
              // Process the addresses we got.
              if(list($age, $text, $adr['cp'], $adr['ville'],
@@@ -174,7 -179,10 +179,7 @@@ function get_nouveau_infos($method, $pa
      }
      // We check we actually have an identification number.
      if(!empty($params[1])) {
 -        $nameTypes = DirEnum::getOptions(DirEnum::NAMETYPES);
 -        $nameTypes = array_flip($nameTypes);
 -
 -        $res = XDB::query("SELECT  pnl.name AS nom, pnu.name AS nom_usage, pnf.name AS prenom,
 +        $res = XDB::query("SELECT  ppn.lastname_initial AS nom, ppn.lastname_ordinary AS nom_usage, ppn.firstname_initial AS prenom,
                                     p.sex = 'female' AS femme, p.deathdate IS NOT NULL AS decede,
                                     p.birthdate, pd.promo, CONCAT(e.email, '@', d.name) AS mail
                               FROM  profiles         AS p
                         INNER JOIN  email_source_account AS s ON (s.uid = ap.uid AND FIND_IN_SET('bestalias', s.flags))
                         INNER JOIN  email_virtual_domains AS d ON (s.domain = s.id)
                         INNER JOIN  profile_display  AS pd PN (p.pid = pd.pid)
 -                       INNER JOIN  profile_name AS pnl ON (p.pid = pnl.pid AND pnl.typeid = {?})
 -                       INNER JOIN  profile_name AS pnf ON (p.pid = pnf.pid AND pnf.typeid = {?})
 -                       INNER JOIN  profile_name AS pnu ON (p.pid = pnu.pid AND pnu.typeid = {?})
 +                       INNER JOIN  profile_public_names AS ppn ON (ppn.pid = p.pid)
                              WHERE  a.flags = 'bestalias' AND p.xorg_id = {?}",
 -                          $nameTypes['name_ini'], $nameTypes['lastname_ordinary'],
 -                          $nameTypes['firstname_ini'], $params[1]);
 +                          $params[1]);
          // $data['mail'] .= '@polytechnique.org';
  
  
diff --combined modules/profile.php
@@@ -33,6 -33,7 +33,7 @@@ class ProfileModule extends PLModul
              'profile/ax'                 => $this->make_hook('ax',                         AUTH_COOKIE, 'admin,edit_directory'),
              'profile/edit'               => $this->make_hook('p_edit',                     AUTH_MDP),
              'profile/ajax/address'       => $this->make_hook('ajax_address',               AUTH_COOKIE, 'user', NO_AUTH),
+             'profile/ajax/address/del'   => $this->make_hook('ajax_address_del',           AUTH_MDP),
              'profile/ajax/tel'           => $this->make_hook('ajax_tel',                   AUTH_COOKIE, 'user', NO_AUTH),
              'profile/ajax/edu'           => $this->make_hook('ajax_edu',                   AUTH_COOKIE, 'user', NO_AUTH),
              'profile/ajax/medal'         => $this->make_hook('ajax_medal',                 AUTH_COOKIE, 'user', NO_AUTH),
@@@ -47,6 -48,7 +48,6 @@@
              'javascript/education.js'    => $this->make_hook('education_js',               AUTH_COOKIE),
              'javascript/grades.js'       => $this->make_hook('grades_js',                  AUTH_COOKIE),
              'profile/medal'              => $this->make_hook('medal',                      AUTH_PUBLIC),
 -            'profile/name_info'          => $this->make_hook('name_info',                  AUTH_PUBLIC),
  
              'referent'                   => $this->make_hook('referent',                   AUTH_COOKIE),
              'referent/country'           => $this->make_hook('ref_country',                AUTH_COOKIE, 'user', NO_AUTH),
@@@ -67,6 -69,7 +68,6 @@@
              'admin/trombino'             => $this->make_hook('admin_trombino',             AUTH_MDP,    'admin'),
              'admin/corps_enum'           => $this->make_hook('admin_corps_enum',           AUTH_MDP,    'admin'),
              'admin/corps_rank'           => $this->make_hook('admin_corps_rank',           AUTH_MDP,    'admin'),
 -            'admin/names'                => $this->make_hook('admin_names',                AUTH_MDP,    'admin'),
          );
      }
  
          exit;
      }
  
 -    function handler_name_info($page)
 -    {
 -        pl_content_headers("text/html");
 -        $page->changeTpl('profile/name_info.tpl', SIMPLE);
 -        $res = XDB::iterator("SELECT  name, explanations,
 -                                      FIND_IN_SET('public', flags) AS public,
 -                                      FIND_IN_SET('has_particle', flags) AS has_particle
 -                                FROM  profile_name_enum
 -                               WHERE  NOT FIND_IN_SET('not_displayed', flags)
 -                            ORDER BY  NOT FIND_IN_SET('public', flags)");
 -        $page->assign('types', $res);
 -    }
 -
      function handler_networking($page, $mid)
      {
          $res = XDB::query("SELECT  icon
          }
  
         $page->setTitle('Mon Profil');
+        $page->assign('hrpid', $profile->hrid());
         if (isset($success) && $success) {
             $page->trigSuccess('Ton profil a bien été mis à jour.');
         }
          $page->assign('medal_list', $mlist);
      }
  
-     function handler_ajax_address($page, $id)
+     function handler_ajax_address($page, $id, $pid)
      {
          pl_content_headers("text/html");
          $page->changeTpl('profile/adresses.address.tpl', NO_SKIN);
          $page->assign('i', $id);
          $page->assign('address', array());
+         $page->assign('profile', Profile::get($pid));
+         $page->assign('isMe', true);
+         $page->assign('geocoding_removal', true);
      }
  
      function handler_ajax_tel($page, $prefid, $prefname, $telid, $subField, $mainField, $mainId)
      function handler_ajax_searchname($page, $id, $isFemale)
      {
          pl_content_headers("text/html");
 -        $page->changeTpl('profile/general.searchname.tpl', NO_SKIN);
 -        $res = XDB::query("SELECT  id, name, FIND_IN_SET('public', flags) AS pub
 -                             FROM  profile_name_enum
 -                            WHERE  NOT FIND_IN_SET('not_displayed', flags)
 -                                   AND NOT FIND_IN_SET('always_displayed', flags)");
 -        $page->assign('sn_type_list', $res->fetchAllAssoc());
 +        $page->changeTpl('profile/general.private_name.tpl', NO_SKIN);
 +        $page->assign('other_names', array('nickname' => 'Surnom', 'firstname' => 'Autre prénom', 'lastname' => 'Autre nom'));
 +        $page->assign('new_name', true);
          $page->assign('isFemale', $isFemale);
 -        $page->assign('i', $id);
 +        $page->assign('id', $id);
      }
  
      function handler_ajax_buildnames($page, $data, $isFemale)
                  break;
          }
      }
 -    function handler_admin_names($page, $action = 'list', $id = null) {
 -        $page->setTitle('Administration - Types de noms');
 -        $page->assign('title', 'Gestion des types de noms');
 -        $table_editor = new PLTableEditor('admin/names', 'profile_name_enum', 'id', true);
 -        $table_editor->describe('name', 'Nom', true);
 -        $table_editor->describe('explanations', 'Explications', true);
 -        $table_editor->describe('type', 'Type', true);
 -        $table_editor->describe('flags', 'Flags', true);
 -        $table_editor->describe('score', 'Score', true);
 -        $table_editor->apply($page, $action, $id);
 -    }
      function handler_admin_binets($page, $action = 'list', $id = null) {
          $page->setTitle('Administration - Binets');
          $page->assign('title', 'Gestion des binets');
diff --combined upgrade/1.1.2/README
@@@ -1,4 -1,7 +1,12 @@@
 +Check that the following queries return the same results before updating:
 +SELECT COUNT(*), COUNT(DISTINCT(pid)) FROM profile_name AS pn INNER JOIN profile_name_enum AS pne ON (pn.typeid = pne.id) WHERE pne.type = 'nickname';
 +SELECT COUNT(*), COUNT(DISTINCT(pid)) FROM profile_name AS pn INNER JOIN profile_name_enum AS pne ON (pn.typeid = pne.id) WHERE pne.type = 'name_other';
 +SELECT COUNT(*), COUNT(DISTINCT(pid)) FROM profile_name AS pn INNER JOIN profile_name_enum AS pne ON (pn.typeid = pne.id) WHERE pne.type = 'firstname_other';
++
+ Once all sql/php scripts have be run, run retrieve_address_tables.sh and finally xx_retrieve_geocoding.sql.
+ Then "./formatAddresses.php -g -t g -r e" will format the last ungeocoded addresses.
+ Additions to platal.conf:
+ [Maps]
+ api_version = "3.4" (depending on current version when releasing)
+ language = "fr"