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