Merge branch 'platal-1.0.0'
[platal.git] / classes / address.php
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;
77 public $geocodedPostalText = null;
78 public $geocodeChosen = null;
79
80 // Database's field required for both 'home' and 'job' addresses.
81 public $pub = 'private';
82
83 // Database's fields required for 'home' addresses.
84 public $flags = null; // 'current', 'temporary', 'secondary', 'mail', 'cedex'
85 public $comment = null;
86 public $current = null;
87 public $temporary = null;
88 public $secondary = null;
89 public $mail = null;
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
105 if ($this->type == self::LINK_PROFILE) {
106 if (!is_null($this->flags)) {
107 $this->flags = new PlFlagSet($this->flags);
108 } else {
109 static $flags = array('current', 'temporary', 'secondary', 'mail');
110
111 $this->flags = new PlFlagSet();
112 foreach ($flags as $flag) {
113 if (!is_null($this->$flag) && ($this->$flag == 1 || $this->$flag == 'on')) {
114 $this->flags->addFlag($flag, 1);
115 $this->$flag = null;
116 }
117 $this->flags->addFlag('cedex', (strpos(strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
118 array('', "\n"), $this->text)), 'CEDEX')) !== false);
119 }
120 }
121 }
122 }
123
124 public function phones()
125 {
126 return $this->phones;
127 }
128
129 public function addPhone(Phone &$phone)
130 {
131 if ($phone->linkType() == Phone::LINK_ADDRESS && $phone->pid() == $this->pid) {
132 $this->phones[$phone->uniqueId()] = $phone;
133 }
134 }
135
136 public function hasFlag($flag)
137 {
138 return ($this->flags != null && $this->flags->hasFlag($flag));
139 }
140
141 public function format(array $format = array())
142 {
143 if (empty($format)) {
144 $format['requireGeocoding'] = false;
145 $format['stripGeocoding'] = false;
146 }
147 $this->text = trim($this->text);
148 if ($this->removed == 1) {
149 $this->text = '';
150 return true;
151 }
152
153 if ($format['requireGeocoding'] || $this->changed == 1) {
154 $gmapsGeocoder = new GMapsGeocoder();
155 $gmapsGeocoder->getGeocodedAddress($this);
156 $this->changed = 0;
157 $this->error = !empty($this->geocodedText);
158 }
159 if ($format['stripGeocoding'] || ($this->type == self::LINK_COMPANY && $this->error) || $this->geocodeChosen === '0') {
160 $gmapsGeocoder = new GMapsGeocoder();
161 $gmapsGeocoder->stripGeocodingFromAddress($this);
162 if ($this->geocodeChosen === '0') {
163 $mailer = new PlMailer('profile/geocoding.mail.tpl');
164 $mailer->assign('text', $this->text);
165 $mailer->assign('geoloc', $this->geocodedText);
166 $mailer->send();
167 }
168 }
169 if ($this->countryId == '') {
170 $this->countryId = null;
171 }
172 $this->geocodeChosen = null;
173 $this->phones = Phone::formatFormArray($this->phones, $this->error);
174 return !$this->error;
175 }
176
177 public function toFormArray()
178 {
179 $address = array(
180 'accuracy' => $this->accuracy,
181 'text' => $this->text,
182 'postalText' => $this->postalText,
183 'postalCode' => $this->postalCode,
184 'localityId' => $this->localityId,
185 'subAdministrativeAreaId' => $this->subAdministrativeAreaId,
186 'administrativeAreaId' => $this->administrativeAreaId,
187 'countryId' => $this->countryId,
188 'localityName' => $this->localityName,
189 'subAdministrativeAreaName' => $this->subAdministrativeAreaName,
190 'administrativeAreaName' => $this->administrativeAreaName,
191 'latitude' => $this->latitude,
192 'longitude' => $this->longitude,
193 'north' => $this->north,
194 'south' => $this->south,
195 'east' => $this->east,
196 'west' => $this->west,
197 'error' => $this->error,
198 'changed' => $this->changed,
199 'removed' => $this->removed,
200 );
201 if (!is_null($this->geocodedText)) {
202 $address['geocodedText'] = $this->geocodedText;
203 $address['geocodedPostalText'] = $this->geocodedPostalText;
204 $address['geocodeChosen'] = $this->geocodeChosen;
205 }
206
207 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
208 $address['pub'] = $this->pub;
209 }
210 if ($this->type == self::LINK_PROFILE) {
211 static $flags = array('current', 'temporary', 'secondary', 'mail', 'cedex');
212
213 foreach ($flags as $flag) {
214 $address[$flag] = $this->flags->hasFlag($flag);
215 }
216 $address['comment'] = $this->comment;
217 $address['phones'] = Phone::formatFormArray($this->phones);
218 }
219
220 return $address;
221 }
222
223 private function toString()
224 {
225 $address = 'Adresse : ' . $this->text;
226 if ($this->type == self::LINK_PROFILE || $this->type == self::LINK_JOB) {
227 $address .= ', affichage : ' . $this->pub;
228 }
229 if ($this->type == self::LINK_PROFILE) {
230 static $flags = array(
231 'current' => 'actuelle',
232 'temporary' => 'temporaire',
233 'secondary' => 'secondaire',
234 'mail' => 'conctactable par courier',
235 'cedex' => 'type cédex',
236 );
237
238 $address .= ', commentaire : ' . $this->comment;
239 foreach ($flags as $flag => $flagName) {
240 if ($this->flags->hasFlag($flag)) {
241 $address .= ', ' . $flagName;
242 }
243 }
244 if ($phones = Phone::formArrayToString($this->phones)) {
245 $address .= ', ' . $phones;
246 }
247 }
248 return $address;
249 }
250
251 private function isEmpty()
252 {
253 return (!$this->text || $this->text == '');
254 }
255
256 public function save()
257 {
258 static $areas = array('administrativeArea', 'subAdministrativeArea', 'locality');
259
260 $this->format();
261 if (!$this->isEmpty()) {
262 foreach ($areas as $area) {
263 Geocoder::getAreaId($this, $area);
264 }
265
266 XDB::execute('INSERT INTO profile_addresses (pid, jobid, type, id, flags, accuracy,
267 text, postalText, postalCode, localityId,
268 subAdministrativeAreaId, administrativeAreaId,
269 countryId, latitude, longitude, pub, comment,
270 north, south, east, west)
271 VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?},
272 {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})',
273 $this->pid, $this->jobid, $this->type, $this->id, $this->flags, $this->accuracy,
274 $this->text, $this->postalText, $this->postalCode, $this->localityId,
275 $this->subAdministrativeAreaId, $this->administrativeAreaId,
276 $this->countryId, $this->latitude, $this->longitude,
277 $this->pub, $this->comment,
278 $this->north, $this->south, $this->east, $this->west);
279
280 if ($this->type == self::LINK_PROFILE) {
281 Phone::savePhones($this->phones, $this->pid, Phone::LINK_ADDRESS, $this->id);
282 }
283 }
284 }
285
286 static public function delete($pid, $type, $jobid = null)
287 {
288 $where = '';
289 if (!is_null($pid)) {
290 $where = XDB::format(' AND pid = {?}', $pid);
291 }
292 if (!is_null($jobid)) {
293 $where = XDB::format(' AND jobid = {?}', $jobid);
294 }
295 XDB::execute('DELETE FROM profile_addresses
296 WHERE type = {?}' . $where,
297 $type);
298 if ($type == self::LINK_PROFILE) {
299 Phone::deletePhones($pid, Phone::LINK_ADDRESS);
300 }
301 }
302
303 /** Saves addresses into the database.
304 * @param $data: an array of form formatted addresses.
305 * @param $pid, $type, $linkid: pid, type and id concerned by the update.
306 */
307 static public function saveFromArray(array $data, $pid, $type = self::LINK_PROFILE, $linkid = null)
308 {
309 foreach ($data as $id => $value) {
310 if (!is_null($linkid)) {
311 $value['id'] = $linkid;
312 } else {
313 $value['id'] = $id;
314 }
315 if (!is_null($pid)) {
316 $value['pid'] = $pid;
317 }
318 if (!is_null($type)) {
319 $value['type'] = $type;
320 }
321 $address = new Address($value);
322 $address->save();
323 }
324 }
325
326 static private function formArrayWalk(array $data, $function, &$success = true, $requiresEmptyAddress = false)
327 {
328 $addresses = array();
329 foreach ($data as $item) {
330 $address = new Address($item);
331 $success = ($address->format() && $success);
332 if (!$address->isEmpty()) {
333 $addresses[] = call_user_func(array($address, $function));
334 }
335 }
336 if (count($address) == 0 && $requiresEmptyAddress) {
337 $address = new Address();
338 $addresses[] = call_user_func(array($address, $function));
339 }
340 return $addresses;
341 }
342
343 // Formats an array of form addresses into an array of form formatted addresses.
344 static public function formatFormArray(array $data, &$success = true)
345 {
346 // Only a single address can be the profile's current address and she must have one.
347 $hasCurrent = false;
348 foreach ($data as $key => &$address) {
349 if (isset($address['current']) && $address['current']) {
350 if ($hasCurrent) {
351 $address['current'] = false;
352 } else {
353 $hasCurrent = true;
354 }
355 }
356 }
357 if (!$hasCurrent && count($value) > 0) {
358 foreach ($value as &$address) {
359 $address['current'] = true;
360 break;
361 }
362 }
363
364 return self::formArrayWalk($data, 'toFormArray', $success, true);
365 }
366
367 static public function formArrayToString(array $data)
368 {
369 return implode(' ; ', self::formArrayWalk($data, 'toString'));
370 }
371
372 static public function iterate(array $pids = array(), array $types = array(),
373 array $jobids = array(), array $pubs = array())
374 {
375 return new AddressIterator($pids, $types, $jobids, $pubs);
376 }
377 }
378
379 /** Iterator over a set of Phones
380 *
381 * @param $pid, $type, $jobid, $pub
382 *
383 * The iterator contains the phones that correspond to the value stored in the
384 * parameters' arrays.
385 */
386 class AddressIterator implements PlIterator
387 {
388 private $dbiter;
389
390 public function __construct(array $pids, array $types, array $jobids, array $pubs)
391 {
392 $where = array();
393 if (count($pids) != 0) {
394 $where[] = XDB::format('(pa.pid IN {?})', $pids);
395 }
396 if (count($types) != 0) {
397 $where[] = XDB::format('(pa.type IN {?})', $types);
398 }
399 if (count($jobids) != 0) {
400 $where[] = XDB::format('(pa.jobid IN {?})', $jobids);
401 }
402 if (count($pubs) != 0) {
403 $where[] = XDB::format('(pa.pub IN {?})', $pubs);
404 }
405 $sql = 'SELECT pa.pid, pa.jobid, pa.type, pa.id, pa.flags,
406 pa.accuracy, pa.text, pa.postalText, pa.postalCode,
407 pa.localityId, pa.subAdministrativeAreaId,
408 pa.administrativeAreaId, pa.countryId,
409 pa.latitude, pa.longitude, pa.north, pa.south, pa.east, pa.west,
410 pa.pub, pa.comment,
411 gl.name AS locality, gs.name AS subAdministrativeArea,
412 ga.name AS administrativeArea, gc.countryFR AS country
413 FROM profile_addresses AS pa
414 LEFT JOIN geoloc_localities AS gl ON (gl.id = pa.localityId)
415 LEFT JOIN geoloc_administrativeareas AS ga ON (ga.id = pa.administrativeAreaId)
416 LEFT JOIN geoloc_subadministrativeareas AS gs ON (gs.id = pa.subAdministrativeAreaId)
417 LEFT JOIN geoloc_countries AS gc ON (gc.iso_3166_1_a2 = pa.countryId)
418 WHERE ' . implode(' AND ', $where) . '
419 ORDER BY pa.pid, pa.jobid, pa.id';
420 $this->dbiter = XDB::iterator($sql);
421 }
422
423 public function next()
424 {
425 if (is_null($this->dbiter)) {
426 return null;
427 }
428 $data = $this->dbiter->next();
429 if (is_null($data)) {
430 return null;
431 }
432 // Adds phones to addresses.
433 $it = Phone::iterate(array($data['pid']), array(Phone::LINK_ADDRESS), array($data['id']));
434 while ($phone = $it->next()) {
435 $data['phones'][$phone->id()] = $phone->toFormArray();
436 }
437 return new Address($data);
438 }
439
440 public function total()
441 {
442 return $this->dbiter->total();
443 }
444
445 public function first()
446 {
447 return $this->dbiter->first();
448 }
449
450 public function last()
451 {
452 return $this->dbiter->last();
453 }
454
455 public function value()
456 {
457 return $this->dbiter;
458 }
459 }
460
461 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
462 ?>