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