2 /***************************************************************************
3 * Copyright (C) 2003-2011 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
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. *
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. *
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 *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
22 /** Class Phone is meant to perform most of the access to the table profile_phones.
24 * profile_phone describes a Phone, which can be related to an Address,
25 * a Job, a Profile or a Company:
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)
32 * - `link_type` is set to 'hq'
33 * - `link_id` is set to the id of the related Company (in profile_job_enum)
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)
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)
46 * Thus a Phone can be linked to a Company, a Profile, a Job, or a Profile-related Address.
50 const TYPE_FAX
= 'fax';
51 const TYPE_FIXED
= 'fixed';
52 const TYPE_MOBILE
= 'mobile';
54 const LINK_JOB
= 'pro';
55 const LINK_ADDRESS
= 'address';
56 const LINK_PROFILE
= 'user';
57 const LINK_COMPANY
= 'hq';
58 const LINK_GROUP
= 'group';
60 /** The following fields, but $error, all correspond to the fields of the
61 * database table profile_phones.
66 public $link_type = 'user';
68 // The following fields are the fields of the form in the profile edition.
69 public $type = 'fixed';
73 public $error = false
;
75 public function __construct(array $data = array())
77 if (count($data) > 0) {
78 foreach ($data as $key => $val) {
84 public function setId($id)
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).
94 public function uniqueId() {
95 return $this->link_type
. '_' . $this->link_id
. '_' . $this->id
;
98 public function hasFlags($flags) {
99 return $this->hasType($flags) && $this->hasLink($flags);
102 /** Returns true if this phone's type matches the flags.
104 public function hasType($flags) {
105 $flags = $flags & Profile
::PHONE_TYPE_ANY
;
107 ($flags == Profile
::PHONE_TYPE_ANY
)
109 (($flags & Profile
::PHONE_TYPE_FAX
) && $this->type
== self
::TYPE_FAX
)
111 (($flags & Profile
::PHONE_TYPE_FIXED
) && $this->type
== self
::TYPE_FIXED
)
113 (($flags & Profile
::PHONE_TYPE_MOBILE
) && $this->type
== self
::TYPE_MOBILE
)
117 /** User-friendly accessible version of the type.
119 public function displayType($short = false
)
121 switch ($this->type
) {
122 case Phone
::TYPE_FIXED
:
123 return $short ?
'Tél' : 'Fixe';
124 case Phone
::TYPE_FAX
:
126 case Phone
::TYPE_MOBILE
:
127 return $short ?
'Mob' : 'Mobile';
133 /** Returns true if this phone's link matches the flags.
135 public function hasLink($flags)
137 $flags = $flags & Profile
::PHONE_LINK_ANY
;
139 ($flags == Profile
::PHONE_LINK_ANY
)
141 (($flags & Profile
::PHONE_LINK_COMPANY
) && $this->link_type
== self
::LINK_COMPANY
)
143 (($flags & Profile
::PHONE_LINK_JOB
) && $this->link_type
== self
::LINK_JOB
)
145 (($flags & Profile
::PHONE_LINK_ADDRESS
) && $this->link_type
== self
::LINK_ADDRESS
)
147 (($flags & Profile
::PHONE_LINK_PROFILE
) && $this->link_type
== self
::LINK_PROFILE
)
151 /* Properly formats the search phone, based on actual display phone.
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.
158 private function formatSearch()
160 $tel = trim($this->display
);
161 // Number starting with "(0)" is a French number.
162 if (substr($tel, 0, 3) === '(0)') {
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);
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);
177 $this->search
= $tel;
180 // Properly formats the display phone, it requires the search phone to be already formatted.
181 private function formatDisplay($format = array())
183 $tel = $this->search
;
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
190 WHERE phonePrefix = {?} OR phonePrefix = {?} OR phonePrefix = {?}
192 substr($tel, 0, 1), substr($tel, 0, 2), substr($tel, 0, 3));
193 if ($res->numRows() == 0) {
194 // No country found, does not format more than prepending a '+' sign.
196 $this->display
= '+' . $tel;
199 $format = $res->fetchOneAssoc();
201 if ($format['format'] == '') {
202 // If the country does not have a phone number format, the number will be displayed
203 // as "+prefix ## ## ## ##...".
204 $format['format'] = '(+p)';
207 /* Formats the phone number according t the template with these rules:
208 * - p is replaced by the international prefix,
209 * - # is replaced by one digit,
210 * - other chars are left intact.
211 * If the number is longer than the format, remaining digits are
212 * appended by blocks of two digits separated by spaces.
213 * The last block can have 3 digits to avoid a final single-digit block.
216 $i = strlen($format['phoneprf']);
217 $lengthFormat = strlen($format['format']);
218 while (($i < $telLength) && ($j < $lengthFormat)) {
219 if ($format['format'][$j] == '#') {
222 } else if ($format['format'][$j] == 'p') {
223 $ret .= $format['phoneprf'];
225 $ret .= $format['format'][$j];
229 for (; $i < $telLength - 1; $i +
= 2) {
230 $ret .= ' ' . substr($tel, $i, 2);
232 // Appends last left alone numbers to the last block.
233 if ($i < $telLength) {
234 $ret .= substr($tel, $i);
236 $this->display
= $ret;
240 public function format($format = array())
242 if (!($this->type
== Phone
::TYPE_FIXED
243 ||
$this->type
== Phone
::TYPE_MOBILE
244 ||
$this->type
== Phone
::TYPE_FAX
)) {
245 $this->type
= Phone
::TYPE_FIXED
;
247 $this->formatSearch();
248 $this->formatDisplay($format);
249 return !$this->error
;
252 public function toFormArray()
255 'type' => $this->type
,
256 'display' => $this->display
,
258 'comment' => $this->comment
,
259 'error' => $this->error
263 private function toString()
265 static $pubs = array('public' => 'publique', 'ax' => 'annuaire AX', 'private' => 'privé');
266 static $types = array('fax' => 'fax', 'fixed' => 'fixe', 'mobile' => 'mobile');
267 return $this->display
. ' (' . $types[$this->type
] . (($this->comment
) ?
', commentaire : « ' . $this->comment
. ' »' : '')
268 . ', affichage ' . $pubs[$this->pub
] . ')';
271 private function isEmpty()
273 return (!$this->search ||
$this->search
== '');
276 public function save()
279 if (!$this->isEmpty()) {
280 XDB
::execute('INSERT IGNORE INTO profile_phones (pid, link_type, link_id, tel_id, tel_type,
281 search_tel, display_tel, pub, comment)
282 VALUES ({?}, {?}, {?}, {?}, {?}, {?}, {?}, {?}, {?})',
283 $this->pid
, $this->link_type
, $this->link_id
, $this->id
, $this->type
,
284 $this->search
, $this->display
, $this->pub
, $this->comment
);
288 public function delete()
290 XDB
::execute('DELETE FROM profile_phones
291 WHERE pid = {?} AND link_type = {?} AND link_id = {?} AND tel_id = {?}',
292 $this->pid
, $this->link_type
, $this->link_id
, $this->id
);
295 static public function deletePhones($pid, $link_type, $link_id = null
, $deletePrivate = true
)
298 if (!is_null($link_id)) {
299 $where = XDB
::format(' AND link_id = {?}', $link_id);
301 XDB
::execute('DELETE FROM profile_phones
302 WHERE pid = {?} AND link_type = {?}' . $where . (($deletePrivate) ?
'' : ' AND pub IN (\'public\', \'ax\')'),
306 /** Saves phones into the database.
307 * @param $data: an array of form formatted phones.
308 * @param $pid, $link_type, $link_id: pid, link_type and link_id concerned by the update.
310 static public function savePhones(array $data, $pid, $link_type, $link_id = null
)
312 foreach ($data as $id => $value) {
314 if (!is_null($pid)) {
315 $value['pid'] = $pid ;
317 if (!is_null($link_type)) {
318 $value['link_type'] = $link_type ;
320 if (!is_null($link_id)) {
321 $value['link_id'] = $link_id ;
323 $phone = new Phone($value);
328 static private function formArrayWalk(array $data, $function, &$success = true
, $requiresEmptyPhone = false
, $maxPublicity = null
)
331 foreach ($data as $item) {
332 $phone = new Phone($item);
333 $success = (!$phone->error
&& ($phone->format() ||
$phone->isEmpty()) && $success);
334 if (!$phone->isEmpty()) {
335 if (!is_null($maxPublicity) && $maxPublicity->isVisible($phone->pub
)) {
336 $phone->pub
= $maxPublicity->level();
338 $phones[] = call_user_func(array($phone, $function));
341 if (count($phones) == 0 && $requiresEmptyPhone) {
342 $phone = new Phone();
343 if (!is_null($maxPublicity) && $maxPublicity->isVisible($phone->pub
)) {
344 $phone->pub
= $maxPublicity->level();
346 $phones[] = call_user_func(array($phone, $function));
351 // Formats an array of form phones into an array of form formatted phones.
352 static public function formatFormArray(array $data, &$success = true
, $maxPublicity = null
)
354 $phones = self
::formArrayWalk($data, 'toFormArray', $success, true
, $maxPublicity);
355 usort($phones, 'ProfileVisibility::comparePublicity');
359 static public function formArrayToString(array $data)
361 return implode(', ', self
::formArrayWalk($data, 'toString'));
364 static public function hasPrivate(array $phones)
366 foreach ($phones as $phone) {
367 if ($phone['pub'] == 'private') {
374 static public function iterate(array $pids = array(), array $link_types = array(),
375 array $link_ids = array(), array $pubs = array())
377 return new PhoneIterator($pids, $link_types, $link_ids, $pubs);
381 /** Iterator over a set of Phones
383 * @param $pid, $link_type, $link_id, $pub
385 * The iterator contains the phones that correspond to the value stored in the
386 * parameters' arrays.
388 class PhoneIterator
implements PlIterator
392 public function __construct(array $pids, array $link_types, array $link_ids, array $pubs)
395 if (count($pids) != 0) {
396 $where[] = XDB
::format('(pid IN {?})', $pids);
398 if (count($link_types) != 0) {
399 $where[] = XDB
::format('(link_type IN {?})', $link_types);
401 if (count($link_ids) != 0) {
402 $where[] = XDB
::format('(link_id IN {?})', $link_ids);
404 if (count($pubs) != 0) {
405 $where[] = XDB
::format('(pub IN {?})', $pubs);
407 $sql = 'SELECT search_tel AS search, display_tel AS display, comment, link_id,
408 tel_type AS type, link_type, tel_id AS id, pid, pub
410 ' . ((count($where) > 0) ?
'WHERE ' . implode(' AND ', $where) : '') . '
411 ORDER BY pid, link_id, tel_id';
412 $this->dbiter
= XDB
::iterator($sql);
415 public function next()
417 if (is_null($this->dbiter
)) {
420 $data = $this->dbiter
->next();
421 if (is_null($data)) {
424 return new Phone($data);
427 public function total()
429 return $this->dbiter
->total();
432 public function first()
434 return $this->dbiter
->first();
437 public function last()
439 return $this->dbiter
->last();
442 public function value()
444 return $this->dbiter
;
448 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: