The first word of the street line should be truncated to 4 letters.
[platal.git] / classes / address.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2010 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22 /** Class Address is meant to perform most of the access to the table profile_addresses.
23 *
24 * profile_addresses describes an Address, which can be related to either a
25 * Profile, a Job or a Company:
26 * - for a Profile:
27 * - `type` is set to 'home'
28 * - `pid` is set to the related profile pid (in profiles)
29 * - `id` is the id of the address in the list of those related to that profile
30 * - `jobid` is set to 0
31 *
32 * - for a Company:
33 * - `type` is set to 'hq'
34 * - `pid` is set to 0
35 * - `jobid` is set to the id of the company (in profile_job_enum)
36 * - `id` is set to 0 (only one address per Company)
37 *
38 * - for a Job:
39 * - `type` is set to 'job'
40 * - `pid` is set to the pid of the Profile of the related Job (in both profiles and profile_job)
41 * - `id` is the id of the job to which we refer (in profile_job)
42 * - `jobid` is set to 0
43 *
44 * Thus an Address can be linked to a Company, a Profile, or a Job.
45 */
46 class Address
47 {
48 const LINK_JOB = 'job';
49 const LINK_COMPANY = 'hq';
50 const LINK_PROFILE = 'home';
51
52 // List of all available postal formattings.
53 private static $formattings = array('FRANCE' => 'FR');
54
55 // Abbreviations to be used to format French postal addresses.
56 private static $streetAbbreviations = array(
57 'ALLEE' => 'ALL',
58 'AVENUE' => 'AV',
59 'BOULEVARD' => 'BD',
60 'CENTRE' => 'CTRE',
61 'CENTRE COMMERCIAL' => 'CCAL',
62 'IMMEUBLE' => 'IMM',
63 'IMMEUBLES' => 'IMM',
64 'IMPASSE' => 'IMP',
65 'LIEU-DIT' => 'LD',
66 'LOTISSEMENT' => 'LOT',
67 'PASSAGE' => 'PAS',
68 'PLACE' => 'PL',
69 'RESIDENCE' => 'RES',
70 'ROND-POINT' => 'RPT',
71 'ROUTE' => 'RTE',
72 'SQUARE' => 'SQ',
73 'VILLAGE' => 'VLGE',
74 'ZONE D\'ACTIVITE' => 'ZA',
75 'ZONE D\'AMENAGEMENT CONCERTE' => 'ZAC',
76 'ZONE D\'AMENAGEMENT DIFFERE' => 'ZAD',
77 'ZONE INDUSTRIELLE' => 'ZI'
78 );
79 private static $otherAbbreviations = array(
80 'ADJUDANT' => 'ADJ',
81 'AERODROME' => 'AERD',
82 'AEROGARE' => 'AERG',
83 'AERONAUTIQUE' => 'AERN',
84 'AEROPORT' => 'AERP',
85 'AGENCE' => 'AGCE',
86 'AGRICOLE' => 'AGRIC',
87 'ANCIEN' => 'ANC',
88 'ANCIENNEMENT' => 'ANC',
89 'APPARTEMENT' => 'APP',
90 'APPARTEMENTS' => 'APP',
91 'ARMEMENT' => 'ARMT',
92 'ARRONDISSEMENT' => 'ARR',
93 'ASPIRANT' => 'ASP',
94 'ASSOCIATION' => 'ASSOC',
95 'ASSURANCE' => 'ASSUR',
96 'ATELIER' => 'AT',
97 'BARAQUEMENT' => 'BRQ',
98 'BAS' => 'BAS',
99 'BASSE' => 'BAS',
100 'BASSES' => 'BAS',
101 'BATAILLON' => 'BTN',
102 'BATAILLONS' => 'BTN',
103 'BATIMENT' => 'BAT',
104 'BATIMENTS' => 'BAT',
105 'BIS' => 'B',
106 'BOITE POSTALE' => 'BP',
107 'CABINET' => 'CAB',
108 'CANTON' => 'CANT',
109 'CARDINAL' => 'CDL',
110 'CASE POSTALE' => 'CP',
111 'CHAMBRE' => 'CHBR',
112 'CITADELLE' => 'CTD',
113 'COLLEGE' => 'COLL',
114 'COLONEL' => 'CNL',
115 'COLONIE' => 'COLO',
116 'COMITE' => 'CTE',
117 'COMMANDANT' => 'CDT',
118 'COMMERCIAL' => 'CIAL',
119 'COMMUNE' => 'COM',
120 'COMMUNAL' => 'COM',
121 'COMMUNAUX' => 'COM',
122 'COMPAGNIE' => 'CIE',
123 'COMPAGNON' => 'COMP',
124 'COMPAGNONS' => 'COMP',
125 'COOPERATIVE' => 'COOP',
126 'COURSE SPECIALE' => 'CS',
127 'CROIX' => 'CRX',
128 'DELEGATION' => 'DELEG',
129 'DEPARTEMENTAL' => 'DEP',
130 'DEPARTEMENTAUX' => 'DEP',
131 'DIRECTEUR' => 'DIR',
132 'DIRECTECTION' => 'DIR',
133 'DIVISION' => 'DIV',
134 'DOCTEUR' => 'DR',
135 'ECONOMIE' => 'ECO',
136 'ECONOMIQUE' => 'ECO',
137 'ECRIVAIN' => 'ECRIV',
138 'ECRIVAINS' => 'ECRIV',
139 'ENSEIGNEMENT' => 'ENST',
140 'ENSEMBLE' => 'ENS',
141 'ENTREE' => 'ENT',
142 'ENTREES' => 'ENT',
143 'ENTREPRISE' => 'ENTR',
144 'EPOUX' => 'EP',
145 'EPOUSE' => 'EP',
146 'ETABLISSEMENT' => 'ETS',
147 'ETAGE' => 'ETG',
148 'ETAT MAJOR' => 'EM',
149 'EVEQUE' => 'EVQ',
150 'FACULTE' => 'FAC',
151 'FORET' => 'FOR',
152 'FORESTIER' => 'FOR',
153 'FRANCAIS' => 'FR',
154 'FRANCAISE' => 'FR',
155 'FUSILIER' => 'FUS',
156 'GENDARMERIE' => 'GEND',
157 'GENERAL' => 'GAL',
158 'GOUVERNEMENTAL' => 'GOUV',
159 'GOUVERNEUR' => 'GOU',
160 'GRAND' => 'GD',
161 'GRANDE' => 'GDE',
162 'GRANDES' => 'GDES',
163 'GRANDS' => 'GDS',
164 'HAUT' => 'HT',
165 'HAUTE' => 'HTE',
166 'HAUTES' => 'HTES',
167 'HAUTS' => 'HTS',
168 'HOPITAL' => 'HOP',
169 'HOPITAUX' => 'HOP',
170 'HOSPICE' => 'HOSP',
171 'HOSPITALIER' => 'HOSP',
172 'HOTEL' => 'HOT',
173 'INFANTERIE' => 'INFANT',
174 'INFERIEUR' => 'INF',
175 'INFERIEUR' => 'INF',
176 'INGENIEUR' => 'ING',
177 'INSPECTEUR' => 'INSP',
178 'INSTITUT' => 'INST',
179 'INTERNATIONAL' => 'INTERN',
180 'INTERNATIONALE' => 'INTERN',
181 'LABORATOIRE' => 'LABO',
182 'LIEUTENANT' => 'LT',
183 'LIEUTENANT DE VAISSEAU' => 'LTDV',
184 'MADAME' => 'MME',
185 'MADEMOISELLE' => 'MLLE',
186 'MAGASIN' => 'MAG',
187 'MAISON' => 'MAIS',
188 'MAITRE' => 'ME',
189 'MARECHAL' => 'MAL',
190 'MARITIME' => 'MAR',
191 'MEDECIN' => 'MED',
192 'MEDICAL' => 'MED',
193 'MESDAMES' => 'MMES',
194 'MESDEMOISELLES' => 'MLLES',
195 'MESSIEURS' => 'MM',
196 'MILITAIRE' => 'MIL',
197 'MINISTERE' => 'MIN',
198 'MONSEIGNEUR' => 'MGR',
199 'MONSIEUR' => 'M',
200 'MUNICIPAL' => 'MUN',
201 'MUTUEL' => 'MUT',
202 'NATIONAL' => 'NAL',
203 'NOTRE DAME' => 'ND',
204 'NOUVEAU' => 'NOUV',
205 'NOUVEL' => 'NOUV',
206 'NOUVELLE' => 'NOUV',
207 'OBSERVATOIRE' => 'OBS',
208 'PASTEUR' => 'PAST',
209 'PETIT' => 'PT',
210 'PETITE' => 'PTE',
211 'PETITES' => 'PTES',
212 'PETITS' => 'PTS',
213 'POLICE' => 'POL',
214 'PREFET' => 'PREF',
215 'PREFECTURE' => 'PREF',
216 'PRESIDENT' => 'PDT',
217 'PROFESSEUR' => 'PR',
218 'PROFESSIONNEL' => 'PROF',
219 'PROFESSIONNELE' => 'PROF',
220 'PROLONGE' => 'PROL',
221 'PROLONGEE' => 'PROL',
222 'PROPRIETE' => 'PROP',
223 'QUATER' => 'Q',
224 'QUINQUIES' => 'C',
225 'RECTEUR' => 'RECT',
226 'REGIMENT' => 'RGT',
227 'REGION' => 'REG',
228 'REGIONAL' => 'REG',
229 'REGIONALE' => 'REG',
230 'REPUBLIQUE' => 'REP',
231 'RESTAURANT' => 'REST',
232 'SAINT' => 'ST',
233 'SAINTE' => 'STE',
234 'SAINTES' => 'STES',
235 'SAINTS' => 'STS',
236 'SANATORIUM' => 'SANA',
237 'SERGENT' => 'SGT',
238 'SERVICE' => 'SCE',
239 'SOCIETE' => 'SOC',
240 'SOUS COUVERT' => 'SC',
241 'SOUS-PREFET' => 'SPREF',
242 'SUPERIEUR' => 'SUP',
243 'SUPERIEURE' => 'SUP',
244 'SYNDICAT' => 'SYND',
245 'TECHNICIEN' => 'TECH',
246 'TECHNICIENNE' => 'TECH',
247 'TECHNICIQUE' => 'TECH',
248 'TER' => 'T',
249 'TRI SERVICE ARRIVEE' => 'TSA',
250 'TUNNEL' => 'TUN',
251 'UNIVERSITAIRE' => 'UNVT',
252 'UNIVERSITE' => 'UNIV',
253 'VELODROME' => 'VELOD',
254 'VEUVE' => 'VVE',
255 'VIEILLE' => 'VIEL',
256 'VIEILLES' => 'VIEL',
257 'VIEUX' => 'VX'
258 );
259 private static $entrepriseAbbreviations = array(
260 'COOPERATIVE D\'UTILISATION DE MATERIEL AGRICOLE EN COMMUN' => 'CUMA',
261 'ETABLISSEMENT PUBLIC A CARACTERE INDUSTRIEL ET COMMERCIAL' => 'EPIC',
262 'ETABLISSEMENT PUBLIC ADMINISTRATIF' => 'EPA',
263 'GROUPEMENT AGRICOLE D\'EXPLOITATION EN COMMUN' => 'GAEC',
264 'GROUPEMENT D\'INTERET ECONOMIQUE' => 'GIE',
265 'GROUPEMENT D\'INTERET PUBLIC' => 'GIP',
266 'GROUPEMENT EUROPEEN D\'INTERET ECONOMIQUE' => 'GEIE',
267 'OFFICE PUBLIC D\'HABITATION A LOYER MODERE' => 'OPHLM',
268 'SOCIETE A RESPONSABILITE LIMITEE' => 'SARL',
269 'SOCIETE ANONYME' => 'SA',
270 'SOCIETE CIVILE DE PLACEMENT COLLECTIF IMMOBILIER' => 'SCPI',
271 'SOCIETE CIVILE PROFESSIONNELLE' => 'SCP',
272 'SOCIETE COOPERATIVE OUVRIERE DE PRODUCTION ET DE CREDIT' => 'SCOP',
273 'SOCIETE D\'AMENAGEMENT FONCIER ET D\'EQUIPEMENT RURAL' => 'SAFER',
274 'SOCIETE D\'ECONOMIE MIXTE' => 'SEM',
275 'SOCIETE D\'INTERET COLLECTIF AGRICOLE' => 'SICA',
276 'SOCIETE D\'INVESTISSEMENT A CAPITAL VARIABLE' => 'SICAV',
277 'SOCIETE EN NOM COLLECTIF' => 'SNC',
278 'SOCIETE IMMOBILIERE POUR LE COMMERCE ET L\'INDUSTRIE' => 'SICOMI',
279 'SOCIETE MIXTE D\'INTERET AGRICOLE' => 'SMIA',
280 'SYNDICAT INTERCOMMUNAL A VOCATION MULTIPLE' => 'SIVOM',
281 'SYNDICAT INTERCOMMUNAL A VOCATION UNIQUE' => 'SIVU'
282 );
283
284 // Primary key fields: the quadruplet ($pid, $jobid, $type, $id) defines a unique address.
285 public $pid = 0;
286 public $jobid = 0;
287 public $type = Address::LINK_PROFILE;
288 public $id = 0;
289
290 // Geocoding fields.
291 public $accuracy = 0;
292 public $text = '';
293 public $postalText = '';
294 public $postalCode = null;
295 public $localityId = null;
296 public $subAdministrativeAreaId = null;
297 public $administrativeAreaId = null;
298 public $localityName = null;
299 public $subAdministrativeAreaName = null;
300 public $administrativeAreaName = null;
301 public $countryId = null;
302 public $latitude = null;
303 public $longitude = null;
304 public $north = null;
305 public $south = null;
306 public $east = null;
307 public $west = null;
308 public $geocodedText = null;
309 public $geocodeChosen = null;
310
311 // Database's field required for both 'home' and 'job' addresses.
312 public $pub = 'ax';
313
314 // Database's fields required for 'home' addresses.
315 public $flags = null; // 'current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue'
316 public $comment = null;
317 public $current = null;
318 public $temporary = null;
319 public $secondary = null;
320 public $mail = null;
321 public $deliveryIssue = null;
322
323 // Remaining fields that do not belong to profile_addresses.
324 public $phones = array();
325 public $error = false;
326 public $changed = 0;
327 public $removed = 0;
328
329 public function __construct(array $data = array())
330 {
331 if (count($data) > 0) {
332 foreach ($data as $key => $val) {
333 $this->$key = $val;
334 }
335 }
336
337 if (!is_null($this->flags)) {
338 $this->flags = new PlFlagSet($this->flags);
339 } else {
340 static $flags = array('current', 'temporary', 'secondary', 'mail', 'deliveryIssue');
341
342 $this->flags = new PlFlagSet();
343 foreach ($flags as $flag) {
344 if (!is_null($this->$flag) && ($this->$flag == 1 || $this->$flag == 'on')) {
345 $this->flags->addFlag($flag, 1);
346 $this->$flag = null;
347 }
348 $this->flags->addFlag('cedex', (strpos(strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
349 array('', "\n"), $this->text)), 'CEDEX')) !== false);
350 }
351 }
352 }
353
354 public function setId($id)
355 {
356 $this->id = $id;
357 }
358
359 public function phones()
360 {
361 return $this->phones;
362 }
363
364 public function addPhone(Phone &$phone)
365 {
366 if ($phone->linkType() == Phone::LINK_ADDRESS && $phone->pid() == $this->pid) {
367 $this->phones[$phone->uniqueId()] = $phone;
368 }
369 }
370
371 public function hasFlag($flag)
372 {
373 return ($this->flags != null && $this->flags->hasFlag($flag));
374 }
375
376 /** Auxilary function for formatting postal addresses.
377 * If the needle is found in the haystack, it notifies the substitution's
378 * success, modifies the length accordingly and returns either the matching
379 * substitution or the needle.
380 */
381 private function substitute($needle, $haystack, &$length, &$success, $trim = false)
382 {
383 if (array_key_exists($needle, $haystack)) {
384 $success = true;
385 $length -= (strlen($needle) - strlen($haystack[$needle]));
386 return $haystack[$needle];
387 } elseif ($trim) {
388 $success = true;
389 if (strlen($needle) > 4) {
390 $length -= (strlen($needle) - 4);
391 $needle = $needle{4};
392 }
393 }
394 return $needle;
395 }
396
397 /** Checks if the line corresponds to a French street line.
398 * A line is considered a French street line if it starts by between 1 and 4 numbers.
399 */
400 private function isStreetFR($line)
401 {
402 return preg_match('/^\d{1,4}\D/', $line);
403 }
404
405 /** Retrieves a French street number and slit the rest of the line into an array.
406 * @param $words: array containing the rest of the line (a word per cell).
407 * @param $line: line to consider.
408 * Returns the street number.
409 */
410 private function getStreetNumberFR(&$line)
411 {
412 // First we define numbers and separators.
413 $numberReq = '(\d{1,4})\s*(BIS|TER|QUATER|[A-Z])?';
414 $separatorReq = '\s*(?:\\|-|&|A|ET)?\s*';
415
416 // Then we retrieve the number(s) and the rest of the line.
417 // $matches contains:
418 // -0: the full patern, here the given line,
419 // -1: the number,
420 // -2: its optionnal quantifier,
421 // -3: an optionnal second number,
422 // -4: the second number's optionnal quantifier,
423 // -5: the rest of the line.
424 preg_match('/^' . $numberReq . '(?:' . $separatorReq . $numberReq . ')?\s+(.*)/', $line, $matches);
425 $number = $matches[1];
426 $line = $matches[5];
427
428 // If there is a precision on the address, we concatenate it to the number.
429 if ($matches[2] != '') {
430 $number .= $matches[2]{0};
431 } elseif ($matches[4] != '') {
432 $number .= $matches[4]{0};
433 }
434
435 return $number;
436 }
437
438 /** Checks if the line corresponds to a French locality line.
439 * A line is considered a French locality line if it starts by exactly a
440 * postal code of exactly 5 numbers.
441 */
442 private function isLocalityFR($line)
443 {
444 return preg_match('/^\d{5}\D/', $line);
445 }
446
447 /** Retrieves a French postal code and slit the rest of the line into an array.
448 * @param $words: array containing the rest of the line (a word per cell).
449 * @param $line: line to consider.
450 * Returns the postal code, and cuts it out from the line.
451 */
452 private function getPostalCodeFR(&$line)
453 {
454 $number = substr($line, 0, 5);
455 $line = trim(substr($line, 5));
456 return $number;
457 }
458
459 /** Returns the address formated for French postal use (cf AFNOR XPZ 10-011).
460 * A postal addresse containts at most 6 lines of at most 38 characters each:
461 * - addressee's identification ("MONSIEUR JEAN DURAND", "DURAND SA"…),
462 * - delivery point identification ("CHEZ TOTO APPARTEMENT 2", "SERVICE ACHAT"…),
463 * - building localisation complement ("ENTREE A BATIMENT DES JONQUILLES", "ZONE INDUSTRIELLE OUEST"…),
464 * - N° and street name ("25 RUE DES FLEURS", "LES VIGNES"…),
465 * - delivery service, street localisation complement ("BP 40122", "BP 40112 AREYRES"…),
466 * - postal code and locality or cedex code and cedex ("33500 LIBOURNE", "33506 LIBOURNE CEDEX"…).
467 * Punctuation must be removed, all leters must be uppercased.
468 * Both locality and street name must not take more than 32 characters.
469 *
470 * @param $arrayText: array containing the address to be formated, one
471 * address line per array line.
472 * @param $count: array size.
473 */
474 private function formatPostalAddressFR($arrayText)
475 {
476 // First removes country if any.
477 $count = count($arrayText);
478 if ($arrayText[$count - 1] == 'FRANCE') {
479 unset($arrayText[$count - 1]);
480 --$count;
481 }
482
483 // All the lines must have less than 38 characters but street and
484 // locality lines whose limit is 32 characters.
485 foreach ($arrayText as $lineNumber => $line) {
486 if ($isStreetLine = $this->isStreetFR($line)) {
487 $formattedLine = $this->getStreetNumberFR($line) . ' ';
488 $limit = 32;
489 } elseif ($this->isLocalityFR($line)) {
490 $formattedLine = $this->getPostalCodeFR($line) . ' ';
491 $limit = 32;
492 } else {
493 $formattedLine = '';
494 $limit = 38;
495 }
496
497 $words = explode(' ', $line);
498 $count = count($words);
499 $length = $count - 1;
500 foreach ($words as $word) {
501 $length += strlen($word);
502 }
503
504 // Checks is length is ok. Otherwise, we try to shorten words and
505 // update the length of the current line accordingly.
506 for ($i = 0; $i < $count && $length > $limit; ++$i) {
507 $success = false;
508 if ($isStreetLine) {
509 $sub = $this->substitute($words[$i], Address::$streetAbbreviations, $length, $success, ($i == 0));
510 }
511 // Entreprises' substitution are only suitable for the first two lines.
512 if ($lineNumber <= 2 && !$success) {
513 $sub = $this->substitute($words[$i], Address::$entrepriseAbbreviations, $length, $success);
514 }
515 if (!$success) {
516 $sub = $this->substitute($words[$i], Address::$otherAbbreviations, $length, $success);
517 }
518
519 $formattedLine .= $sub . ' ';
520 }
521 for (; $i < $count; ++$i) {
522 $formattedLine .= $words[$i] . ' ';
523 }
524 $arrayText[$lineNumber] = trim($formattedLine);
525 }
526
527 return implode("\n", $arrayText);
528 }
529
530 // Formats postal addresses.
531 // First erases punctuation, accents… Then uppercase the address and finally
532 // calls the country's dedicated formatting function.
533 public function formatPostalAddress()
534 {
535 // Performs rough formatting.
536 $text = mb_strtoupper(replace_accent($this->text));
537 $text = str_replace(array(',', ';', '.', ':', '!', '?', '"', '«', '»'), '', $text);
538 $text = preg_replace('/( |\t)+/', ' ', $text);
539 $arrayText = explode("\n", $text);
540 $arrayText = array_map('trim', $arrayText);
541
542 // Search for country.
543 $countries = DirEnum::getOptions(DirEnum::COUNTRIES);
544 $countries = array_map('replace_accent', $countries);
545 $countries = array_map('strtoupper', $countries);
546 $count = count($arrayText);
547 if (in_array(strtoupper($this->countryId), Address::$formattings)) {
548 $text = call_user_func(array($this, 'formatPostalAddress' . strtoupper($this->countryId)), $arrayText);
549 } elseif (array_key_exists($arrayText[$count - 1], Address::$formattings)) {
550 $text = call_user_func(array($this, 'formatPostalAddress' . Address::$formattings[$arrayText[$count - 1]]), $arrayText);
551 } elseif (!in_array($arrayText[$count - 1], $countries)) {
552 $text = $this->formatPostalAddressFR($arrayText);
553 } else {
554 $text = implode("\n", $arrayText);
555 }
556
557 $this->postalText = $text;
558 }
559
560 public function format(array $format = array())
561 {
562 if (empty($format)) {
563 $format['requireGeocoding'] = false;
564 $format['stripGeocoding'] = false;
565 $format['postalText'] = false;
566 } else {
567 foreach (array('requireGeocoding', 'stripGeocoding', 'postalText') as $type) {
568 $format[$type] = (isset($format[$type])) ? $format[$type] : false;
569 }
570 }
571 $this->text = trim($this->text);
572 if ($this->removed == 1) {
573 $this->text = '';
574 return true;
575 }
576
577 if ($format['requireGeocoding'] || $this->changed == 1) {
578 $gmapsGeocoder = new GMapsGeocoder();
579 $gmapsGeocoder->getGeocodedAddress($this);
580 $this->changed = 0;
581 $this->error = !empty($this->geocodedText);
582 }
583 if ($format['stripGeocoding'] || ($this->type == self::LINK_COMPANY && $this->error) || $this->geocodeChosen === '0') {
584 $gmapsGeocoder = new GMapsGeocoder();
585 $gmapsGeocoder->stripGeocodingFromAddress($this);
586 if ($this->geocodeChosen === '0') {
587 $mailer = new PlMailer('profile/geocoding.mail.tpl');
588 $mailer->assign('text', $this->text);
589 $mailer->assign('geoloc', $this->geocodedText);
590 $mailer->send();
591 }
592 }
593 if ($this->countryId == '') {
594 $this->countryId = null;
595 }
596 $this->geocodeChosen = null;
597 $this->phones = Phone::formatFormArray($this->phones, $this->error, new ProfileVisibility($this->pub));
598 if ($format['postalText']) {
599 $this->formatPostalAddress();
600 }
601 return !$this->error;
602 }
603
604 public function toFormArray()
605 {
606 $address = array(
607 'accuracy' => $this->accuracy,
608 'text' => $this->text,
609 'postalText' => $this->postalText,
610 'postalCode' => $this->postalCode,
611 'localityId' => $this->localityId,
612 'subAdministrativeAreaId' => $this->subAdministrativeAreaId,
613 'administrativeAreaId' => $this->administrativeAreaId,
614 'countryId' => $this->countryId,
615 'localityName' => $this->localityName,
616 'subAdministrativeAreaName' => $this->subAdministrativeAreaName,
617 'administrativeAreaName' => $this->administrativeAreaName,
618 'latitude' => $this->latitude,
619 'longitude' => $this->longitude,
620 'north' => $this->north,
621 'south' => $this->south,
622 'east' => $this->east,
623 'west' => $this->west,
624 'error' => $this->error,
625 'changed' => $this->changed,
626 'removed' => $this->removed,
627 );
628 if (!is_null($this->geocodedText)) {
629 $address['geocodedText'] = $this->geocodedText;
630 $address['geocodeChosen'] = $this->geocodeChosen;
631 }
632
633 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
634 $address['pub'] = $this->pub;
635 }
636 if ($this->type == self::LINK_PROFILE) {
637 static $flags = array('current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue');
638
639 foreach ($flags as $flag) {
640 $address[$flag] = $this->flags->hasFlag($flag);
641 }
642 $address['comment'] = $this->comment;
643 $address['phones'] = Phone::formatFormArray($this->phones);
644 }
645
646 return $address;
647 }
648
649 private function toString()
650 {
651 $address = $this->text;
652 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
653 static $pubs = array('public' => 'publique', 'ax' => 'annuaire AX', 'private' => 'privé');
654 $address .= ' (affichage ' . $pubs[$this->pub];
655 }
656 if ($this->type == self::LINK_PROFILE) {
657 static $flags = array(
658 'current' => 'actuelle',
659 'temporary' => 'temporaire',
660 'secondary' => 'secondaire',
661 'mail' => 'conctactable par courier',
662 'deliveryIssue' => 'n\'habite pas à l\'adresse indiquée',
663 'cedex' => 'type cédex',
664 );
665
666 if (!$this->flags->hasFlag('temporary')) {
667 $address .= ', permanente';
668 }
669 if (!$this->flags->hasFlag('secondary')) {
670 $address .= ', principale';
671 }
672 foreach ($flags as $flag => $flagName) {
673 if ($this->flags->hasFlag($flag)) {
674 $address .= ', ' . $flagName;
675 }
676 }
677 if ($this->comment) {
678 $address .= ', commentaire : ' . $this->comment;
679 }
680 if ($phones = Phone::formArrayToString($this->phones)) {
681 $address .= ', ' . $phones;
682 }
683 } elseif ($this->type == self::LINK_JOB) {
684 $address .= ')';
685 }
686 return $address;
687 }
688
689 private function isEmpty()
690 {
691 return (!$this->text || $this->text == '');
692 }
693
694 public function save()
695 {
696 static $areas = array('administrativeArea', 'subAdministrativeArea', 'locality');
697
698 $this->format(array('postalText' => true));
699 if (!$this->isEmpty()) {
700 foreach ($areas as $area) {
701 Geocoder::getAreaId($this, $area);
702 }
703
704 XDB::execute('INSERT INTO profile_addresses (pid, jobid, type, id, flags, accuracy,
705 text, postalText, postalCode, localityId,
706 subAdministrativeAreaId, administrativeAreaId,
707 countryId, latitude, longitude, pub, comment,
708 north, south, east, west)
709 VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?},
710 {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})',
711 $this->pid, $this->jobid, $this->type, $this->id, $this->flags, $this->accuracy,
712 $this->text, $this->postalText, $this->postalCode, $this->localityId,
713 $this->subAdministrativeAreaId, $this->administrativeAreaId,
714 $this->countryId, $this->latitude, $this->longitude,
715 $this->pub, $this->comment,
716 $this->north, $this->south, $this->east, $this->west);
717
718 if ($this->type == self::LINK_PROFILE) {
719 Phone::savePhones($this->phones, $this->pid, Phone::LINK_ADDRESS, $this->id);
720 }
721 }
722 }
723
724 public function delete()
725 {
726 XDB::execute('DELETE FROM profile_addresses
727 WHERE pid = {?} AND jobid = {?} AND type = {?} AND id = {?}',
728 $this->pid, $this->jobid, $this->type, $this->id);
729 }
730
731 static public function deleteAddresses($pid, $type, $jobid = null, $deletePrivate = true)
732 {
733 $where = '';
734 if (!is_null($pid)) {
735 $where = XDB::format(' AND pid = {?}', $pid);
736 }
737 if (!is_null($jobid)) {
738 $where = XDB::format(' AND jobid = {?}', $jobid);
739 }
740 XDB::execute('DELETE FROM profile_addresses
741 WHERE type = {?}' . $where . (($deletePrivate) ? '' : ' AND pub IN (\'public\', \'ax\')'),
742 $type);
743 if ($type == self::LINK_PROFILE) {
744 Phone::deletePhones($pid, Phone::LINK_ADDRESS, null, $deletePrivate);
745 }
746 }
747
748 /** Saves addresses into the database.
749 * @param $data: an array of form formatted addresses.
750 * @param $pid, $type, $linkid: pid, type and id concerned by the update.
751 */
752 static public function saveFromArray(array $data, $pid, $type = self::LINK_PROFILE, $linkid = null)
753 {
754 foreach ($data as $id => $value) {
755 if (!is_null($linkid)) {
756 $value['id'] = $linkid;
757 } else {
758 $value['id'] = $id;
759 }
760 if (!is_null($pid)) {
761 $value['pid'] = $pid;
762 }
763 if (!is_null($type)) {
764 $value['type'] = $type;
765 }
766 $address = new Address($value);
767 $address->save();
768 }
769 }
770
771 static private function formArrayWalk(array $data, $function, &$success = true, $requiresEmptyAddress = false)
772 {
773 $addresses = array();
774 foreach ($data as $item) {
775 $address = new Address($item);
776 $success = ($address->format() && $success);
777 if (!$address->isEmpty()) {
778 $addresses[] = call_user_func(array($address, $function));
779 }
780 }
781 if (count($address) == 0 && $requiresEmptyAddress) {
782 $address = new Address();
783 $addresses[] = call_user_func(array($address, $function));
784 }
785 return $addresses;
786 }
787
788 // Formats an array of form addresses into an array of form formatted addresses.
789 static public function formatFormArray(array $data, &$success = true)
790 {
791 // Only a single address can be the profile's current address and she must have one.
792 $hasCurrent = false;
793 foreach ($data as $key => &$address) {
794 if (isset($address['current']) && $address['current']) {
795 if ($hasCurrent) {
796 $address['current'] = false;
797 } else {
798 $hasCurrent = true;
799 }
800 }
801 }
802 if (!$hasCurrent && count($value) > 0) {
803 foreach ($value as &$address) {
804 $address['current'] = true;
805 break;
806 }
807 }
808
809 return self::formArrayWalk($data, 'toFormArray', $success, true);
810 }
811
812 static public function formArrayToString(array $data)
813 {
814 return implode(', ', self::formArrayWalk($data, 'toString'));
815 }
816
817 static public function iterate(array $pids = array(), array $types = array(),
818 array $jobids = array(), array $pubs = array())
819 {
820 return new AddressIterator($pids, $types, $jobids, $pubs);
821 }
822 }
823
824 /** Iterator over a set of Phones
825 *
826 * @param $pid, $type, $jobid, $pub
827 *
828 * The iterator contains the phones that correspond to the value stored in the
829 * parameters' arrays.
830 */
831 class AddressIterator implements PlIterator
832 {
833 private $dbiter;
834
835 public function __construct(array $pids, array $types, array $jobids, array $pubs)
836 {
837 $where = array();
838 if (count($pids) != 0) {
839 $where[] = XDB::format('(pa.pid IN {?})', $pids);
840 }
841 if (count($types) != 0) {
842 $where[] = XDB::format('(pa.type IN {?})', $types);
843 }
844 if (count($jobids) != 0) {
845 $where[] = XDB::format('(pa.jobid IN {?})', $jobids);
846 }
847 if (count($pubs) != 0) {
848 $where[] = XDB::format('(pa.pub IN {?})', $pubs);
849 }
850 $sql = 'SELECT pa.pid, pa.jobid, pa.type, pa.id, pa.flags,
851 pa.accuracy, pa.text, pa.postalText, pa.postalCode,
852 pa.localityId, pa.subAdministrativeAreaId,
853 pa.administrativeAreaId, pa.countryId,
854 pa.latitude, pa.longitude, pa.north, pa.south, pa.east, pa.west,
855 pa.pub, pa.comment,
856 gl.name AS locality, gs.name AS subAdministrativeArea,
857 ga.name AS administrativeArea, gc.countryFR AS country
858 FROM profile_addresses AS pa
859 LEFT JOIN geoloc_localities AS gl ON (gl.id = pa.localityId)
860 LEFT JOIN geoloc_administrativeareas AS ga ON (ga.id = pa.administrativeAreaId)
861 LEFT JOIN geoloc_subadministrativeareas AS gs ON (gs.id = pa.subAdministrativeAreaId)
862 LEFT JOIN geoloc_countries AS gc ON (gc.iso_3166_1_a2 = pa.countryId)
863 ' . ((count($where) > 0) ? 'WHERE ' . implode(' AND ', $where) : '') . '
864 ORDER BY pa.pid, pa.jobid, pa.id';
865 $this->dbiter = XDB::iterator($sql);
866 }
867
868 public function next()
869 {
870 if (is_null($this->dbiter)) {
871 return null;
872 }
873 $data = $this->dbiter->next();
874 if (is_null($data)) {
875 return null;
876 }
877 // Adds phones to addresses.
878 $it = Phone::iterate(array($data['pid']), array(Phone::LINK_ADDRESS), array($data['id']));
879 while ($phone = $it->next()) {
880 $data['phones'][$phone->id()] = $phone->toFormArray();
881 }
882 return new Address($data);
883 }
884
885 public function total()
886 {
887 return $this->dbiter->total();
888 }
889
890 public function first()
891 {
892 return $this->dbiter->first();
893 }
894
895 public function last()
896 {
897 return $this->dbiter->last();
898 }
899
900 public function value()
901 {
902 return $this->dbiter;
903 }
904 }
905
906 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
907 ?>