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