Merge branch 'xorg/master' into xorg/f/geocoding
[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;
eb54852e
SJ
319
320 // Database's field required for both 'home' and 'job' addresses.
5f096c57 321 public $pub = 'ax';
eb54852e
SJ
322
323 // Database's fields required for 'home' addresses.
baee0f5a 324 public $flags = null; // 'current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue'
eb54852e
SJ
325 public $comment = null;
326 public $current = null;
327 public $temporary = null;
328 public $secondary = null;
329 public $mail = null;
baee0f5a 330 public $deliveryIssue = null;
eb54852e
SJ
331
332 // Remaining fields that do not belong to profile_addresses.
333 public $phones = array();
334 public $error = false;
335 public $changed = 0;
336 public $removed = 0;
337
338 public function __construct(array $data = array())
339 {
340 if (count($data) > 0) {
341 foreach ($data as $key => $val) {
342 $this->$key = $val;
343 }
344 }
345
545bc699
FB
346 if (!is_null($this->flags)) {
347 $this->flags = new PlFlagSet($this->flags);
348 } else {
baee0f5a 349 static $flags = array('current', 'temporary', 'secondary', 'mail', 'deliveryIssue');
eb54852e 350
545bc699
FB
351 $this->flags = new PlFlagSet();
352 foreach ($flags as $flag) {
353 if (!is_null($this->$flag) && ($this->$flag == 1 || $this->$flag == 'on')) {
354 $this->flags->addFlag($flag, 1);
355 $this->$flag = null;
eb54852e 356 }
545bc699
FB
357 $this->flags->addFlag('cedex', (strpos(strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
358 array('', "\n"), $this->text)), 'CEDEX')) !== false);
eb54852e
SJ
359 }
360 }
361 }
362
323ac187
SJ
363 public function setId($id)
364 {
365 $this->id = $id;
366 }
367
eb54852e
SJ
368 public function phones()
369 {
370 return $this->phones;
371 }
372
26ba053e 373 public function addPhone(Phone $phone)
eb54852e 374 {
0b53817f 375 if ($phone->link_type == Phone::LINK_ADDRESS && $phone->pid == $this->pid) {
eb54852e
SJ
376 $this->phones[$phone->uniqueId()] = $phone;
377 }
378 }
379
380 public function hasFlag($flag)
381 {
622e7063 382 return ($this->flags != null && $this->flags->hasFlag($flag));
eb54852e
SJ
383 }
384
24ede2d2
SJ
385 public function addFlag($flag)
386 {
387 $this->flags->addFlag($flag);
388 }
389
f5f68e07
SJ
390 /** Auxilary function for formatting postal addresses.
391 * If the needle is found in the haystack, it notifies the substitution's
392 * success, modifies the length accordingly and returns either the matching
393 * substitution or the needle.
394 */
9070ff05 395 private function substitute($needle, $haystack, &$length, &$success, $trim = false)
f5f68e07
SJ
396 {
397 if (array_key_exists($needle, $haystack)) {
398 $success = true;
399 $length -= (strlen($needle) - strlen($haystack[$needle]));
400 return $haystack[$needle];
9070ff05
SJ
401 } elseif ($trim) {
402 $success = true;
403 if (strlen($needle) > 4) {
404 $length -= (strlen($needle) - 4);
405 $needle = $needle{4};
406 }
f5f68e07
SJ
407 }
408 return $needle;
409 }
410
411 /** Checks if the line corresponds to a French street line.
412 * A line is considered a French street line if it starts by between 1 and 4 numbers.
413 */
414 private function isStreetFR($line)
415 {
416 return preg_match('/^\d{1,4}\D/', $line);
417 }
418
419 /** Retrieves a French street number and slit the rest of the line into an array.
420 * @param $words: array containing the rest of the line (a word per cell).
421 * @param $line: line to consider.
422 * Returns the street number.
423 */
424 private function getStreetNumberFR(&$line)
425 {
426 // First we define numbers and separators.
427 $numberReq = '(\d{1,4})\s*(BIS|TER|QUATER|[A-Z])?';
cce807c5
SJ
428 $separatorReq = '\s*(?:\\|-|&|A|ET)?\s*';
429
430 // Then we retrieve the number(s) and the rest of the line.
431 // $matches contains:
432 // -0: the full patern, here the given line,
433 // -1: the number,
434 // -2: its optionnal quantifier,
435 // -3: an optionnal second number,
436 // -4: the second number's optionnal quantifier,
437 // -5: the rest of the line.
438 preg_match('/^' . $numberReq . '(?:' . $separatorReq . $numberReq . ')?\s+(.*)/', $line, $matches);
f5f68e07
SJ
439 $number = $matches[1];
440 $line = $matches[5];
441
442 // If there is a precision on the address, we concatenate it to the number.
443 if ($matches[2] != '') {
cce807c5 444 $number .= $matches[2]{0};
f5f68e07 445 } elseif ($matches[4] != '') {
cce807c5 446 $number .= $matches[4]{0};
f5f68e07
SJ
447 }
448
449 return $number;
450 }
451
452 /** Checks if the line corresponds to a French locality line.
453 * A line is considered a French locality line if it starts by exactly a
454 * postal code of exactly 5 numbers.
455 */
456 private function isLocalityFR($line)
457 {
458 return preg_match('/^\d{5}\D/', $line);
459 }
460
461 /** Retrieves a French postal code and slit the rest of the line into an array.
462 * @param $words: array containing the rest of the line (a word per cell).
463 * @param $line: line to consider.
464 * Returns the postal code, and cuts it out from the line.
465 */
466 private function getPostalCodeFR(&$line)
467 {
468 $number = substr($line, 0, 5);
469 $line = trim(substr($line, 5));
470 return $number;
471 }
472
473 /** Returns the address formated for French postal use (cf AFNOR XPZ 10-011).
474 * A postal addresse containts at most 6 lines of at most 38 characters each:
475 * - addressee's identification ("MONSIEUR JEAN DURAND", "DURAND SA"…),
476 * - delivery point identification ("CHEZ TOTO APPARTEMENT 2", "SERVICE ACHAT"…),
477 * - building localisation complement ("ENTREE A BATIMENT DES JONQUILLES", "ZONE INDUSTRIELLE OUEST"…),
478 * - N° and street name ("25 RUE DES FLEURS", "LES VIGNES"…),
479 * - delivery service, street localisation complement ("BP 40122", "BP 40112 AREYRES"…),
480 * - postal code and locality or cedex code and cedex ("33500 LIBOURNE", "33506 LIBOURNE CEDEX"…).
481 * Punctuation must be removed, all leters must be uppercased.
482 * Both locality and street name must not take more than 32 characters.
483 *
484 * @param $arrayText: array containing the address to be formated, one
485 * address line per array line.
486 * @param $count: array size.
487 */
cce807c5 488 private function formatPostalAddressFR($arrayText)
f5f68e07
SJ
489 {
490 // First removes country if any.
cce807c5 491 $count = count($arrayText);
f5f68e07
SJ
492 if ($arrayText[$count - 1] == 'FRANCE') {
493 unset($arrayText[$count - 1]);
494 --$count;
495 }
496
497 // All the lines must have less than 38 characters but street and
498 // locality lines whose limit is 32 characters.
499 foreach ($arrayText as $lineNumber => $line) {
500 if ($isStreetLine = $this->isStreetFR($line)) {
501 $formattedLine = $this->getStreetNumberFR($line) . ' ';
502 $limit = 32;
503 } elseif ($this->isLocalityFR($line)) {
504 $formattedLine = $this->getPostalCodeFR($line) . ' ';
505 $limit = 32;
59e8fb00 506 } else {
f5f68e07
SJ
507 $formattedLine = '';
508 $limit = 38;
509 }
510
511 $words = explode(' ', $line);
512 $count = count($words);
513 $length = $count - 1;
514 foreach ($words as $word) {
515 $length += strlen($word);
59e8fb00 516 }
f5f68e07
SJ
517
518 // Checks is length is ok. Otherwise, we try to shorten words and
519 // update the length of the current line accordingly.
520 for ($i = 0; $i < $count && $length > $limit; ++$i) {
521 $success = false;
522 if ($isStreetLine) {
9070ff05 523 $sub = $this->substitute($words[$i], Address::$streetAbbreviations, $length, $success, ($i == 0));
f5f68e07
SJ
524 }
525 // Entreprises' substitution are only suitable for the first two lines.
526 if ($lineNumber <= 2 && !$success) {
527 $sub = $this->substitute($words[$i], Address::$entrepriseAbbreviations, $length, $success);
528 }
529 if (!$success) {
530 $sub = $this->substitute($words[$i], Address::$otherAbbreviations, $length, $success);
531 }
532
533 $formattedLine .= $sub . ' ';
534 }
535 for (; $i < $count; ++$i) {
536 $formattedLine .= $words[$i] . ' ';
537 }
538 $arrayText[$lineNumber] = trim($formattedLine);
59e8fb00 539 }
f5f68e07
SJ
540
541 return implode("\n", $arrayText);
542 }
543
544 // Formats postal addresses.
545 // First erases punctuation, accents… Then uppercase the address and finally
546 // calls the country's dedicated formatting function.
547 public function formatPostalAddress()
548 {
549 // Performs rough formatting.
550 $text = mb_strtoupper(replace_accent($this->text));
551 $text = str_replace(array(',', ';', '.', ':', '!', '?', '"', '«', '»'), '', $text);
552 $text = preg_replace('/( |\t)+/', ' ', $text);
553 $arrayText = explode("\n", $text);
554 $arrayText = array_map('trim', $arrayText);
555
928c303e
SJ
556 // Formats according to country rules. Thus we first identify the
557 // country, then apply corresponding formatting or translate country
558 // into default language.
f5f68e07 559 $count = count($arrayText);
b925fb20
SJ
560 if (in_array(strtoupper($this->countryId), Address::$formattings)) {
561 $text = call_user_func(array($this, 'formatPostalAddress' . strtoupper($this->countryId)), $arrayText);
f5f68e07 562 } else {
928c303e
SJ
563 list($countryId, $country) = XDB::fetchOneRow('SELECT gc.iso_3166_1_a2, gc.country
564 FROM geoloc_countries AS gc
565 INNER JOIN geoloc_languages AS gl ON (gc.iso_3166_1_a2 = gl.iso_3166_1_a2)
566 WHERE gc.iso_3166_1_a2 = {?} OR gl.countryPlain = {?} OR gc.countryPlain = {?}',
567 $this->countryId, $arrayText[$count - 1], $arrayText[$count - 1]);
568 if (is_null($countryId)) {
569 $text = $this->formatPostalAddressFR($arrayText);
570 } elseif (in_array(strtoupper($countryId), Address::$formattings)) {
571 $text = call_user_func(array($this, 'formatPostalAddress' . strtoupper($countryId)), $arrayText);
572 } else {
573 $arrayText[$count - 1] = mb_strtoupper(replace_accent($country));
574 $text = implode("\n", $arrayText);
575 }
f5f68e07
SJ
576 }
577
578 $this->postalText = $text;
59e8fb00
SJ
579 }
580
00b2bc89 581 public function format()
eb54852e 582 {
eb54852e 583 $this->text = trim($this->text);
0c1a29ba 584 $this->phones = Phone::formatFormArray($this->phones, $this->error, new ProfileVisibility($this->pub));
eb54852e 585 if ($this->removed == 1) {
0409b3c2 586 if (!S::user()->checkPerms('directory_private') && Phone::hasPrivate($this->phones)) {
0c1a29ba
SJ
587 Platal::page()->trigWarning("L'adresse ne peut être supprimée car elle contient des informations pour lesquelles vous n'avez le droit d'édition.");
588 } else {
589 $this->text = '';
590 return true;
591 }
eb54852e
SJ
592 }
593
00b2bc89
SJ
594 $this->formatPostalAddress();
595 if ($this->changed == 1) {
eb54852e
SJ
596 $gmapsGeocoder = new GMapsGeocoder();
597 $gmapsGeocoder->getGeocodedAddress($this);
eb54852e 598 }
00b2bc89
SJ
599 foreach (array('administrativeArea', 'subAdministrativeArea', 'locality') as $area) {
600 Geocoder::getAreaId($this, $area);
eb54852e 601 }
f2ac8f49
SJ
602 if ($this->countryId == '') {
603 $this->countryId = null;
604 }
00b2bc89
SJ
605
606 return true;
eb54852e
SJ
607 }
608
609 public function toFormArray()
610 {
611 $address = array(
803612ae
SJ
612 'accuracy' => $this->accuracy,
613 'text' => $this->text,
614 'postalText' => $this->postalText,
615 'postalCode' => $this->postalCode,
616 'localityId' => $this->localityId,
617 'subAdministrativeAreaId' => $this->subAdministrativeAreaId,
618 'administrativeAreaId' => $this->administrativeAreaId,
619 'countryId' => $this->countryId,
620 'localityName' => $this->localityName,
621 'subAdministrativeAreaName' => $this->subAdministrativeAreaName,
622 'administrativeAreaName' => $this->administrativeAreaName,
623 'localityNameLocal' => $this->localityNameLocal,
624 'subAdministrativeAreaNameLocal' => $this->subAdministrativeAreaNameLocal,
625 'administrativeAreaNameLocal' => $this->administrativeAreaNameLocal,
626 'latitude' => $this->latitude,
627 'longitude' => $this->longitude,
628 'north' => $this->north,
629 'south' => $this->south,
630 'east' => $this->east,
631 'west' => $this->west,
632 'error' => $this->error,
633 'changed' => $this->changed,
634 'removed' => $this->removed,
eb54852e 635 );
eb54852e
SJ
636
637 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
638 $address['pub'] = $this->pub;
639 }
640 if ($this->type == self::LINK_PROFILE) {
baee0f5a 641 static $flags = array('current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue');
eb54852e
SJ
642
643 foreach ($flags as $flag) {
644 $address[$flag] = $this->flags->hasFlag($flag);
645 }
646 $address['comment'] = $this->comment;
647 $address['phones'] = Phone::formatFormArray($this->phones);
648 }
649
650 return $address;
651 }
652
653 private function toString()
654 {
14aba233 655 $address = $this->text;
eb54852e 656 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
14aba233
SJ
657 static $pubs = array('public' => 'publique', 'ax' => 'annuaire AX', 'private' => 'privé');
658 $address .= ' (affichage ' . $pubs[$this->pub];
eb54852e
SJ
659 }
660 if ($this->type == self::LINK_PROFILE) {
661 static $flags = array(
baee0f5a
SJ
662 'current' => 'actuelle',
663 'temporary' => 'temporaire',
664 'secondary' => 'secondaire',
665 'mail' => 'conctactable par courier',
666 'deliveryIssue' => 'n\'habite pas à l\'adresse indiquée',
667 'cedex' => 'type cédex',
eb54852e
SJ
668 );
669
14aba233
SJ
670 if (!$this->flags->hasFlag('temporary')) {
671 $address .= ', permanente';
672 }
673 if (!$this->flags->hasFlag('secondary')) {
674 $address .= ', principale';
675 }
eb54852e
SJ
676 foreach ($flags as $flag => $flagName) {
677 if ($this->flags->hasFlag($flag)) {
678 $address .= ', ' . $flagName;
679 }
680 }
14aba233
SJ
681 if ($this->comment) {
682 $address .= ', commentaire : ' . $this->comment;
683 }
eb54852e
SJ
684 if ($phones = Phone::formArrayToString($this->phones)) {
685 $address .= ', ' . $phones;
686 }
14aba233
SJ
687 } elseif ($this->type == self::LINK_JOB) {
688 $address .= ')';
eb54852e
SJ
689 }
690 return $address;
691 }
692
693 private function isEmpty()
694 {
695 return (!$this->text || $this->text == '');
696 }
697
698 public function save()
699 {
eb54852e 700 if (!$this->isEmpty()) {
baab50c7 701 XDB::execute('INSERT IGNORE INTO profile_addresses (pid, jobid, groupid, type, id, flags, accuracy,
0c1a29ba
SJ
702 text, postalText, postalCode, localityId,
703 subAdministrativeAreaId, administrativeAreaId,
704 countryId, latitude, longitude, pub, comment,
705 north, south, east, west)
706 VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?},
baab50c7
SJ
707 {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})',
708 $this->pid, $this->jobid, $this->groupid, $this->type, $this->id, $this->flags, $this->accuracy,
eb54852e
SJ
709 $this->text, $this->postalText, $this->postalCode, $this->localityId,
710 $this->subAdministrativeAreaId, $this->administrativeAreaId,
711 $this->countryId, $this->latitude, $this->longitude,
51d30e26 712 $this->pub, $this->comment,
eb54852e
SJ
713 $this->north, $this->south, $this->east, $this->west);
714
715 if ($this->type == self::LINK_PROFILE) {
716 Phone::savePhones($this->phones, $this->pid, Phone::LINK_ADDRESS, $this->id);
717 }
718 }
719 }
720
781a24bc
SJ
721 public function delete()
722 {
723 XDB::execute('DELETE FROM profile_addresses
baab50c7
SJ
724 WHERE pid = {?} AND jobid = {?} AND groupid = {?} AND type = {?} AND id = {?}',
725 $this->pid, $this->jobid, $this->groupid, $this->type, $this->id);
781a24bc
SJ
726 }
727
baab50c7 728 static public function deleteAddresses($pid, $type, $jobid = null, $groupid = null, $deletePrivate = true)
eb54852e
SJ
729 {
730 $where = '';
731 if (!is_null($pid)) {
732 $where = XDB::format(' AND pid = {?}', $pid);
733 }
734 if (!is_null($jobid)) {
735 $where = XDB::format(' AND jobid = {?}', $jobid);
736 }
baab50c7
SJ
737 if (!is_null($groupid)) {
738 $where = XDB::format(' AND groupid = {?}', $groupid);
739 }
eb54852e 740 XDB::execute('DELETE FROM profile_addresses
6592a264 741 WHERE type = {?}' . $where . (($deletePrivate) ? '' : ' AND pub IN (\'public\', \'ax\')'),
eb54852e
SJ
742 $type);
743 if ($type == self::LINK_PROFILE) {
6592a264 744 Phone::deletePhones($pid, Phone::LINK_ADDRESS, null, $deletePrivate);
eb54852e
SJ
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 */
d3ce1117 752 static public function saveFromArray(array $data, $pid, $type = self::LINK_PROFILE, $linkid = null, $savePrivate = true)
eb54852e
SJ
753 {
754 foreach ($data as $id => $value) {
d3ce1117
SJ
755 if ($value['pub'] != 'private' || $savePrivate) {
756 if (!is_null($linkid)) {
757 $value['id'] = $linkid;
758 } else {
759 $value['id'] = $id;
760 }
761 if (!is_null($pid)) {
762 $value['pid'] = $pid;
763 }
764 if (!is_null($type)) {
765 $value['type'] = $type;
766 }
767 $address = new Address($value);
768 $address->save();
eb54852e 769 }
eb54852e
SJ
770 }
771 }
772
773 static private function formArrayWalk(array $data, $function, &$success = true, $requiresEmptyAddress = false)
774 {
775 $addresses = array();
776 foreach ($data as $item) {
777 $address = new Address($item);
778 $success = ($address->format() && $success);
779 if (!$address->isEmpty()) {
780 $addresses[] = call_user_func(array($address, $function));
781 }
782 }
783 if (count($address) == 0 && $requiresEmptyAddress) {
784 $address = new Address();
785 $addresses[] = call_user_func(array($address, $function));
786 }
787 return $addresses;
788 }
789
1f61d10d
SJ
790 // Compares two addresses. First sort by publicity, then place primary
791 // addresses before secondary addresses.
792 static private function compare(array $a, array $b)
793 {
794 $value = ProfileVisibility::comparePublicity($a, $b);
795 if ($value == 0) {
796 if ($a['secondary'] != $b['secondary']) {
797 $value = $a['secondary'] ? 1 : -1;
798 }
799 }
800 return $value;
801 }
802
eb54852e
SJ
803 // Formats an array of form addresses into an array of form formatted addresses.
804 static public function formatFormArray(array $data, &$success = true)
805 {
1f61d10d
SJ
806 $addresses = self::formArrayWalk($data, 'toFormArray', $success, true);
807
eb54852e
SJ
808 // Only a single address can be the profile's current address and she must have one.
809 $hasCurrent = false;
1f61d10d 810 foreach ($addresses as $key => &$address) {
eb54852e
SJ
811 if (isset($address['current']) && $address['current']) {
812 if ($hasCurrent) {
813 $address['current'] = false;
814 } else {
815 $hasCurrent = true;
816 }
817 }
818 }
819 if (!$hasCurrent && count($value) > 0) {
820 foreach ($value as &$address) {
821 $address['current'] = true;
822 break;
823 }
824 }
825
1f61d10d 826 usort($addresses, 'Address::compare');
b3d5464e 827 return $addresses;
eb54852e
SJ
828 }
829
830 static public function formArrayToString(array $data)
831 {
14aba233 832 return implode(', ', self::formArrayWalk($data, 'toString'));
eb54852e
SJ
833 }
834
0c1a29ba
SJ
835 static public function hasPrivate(array $addresses)
836 {
837 foreach ($addresses as $address) {
838 if ($address['pub'] == 'private') {
839 return true;
840 }
841 }
842 return false;
843 }
844
eb54852e
SJ
845 static public function iterate(array $pids = array(), array $types = array(),
846 array $jobids = array(), array $pubs = array())
847 {
848 return new AddressIterator($pids, $types, $jobids, $pubs);
849 }
850}
851
852/** Iterator over a set of Phones
853 *
854 * @param $pid, $type, $jobid, $pub
855 *
856 * The iterator contains the phones that correspond to the value stored in the
857 * parameters' arrays.
858 */
859class AddressIterator implements PlIterator
860{
861 private $dbiter;
862
863 public function __construct(array $pids, array $types, array $jobids, array $pubs)
864 {
865 $where = array();
866 if (count($pids) != 0) {
867 $where[] = XDB::format('(pa.pid IN {?})', $pids);
868 }
869 if (count($types) != 0) {
870 $where[] = XDB::format('(pa.type IN {?})', $types);
871 }
872 if (count($jobids) != 0) {
873 $where[] = XDB::format('(pa.jobid IN {?})', $jobids);
874 }
875 if (count($pubs) != 0) {
876 $where[] = XDB::format('(pa.pub IN {?})', $pubs);
877 }
878 $sql = 'SELECT pa.pid, pa.jobid, pa.type, pa.id, pa.flags,
879 pa.accuracy, pa.text, pa.postalText, pa.postalCode,
880 pa.localityId, pa.subAdministrativeAreaId,
881 pa.administrativeAreaId, pa.countryId,
882 pa.latitude, pa.longitude, pa.north, pa.south, pa.east, pa.west,
883 pa.pub, pa.comment,
1c305d4c
SJ
884 gl.name AS locality, gl.nameLocal AS localityLocal,
885 gs.name AS subAdministrativeArea, gs.nameLocal AS subAdministrativeAreaLocal,
886 ga.name AS administrativeArea, ga.nameLocal AS administrativeAreaLocal,
ee1f5535 887 gc.country
eb54852e
SJ
888 FROM profile_addresses AS pa
889 LEFT JOIN geoloc_localities AS gl ON (gl.id = pa.localityId)
890 LEFT JOIN geoloc_administrativeareas AS ga ON (ga.id = pa.administrativeAreaId)
891 LEFT JOIN geoloc_subadministrativeareas AS gs ON (gs.id = pa.subAdministrativeAreaId)
892 LEFT JOIN geoloc_countries AS gc ON (gc.iso_3166_1_a2 = pa.countryId)
323ac187 893 ' . ((count($where) > 0) ? 'WHERE ' . implode(' AND ', $where) : '') . '
eb54852e
SJ
894 ORDER BY pa.pid, pa.jobid, pa.id';
895 $this->dbiter = XDB::iterator($sql);
896 }
897
898 public function next()
899 {
900 if (is_null($this->dbiter)) {
901 return null;
902 }
903 $data = $this->dbiter->next();
904 if (is_null($data)) {
905 return null;
906 }
907 // Adds phones to addresses.
908 $it = Phone::iterate(array($data['pid']), array(Phone::LINK_ADDRESS), array($data['id']));
909 while ($phone = $it->next()) {
0b53817f 910 $data['phones'][$phone->id] = $phone->toFormArray();
eb54852e
SJ
911 }
912 return new Address($data);
913 }
914
915 public function total()
916 {
917 return $this->dbiter->total();
918 }
919
920 public function first()
921 {
922 return $this->dbiter->first();
923 }
924
925 public function last()
926 {
927 return $this->dbiter->last();
928 }
929
930 public function value()
931 {
932 return $this->dbiter;
933 }
934}
935
936// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
937?>