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