Moves postal address formating to Address class.
[platal.git] / classes / address.php
CommitLineData
eb54852e
SJ
1<?php
2/***************************************************************************
3 * Copyright (C) 2003-2010 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22/** Class Address is meant to perform most of the access to the table profile_addresses.
23 *
24 * profile_addresses describes an Address, which can be related to either a
25 * Profile, a Job or a Company:
26 * - for a Profile:
27 * - `type` is set to 'home'
28 * - `pid` is set to the related profile pid (in profiles)
29 * - `id` is the id of the address in the list of those related to that profile
30 * - `jobid` is set to 0
31 *
32 * - for a Company:
33 * - `type` is set to 'hq'
34 * - `pid` is set to 0
35 * - `jobid` is set to the id of the company (in profile_job_enum)
36 * - `id` is set to 0 (only one address per Company)
37 *
38 * - for a Job:
39 * - `type` is set to 'job'
40 * - `pid` is set to the pid of the Profile of the related Job (in both profiles and profile_job)
41 * - `id` is the id of the job to which we refer (in profile_job)
42 * - `jobid` is set to 0
43 *
44 * Thus an Address can be linked to a Company, a Profile, or a Job.
45 */
46class Address
47{
48 const LINK_JOB = 'job';
49 const LINK_COMPANY = 'hq';
50 const LINK_PROFILE = 'home';
51
52 // Primary key fields: the quadruplet ($pid, $jobid, $type, $id) defines a unique address.
53 public $pid = 0;
54 public $jobid = 0;
55 public $type = Address::LINK_PROFILE;
56 public $id = 0;
57
58 // Geocoding fields.
59 public $accuracy = 0;
60 public $text = '';
61 public $postalText = '';
62 public $postalCode = null;
63 public $localityId = null;
64 public $subAdministrativeAreaId = null;
65 public $administrativeAreaId = null;
66 public $localityName = null;
67 public $subAdministrativeAreaName = null;
68 public $administrativeAreaName = null;
69 public $countryId = null;
70 public $latitude = null;
71 public $longitude = null;
72 public $north = null;
73 public $south = null;
74 public $east = null;
75 public $west = null;
76 public $geocodedText = null;
eb54852e
SJ
77 public $geocodeChosen = null;
78
79 // Database's field required for both 'home' and 'job' addresses.
5f096c57 80 public $pub = 'ax';
eb54852e
SJ
81
82 // Database's fields required for 'home' addresses.
baee0f5a 83 public $flags = null; // 'current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue'
eb54852e
SJ
84 public $comment = null;
85 public $current = null;
86 public $temporary = null;
87 public $secondary = null;
88 public $mail = null;
baee0f5a 89 public $deliveryIssue = null;
eb54852e
SJ
90
91 // Remaining fields that do not belong to profile_addresses.
92 public $phones = array();
93 public $error = false;
94 public $changed = 0;
95 public $removed = 0;
96
97 public function __construct(array $data = array())
98 {
99 if (count($data) > 0) {
100 foreach ($data as $key => $val) {
101 $this->$key = $val;
102 }
103 }
104
545bc699
FB
105 if (!is_null($this->flags)) {
106 $this->flags = new PlFlagSet($this->flags);
107 } else {
baee0f5a 108 static $flags = array('current', 'temporary', 'secondary', 'mail', 'deliveryIssue');
eb54852e 109
545bc699
FB
110 $this->flags = new PlFlagSet();
111 foreach ($flags as $flag) {
112 if (!is_null($this->$flag) && ($this->$flag == 1 || $this->$flag == 'on')) {
113 $this->flags->addFlag($flag, 1);
114 $this->$flag = null;
eb54852e 115 }
545bc699
FB
116 $this->flags->addFlag('cedex', (strpos(strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
117 array('', "\n"), $this->text)), 'CEDEX')) !== false);
eb54852e
SJ
118 }
119 }
120 }
121
323ac187
SJ
122 public function setId($id)
123 {
124 $this->id = $id;
125 }
126
eb54852e
SJ
127 public function phones()
128 {
129 return $this->phones;
130 }
131
132 public function addPhone(Phone &$phone)
133 {
134 if ($phone->linkType() == Phone::LINK_ADDRESS && $phone->pid() == $this->pid) {
135 $this->phones[$phone->uniqueId()] = $phone;
136 }
137 }
138
139 public function hasFlag($flag)
140 {
622e7063 141 return ($this->flags != null && $this->flags->hasFlag($flag));
eb54852e
SJ
142 }
143
59e8fb00
SJ
144 // Returns the address formated for postal use.
145 // The main rules are (cf AFNOR XPZ 10-011):
146 // -everything in upper case;
147 // -if there are more then than 38 characters in a line, split it;
148 // -if there are more then than 32 characters in the description of the "street", use abbreviations.
149 public function formatPostalAddress() {
150 static $abbreviations = array(
151 'IMPASSE' => 'IMP',
152 'RUE' => 'R',
153 'AVENUE' => 'AV',
154 'BOULEVARD' => 'BVD',
155 'ROUTE' => 'R',
156 'STREET' => 'ST',
157 'ROAD' => 'RD',
158 );
159
160 $text = strtoupper($text);
161 $arrayText = explode("\n", $text);
162 $postalText = '';
163
164 foreach ($arrayText as $i => $line) {
165 $postalText .= (($i == 0) ? '' : "\n");
166 if (($length = strlen($line)) > 32) {
167 $words = explode(' ', $line);
168 $count = 0;
169 foreach ($words as $word) {
170 if (isset($abbreviations[$word])) {
171 $word = $abbreviations[$word];
172 }
173 if ($count + ($wordLength = strlen($word)) <= 38) {
174 $postalText .= (($count == 0) ? '' : ' ') . $word;
175 $count += (($count == 0) ? 0 : 1) + $wordLength;
176 } else {
177 $postalText .= "\n" . $word;
178 $count = strlen($word);
179 }
180 }
181 } else {
182 $postalText .= $line;
183 }
184 }
185 $this->postalText = $postalText;
186 }
187
eb54852e
SJ
188 public function format(array $format = array())
189 {
190 if (empty($format)) {
191 $format['requireGeocoding'] = false;
192 $format['stripGeocoding'] = false;
59e8fb00 193 $format['postalText'] = false;
eb54852e
SJ
194 }
195 $this->text = trim($this->text);
196 if ($this->removed == 1) {
197 $this->text = '';
198 return true;
199 }
200
eb54852e
SJ
201 if ($format['requireGeocoding'] || $this->changed == 1) {
202 $gmapsGeocoder = new GMapsGeocoder();
203 $gmapsGeocoder->getGeocodedAddress($this);
204 $this->changed = 0;
205 $this->error = !empty($this->geocodedText);
206 }
207 if ($format['stripGeocoding'] || ($this->type == self::LINK_COMPANY && $this->error) || $this->geocodeChosen === '0') {
208 $gmapsGeocoder = new GMapsGeocoder();
209 $gmapsGeocoder->stripGeocodingFromAddress($this);
210 if ($this->geocodeChosen === '0') {
211 $mailer = new PlMailer('profile/geocoding.mail.tpl');
212 $mailer->assign('text', $this->text);
213 $mailer->assign('geoloc', $this->geocodedText);
214 $mailer->send();
215 }
216 }
59e8fb00
SJ
217 if ($format['postalText']) {
218 $this->formatPostalAddress();
219 }
f2ac8f49
SJ
220 if ($this->countryId == '') {
221 $this->countryId = null;
222 }
eb54852e
SJ
223 $this->geocodeChosen = null;
224 $this->phones = Phone::formatFormArray($this->phones, $this->error);
225 return !$this->error;
226 }
227
228 public function toFormArray()
229 {
230 $address = array(
231 'accuracy' => $this->accuracy,
232 'text' => $this->text,
233 'postalText' => $this->postalText,
234 'postalCode' => $this->postalCode,
235 'localityId' => $this->localityId,
236 'subAdministrativeAreaId' => $this->subAdministrativeAreaId,
237 'administrativeAreaId' => $this->administrativeAreaId,
238 'countryId' => $this->countryId,
239 'localityName' => $this->localityName,
240 'subAdministrativeAreaName' => $this->subAdministrativeAreaName,
241 'administrativeAreaName' => $this->administrativeAreaName,
242 'latitude' => $this->latitude,
243 'longitude' => $this->longitude,
244 'north' => $this->north,
245 'south' => $this->south,
246 'east' => $this->east,
247 'west' => $this->west,
248 'error' => $this->error,
249 'changed' => $this->changed,
250 'removed' => $this->removed,
251 );
252 if (!is_null($this->geocodedText)) {
253 $address['geocodedText'] = $this->geocodedText;
eb54852e
SJ
254 $address['geocodeChosen'] = $this->geocodeChosen;
255 }
256
257 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
258 $address['pub'] = $this->pub;
259 }
260 if ($this->type == self::LINK_PROFILE) {
baee0f5a 261 static $flags = array('current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue');
eb54852e
SJ
262
263 foreach ($flags as $flag) {
264 $address[$flag] = $this->flags->hasFlag($flag);
265 }
266 $address['comment'] = $this->comment;
267 $address['phones'] = Phone::formatFormArray($this->phones);
268 }
269
270 return $address;
271 }
272
273 private function toString()
274 {
275 $address = 'Adresse : ' . $this->text;
276 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
277 $address .= ', affichage : ' . $this->pub;
278 }
279 if ($this->type == self::LINK_PROFILE) {
280 static $flags = array(
baee0f5a
SJ
281 'current' => 'actuelle',
282 'temporary' => 'temporaire',
283 'secondary' => 'secondaire',
284 'mail' => 'conctactable par courier',
285 'deliveryIssue' => 'n\'habite pas à l\'adresse indiquée',
286 'cedex' => 'type cédex',
eb54852e
SJ
287 );
288
289 $address .= ', commentaire : ' . $this->comment;
290 foreach ($flags as $flag => $flagName) {
291 if ($this->flags->hasFlag($flag)) {
292 $address .= ', ' . $flagName;
293 }
294 }
295 if ($phones = Phone::formArrayToString($this->phones)) {
296 $address .= ', ' . $phones;
297 }
298 }
299 return $address;
300 }
301
302 private function isEmpty()
303 {
304 return (!$this->text || $this->text == '');
305 }
306
307 public function save()
308 {
309 static $areas = array('administrativeArea', 'subAdministrativeArea', 'locality');
310
59e8fb00 311 $this->format(array('postalText'));
eb54852e 312 if (!$this->isEmpty()) {
eb54852e
SJ
313 foreach ($areas as $area) {
314 Geocoder::getAreaId($this, $area);
315 }
316
317 XDB::execute('INSERT INTO profile_addresses (pid, jobid, type, id, flags, accuracy,
318 text, postalText, postalCode, localityId,
319 subAdministrativeAreaId, administrativeAreaId,
51d30e26 320 countryId, latitude, longitude, pub, comment,
eb54852e 321 north, south, east, west)
51d30e26
SJ
322 VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?},
323 {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})',
eb54852e
SJ
324 $this->pid, $this->jobid, $this->type, $this->id, $this->flags, $this->accuracy,
325 $this->text, $this->postalText, $this->postalCode, $this->localityId,
326 $this->subAdministrativeAreaId, $this->administrativeAreaId,
327 $this->countryId, $this->latitude, $this->longitude,
51d30e26 328 $this->pub, $this->comment,
eb54852e
SJ
329 $this->north, $this->south, $this->east, $this->west);
330
331 if ($this->type == self::LINK_PROFILE) {
332 Phone::savePhones($this->phones, $this->pid, Phone::LINK_ADDRESS, $this->id);
333 }
334 }
335 }
336
781a24bc
SJ
337 public function delete()
338 {
339 XDB::execute('DELETE FROM profile_addresses
340 WHERE pid = {?} AND jobid = {?} AND type = {?} AND id = {?}',
341 $this->pid, $this->jobid, $this->type, $this->id);
342 }
343
344 static public function deleteAddresses($pid, $type, $jobid = null)
eb54852e
SJ
345 {
346 $where = '';
347 if (!is_null($pid)) {
348 $where = XDB::format(' AND pid = {?}', $pid);
349 }
350 if (!is_null($jobid)) {
351 $where = XDB::format(' AND jobid = {?}', $jobid);
352 }
353 XDB::execute('DELETE FROM profile_addresses
354 WHERE type = {?}' . $where,
355 $type);
356 if ($type == self::LINK_PROFILE) {
357 Phone::deletePhones($pid, Phone::LINK_ADDRESS);
358 }
359 }
360
361 /** Saves addresses into the database.
362 * @param $data: an array of form formatted addresses.
363 * @param $pid, $type, $linkid: pid, type and id concerned by the update.
364 */
365 static public function saveFromArray(array $data, $pid, $type = self::LINK_PROFILE, $linkid = null)
366 {
367 foreach ($data as $id => $value) {
368 if (!is_null($linkid)) {
369 $value['id'] = $linkid;
370 } else {
371 $value['id'] = $id;
372 }
373 if (!is_null($pid)) {
374 $value['pid'] = $pid;
375 }
376 if (!is_null($type)) {
377 $value['type'] = $type;
378 }
379 $address = new Address($value);
380 $address->save();
381 }
382 }
383
384 static private function formArrayWalk(array $data, $function, &$success = true, $requiresEmptyAddress = false)
385 {
386 $addresses = array();
387 foreach ($data as $item) {
388 $address = new Address($item);
389 $success = ($address->format() && $success);
390 if (!$address->isEmpty()) {
391 $addresses[] = call_user_func(array($address, $function));
392 }
393 }
394 if (count($address) == 0 && $requiresEmptyAddress) {
395 $address = new Address();
396 $addresses[] = call_user_func(array($address, $function));
397 }
398 return $addresses;
399 }
400
401 // Formats an array of form addresses into an array of form formatted addresses.
402 static public function formatFormArray(array $data, &$success = true)
403 {
404 // Only a single address can be the profile's current address and she must have one.
405 $hasCurrent = false;
406 foreach ($data as $key => &$address) {
407 if (isset($address['current']) && $address['current']) {
408 if ($hasCurrent) {
409 $address['current'] = false;
410 } else {
411 $hasCurrent = true;
412 }
413 }
414 }
415 if (!$hasCurrent && count($value) > 0) {
416 foreach ($value as &$address) {
417 $address['current'] = true;
418 break;
419 }
420 }
421
422 return self::formArrayWalk($data, 'toFormArray', $success, true);
423 }
424
425 static public function formArrayToString(array $data)
426 {
427 return implode(' ; ', self::formArrayWalk($data, 'toString'));
428 }
429
430 static public function iterate(array $pids = array(), array $types = array(),
431 array $jobids = array(), array $pubs = array())
432 {
433 return new AddressIterator($pids, $types, $jobids, $pubs);
434 }
435}
436
437/** Iterator over a set of Phones
438 *
439 * @param $pid, $type, $jobid, $pub
440 *
441 * The iterator contains the phones that correspond to the value stored in the
442 * parameters' arrays.
443 */
444class AddressIterator implements PlIterator
445{
446 private $dbiter;
447
448 public function __construct(array $pids, array $types, array $jobids, array $pubs)
449 {
450 $where = array();
451 if (count($pids) != 0) {
452 $where[] = XDB::format('(pa.pid IN {?})', $pids);
453 }
454 if (count($types) != 0) {
455 $where[] = XDB::format('(pa.type IN {?})', $types);
456 }
457 if (count($jobids) != 0) {
458 $where[] = XDB::format('(pa.jobid IN {?})', $jobids);
459 }
460 if (count($pubs) != 0) {
461 $where[] = XDB::format('(pa.pub IN {?})', $pubs);
462 }
463 $sql = 'SELECT pa.pid, pa.jobid, pa.type, pa.id, pa.flags,
464 pa.accuracy, pa.text, pa.postalText, pa.postalCode,
465 pa.localityId, pa.subAdministrativeAreaId,
466 pa.administrativeAreaId, pa.countryId,
467 pa.latitude, pa.longitude, pa.north, pa.south, pa.east, pa.west,
468 pa.pub, pa.comment,
469 gl.name AS locality, gs.name AS subAdministrativeArea,
470 ga.name AS administrativeArea, gc.countryFR AS country
471 FROM profile_addresses AS pa
472 LEFT JOIN geoloc_localities AS gl ON (gl.id = pa.localityId)
473 LEFT JOIN geoloc_administrativeareas AS ga ON (ga.id = pa.administrativeAreaId)
474 LEFT JOIN geoloc_subadministrativeareas AS gs ON (gs.id = pa.subAdministrativeAreaId)
475 LEFT JOIN geoloc_countries AS gc ON (gc.iso_3166_1_a2 = pa.countryId)
323ac187 476 ' . ((count($where) > 0) ? 'WHERE ' . implode(' AND ', $where) : '') . '
eb54852e
SJ
477 ORDER BY pa.pid, pa.jobid, pa.id';
478 $this->dbiter = XDB::iterator($sql);
479 }
480
481 public function next()
482 {
483 if (is_null($this->dbiter)) {
484 return null;
485 }
486 $data = $this->dbiter->next();
487 if (is_null($data)) {
488 return null;
489 }
490 // Adds phones to addresses.
491 $it = Phone::iterate(array($data['pid']), array(Phone::LINK_ADDRESS), array($data['id']));
492 while ($phone = $it->next()) {
493 $data['phones'][$phone->id()] = $phone->toFormArray();
494 }
495 return new Address($data);
496 }
497
498 public function total()
499 {
500 return $this->dbiter->total();
501 }
502
503 public function first()
504 {
505 return $this->dbiter->first();
506 }
507
508 public function last()
509 {
510 return $this->dbiter->last();
511 }
512
513 public function value()
514 {
515 return $this->dbiter;
516 }
517}
518
519// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
520?>