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