Issues (4868)

addressbook/inc/class.addressbook_zpush.inc.php (15 issues)

1
<?php
2
/**
3
 * EGroupware: eSync: Addressbook plugin
4
 *
5
 * @link http://www.egroupware.org
6
 * @package addressbook
7
 * @subpackage esync
8
 * @author Ralf Becker <[email protected]>
9
 * @author Klaus Leithoff <[email protected]>
10
 * @author Philip Herbert <[email protected]>
11
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
12
 * @version $Id$
13
 */
14
15
use EGroupware\Api;
16
use EGroupware\Api\Acl;
17
18
/**
19
 * Addressbook activesync plugin
20
 */
21
class addressbook_zpush implements activesync_plugin_write, activesync_plugin_search_gal
0 ignored issues
show
The type activesync_plugin_search_gal was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
22
{
23
	/**
24
	 * @var activesync_backend
0 ignored issues
show
The type activesync_backend was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
	 */
26
	private $backend;
27
28
	/**
29
	 * Instance of addressbook_bo
30
	 *
31
	 * @var Api\Contacts
32
	 */
33
	private $addressbook;
34
35
	/**
36
	 * Mapping of ActiveSync SyncContact attributes to EGroupware contact array-keys
37
	 *
38
	 * @var array
39
	 */
40
	static public $mapping = array(
41
		//'anniversary'	=> '',
42
		'assistantname'	=> 'assistent',
43
		'assistnamephonenumber'	=> 'tel_assistent',
44
		'birthday'	=>	'bday',
45
		'body'	=> 'note',
46
		//'bodysize'	=> '',
47
		//'bodytruncated'	=> '',
48
		'business2phonenumber'	=> 'tel_other',
49
		'businesscity'	=>	'adr_one_locality',
50
		'businesscountry'	=> 'adr_one_countryname',
51
		'businesspostalcode'	=> 'adr_one_postalcode',
52
		'businessstate'	=> 'adr_one_region',
53
		'businessstreet'	=> 'adr_one_street',
54
		'businessfaxnumber'	=> 'tel_fax',
55
		'businessphonenumber'	=> 'tel_work',
56
		'carphonenumber'	=> 'tel_car',
57
		'categories'	=> 'cat_id',
58
		//'children'	=> '',	// collection of 'child' elements
59
		'companyname'	=> 'org_name',
60
		'department'	=>	'org_unit',
61
		'email1address'	=> 'email',
62
		'email2address'	=> 'email_home',
63
		//'email3address'	=> '',
64
		'fileas'	=>	'n_fileas',
65
		'firstname'	=>	'n_given',
66
		'home2phonenumber'	=> 'tel_cell_private',
67
		'homecity'	=> 'adr_two_locality',
68
		'homecountry'	=> 'adr_two_countryname',
69
		'homepostalcode'	=> 'adr_two_postalcode',
70
		'homestate'	=> 'adr_two_region',
71
		'homestreet'	=>	'adr_two_street',
72
		'homefaxnumber'	=> 'tel_fax_home',
73
		'homephonenumber'	=>	'tel_home',
74
		'jobtitle'	=>	'title',	// unfortunatly outlook only has title & jobtitle, while EGw has 'n_prefix', 'title' & 'role',
75
		'lastname'	=> 'n_family',
76
		'middlename'	=> 'n_middle',
77
		'mobilephonenumber'	=> 'tel_cell',
78
		'officelocation'	=> 'room',
79
		//'othercity'	=> '',
80
		//'othercountry'	=> '',
81
		//'otherpostalcode'	=> '',
82
		//'otherstate'	=> '',
83
		//'otherstreet'	=> '',
84
		'pagernumber'	=> 'tel_pager',
85
		//'radiophonenumber'	=> '',
86
		//'spouse'	=> '',
87
		'suffix'	=>	'n_suffix',
88
		'title'	=> 'n_prefix',
89
		'webpage'	=> 'url',
90
		//'yomicompanyname'	=> '',
91
		//'yomifirstname'	=>	'',
92
		//'yomilastname'	=>	'',
93
		//'rtf'	=> '',
94
		'picture'	=> 'jpegphoto',
95
		//'nickname'	=>	'',
96
		//'airsyncbasebody'	=>	'',
97
	);
98
	/**
99
	 * ID of private addressbook
100
	 *
101
	 * @var int
102
	 */
103
	const PRIVATE_AB = 0x7fffffff;
104
105
	/**
106
	 * Constructor
107
	 *
108
	 * @param activesync_backend $backend
109
	 */
110
	public function __construct(activesync_backend $backend)
111
	{
112
		$this->backend = $backend;
113
	}
114
115
	/**
116
	 * Get addressbooks (no extra private one and do some caching)
117
	 *
118
	 * Takes addessbook-abs and addressbook-all-in-one preference into account.
119
	 *
120
	 * @param int $account =null account_id of addressbook or null to get array of all addressbooks
121
	 * @param boolean $return_all_in_one =true if false and all-in-one pref is set, return all selected abs
122
	 * 	if true only the all-in-one ab is returned (with id of personal ab)
123
	 * @param booelan $ab_prefix =false prefix personal, private and accounts addressbook with lang('Addressbook').' '
0 ignored issues
show
The type booelan was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
124
	 * @return string|array addressbook name of array with int account_id => label pairs
125
	 */
126
	private function get_addressbooks($account=null,$return_all_in_one=true, $ab_prefix=false)
127
	{
128
		static $abs=null;
129
130
		if (!isset($abs) || !$return_all_in_one)
131
		{
132
			if ($return_all_in_one && $GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'])
133
			{
134
				$abs = array(
135
					$GLOBALS['egw_info']['user']['account_id'] => lang('All'),
136
				);
137
			}
138
			else
139
			{
140
				Api\Translation::add_app('addressbook');	// we need the addressbook translations
141
142
				if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
143
144
				$pref_abs = $GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-abs'];
145
				if (!is_array($pref_abs))
146
				{
147
					$pref_abs = $pref_abs ? explode(',',$pref_abs) : [];
148
				}
149
				foreach ($this->addressbook->get_addressbooks() as $account_id => $label)
150
				{
151
					if ((string)$account_id == $GLOBALS['egw_info']['user']['account_id'].'p')
152
					{
153
						$account_id = self::PRIVATE_AB;
154
					}
155
					if ($account_id && in_array($account_id,$pref_abs) || in_array('A',$pref_abs) ||
156
						$account_id == 0 && in_array('U',$pref_abs) ||
157
						$account_id == $GLOBALS['egw_info']['user']['account_id'] ||	// allways sync pers. AB
158
						$account_id == $GLOBALS['egw_info']['user']['account_primary_group'] && in_array('G',$pref_abs))
159
					{
160
						$abs[$account_id] = $label;
161
					}
162
				}
163
			}
164
		}
165
		$ret = is_null($account) ? $abs :
166
			($ab_prefix && (!$account || (int)$account == (int)$GLOBALS['egw_info']['user']['account_id']) ?
167
				lang('Addressbook').' ' : '').$abs[$account];
168
		//error_log(__METHOD__."($account, $return_all_in_one, $ab_prefix) returning ".array2string($ret));
169
		return $ret;
170
	}
171
172
	/**
173
	 *  This function is analogous to GetMessageList.
174
	 *
175
	 *  @ToDo implement preference, include own private calendar
176
	 */
177
	public function GetFolderList()
178
	{
179
		// error_log(print_r($this->addressbook->get_addressbooks(Acl::READ),true));
180
		$folderlist = array();
181
		foreach ($this->get_addressbooks() as $account => $label)
182
		{
183
			$folderlist[] = array(
184
				'id'	=>	$this->backend->createID('addressbook',$account),
185
				'mod'	=>	$label,
186
				'parent'=>	'0',
187
			);
188
		}
189
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."() returning ".array2string($folderlist));
190
		//error_log(__METHOD__."() returning ".array2string($folderlist));
191
		return $folderlist;
192
	}
193
194
	/**
195
	 * Get Information about a folder
196
	 *
197
	 * @param string $id
198
	 * @return SyncFolder|boolean false on error
199
	 */
200
	public function GetFolder($id)
201
	{
202
		$type = $owner = null;
203
		$this->backend->splitID($id, $type, $owner);
204
205
		$folderObj = new SyncFolder();
206
		$folderObj->serverid = $id;
207
		$folderObj->parentid = '0';
208
		$folderObj->displayname = $this->get_addressbooks($owner);
209
210
		if ($owner == $GLOBALS['egw_info']['user']['account_id'])
211
		{
212
			$folderObj->type = SYNC_FOLDER_TYPE_CONTACT;
213
		}
214
		else
215
		{
216
			$folderObj->type = SYNC_FOLDER_TYPE_USER_CONTACT;
217
		}
218
/*
219
		// not existing folder requested --> return false
220
		if (is_null($folderObj->displayname))
221
		{
222
			$folderObj = false;
223
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."($id) returning ".array2string($folderObj));
224
		}
225
*/
226
		//error_log(__METHOD__."('$id') returning ".array2string($folderObj));
227
		return $folderObj;
228
	}
229
230
	/**
231
	 * Return folder stats. This means you must return an associative array with the
232
	 * following properties:
233
	 *
234
	 * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
235
	 *		 How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
236
	 * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
237
	 * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
238
	 *		  the folder has not changed. In practice this means that 'mod' can be equal to the folder name
239
	 *		  as this is the only thing that ever changes in folders. (the type is normally constant)
240
	 *
241
	 * @return array with values for keys 'id', 'mod' and 'parent'
242
	 */
243
	public function StatFolder($id)
244
	{
245
		$type = $owner = null;
246
		$this->backend->splitID($id, $type, $owner);
247
248
		$stat = array(
249
			'id'	 => $id,
250
			'mod'	=> $this->get_addressbooks($owner),
251
			'parent' => '0',
252
		);
253
/*
254
		// not existing folder requested --> return false
255
		if (is_null($stat['mod']))
256
		{
257
			$stat = false;
258
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$id') ".function_backtrace());
259
		}
260
*/
261
		//error_log(__METHOD__."('$id') returning ".array2string($stat));
262
		ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$id') returning ".array2string($stat));
263
		return $stat;
264
	}
265
266
	/**
267
	 * Should return a list (array) of messages, each entry being an associative array
268
	 * with the same entries as StatMessage(). This function should return stable information; ie
269
	 * if nothing has changed, the items in the array must be exactly the same. The order of
270
	 * the items within the array is not important though.
271
	 *
272
	 * The cutoffdate is a date in the past, representing the date since which items should be shown.
273
	 * This cutoffdate is determined by the user's setting of getting 'Last 3 days' of e-mail, etc. If
274
	 * you ignore the cutoffdate, the user will not be able to select their own cutoffdate, but all
275
	 * will work OK apart from that.
276
	 *
277
	 * @todo if AB supports an extra private addressbook and AS prefs want an all-in-one AB, the private AB is always included, even if not selected in the prefs
278
	 * @param string $id folder id
279
	 * @param int $cutoffdate =null
280
	 * @return array
281
  	 */
282
	function GetMessageList($id, $cutoffdate=NULL)
283
	{
284
		unset($cutoffdate);	// not used, but required by function signature
285
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
286
287
		$type = $user = null;
288
		$this->backend->splitID($id,$type,$user);
289
		$filter = array('owner' => $user);
290
291
		// handle all-in-one addressbook
292
		if ($GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'] &&
293
			$user == $GLOBALS['egw_info']['user']['account_id'])
294
		{
295
			$filter['owner'] = array_keys($this->get_addressbooks(null,false));	// false = return all selected abs
296
			// translate AS private AB ID to EGroupware one
297
			if (($key = array_search(self::PRIVATE_AB, $filter['owner'])) !== false)
298
			{
299
				$filter['owner'][$key] = $GLOBALS['egw_info']['user']['account_id'].'p';
300
			}
301
		}
302
		// handle private/personal addressbooks
303
		elseif ($this->addressbook->private_addressbook &&
304
			($user == self::PRIVATE_AB || $user == $GLOBALS['egw_info']['user']['account_id']))
305
		{
306
			$filter['owner'] = $GLOBALS['egw_info']['user']['account_id'];
307
			$filter['private'] = (int)($user == self::PRIVATE_AB);
308
		}
309
310
		$messagelist = array();
311
		$criteria = null;
312
		if (($contacts =& $this->addressbook->search($criteria, 'contact_id,contact_etag', '', '', '',
313
			false, 'AND', false,$filter)))
314
		{
315
			foreach($contacts as $contact)
316
			{
317
				$messagelist[] = $this->StatMessage($id, $contact);
318
			}
319
		}
320
		//error_log(__METHOD__."('$id', $cutoffdate) filter=".array2string($filter)." returning ".count($messagelist).' entries');
321
		return $messagelist;
322
	}
323
324
	/**
325
	 * Get specified item from specified folder.
326
	 *
327
	 * @param string $folderid
328
	 * @param string $id
329
	 * @param ContentParameters $contentparameters  parameters of the requested message (truncation, mimesupport etc)
330
	 *  object with attributes foldertype, truncation, rtftruncation, conflict, filtertype, bodypref, deletesasmoves, filtertype, contentclass, mimesupport, conversationmode
331
	 *  bodypref object with attributes: ]truncationsize, allornone, preview
332
	 * @return $messageobject|boolean false on error
0 ignored issues
show
Documentation Bug introduced by
The doc comment $messageobject|boolean at position 0 could not be parsed: Unknown type name '$messageobject' at position 0 in $messageobject|boolean.
Loading history...
333
	 */
334
	public function GetMessage($folderid, $id, $contentparameters)
335
	{
336
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
337
338
		//$truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
339
		//$mimesupport = $contentparameters->GetMimeSupport();
340
		$bodypreference = $contentparameters->GetBodyPreference(); /* fmbiete's contribution r1528, ZP-320 */
341
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid', $id, ...) truncsize=$truncsize, mimesupport=$mimesupport, bodypreference=".array2string($bodypreference));
342
343
		$type = $account = null;
344
		$this->backend->splitID($folderid, $type, $account);
345
		if ($type != 'addressbook' || !($contact = $this->addressbook->read($id)))
0 ignored issues
show
The condition $type != 'addressbook' is always true.
Loading history...
346
		{
347
			error_log(__METHOD__."('$folderid',$id,...) Folder wrong (type=$type, account=$account) or contact not existing (read($id)=".array2string($contact).")! returning false");
348
			return false;
349
		}
350
		$emailname = isset($contact['n_given']) ? $contact['n_given'].' ' : '';
351
		$emailname .= isset($contact['n_middle']) ? $contact['n_middle'].' ' : '';
352
		$emailname .= isset($contact['n_family']) ? $contact['n_family']: '';
353
		$message = new SyncContact();
354
		foreach(self::$mapping as $key => $attr)
355
		{
356
			switch ($attr)
357
			{
358
				case 'note':
359
					if ($bodypreference == false)
360
					{
361
						$message->body = $contact[$attr];
362
						$message->bodysize = strlen($message->body);
363
						$message->bodytruncated = 0;
364
					}
365
					else
366
					{
367
						if (strlen ($contact[$attr]) > 0)
368
						{
369
							$message->asbody = new SyncBaseBody();
370
							$this->backend->note2messagenote($contact[$attr], $bodypreference, $message->asbody);
371
						}
372
					}
373
					break;
374
375
				case 'jpegphoto':
376
					if (empty($contact[$attr]) && ($contact['files'] & Api\Contacts::FILES_BIT_PHOTO))
377
					{
378
						$contact[$attr] = file_get_contents(Api\Link::vfs_path('addressbook', $contact['id'], Api\Contacts::FILES_PHOTO));
379
					}
380
					if (!empty($contact[$attr])) $message->$key = base64_encode($contact[$attr]);
381
					break;
382
383
				case 'bday':	// zpush seems to use a timestamp in utc (at least vcard backend does)
384
					if (!empty($contact[$attr]))
385
					{
386
            			$tz = date_default_timezone_get();
387
            			date_default_timezone_set('UTC');
388
            			$message->birthday = strtotime($contact[$attr]);
389
            			date_default_timezone_set($tz);
390
					}
391
					break;
392
393
				case 'cat_id':
394
					$message->$key = array();
395
					foreach($contact[$attr] ? explode(',',$contact[$attr]) : array() as $cat_id)
396
					{
397
						$message->categories[] = Api\Categories::id2name($cat_id);
398
					}
399
					// for all addressbooks in one, add addressbook name itself as category
400
					if ($GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'])
401
					{
402
						$message->categories[] = $this->get_addressbooks($contact['owner'].($contact['private']?'p':''), false, true);
403
					}
404
					break;
405
				// HTC Desire needs at least one telefon number, otherwise sync of contact fails without error,
406
				// but will be retired forerver --> we always return work-phone xml element, even if it's empty
407
				// (Mircosoft ActiveSync Contact Class Protocol Specification says all phone-numbers are optional!)
408
				case 'tel_work':
409
					$message->$key = (string)$contact[$attr];
410
					break;
411
				case 'n_fileas':
412
					if ($GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-force-fileas'])
413
					{
414
						$message->$key = $this->addressbook->fileas($contact,$GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-force-fileas']);
415
						break;
416
					}
417
					// fall through
418
				default:
419
					if (!empty($contact[$attr])) $message->$key = $contact[$attr];
420
			}
421
		}
422
		//error_log(__METHOD__."(folder='$folderid',$id,...) returning ".array2string($message));
423
		return $message;
424
	}
425
426
	/**
427
	 * StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
428
	 * 'id'	 => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
429
	 * 'flags'	 => simply '0' for unread, '1' for read
430
	 * 'mod'	=> modification signature. As soon as this signature changes, the item is assumed to be completely
431
	 *			 changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
432
	 *			 time for this field, which will change as soon as the contents have changed.
433
	 *
434
	 * @param string $folderid
435
	 * @param int|array $contact contact id or array
436
	 * @return array
437
	 */
438
	public function StatMessage($folderid, $contact)
439
	{
440
		unset($folderid);	// not used (contact_id is global), but required by function signaure
441
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
442
443
		if (!is_array($contact)) $contact = $this->addressbook->read($contact);
444
445
		if (!$contact)
446
		{
447
			$stat = false;
448
		}
449
		else
450
		{
451
			$stat = array(
452
				'mod' => $contact['etag'],
453
				'id' => $contact['id'],
454
				'flags' => 1,
455
			);
456
		}
457
		//ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid',".array2string($id).") returning ".array2string($stat));
458
		//error_log(__METHOD__."('$folderid',$contact) returning ".array2string($stat));
459
		return $stat;
460
	}
461
462
	/**
463
	 *  Creates or modifies a folder
464
	 *
465
	 * @param $id of the parent folder
466
	 * @param $oldid => if empty -> new folder created, else folder is to be renamed
0 ignored issues
show
Documentation Bug introduced by
The doc comment => at position 0 could not be parsed: Unknown type name '=' at position 0 in =>.
Loading history...
467
	 * @param $displayname => new folder name (to be created, or to be renamed to)
468
	 * @param type => folder type, ignored in IMAP
469
	 *
470
	 * @return stat | boolean false on error
471
	 *
472
	 */
473
	public function ChangeFolder($id, $oldid, $displayname, $type)
474
	{
475
		unset($id, $oldid, $displayname, $type);	// not used, but required by function signature
476
		ZLog::Write(LOGLEVEL_DEBUG, __METHOD__." not implemented");
477
	}
478
479
	/**
480
	 * Deletes (really delete) a Folder
481
	 *
482
	 * @param $parentid of the folder to delete
0 ignored issues
show
The type of was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
483
	 * @param $id of the folder to delete
484
	 *
485
	 * @return
486
	 * @TODO check what is to be returned
487
	 *
488
	 */
489
	public function DeleteFolder($parentid, $id)
490
	{
491
		unset($parentid, $id);
492
		ZLog::Write(LOGLEVEL_DEBUG, __METHOD__." not implemented");
493
	}
494
495
	/**
496
	 * Changes or adds a message on the server
497
	 *
498
	 * @param string $folderid
499
	 * @param int $id for change | empty for create new
500
	 * @param SyncContact $message object to SyncObject to create
501
	 * @param ContentParameters   $contentParameters
502
	 *
503
	 * @return array $stat whatever would be returned from StatMessage
504
	 *
505
	 * This function is called when a message has been changed on the PDA. You should parse the new
506
	 * message here and save the changes to disk. The return value must be whatever would be returned
507
	 * from StatMessage() after the message has been saved. This means that both the 'flags' and the 'mod'
508
	 * properties of the StatMessage() item may change via ChangeMessage().
509
	 * Note that this function will never be called on E-mail items as you can't change e-mail items, you
510
	 * can only set them as 'read'.
511
	 */
512
	public function ChangeMessage($folderid, $id, $message, $contentParameters)
513
	{
514
		unset($contentParameters);	// not used, but required by function signature
515
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
516
517
		$type = $account = null;
518
		$this->backend->splitID($folderid, $type, $account);
519
		$is_private = false;
520
		if ($account == self::PRIVATE_AB)
0 ignored issues
show
The condition $account == self::PRIVATE_AB is always false.
Loading history...
521
		{
522
			$account = $GLOBALS['egw_info']['user']['account_id'];
523
			$is_private = true;
524
525
		}
526
		// error_log(__METHOD__. " Id " .$id. " Account ". $account . " FolderID " . $folderid);
527
		if ($type != 'addressbook') // || !($contact = $this->addressbook->read($id)))
0 ignored issues
show
The condition $type != 'addressbook' is always true.
Loading history...
528
		{
529
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__." Folder wrong or contact not existing");
530
			return false;
531
		}
532
		if ($account == 0)	// as a precausion, we currently do NOT allow to change Api\Accounts
533
		{
534
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__." Changing of Api\Accounts denied!");
535
			return false;			//no changing of Api\Accounts
536
		}
537
		$contact = array();
538
		if (empty($id) && ($this->addressbook->grants[$account] & Acl::EDIT) || ($contact = $this->addressbook->read($id)) && $this->addressbook->check_perms(Acl::EDIT, $contact))
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: (empty($id) && $this->ad...pi\Acl::EDIT, $contact), Probably Intended Meaning: empty($id) && ($this->ad...i\Acl::EDIT, $contact))
Loading history...
539
		{
540
			// remove all fields supported by AS, leaving all unsupported fields unchanged
541
			$contact = array_diff_key($contact, array_flip(self::$mapping));
542
			foreach (self::$mapping as $key => $attr)
543
			{
544
				switch ($attr)
545
				{
546
					case 'note':
547
						$contact[$attr] = $this->backend->messagenote2note($message->body, $message->rtf, $message->asbody);
548
						break;
549
550
					case 'bday':	// zpush uses timestamp in servertime
551
						$contact[$attr] = $message->$key ? date('Y-m-d',$message->$key) : null;
552
						break;
553
554
					case 'jpegphoto':
555
						$contact[$attr] = base64_decode($message->$key);
556
						break;
557
558
					case 'cat_id':
559
						// for existing entries in all-in-one addressbook, remove addressbook name as category
560
						if ($contact && $GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'] &&
561
							($k=array_search($this->get_addressbooks($contact['owner'].($contact['private']?'p':''), false, true),$message->$key)))
562
						{
563
							unset($message->categories[$k]);
564
						}
565
						if (is_array($message->$key))
566
						{
567
							$contact[$attr] = implode(',', array_filter($this->addressbook->find_or_add_categories($message->$key, $id),'strlen'));
568
						}
569
						break;
570
					case 'email':
571
					case 'email_home':
572
						if (function_exists ('imap_rfc822_parse_adrlist'))
573
						{
574
							$email_array = array_shift(imap_rfc822_parse_adrlist($message->$key,""));
0 ignored issues
show
imap_rfc822_parse_adrlist($message->$key, '') cannot be passed to array_shift() as the parameter $array expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

574
							$email_array = array_shift(/** @scrutinizer ignore-type */ imap_rfc822_parse_adrlist($message->$key,""));
Loading history...
575
							if (!empty($email_array->mailbox) && $email_array->mailbox != 'INVALID_ADDRESS' && !empty($email_array->host))
576
							{
577
								$contact[$attr] = $email_array->mailbox.'@'.$email_array->host;
578
							}
579
							else
580
							{
581
								$contact[$attr] = $message->$key;
582
							}
583
						}
584
						else
585
						{
586
							ZLog::Write(LOGLEVEL_DEBUG, __METHOD__. " Warning : php-imap not available");
587
							$contact[$attr] = $message->$key;
588
						}
589
						break;
590
					case 'n_fileas':	// only change fileas, if not forced on the client
591
						if (!$GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-force-fileas'])
592
						{
593
							$contact[$attr] = $message->$key;
594
						}
595
						break;
596
					case 'title':	// as ol jobtitle mapping changed in egw from role to title, do NOT overwrite title with value of role
597
						if ($id && $message->$key == $contact['role']) break;
598
						// fall throught
599
					default:
600
						$contact[$attr] = $message->$key;
601
						break;
602
				}
603
			}
604
			// for all-in-one addressbook, account is meaningless and wrong!
605
			// Api\Contacts::save() keeps the owner or sets an appropriate one if none given
606
			if (!isset($contact['private'])) $contact['private'] = (int)$is_private;
607
			if (!$GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'])
608
			{
609
				$contact['owner'] = $account;
610
				$contact['private'] = (int)$is_private;
611
			}
612
			// if default addressbook for new contacts is NOT synced --> use personal addressbook
613
			elseif($GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'] &&
614
				!in_array($GLOBALS['egw_info']['user']['preferences']['addressbook']['add_default'],
615
					array_keys($this->get_addressbooks(null,false))))
616
			{
617
				$contact['owner'] = $GLOBALS['egw_info']['user']['account_id'];
618
			}
619
			if (!empty($id)) $contact['id'] = $id;
620
			$this->addressbook->fixup_contact($contact);
621
			$newid = $this->addressbook->save($contact);
622
			//error_log(__METHOD__."($folderid,$id) contact=".array2string($contact)." returning ".array2string($newid));
623
			return $this->StatMessage($folderid, $newid);
624
		}
625
		ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."($folderid, $id) returning false: Permission denied");
626
		return false;
627
	}
628
629
	/**
630
	 * Moves a message from one folder to another
631
	 *
632
	 * @param $folderid of the current folder
633
	 * @param $id of the message
634
	 * @param $newfolderid
635
     * @param ContentParameters   $contentParameters
636
	 *
637
	 * @return $newid as a string | boolean false on error
0 ignored issues
show
Documentation Bug introduced by
The doc comment $newid at position 0 could not be parsed: Unknown type name '$newid' at position 0 in $newid.
Loading history...
638
	 *
639
	 * After this call, StatMessage() and GetMessageList() should show the items
640
	 * to have a new parent. This means that it will disappear from GetMessageList() will not return the item
641
	 * at all on the source folder, and the destination folder will show the new message
642
	 *
643
	 * @ToDo: If this gets implemented, we have to take into account the 'addressbook-all-in-one' pref!
644
	 */
645
	public function MoveMessage($folderid, $id, $newfolderid, $contentParameters)
646
	{
647
		unset($contentParameters);	// not used, but required by function signature
648
		if ($GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'])
649
		{
650
			ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid', $id, $newfolderid) NOT allowed for an all-in-one addressbook --> returning false");
651
			return false;
652
		}
653
		ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid', $id, $newfolderid) NOT implemented --> returning false");
654
		return false;
655
	}
656
657
658
	/**
659
	 * Delete (really delete) a message in a folder
660
	 *
661
	 * @param $folderid
662
	 * @param $id
663
     * @param ContentParameters   $contentParameters
664
	 *
665
	 * @return boolean true on success, false on error, diffbackend does NOT use the returnvalue
666
	 *
667
	 * @DESC After this call has succeeded, a call to
668
	 * GetMessageList() should no longer list the message. If it does, the message will be re-sent to the PDA
669
	 * as it will be seen as a 'new' item. This means that if you don't implement this function, you will
670
	 * be able to delete messages on the PDA, but as soon as you sync, you'll get the item back
671
	 */
672
	public function DeleteMessage($folderid, $id, $contentParameters)
673
	{
674
		unset($contentParameters);	// not used, but required by function signature
675
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
676
677
		$ret = $this->addressbook->delete($id);
678
		ZLog::Write(LOGLEVEL_DEBUG, __METHOD__."('$folderid', $id) delete($id) returned ".array2string($ret));
679
		return $ret;
680
	}
681
682
    /**
683
     * Changes the 'read' flag of a message on disk. The $flags
684
     * parameter can only be '1' (read) or '0' (unread). After a call to
685
     * SetReadFlag(), GetMessageList() should return the message with the
686
     * new 'flags' but should not modify the 'mod' parameter. If you do
687
     * change 'mod', simply setting the message to 'read' on the mobile will trigger
688
     * a full resync of the item from the server.
689
     *
690
     * @param string              $folderid            id of the folder
691
     * @param string              $id                  id of the message
692
     * @param int                 $flags               read flag of the message
693
     * @param ContentParameters   $contentParameters
694
     *
695
     * @access public
696
     * @return boolean                      status of the operation
697
     * @throws StatusException              could throw specific SYNC_STATUS_* exceptions
698
     */
699
	function SetReadFlag($folderid, $id, $flags, $contentParameters)
700
	{
701
		unset($folderid, $id, $flags, $contentParameters);
702
		return false;
703
	}
704
705
	/**
706
	 * modify olflags (outlook style) flag of a message
707
	 *
708
	 * @param $folderid
709
	 * @param $id
710
	 * @param $flags
711
	 *
712
	 *
713
	 * @DESC The $flags parameter must contains the poommailflag Object
714
 	 */
715
	function ChangeMessageFlag($folderid, $id, $flags)
716
	{
717
		unset($folderid, $id, $flags);
718
		return false;
719
	}
720
721
	/**
722
	 * Return a changes array
723
	 *
724
	 * if changes occurr default diff engine computes the actual changes
725
	 *
726
	 * @param string $folderid
727
	 * @param string &$syncstate on call old syncstate, on return new syncstate
728
	 * @return array|boolean false if $folderid not found, array() if no changes or array(array("type" => "fakeChange"))
729
	 */
730
	function AlterPingChanges($folderid, &$syncstate)
731
	{
732
		$type = $owner = null;
733
		$this->backend->splitID($folderid, $type, $owner);
734
735
		if ($type != 'addressbook') return false;
0 ignored issues
show
The condition $type != 'addressbook' is always true.
Loading history...
736
737
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
738
739
		// handle all-in-one addressbook
740
		if ($GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-all-in-one'] &&
741
			$owner == $GLOBALS['egw_info']['user']['account_id'])
742
		{
743
			$prefs_abs = $GLOBALS['egw_info']['user']['preferences']['activesync']['addressbook-abs'];
744
			if (!is_array($prefs_abs))
745
			{
746
				$prefs_abs = $prefs_abs ? explode(',', $prefs_abs) : [];
747
			}
748
			if (in_array('A', $prefs_abs))
749
			{
750
				$owner = null;	// all AB's
751
			}
752
			else
753
			{
754
				$owner = array_keys($this->get_addressbooks(null,false));	// false = return all selected abs
755
				// translate AS private AB ID to current user
756
				if (($key = array_search(self::PRIVATE_AB, $owner)) !== false)
757
				{
758
					unset($owner[$key]);
759
					if (!in_array($GLOBALS['egw_info']['user']['account_id'],$owner))
760
					{
761
						$owner[] = $GLOBALS['egw_info']['user']['account_id'];
762
					}
763
				}
764
			}
765
		}
766
		if ($owner == self::PRIVATE_AB)
767
		{
768
			$owner = $GLOBALS['egw_info']['user']['account_id'];
769
		}
770
		$ctag = $this->addressbook->get_ctag($owner);
771
772
		$changes = array();	// no change
773
		//$syncstate_was = $syncstate;
774
775
		if ($ctag !== $syncstate)
776
		{
777
			$syncstate = $ctag;
778
			$changes = array(array('type' => 'fakeChange'));
779
		}
780
		//error_log(__METHOD__."('$folderid','$syncstate_was') syncstate='$syncstate' returning ".array2string($changes));
781
		return $changes;
782
	}
783
784
	/**
785
	 * Search global address list for a given pattern
786
	 *
787
	 * @param array $searchquery value for keys 'query' and 'range' (eg. "0-50")
788
	 * @return array with just rows (no values for keys rows, status or global_search_status!)
789
	 * @todo search range not verified, limits might be a good idea
790
	 */
791
	function getSearchResultsGAL($searchquery)
792
	{
793
		if (!isset($this->addressbook)) $this->addressbook = new Api\Contacts();
794
		//error_log(__METHOD__.'('.array2string($searchquery).')');
795
796
		// only return items in given range, eg. "0-50"
797
		$range = false;
798
		if (isset($searchquery['range']) && preg_match('/^\d+-\d+$/', $searchquery['range']))
799
		{
800
			list($start,$end) = explode('-', $searchquery['range']);
801
			$range = array($start, $end-$start+1);	// array(start, num_entries)
802
		}
803
		//error_log(__METHOD__.'('.array2string($searchquery).') range='.array2string($range));
804
805
		$items = $filter = array();
806
		$filter['cols_to_search'] = array('n_fn', 'n_family', 'n_given',
807
						'room','org_name', 'title', 'role', 'tel_work', 'tel_home', 'tel_cell',
808
						'email', 'email_home');
809
		if (($contacts =& $this->addressbook->search($searchquery['query'], false, false, '', '%', false, 'OR', $range, $filter)))
810
		{
811
			foreach($contacts as $contact)
812
			{
813
				//$item[SYNC_GAL_ALIAS] = $contact['contact_id'];
814
			  	$item[SYNC_GAL_LASTNAME] = $contact['n_family']?$contact['n_family']:$contact['org_name'];
815
			  	$item[SYNC_GAL_FIRSTNAME] = $contact['n_given'];
816
				$item[SYNC_GAL_DISPLAYNAME] = $contact['n_fn'];
817
				if (!trim($item[SYNC_GAL_DISPLAYNAME])) $item[SYNC_GAL_DISPLAYNAME] = $contact['n_family']?$contact['n_family']:$contact['org_name'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $item seems to be defined later in this foreach loop on line 814. Are you sure it is defined here?
Loading history...
818
				$item[SYNC_GAL_EMAILADDRESS] = $contact['email'] ? $contact['email'] : (string)$contact['email_private'] ;
819
				//$item['nameid'] = $searchquery;
820
				$item[SYNC_GAL_PHONE] = (string)$contact['tel_work'];
821
				$item[SYNC_GAL_HOMEPHONE] = (string)$contact['tel_home'];
822
				$item[SYNC_GAL_MOBILEPHONE] = (string)$contact['tel_cell'];
823
				$item[SYNC_GAL_COMPANY] = (string)$contact['org_name'];
824
				$item[SYNC_GAL_OFFICE] = $contact['room'];
825
				$item[SYNC_GAL_TITLE ] = $contact['title'];
826
827
			  	//do not return users without email
828
				if (!trim($item[SYNC_GAL_EMAILADDRESS])) continue;
829
830
				$items[] = $item;
831
			}
832
		}
833
		$items['searchtotal']=count($items);
834
		$items['range']=$searchquery['range'];
835
		return $items;
836
	}
837
838
	/**
839
	 * Populates $settings for the preferences
840
	 *
841
	 * @param array|string $hook_data
842
	 * @return array
843
	 */
844
	function egw_settings($hook_data)
845
	{
846
		$addressbooks = array();
847
848
		if (!isset($hook_data['setup']) && in_array($hook_data['type'], array('user', 'group')))
849
		{
850
			$user = $hook_data['account_id'];
851
			Api\Translation::add_app('addressbook');
852
			$addressbook_bo = new Api\Contacts();
853
			$addressbooks = $addressbook_bo->get_addressbooks(Acl::READ, null, $user);
854
			if ($user > 0)
855
			{
856
				unset($addressbooks[$user]);	// personal addressbook is allways synced
857
				if (isset($addressbooks[$user.'p']))
858
				{
859
					$addressbooks[self::PRIVATE_AB] = lang('Private');
860
				}
861
			}
862
			unset($addressbooks[$user.'p']);// private addressbook uses ID self::PRIVATE_AB
863
			$fileas_options = array('0' => lang('use addressbooks "own sorting" attribute'))+$addressbook_bo->fileas_options();
864
		}
865
		$addressbooks += array(
866
			'G'	=> lang('Primary Group'),
867
			'U' => lang('Accounts'),
868
			'A'	=> lang('All'),
869
		);
870
		// allow to force "none", to not show the prefs to the users
871
		if ($hook_data['type'] == 'forced')
872
		{
873
			$addressbooks['N'] = lang('None');
874
		}
875
876
		// rewriting owner=0 to 'U', as 0 get's always selected by prefs
877
		// not removing it for default or forced prefs based on current users pref
878
		if (!isset($addressbooks[0]) && (in_array($hook_data['type'], array('user', 'group')) ||
879
			$GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] === '1'))
880
		{
881
			unset($addressbooks['U']);
882
		}
883
		else
884
		{
885
			unset($addressbooks[0]);
886
		}
887
888
		$settings['addressbook-abs'] = array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$settings was never initialized. Although not strictly required by PHP, it is generally a good practice to add $settings = array(); before regardless.
Loading history...
889
			'type'   => 'multiselect',
890
			'label'  => 'Additional addressbooks to sync',
891
			'name'   => 'addressbook-abs',
892
			'help'   => 'Global address search always searches in all addressbooks, so you dont need to sync all addressbooks to be able to access them, if you are online.',
893
			'values' => $addressbooks,
894
			'xmlrpc' => True,
895
			'admin'  => False,
896
		);
897
898
		$settings['addressbook-all-in-user'] = array(
899
			'type'   => 'check',
900
			'label'  => 'Sync all addressbooks as one',
901
			'name'   => 'addressbook-all-in-one',
902
			'help'   => 'Not all devices support multiple addressbooks, so you can choose to sync all above selected addressbooks as one.',
903
			'xmlrpc' => true,
904
			'admin'  => false,
905
			'default' => '0',
906
		);
907
908
		$settings['addressbook-force-fileas'] = array(
909
			'type'   => 'select',
910
			'label'  => 'Force sorting on device to',
911
			'name'   => 'addressbook-force-fileas',
912
			'help'   => 'Some devices (eg. Windows Mobil, but not iOS) sort by addressbooks "own sorting" attribute, which might not be what you want on the device. With this setting you can force the device to use a different sorting for all contacts, without changing it in addressbook.',
913
			'values' => $fileas_options,
914
			'xmlrpc' => true,
915
			'admin'  => false,
916
			'default' => '0',
917
		);
918
		return $settings;
919
	}
920
}
921