Commit | Line | Data |
---|---|---|
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 | */ | |
46 | class 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; |
9783cb98 SJ |
194 | } else { |
195 | foreach (array('requireGeocoding', 'stripGeocoding', 'postalText') as $type) { | |
196 | $format[$type] = (isset($format[$type])) ? $format[$type] : false; | |
197 | } | |
eb54852e SJ |
198 | } |
199 | $this->text = trim($this->text); | |
200 | if ($this->removed == 1) { | |
201 | $this->text = ''; | |
202 | return true; | |
203 | } | |
204 | ||
eb54852e SJ |
205 | if ($format['requireGeocoding'] || $this->changed == 1) { |
206 | $gmapsGeocoder = new GMapsGeocoder(); | |
207 | $gmapsGeocoder->getGeocodedAddress($this); | |
208 | $this->changed = 0; | |
209 | $this->error = !empty($this->geocodedText); | |
210 | } | |
211 | if ($format['stripGeocoding'] || ($this->type == self::LINK_COMPANY && $this->error) || $this->geocodeChosen === '0') { | |
212 | $gmapsGeocoder = new GMapsGeocoder(); | |
213 | $gmapsGeocoder->stripGeocodingFromAddress($this); | |
214 | if ($this->geocodeChosen === '0') { | |
215 | $mailer = new PlMailer('profile/geocoding.mail.tpl'); | |
216 | $mailer->assign('text', $this->text); | |
217 | $mailer->assign('geoloc', $this->geocodedText); | |
218 | $mailer->send(); | |
219 | } | |
220 | } | |
59e8fb00 SJ |
221 | if ($format['postalText']) { |
222 | $this->formatPostalAddress(); | |
223 | } | |
f2ac8f49 SJ |
224 | if ($this->countryId == '') { |
225 | $this->countryId = null; | |
226 | } | |
eb54852e SJ |
227 | $this->geocodeChosen = null; |
228 | $this->phones = Phone::formatFormArray($this->phones, $this->error); | |
229 | return !$this->error; | |
230 | } | |
231 | ||
232 | public function toFormArray() | |
233 | { | |
234 | $address = array( | |
235 | 'accuracy' => $this->accuracy, | |
236 | 'text' => $this->text, | |
237 | 'postalText' => $this->postalText, | |
238 | 'postalCode' => $this->postalCode, | |
239 | 'localityId' => $this->localityId, | |
240 | 'subAdministrativeAreaId' => $this->subAdministrativeAreaId, | |
241 | 'administrativeAreaId' => $this->administrativeAreaId, | |
242 | 'countryId' => $this->countryId, | |
243 | 'localityName' => $this->localityName, | |
244 | 'subAdministrativeAreaName' => $this->subAdministrativeAreaName, | |
245 | 'administrativeAreaName' => $this->administrativeAreaName, | |
246 | 'latitude' => $this->latitude, | |
247 | 'longitude' => $this->longitude, | |
248 | 'north' => $this->north, | |
249 | 'south' => $this->south, | |
250 | 'east' => $this->east, | |
251 | 'west' => $this->west, | |
252 | 'error' => $this->error, | |
253 | 'changed' => $this->changed, | |
254 | 'removed' => $this->removed, | |
255 | ); | |
256 | if (!is_null($this->geocodedText)) { | |
257 | $address['geocodedText'] = $this->geocodedText; | |
eb54852e SJ |
258 | $address['geocodeChosen'] = $this->geocodeChosen; |
259 | } | |
260 | ||
261 | if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) { | |
262 | $address['pub'] = $this->pub; | |
263 | } | |
264 | if ($this->type == self::LINK_PROFILE) { | |
baee0f5a | 265 | static $flags = array('current', 'temporary', 'secondary', 'mail', 'cedex', 'deliveryIssue'); |
eb54852e SJ |
266 | |
267 | foreach ($flags as $flag) { | |
268 | $address[$flag] = $this->flags->hasFlag($flag); | |
269 | } | |
270 | $address['comment'] = $this->comment; | |
271 | $address['phones'] = Phone::formatFormArray($this->phones); | |
272 | } | |
273 | ||
274 | return $address; | |
275 | } | |
276 | ||
277 | private function toString() | |
278 | { | |
279 | $address = 'Adresse : ' . $this->text; | |
280 | if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) { | |
281 | $address .= ', affichage : ' . $this->pub; | |
282 | } | |
283 | if ($this->type == self::LINK_PROFILE) { | |
284 | static $flags = array( | |
baee0f5a SJ |
285 | 'current' => 'actuelle', |
286 | 'temporary' => 'temporaire', | |
287 | 'secondary' => 'secondaire', | |
288 | 'mail' => 'conctactable par courier', | |
289 | 'deliveryIssue' => 'n\'habite pas à l\'adresse indiquée', | |
290 | 'cedex' => 'type cédex', | |
eb54852e SJ |
291 | ); |
292 | ||
293 | $address .= ', commentaire : ' . $this->comment; | |
294 | foreach ($flags as $flag => $flagName) { | |
295 | if ($this->flags->hasFlag($flag)) { | |
296 | $address .= ', ' . $flagName; | |
297 | } | |
298 | } | |
299 | if ($phones = Phone::formArrayToString($this->phones)) { | |
300 | $address .= ', ' . $phones; | |
301 | } | |
302 | } | |
303 | return $address; | |
304 | } | |
305 | ||
306 | private function isEmpty() | |
307 | { | |
308 | return (!$this->text || $this->text == ''); | |
309 | } | |
310 | ||
311 | public function save() | |
312 | { | |
313 | static $areas = array('administrativeArea', 'subAdministrativeArea', 'locality'); | |
314 | ||
739c49b5 | 315 | $this->format(array('postalText' => true)); |
eb54852e | 316 | if (!$this->isEmpty()) { |
eb54852e SJ |
317 | foreach ($areas as $area) { |
318 | Geocoder::getAreaId($this, $area); | |
319 | } | |
320 | ||
321 | XDB::execute('INSERT INTO profile_addresses (pid, jobid, type, id, flags, accuracy, | |
322 | text, postalText, postalCode, localityId, | |
323 | subAdministrativeAreaId, administrativeAreaId, | |
51d30e26 | 324 | countryId, latitude, longitude, pub, comment, |
eb54852e | 325 | north, south, east, west) |
51d30e26 SJ |
326 | VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, |
327 | {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})', | |
eb54852e SJ |
328 | $this->pid, $this->jobid, $this->type, $this->id, $this->flags, $this->accuracy, |
329 | $this->text, $this->postalText, $this->postalCode, $this->localityId, | |
330 | $this->subAdministrativeAreaId, $this->administrativeAreaId, | |
331 | $this->countryId, $this->latitude, $this->longitude, | |
51d30e26 | 332 | $this->pub, $this->comment, |
eb54852e SJ |
333 | $this->north, $this->south, $this->east, $this->west); |
334 | ||
335 | if ($this->type == self::LINK_PROFILE) { | |
336 | Phone::savePhones($this->phones, $this->pid, Phone::LINK_ADDRESS, $this->id); | |
337 | } | |
338 | } | |
339 | } | |
340 | ||
781a24bc SJ |
341 | public function delete() |
342 | { | |
343 | XDB::execute('DELETE FROM profile_addresses | |
344 | WHERE pid = {?} AND jobid = {?} AND type = {?} AND id = {?}', | |
345 | $this->pid, $this->jobid, $this->type, $this->id); | |
346 | } | |
347 | ||
348 | static public function deleteAddresses($pid, $type, $jobid = null) | |
eb54852e SJ |
349 | { |
350 | $where = ''; | |
351 | if (!is_null($pid)) { | |
352 | $where = XDB::format(' AND pid = {?}', $pid); | |
353 | } | |
354 | if (!is_null($jobid)) { | |
355 | $where = XDB::format(' AND jobid = {?}', $jobid); | |
356 | } | |
357 | XDB::execute('DELETE FROM profile_addresses | |
358 | WHERE type = {?}' . $where, | |
359 | $type); | |
360 | if ($type == self::LINK_PROFILE) { | |
361 | Phone::deletePhones($pid, Phone::LINK_ADDRESS); | |
362 | } | |
363 | } | |
364 | ||
365 | /** Saves addresses into the database. | |
366 | * @param $data: an array of form formatted addresses. | |
367 | * @param $pid, $type, $linkid: pid, type and id concerned by the update. | |
368 | */ | |
369 | static public function saveFromArray(array $data, $pid, $type = self::LINK_PROFILE, $linkid = null) | |
370 | { | |
371 | foreach ($data as $id => $value) { | |
372 | if (!is_null($linkid)) { | |
373 | $value['id'] = $linkid; | |
374 | } else { | |
375 | $value['id'] = $id; | |
376 | } | |
377 | if (!is_null($pid)) { | |
378 | $value['pid'] = $pid; | |
379 | } | |
380 | if (!is_null($type)) { | |
381 | $value['type'] = $type; | |
382 | } | |
383 | $address = new Address($value); | |
384 | $address->save(); | |
385 | } | |
386 | } | |
387 | ||
388 | static private function formArrayWalk(array $data, $function, &$success = true, $requiresEmptyAddress = false) | |
389 | { | |
390 | $addresses = array(); | |
391 | foreach ($data as $item) { | |
392 | $address = new Address($item); | |
393 | $success = ($address->format() && $success); | |
394 | if (!$address->isEmpty()) { | |
395 | $addresses[] = call_user_func(array($address, $function)); | |
396 | } | |
397 | } | |
398 | if (count($address) == 0 && $requiresEmptyAddress) { | |
399 | $address = new Address(); | |
400 | $addresses[] = call_user_func(array($address, $function)); | |
401 | } | |
402 | return $addresses; | |
403 | } | |
404 | ||
405 | // Formats an array of form addresses into an array of form formatted addresses. | |
406 | static public function formatFormArray(array $data, &$success = true) | |
407 | { | |
408 | // Only a single address can be the profile's current address and she must have one. | |
409 | $hasCurrent = false; | |
410 | foreach ($data as $key => &$address) { | |
411 | if (isset($address['current']) && $address['current']) { | |
412 | if ($hasCurrent) { | |
413 | $address['current'] = false; | |
414 | } else { | |
415 | $hasCurrent = true; | |
416 | } | |
417 | } | |
418 | } | |
419 | if (!$hasCurrent && count($value) > 0) { | |
420 | foreach ($value as &$address) { | |
421 | $address['current'] = true; | |
422 | break; | |
423 | } | |
424 | } | |
425 | ||
426 | return self::formArrayWalk($data, 'toFormArray', $success, true); | |
427 | } | |
428 | ||
429 | static public function formArrayToString(array $data) | |
430 | { | |
431 | return implode(' ; ', self::formArrayWalk($data, 'toString')); | |
432 | } | |
433 | ||
434 | static public function iterate(array $pids = array(), array $types = array(), | |
435 | array $jobids = array(), array $pubs = array()) | |
436 | { | |
437 | return new AddressIterator($pids, $types, $jobids, $pubs); | |
438 | } | |
439 | } | |
440 | ||
441 | /** Iterator over a set of Phones | |
442 | * | |
443 | * @param $pid, $type, $jobid, $pub | |
444 | * | |
445 | * The iterator contains the phones that correspond to the value stored in the | |
446 | * parameters' arrays. | |
447 | */ | |
448 | class AddressIterator implements PlIterator | |
449 | { | |
450 | private $dbiter; | |
451 | ||
452 | public function __construct(array $pids, array $types, array $jobids, array $pubs) | |
453 | { | |
454 | $where = array(); | |
455 | if (count($pids) != 0) { | |
456 | $where[] = XDB::format('(pa.pid IN {?})', $pids); | |
457 | } | |
458 | if (count($types) != 0) { | |
459 | $where[] = XDB::format('(pa.type IN {?})', $types); | |
460 | } | |
461 | if (count($jobids) != 0) { | |
462 | $where[] = XDB::format('(pa.jobid IN {?})', $jobids); | |
463 | } | |
464 | if (count($pubs) != 0) { | |
465 | $where[] = XDB::format('(pa.pub IN {?})', $pubs); | |
466 | } | |
467 | $sql = 'SELECT pa.pid, pa.jobid, pa.type, pa.id, pa.flags, | |
468 | pa.accuracy, pa.text, pa.postalText, pa.postalCode, | |
469 | pa.localityId, pa.subAdministrativeAreaId, | |
470 | pa.administrativeAreaId, pa.countryId, | |
471 | pa.latitude, pa.longitude, pa.north, pa.south, pa.east, pa.west, | |
472 | pa.pub, pa.comment, | |
473 | gl.name AS locality, gs.name AS subAdministrativeArea, | |
474 | ga.name AS administrativeArea, gc.countryFR AS country | |
475 | FROM profile_addresses AS pa | |
476 | LEFT JOIN geoloc_localities AS gl ON (gl.id = pa.localityId) | |
477 | LEFT JOIN geoloc_administrativeareas AS ga ON (ga.id = pa.administrativeAreaId) | |
478 | LEFT JOIN geoloc_subadministrativeareas AS gs ON (gs.id = pa.subAdministrativeAreaId) | |
479 | LEFT JOIN geoloc_countries AS gc ON (gc.iso_3166_1_a2 = pa.countryId) | |
323ac187 | 480 | ' . ((count($where) > 0) ? 'WHERE ' . implode(' AND ', $where) : '') . ' |
eb54852e SJ |
481 | ORDER BY pa.pid, pa.jobid, pa.id'; |
482 | $this->dbiter = XDB::iterator($sql); | |
483 | } | |
484 | ||
485 | public function next() | |
486 | { | |
487 | if (is_null($this->dbiter)) { | |
488 | return null; | |
489 | } | |
490 | $data = $this->dbiter->next(); | |
491 | if (is_null($data)) { | |
492 | return null; | |
493 | } | |
494 | // Adds phones to addresses. | |
495 | $it = Phone::iterate(array($data['pid']), array(Phone::LINK_ADDRESS), array($data['id'])); | |
496 | while ($phone = $it->next()) { | |
497 | $data['phones'][$phone->id()] = $phone->toFormArray(); | |
498 | } | |
499 | return new Address($data); | |
500 | } | |
501 | ||
502 | public function total() | |
503 | { | |
504 | return $this->dbiter->total(); | |
505 | } | |
506 | ||
507 | public function first() | |
508 | { | |
509 | return $this->dbiter->first(); | |
510 | } | |
511 | ||
512 | public function last() | |
513 | { | |
514 | return $this->dbiter->last(); | |
515 | } | |
516 | ||
517 | public function value() | |
518 | { | |
519 | return $this->dbiter; | |
520 | } | |
521 | } | |
522 | ||
523 | // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: | |
524 | ?> |