Adds setId functionnality to Phone and Address classes.
[platal.git] / classes / phone.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 Phone is meant to perform most of the access to the table profile_phones.
23 *
24 * profile_phone describes a Phone, which can be related to an Address,
25 * a Job, a Profile or a Company:
26 * - for a Profile:
27 * - `link_type` is set to 'user'
28 * - `link_id` is set to 0
29 * - `pid` is set to the id of the related Profile (in profiles)
30 *
31 * - for a Company:
32 * - `link_type` is set to 'hq'
33 * - `link_id` is set to the id of the related Company (in profile_job_enum)
34 * - `pid` is set to 0
35 *
36 * - for an Address (this only applies to a personal address)
37 * - `link_type` is set to 'address'
38 * - `link_id` is set to the related Address `id` (in profile_addresses)
39 * - `pid` is set to the related Address `pid` (in both profiles and profile_addresses)
40 *
41 * - for a Job:
42 * - `link_type` is set to 'pro'
43 * - `link_id` is set to the related Job `id` (not `jobid`) (in profile_job)
44 * - `pid` is set to the related Job `pid` (in both profiles and profile_job)
45 *
46 * Thus a Phone can be linked to a Company, a Profile, a Job, or a Profile-related Address.
47 */
48 class Phone
49 {
50 const TYPE_FAX = 'fax';
51 const TYPE_FIXED = 'fixed';
52 const TYPE_MOBILE = 'mobile';
53
54 const LINK_JOB = 'pro';
55 const LINK_ADDRESS = 'address';
56 const LINK_PROFILE = 'user';
57 const LINK_COMPANY = 'hq';
58
59 /** The following fields, but $error, all correspond to the fields of the
60 * database table profile_phones.
61 */
62 private $id = 0;
63 private $pid = 0;
64 private $search = '';
65 private $link_type = 'user';
66 private $link_id = 0;
67 // The following fields are the fields of the form in the profile edition.
68 private $type = 'fixed';
69 public $display = '';
70 private $pub = 'private';
71 public $comment = '';
72 private $error = false;
73
74 public function __construct(array $data = array())
75 {
76 if (count($data) > 0) {
77 foreach ($data as $key => $val) {
78 $this->$key = $val;
79 }
80 }
81 }
82
83 public function linkType()
84 {
85 return $this->link_type;
86 }
87
88 public function linkId()
89 {
90 return $this->link_id;
91 }
92
93 public function id()
94 {
95 return $this->id;
96 }
97
98 public function pid()
99 {
100 return $this->pid;
101 }
102
103 public function search()
104 {
105 return $this->search;
106 }
107
108 public function setId($id)
109 {
110 $this->id = $id;
111 }
112
113 /** Returns the unique ID of a phone.
114 * This ID will allow to link it to an address, a user or a job.
115 * The format is address_addressId_phoneId (where phoneId is the id
116 * of the phone in the list of those associated with the address).
117 */
118 public function uniqueId() {
119 return $this->link_type . '_' . $this->link_id . '_' . $this->id;
120 }
121
122 public function hasFlags($flags) {
123 return $this->hasType($flags) && $this->hasLink($flags);
124 }
125
126 /** Returns true if this phone's type matches the flags.
127 */
128 public function hasType($flags) {
129 $flags = $flags & Profile::PHONE_TYPE_ANY;
130 return (
131 ($flags == Profile::PHONE_TYPE_ANY)
132 ||
133 (($flags & Profile::PHONE_TYPE_FAX) && $this->type == self::TYPE_FAX)
134 ||
135 (($flags & Profile::PHONE_TYPE_FIXED) && $this->type == self::TYPE_FIXED)
136 ||
137 (($flags & Profile::PHONE_TYPE_MOBILE) && $this->type == self::TYPE_MOBILE)
138 );
139 }
140
141 /** User-friendly accessible version of the type.
142 */
143 public function displayType($short = false)
144 {
145 switch ($this->type) {
146 case Phone::TYPE_FIXED:
147 return $short ? 'Tél' : 'Fixe';
148 case Phone::TYPE_FAX:
149 return 'Fax';
150 case Phone::TYPE_MOBILE:
151 return $short ? 'Mob' : 'Mobile';
152 default:
153 return $this->type;
154 }
155 }
156
157 /** Returns true if this phone's link matches the flags.
158 */
159 public function hasLink($flags)
160 {
161 $flags = $flags & Profile::PHONE_LINK_ANY;
162 return (
163 ($flags == Profile::PHONE_LINK_ANY)
164 ||
165 (($flags & Profile::PHONE_LINK_COMPANY) && $this->link_type == self::LINK_COMPANY)
166 ||
167 (($flags & Profile::PHONE_LINK_JOB) && $this->link_type == self::LINK_JOB)
168 ||
169 (($flags & Profile::PHONE_LINK_ADDRESS) && $this->link_type == self::LINK_ADDRESS)
170 ||
171 (($flags & Profile::PHONE_LINK_PROFILE) && $this->link_type == self::LINK_PROFILE)
172 );
173 }
174
175 /* Properly formats the search phone, based on actual display phone.
176 *
177 * Computes a base form of the phone number with the international prefix.
178 * This number only contains digits, thus does not begin with the '+' sign.
179 * Numbers starting with 0 (or '(0)') are considered as French.
180 * This assumes that non-French numbers have their international prefix.
181 */
182 private function formatSearch()
183 {
184 $tel = trim($this->display);
185 // Number starting with "(0)" is a French number.
186 if (substr($tel, 0, 3) === '(0)') {
187 $tel = '33' . $tel;
188 }
189 // Removes all "(0)" often used as local prefix.
190 $tel = str_replace('(0)', '', $tel);
191 // Removes all non-digit chars.
192 $tel = preg_replace('/[^0-9]/', '', $tel);
193
194 if (substr($tel, 0, 2) === '00') {
195 // Removes prefix for international calls.
196 $tel = substr($tel, 2);
197 } else if (substr($tel, 0, 1) === '0') {
198 // Number starting with 0 is a French number.
199 $tel = '33' . substr($tel, 1);
200 }
201 $this->search = $tel;
202 }
203
204 // Properly formats the display phone, it requires the search phone to be already formatted.
205 private function formatDisplay($format = array())
206 {
207 $tel = $this->search;
208 $ret = '';
209 $telLength = strlen($tel);
210 // Try to find the country by trying to find a matching prefix of 1, 2 or 3 digits.
211 if ((!isset($format['phoneprf'])) || ($format['phoneprf'] == '')) {
212 $res = XDB::query('SELECT phonePrefix AS phoneprf, phoneFormat AS format
213 FROM geoloc_countries
214 WHERE phonePrefix = {?} OR phonePrefix = {?} OR phonePrefix = {?}
215 LIMIT 1',
216 substr($tel, 0, 1), substr($tel, 0, 2), substr($tel, 0, 3));
217 if ($res->numRows() == 0) {
218 // No country found, does not format more than prepending a '+' sign.
219 $this->error = true;
220 $this->display = '+' . $tel;
221 return;
222 }
223 $format = $res->fetchOneAssoc();
224 }
225 if ($format['format'] == '') {
226 // If the country does not have a phone number format, the number will be displayed
227 // as "+prefix ## ## ## ##...".
228 $format['format'] = '(+p)';
229 }
230
231 /* Formats the phone number according t the template with these rules:
232 * - p is replaced by the international prefix,
233 * - # is replaced by one digit,
234 * - other chars are left intact.
235 * If the number is longer than the format, remaining digits are
236 * appended by blocks of two digits separated by spaces.
237 * The last block can have 3 digits to avoid a final single-digit block.
238 */
239 $j = 0;
240 $i = strlen($format['phoneprf']);
241 $lengthFormat = strlen($format['format']);
242 while (($i < $telLength) && ($j < $lengthFormat)) {
243 if ($format['format'][$j] == '#') {
244 $ret .= $tel[$i];
245 ++$i;
246 } else if ($format['format'][$j] == 'p') {
247 $ret .= $format['phoneprf'];
248 } else {
249 $ret .= $format['format'][$j];
250 }
251 ++$j;
252 }
253 for (; $i < $telLength - 1; $i += 2) {
254 $ret .= ' ' . substr($tel, $i, 2);
255 }
256 // Appends last left alone numbers to the last block.
257 if ($i < $telLength) {
258 $ret .= substr($tel, $i);
259 }
260 $this->display = $ret;
261 }
262
263
264 public function format($format = array())
265 {
266 if (!($this->type == Phone::TYPE_FIXED
267 || $this->type == Phone::TYPE_MOBILE
268 || $this->type == Phone::TYPE_FAX)) {
269 $this->type = Phone::TYPE_FIXED;
270 }
271 $this->formatSearch();
272 $this->formatDisplay($format);
273 return !$this->error;
274 }
275
276 public function toFormArray()
277 {
278 return array(
279 'type' => $this->type,
280 'display' => $this->display,
281 'pub' => $this->pub,
282 'comment' => $this->comment,
283 'error' => $this->error
284 );
285 }
286
287 private function toString()
288 {
289 return 'type : ' . $this->type .', numéro : ' . $this->display
290 . ', commentaire : « ' . $this->comment . ' », affichage : ' . $this->pub;
291 }
292
293 private function isEmpty()
294 {
295 return (!$this->search || $this->search == '');
296 }
297
298 public function save()
299 {
300 $this->format();
301 if (!$this->isEmpty()) {
302 XDB::execute('INSERT INTO profile_phones (pid, link_type, link_id, tel_id, tel_type,
303 search_tel, display_tel, pub, comment)
304 VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})',
305 $this->pid, $this->link_type, $this->link_id, $this->id, $this->type,
306 $this->search, $this->display, $this->pub, $this->comment);
307 }
308 }
309
310 static public function deletePhones($pid, $link_type, $link_id = null)
311 {
312 $where = '';
313 if (!is_null($link_id)) {
314 $where = XDB::format(' AND link_id = {?}', $link_id);
315 }
316 XDB::execute('DELETE FROM profile_phones
317 WHERE pid = {?} AND link_type = {?}' . $where,
318 $pid, $link_type);
319 }
320
321 /** Saves phones into the database.
322 * @param $data: an array of form formatted phones.
323 * @param $pid, $link_type, $link_id: pid, link_type and link_id concerned by the update.
324 */
325 static public function savePhones(array $data, $pid, $link_type, $link_id = null)
326 {
327 foreach ($data as $id => $value) {
328 $value['id'] = $id;
329 if (!is_null($pid)) {
330 $value['pid'] = $pid ;
331 }
332 if (!is_null($link_type)) {
333 $value['link_type'] = $link_type ;
334 }
335 if (!is_null($link_id)) {
336 $value['link_id'] = $link_id ;
337 }
338 $phone = new Phone($value);
339 $phone->save();
340 }
341 }
342
343 static private function formArrayWalk(array $data, $function, &$success = true, $requiresEmptyPhone = false)
344 {
345 $phones = array();
346 foreach ($data as $item) {
347 $phone = new Phone($item);
348 $success = (!$phone->error && ($phone->format() || $phone->isEmpty()) && $success);
349 if (!$phone->isEmpty()) {
350 $phones[] = call_user_func(array($phone, $function));
351 }
352 }
353 if (count($phones) == 0 && $requiresEmptyPhone) {
354 $phone = new Phone();
355 $phones[] = call_user_func(array($phone, $function));
356 }
357 return $phones;
358 }
359
360 // Formats an array of form phones into an array of form formatted phones.
361 static public function formatFormArray(array $data, &$success = true)
362 {
363 return self::formArrayWalk($data, 'toFormArray', $success, true);
364 }
365
366 static public function formArrayToString(array $data)
367 {
368 return implode(' ; ', self::formArrayWalk($data, 'toString'));
369 }
370
371 static public function iterate(array $pids = array(), array $link_types = array(),
372 array $link_ids = array(), array $pubs = array())
373 {
374 return new PhoneIterator($pids, $link_types, $link_ids, $pubs);
375 }
376 }
377
378 /** Iterator over a set of Phones
379 *
380 * @param $pid, $link_type, $link_id, $pub
381 *
382 * The iterator contains the phones that correspond to the value stored in the
383 * parameters' arrays.
384 */
385 class PhoneIterator implements PlIterator
386 {
387 private $dbiter;
388
389 public function __construct(array $pids, array $link_types, array $link_ids, array $pubs)
390 {
391 $where = array();
392 if (count($pids) != 0) {
393 $where[] = XDB::format('(pid IN {?})', $pids);
394 }
395 if (count($link_types) != 0) {
396 $where[] = XDB::format('(link_type IN {?})', $link_types);
397 }
398 if (count($link_ids) != 0) {
399 $where[] = XDB::format('(link_id IN {?})', $link_ids);
400 }
401 if (count($pubs) != 0) {
402 $where[] = XDB::format('(pub IN {?})', $pubs);
403 }
404 $sql = 'SELECT search_tel AS search, display_tel AS display, comment, link_id,
405 tel_type AS type, link_type, tel_id AS id, pid, pub
406 FROM profile_phones
407 ' . ((count($where) > 0) ? 'WHERE ' . implode(' AND ', $where) : '') . '
408 ORDER BY pid, link_id, tel_id';
409 $this->dbiter = XDB::iterator($sql);
410 }
411
412 public function next()
413 {
414 if (is_null($this->dbiter)) {
415 return null;
416 }
417 $data = $this->dbiter->next();
418 if (is_null($data)) {
419 return null;
420 }
421 return new Phone($data);
422 }
423
424 public function total()
425 {
426 return $this->dbiter->total();
427 }
428
429 public function first()
430 {
431 return $this->dbiter->first();
432 }
433
434 public function last()
435 {
436 return $this->dbiter->last();
437 }
438
439 public function value()
440 {
441 return $this->dbiter;
442 }
443 }
444
445 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
446 ?>