Principals::expand_property_report()   F
last analyzed

Complexity

Conditions 27
Paths 562

Size

Total Lines 101
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 57
nc 562
nop 4
dl 0
loc 101
rs 0.6082
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * EGroupware: CalDAV/CardDAV/GroupDAV access: Principals handlers
4
 *
5
 * @link http://www.egroupware.org
6
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
7
 * @package api
8
 * @subpackage caldav
9
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
10
 * @copyright (c) 2008-18 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
11
 */
12
13
namespace EGroupware\Api\CalDAV;
14
15
use EGroupware\Api;
16
17
// explicit import classes without namespace
18
use resources_bo, resources_acl_bo;
19
use calendar_bo;
20
21
/**
22
 * EGroupware: GroupDAV access: groupdav/caldav/carddav principals handlers
23
 *
24
 * First-level properties used in this class should have the property name as their key,
25
 * to allow to check if required properties are set!
26
 * Principals::add_principal() converts simple associative props (name => value pairs)
27
 * to name => Api\CalDAV(name, value) pairs.
28
 *
29
 * Permanent error_log() calls should use $this->caldav->log($str) instead, to be send to PHP error_log()
30
 * and our request-log (prefixed with "### " after request and response, like exceptions).
31
 */
32
class Principals extends Handler
33
{
34
	/**
35
	 * Instance of resources_bo
36
	 *
37
	 * @var resources_bo
38
	 */
39
	private static $resources;
40
41
	/**
42
	 * Constructor
43
	 *
44
	 * @param string $app 'calendar', 'addressbook' or 'infolog'
45
	 * @param Api\CalDAV $caldav calling class
46
	 */
47
	function __construct($app, Api\CalDAV $caldav)
48
	{
49
		parent::__construct($app, $caldav);
50
51
		if (!isset(self::$resources)) self::$resources = new resources_bo();
52
	}
53
54
	/**
55
	 * Supported reports and methods implementing them or what to return eg. "501 Not Implemented"
56
	 *
57
	 * @var array
58
	 */
59
	public $supported_reports = array(
60
		'acl-principal-prop-set' => array(
61
			'method' => 'acl_principal_prop_set_report',
62
		),
63
		/*'principal-match' => array(
64
			// an other report calendarserver announces
65
		),*/
66
		'principal-property-search' => array(
67
			'method' => 'principal_property_search_report',
68
		),
69
		'principal-search-property-set' => array(
70
			'method' => 'principal_search_property_set_report',
71
		),
72
		'expand-property' => array(
73
			'method' => 'expand_property_report',
74
		),
75
		/* seems only be used 'til OS X 10.6, no longer in 10.7
76
		'addressbook-findshared' => array(
77
			'ns' => Api\CalDAV::ADDRESSBOOKSERVER,
78
			'method' => 'addressbook_findshared_report',
79
		),*/
80
	);
81
82
	/**
83
	 * Generate supported-report-set property
84
	 *
85
	 * Currently we return all reports independed of path
86
	 *
87
	 * @param string $path eg. '/principals/'
88
	 * @param array $reports =null
89
	 * @return array Api\CalDAV::mkprop('supported-report-set', ...)
90
	 */
91
	protected function supported_report_set($path, array $reports=null)
92
	{
93
		unset($path);	// not used, but required by function signature
94
95
		if (is_null($reports)) $reports = $this->supported_reports;
96
97
		$supported = array();
98
		foreach($reports as $name => $data)
99
		{
100
			$supported[$name] = Api\CalDAV::mkprop('supported-report',array(
101
				Api\CalDAV::mkprop('report',array(
102
					!$data['ns'] ? Api\CalDAV::mkprop($name, '') :
103
						Api\CalDAV::mkprop($data['ns'], $name, '')))));
104
		}
105
		return $supported;
106
	}
107
108
	/**
109
	 * Handle propfind request for an application folder
110
	 *
111
	 * @param string $path
112
	 * @param array &$options
113
	 * @param array &$files
114
	 * @param int $user account_id
115
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
116
	 */
117
	function propfind($path,&$options,&$files,$user)
118
	{
119
		if (($report = isset($_GET['report']) ? $_GET['report'] : $options['root']['name']) && $report != 'propfind')
120
		{
121
			$report_data = $this->supported_reports[$report];
122
			if (isset($report_data) && ($method = $report_data['method']) && method_exists($this, $method))
123
			{
124
				return $this->$method($path, $options, $files, $user);
125
			}
126
			$this->caldav->log(__METHOD__."('$path', ".array2string($options).",, $user) not implemented report, returning 501 Not Implemented");
127
			return '501 Not Implemented';
128
		}
129
		list(,,$type,$name,$rest) = explode('/',$path,5);
130
		// /principals/users/$name/
131
		//            /users/$name/calendar-proxy-read/
132
		//            /users/$name/calendar-proxy-write/
133
		//            /groups/$name/
134
		//            /resources/$resource/
135
		//            /locations/$resource/
136
		//            /__uids__/$uid/.../
137
138
		switch($type)
139
		{
140
			case 'users':
141
				$files['files'] = $this->propfind_users($name,$rest,$options);
142
				break;
143
			case 'groups':
144
				$files['files'] = $this->propfind_groups($name,$rest,$options);
145
				break;
146
			case 'resources':
147
				$files['files'] = $this->propfind_resources($name,$rest,$options,false);
148
				break;
149
			case 'locations':
150
				$files['files'] = $this->propfind_resources($name,$rest,$options,true);
151
				break;
152
			/*case '__uids__':
153
				$files['files'] = $this->propfind_uids($name,$rest,$options);
154
				break;*/
155
			case '':
156
				$files['files'] = $this->propfind_principals($options);
157
				break;
158
			default:
159
				return '404 Not Found';
160
		}
161
		if (!is_array($files['files']))
162
		{
163
			return $files['files'];
164
		}
165
		return true;
166
	}
167
168
	/**
169
	 * Handle addressbook-findshared Addressbookserver report
170
	 *
171
	 * Required for Apple Addressbook on Mac (addressbook-findshared REPORT)
172
	 *
173
	 * @param string $path
174
	 * @param array $options
175
	 * @param array &$files
176
	 * @param int $user account_id
177
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
178
	 */
179
	/* Seens only to work 'til OS X 10.6, no longer in 10.7 AND response seems NOT correct for 10.6
180
	function addressbook_findshared_report($path,$options,&$files,$user)
181
	{
182
		error_log(__METHOD__."('$path', ".array2string($options).",, $user)");
183
		$files['files'] = array();
184
		$files['files'][] = $this->add_collection($path);	// will be removed for reports
185
		foreach($this->get_shared_addressbooks() as $path)
186
		{
187
			$files['files'][] = $f = $this->add_collection($path.'addressbook/', array(
188
				'resourcetype' => array(Api\CalDAV::mkprop(Api\CalDAV::CARDDAV,'addressbook','')),
189
			));
190
			error_log(__METHOD__."() ".array2string($f));
191
		}
192
		return true;
193
	}*/
194
195
	/**
196
	 * Handle expand-property reports (all from http://calendarserver.org/ns/ namespace) seen from newer iCal on OS X
197
	 * - expanded-group-member-set
198
	 * - expanded-group-membership
199
	 * - calendar-proxy-read-for
200
	 * - calendar-proxy-write-for
201
	 *
202
	 * Example requests:
203
	 *
204
	 * REPORT /egw/groupdav.php/principals/groups/groupname/ HTTP/1.1
205
	 * User-Agent: CalendarStore/5.0.3 (1204.1); iCal/5.0.3 (1605.3); Mac OS X/10.7.4 (11E53)
206
	 *
207
	 * <?xml version="1.0" encoding="UTF-8"?>
208
	 * <A:expand-property xmlns:A="DAV:">
209
	 *   <A:property name="expanded-group-member-set" namespace="http://calendarserver.org/ns/">
210
	 *     <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
211
	 *     <A:property name="last-name" namespace="http://calendarserver.org/ns/"/>
212
	 *     <A:property name="calendar-user-type" namespace="urn:ietf:params:xml:ns:caldav"/>
213
	 *     <A:property name="principal-URL" namespace="DAV:"/>
214
	 *     <A:property name="displayname" namespace="DAV:"/>
215
	 *     <A:property name="record-type" namespace="http://calendarserver.org/ns/"/>
216
	 *     <A:property name="first-name" namespace="http://calendarserver.org/ns/"/>
217
	 *   </A:property>
218
	 * </A:expand-property>
219
	 *
220
	 * REPORT /egw/groupdav.php/principals/users/username/ HTTP/1.1
221
	 * User-Agent: CalendarStore/5.0.3 (1204.1); iCal/5.0.3 (1605.3); Mac OS X/10.7.4 (11E53)
222
	 *
223
	 * <?xml version="1.0" encoding="UTF-8"?>
224
	 * <A:expand-property xmlns:A="DAV:">
225
	 *   <A:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
226
	 *     <A:property name="displayname" namespace="DAV:"/>
227
	 *     <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
228
	 *     <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
229
	 *   </A:property>
230
	 *   <A:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
231
	 *     <A:property name="displayname" namespace="DAV:"/>
232
	 *     <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
233
	 *     <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
234
	 *   </A:property>
235
	 * </A:expand-property>
236
	 *
237
	 * REPORT /egroupware/groupdav.php/principals/users/username/ HTTP/1.1
238
	 * User-Agent: DAVKit/4.0.3 (732.2); CalendarStore/4.0.4 (997.7); iCal/4.0.4 (1395.7); Mac OS X/10.6.8 (10K549)
239
	 *
240
	 * <?xml version="1.0" encoding="utf-8" ?>
241
	 * <x0:expand-property xmlns:x0="DAV:">
242
	 *  <x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
243
	 *   <x0:property name="displayname"/>
244
	 *   <x0:property name="principal-URL"/>
245
	 *   <x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
246
	 *  </x0:property>
247
	 *  <x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
248
	 *   <x0:property name="displayname"/>
249
	 *   <x0:property name="principal-URL"/>
250
	 *   <x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
251
	 *  </x0:property>
252
	 * </x0:expand-property>
253
	 *
254
	 * @param string $path
255
	 * @param array &$options
256
	 * @param array &$files
257
	 * @param int $user account_id
258
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
259
	 */
260
	function expand_property_report($path,&$options,&$files,$user)
261
	{
262
		//error_log(__METHOD__."('$path', ".array2string($options).",, $user)");
263
		$requested_props = $options['other'];
264
		while(($requested_prop = array_shift($requested_props)))
265
		{
266
			if ($requested_prop['name'] != 'property' || $requested_prop['depth'] != 1) continue;
267
268
			$prop_ns = $requested_prop['attrs']['namespace'];
269
			$prop_name = $requested_prop['attrs']['name'];
270
			$prop_path = $path;
271
			// calendarserver has some special property-names for expansion
272
			switch($prop_name)
273
			{
274
				case 'calendar-proxy-read-for':
275
				case 'calendar-proxy-write-for':
276
					$prop_path = $path . substr($prop_name, 0, -4).'/';
277
					$prop_name = 'group-member-set';
278
					$prop_ns = Api\CalDAV::DAV;
279
					break;
280
281
				case 'expanded-group-member-set':
282
				case 'expanded-group-membership':
283
					// remove 'expanded-' prefix
284
					$prop_name = substr($prop_name, 9);
285
					$prop_ns = Api\CalDAV::DAV;
286
					break;
287
			}
288
			// run regular propfind for requested property
289
			$options['depth'] = '0';
290
			$options['root']['name'] = 'propfind';
291
			$options['props'] = array(array(
292
				'name' => $prop_name,
293
				'xmlns' => $prop_ns,
294
			));
295
			$prop_files = array();
296
			$this->caldav->options = $options;	// also modify global variable
0 ignored issues
show
Bug introduced by
The property options does not exist on EGroupware\Api\CalDAV. Did you mean propfind_options?
Loading history...
297
			if (empty($prop_name) || $this->propfind($prop_path, $options, $prop_files, $user) !== true)
298
			{
299
				$this->caldav->log('### NO expand-property report for '.$requested_prop['attrs']['name']);
300
				continue;
301
			}
302
			// find prop to expand
303
			foreach($prop_files['files'][0]['props'] as $expand_prop)
304
			{
305
				if ($expand_prop['name'] === $prop_name) break;
306
			}
307
			if ($expand_prop['name'] !== $prop_name || !is_array($expand_prop['val']) ||
308
				$expand_prop['val'] && $expand_prop['val'][0]['name'] !== 'href')
309
			{
310
				$this->caldav->log('### NO expand-property report for '.$requested_prop['attrs']['name'].' ('.$prop_name.')');
311
				continue;
312
			}
313
314
			// requested properties of each href are in depth=2 properties
315
			// set them as regular propfind properties to $options['props']
316
			$options2 = array('props' => 'all');
317
			while(($prop = array_shift($requested_props)) && $prop['depth'] >= 2)
318
			{
319
				if ($prop['name'] == 'property' && $prop['depth'] == 2)
320
				{
321
					if (!is_array($options2['props']))	// is "all" initially
322
					{
323
						$options2['props'] = array();
324
					}
325
					$options2['props'][] = array(
326
						'name' => $prop['attrs']['name'],
327
						'xmlns' => isset($prop['attrs']['namespace']) ? $prop['attrs']['namespace'] : $prop['xmlns'],
328
					);
329
				}
330
			}
331
			// put back evtl. read top-level property
332
			if ($prop && $prop['depth'] == 1) array_unshift($requested_props, $prop);
333
			$this->caldav->options = $options2;	// also modify global variable
334
335
			// run regular profind to get requested 2.-level properties for each href
336
			foreach($expand_prop['val'] as $key => &$prop_val)
337
			{
338
				list(,$expand_path) = explode($this->caldav->base_uri, $prop_val['val']);
339
				//error_log(__METHOD__."('$path', ..., $user) calling propfind('$expand_path', ".array2string($options2).', '.array2string($prop_val).", $user)");
340
				if ($this->propfind($expand_path, $options2, $prop_val, $user) !== true || !isset($prop_val['files'][0]))
341
				{
342
					// do NOT return that path, eg. perms give rights but account_selection="groupmembers" forbids access
343
					unset($expand_prop['val'][$key]);
344
					continue;
345
				}
346
				$prop_val = $prop_val['files'][0];
347
			}
348
			// setting top-level name and namespace
349
			$expand_prop['ns'] = $requested_prop['attrs']['namespace'];
350
			$expand_prop['name'] = $requested_prop['attrs']['name'];
351
			// setting 2.-level props, so Api\CalDAV can filter unwanted ones out or mark missing ones
352
			$expand_prop['props'] = $options2['props'];
353
			// add top-level path and property
354
			$files['files'][0]['path'] = $path;
355
			$files['files'][0]['props'][] = $expand_prop;
356
		}
357
		// we can use 'all' here, as we return only requested properties
358
		$options['props'] = 'all';
359
360
		return true;
361
	}
362
363
	/**
364
	 * Handle principal-property-search report
365
	 *
366
	 * Current implementation runs a full infinity propfind and filters out not matching resources.
367
	 *
368
	 * Eg. from Lightning on the principal collection /principals/:
369
	 * <D:principal-property-search xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:caldav">
370
	 *   <D:property-search>
371
	 *     <D:prop>
372
	 *       <C:calendar-home-set/>
373
	 *     </D:prop>
374
	 *     <D:match>/egroupware/groupdav.php</D:match>
375
	 *   </D:property-search>
376
	 *   <D:prop>
377
	 *     <C:calendar-home-set/>
378
	 *     <C:calendar-user-address-set/>
379
	 *     <C:schedule-inbox-URL/>
380
	 *     <C:schedule-outbox-URL/>
381
	 *   </D:prop>
382
	 * </D:principal-property-search>
383
	 *
384
	 * Hack for Lightning: it requests calendar-home-set matching our root (/egroupware/groupdav.php),
385
	 * but interprets returning all principals (all have a matching calendar-home-set) as NOT supporting CalDAV scheduling
386
	 * --> search only current user's principal, when Lightning searches for calendar-home-set
387
	 *
388
	 * Example from iOS iCal autocompleting invitees using calendarserver-principal-property-search WebDAV extension
389
	 * <x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x1="http://calendarserver.org/ns/" xmlns:x0="DAV:" test="anyof">
390
	 *   <x0:property-search>
391
	 *     <x0:prop>
392
	 *       <x0:displayname/>
393
	 *     </x0:prop>
394
	 *     <x0:match match-type="contains">beck</x0:match>
395
	 *   </x0:property-search>
396
	 *   <x0:property-search>
397
	 *     <x0:prop>
398
	 *       <x1:email-address-set/>
399
	 *     </x0:prop>
400
	 *     <x0:match match-type="starts-with">beck</x0:match>
401
	 *   </x0:property-search>
402
	 *   <x0:property-search>
403
	 *     <x0:prop>
404
	 *       <x1:first-name/>
405
	 *     </x0:prop>
406
	 *     <x0:match match-type="starts-with">beck</x0:match>
407
	 *   </x0:property-search>
408
	 *   <x0:property-search>
409
	 *     <x0:prop>
410
	 *       <x1:last-name/>
411
	 *     </x0:prop>
412
	 *     <x0:match match-type="starts-with">beck</x0:match>
413
	 *   </x0:property-search>
414
	 *   <x0:prop>
415
	 *     <x1:first-name/>
416
	 *     <x1:last-name/>
417
	 *     <x0:displayname/>
418
	 *     <x1:email-address-set/>
419
	 *     <x2:calendar-user-address-set/>
420
	 *     <x1:record-type/>
421
	 *     <x0:principal-URL/>
422
	 *   </x0:prop>
423
	 * </x0:principal-property-search>
424
	 *
425
	 * @param string $path
426
	 * @param array $options
427
	 * @param array &$files
428
	 * @param int $user account_id
429
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
430
	 */
431
	function principal_property_search_report($path,&$options,&$files,$user)
432
	{
433
		//error_log(__METHOD__."('$path', ".array2string($options).",, $user)");
434
435
		// cant find the test attribute to root principal-property-search element in WebDAV rfc, but iPhones use it ...
436
		$anyof = !empty($options['root']['attrs']['test']) && $options['root']['attrs']['test'] == 'anyof';	// "allof" (default) or "anyof"
437
438
		// parse property-search prop(s) contained in $options['other']
439
		foreach($options['other'] as $n => $prop)
440
		{
441
			switch($prop['name'])
442
			{
443
				case 'apply-to-principal-collection-set':	// optinal prop to apply search on principal-collection-set == '/principals/'
444
					$path = '/principals/';
445
					break;
446
				case 'property-search':
447
					$property_search = $n;	// should be 1
448
					break;
449
				case 'prop':
450
					if (isset($property_search))
451
					{
452
						$search_props[$property_search] = array();
453
					}
454
					break;
455
				case 'match':
456
					if (isset($property_search) && is_array($search_props[$property_search]))
457
					{
458
						$search_props[$property_search]['match'] = $prop['data'];
459
						// optional match-type: "contains" (default), "starts-with", "ends-with", "equals"
460
						$search_props[$property_search]['match-type'] = $prop['attrs']['match-type'];
461
					}
462
					break;
463
				default:
464
					if (isset($property_search) && $search_props[$property_search] === array())
465
					{
466
						$search_props[$property_search] = $prop;
467
					}
468
					break;
469
			}
470
		}
471
		if (!isset($property_search) || !$search_props || !isset($search_props[$property_search]['match']))
472
		{
473
			$this->caldav->log(__METHOD__."('$path',...) Could not parse options[other]=".array2string($options['other']));
474
			return '400 Bad Request';
475
		}
476
		// make sure search property is included in toplevel props (can be missing and defaults to property-search/prop's)
477
		foreach($search_props as $prop)
478
		{
479
			if (!$this->caldav->prop_requested($prop['name'], $prop['xmlns']))
480
			{
481
				$options['props'][] = array(
482
					'name' => $prop['name'],
483
					'xmlns' => $prop['xmlns'],
484
				);
485
			}
486
			// Hack for Lightning prior 1.1.1 (TB 9): it requests calendar-home-set matching our root (/egroupware/groupdav.php),
487
			// but interprets returning all principals (all have a matching calendar-home-set) as NOT supporting CalDAV scheduling
488
			// --> search only current user's principal
489
			if ($prop['name'] == 'calendar-home-set' && stripos($_SERVER['HTTP_USER_AGENT'], 'Lightning') !== false &&
490
				substr($search_props[0]['match'],-13) == '/groupdav.php')
491
			{
492
				$path = '/principals/users/'.$GLOBALS['egw_info']['user']['account_lid'].'/';
493
				$this->caldav->log('Enabling hack for Lightning prior 1.1.1 for searching calendar-home-set matching "/groupdav.php": limiting search to '.$path);
494
			}
495
		}
496
		// check type attribute to limit search on a certain tree
497
		if (isset($options['root']['attrs']['type']))
498
		{
499
			switch($options['root']['attrs']['type'])
500
			{
501
				case 'INDIVIDUAL':
502
					$path = '/principals/users/';
503
					break;
504
				case 'GROUP':
505
					$path = '/principals/groups/';
506
					break;
507
				case 'ROOM':
508
					$path = '/principals/locations/';
509
					break;
510
				case 'RESOURCE':
511
					$path = '/principals/resources/';
512
					break;
513
			}
514
		}
515
		// run "regular" propfind
516
		$options['other'] = array();
517
		$options['root']['name'] = 'propfind';
518
		// search all principals, but not the proxys, rfc requires depth=0, but to search all principals
519
		$options['depth'] = 5 - count(explode('/', $path)); // /principals/ --> 3
520
521
		if (($ret = $this->propfind($path, $options, $files, $user)) !== true)
522
		{
523
			return $ret;
524
		}
525
		// now filter out not matching "files"
526
		foreach($files['files'] as $n => $resource)
527
		{
528
			if (count(explode('/', $resource['path'])) < 5)	// hack to only return principals, not the collections itself
529
			{
530
				unset($files['files'][$n]);
531
				continue;
532
			}
533
			// match with $search_props
534
			$matches = 0;
535
			foreach($search_props as $search_prop)
536
			{
537
				// search resource for $search_prop
538
				foreach($resource['props'] as $prop)
539
				{
540
					if ($prop['name'] === $search_prop['name']) break;
541
				}
542
				if ($prop['name'] === $search_prop['name'])	// search_prop NOT found
543
				{
544
					foreach((array)$prop['val'] as $value)
545
					{
546
						if (is_array($value)) $value = $value['val'];	// eg. href prop
547
						if (self::match($value, $search_prop['match'], $search_prop['match-type']) !== false)	// prop does match
548
						{
549
							++$matches;
550
							//error_log("$matches: $resource[path]: $search_prop[name]=".array2string($prop['name'] !== $search_prop['name'] ? null : $prop['val'])." does match '$search_prop[match]'");
551
							break;
552
						}
553
					}
554
				}
555
				if ($anyof && $matches || $matches == count($search_props))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($anyof && $matches) || ...== count($search_props), Probably Intended Meaning: $anyof && ($matches || $...= count($search_props))
Loading history...
556
				{
557
					//error_log("$resource[path]: anyof=$anyof, $matches matches --> keep");
558
					continue 2;	// enough matches --> keep
559
				}
560
			}
561
			//error_log("$resource[path]: anyof=$anyof, $matches matches --> skip");
562
			unset($files['files'][$n]);
563
		}
564
		return $ret;
565
	}
566
567
	/**
568
	 * Match using $match_type
569
	 *
570
	 * It's not defined in WebDAV ACL, but CardDAV:text-match seems similar
571
	 *
572
	 * @param string $value value to test
573
	 * @param string $match criteria/sub-string
574
	 * @param string $match_type ='contains' 'starts-with', 'ends-with' or 'equals'
575
	 */
576
	private static function match($value, $match, $match_type='contains')
577
	{
578
		switch($match_type)
579
		{
580
			case 'equals':
581
				return $value === $match;
582
583
			case 'starts-with':
584
				return stripos($value, $match) === 0;
585
586
			case 'ends-with':
587
				return stripos($value, $match) === strlen($value) - strlen($match);
588
589
			case 'contains':
590
			default:
591
				return stripos($value, $match) !== false;
592
		}
593
	}
594
595
	/**
596
	 * Handle principal-search-property-set report
597
	 *
598
	 * REPORT /principals/ HTTP/1.1
599
	 * <?xml version="1.0" encoding="utf-8" ?>
600
	 * <x0:principal-search-property-set xmlns:x0="DAV:"/>
601
	 *
602
	 * <?xml version='1.0' encoding='UTF-8'?>
603
	 * <principal-search-property-set xmlns='DAV:'>
604
	 *   <principal-search-property>
605
	 *     <prop>
606
	 *       <displayname/>
607
	 *     </prop>
608
	 *     <description xml:lang='en'>Display Name</description>
609
	 *   </principal-search-property>
610
	 *   <principal-search-property>
611
	 *     <prop>
612
	 *       <email-address-set xmlns='http://calendarserver.org/ns/'/>
613
	 *     </prop>
614
	 *     <description xml:lang='en'>Email Addresses</description>
615
	 *   </principal-search-property>
616
	 *   <principal-search-property>
617
	 *     <prop>
618
	 *       <last-name xmlns='http://calendarserver.org/ns/'/>
619
	 *     </prop>
620
	 *     <description xml:lang='en'>Last Name</description>
621
	 *   </principal-search-property>
622
	 *   <principal-search-property>
623
	 *     <prop>
624
	 *       <calendar-user-type xmlns='urn:ietf:params:xml:ns:caldav'/>
625
	 *     </prop>
626
	 *     <description xml:lang='en'>Calendar User Type</description>
627
	 *   </principal-search-property>
628
	 *   <principal-search-property>
629
	 *     <prop>
630
	 *       <first-name xmlns='http://calendarserver.org/ns/'/>
631
	 *     </prop>
632
	 *     <description xml:lang='en'>First Name</description>
633
	 *   </principal-search-property>
634
	 *   <principal-search-property>
635
	 *     <prop>
636
	 *       <calendar-user-address-set xmlns='urn:ietf:params:xml:ns:caldav'/>
637
	 *     </prop>
638
	 *     <description xml:lang='en'>Calendar User Address Set</description>
639
	 *   </principal-search-property>
640
	 * </principal-search-property-set>
641
	 *
642
	 * @param string $path
643
	 * @param array $options
644
	 * @param array &$files
645
	 * @param int $user account_id
646
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
647
	 */
648
	function principal_search_property_set_report($path,&$options,&$files,$user)
649
	{
650
		unset($path, $options, $files, $user);	// not used, but required by function signature
651
652
		static $search_props = array(
653
			// from iOS iCal
654
			'displayname' => 'Display Name',
655
			'email-address-set' => array('description' => 'Email Addresses', 'ns' => Api\CalDAV::CALENDARSERVER),
656
			'last-name' => array('description' => 'Last Name', 'ns' => Api\CalDAV::CALENDARSERVER),
657
			'calendar-user-type' => array('description' => 'Calendar User Type', 'ns' => Api\CalDAV::CALDAV),
658
			'first-name' => array('description' => 'First Name', 'ns' => Api\CalDAV::CALENDARSERVER),
659
			'calendar-user-address-set' => array('description' => 'Calendar User Address Set', 'ns' => Api\CalDAV::CALDAV),
660
			// Lightning
661
			'calendar-home-set' => array('description' => 'Calendar Home Set', 'ns' => Api\CalDAV::CALENDARSERVER),
662
			// others, we generally support all properties of the principal
663
		);
664
		header('Content-type: text/xml; charset=UTF-8');
665
666
		$xml = new \XMLWriter;
667
		$xml->openMemory();
668
		$xml->setIndent(true);
669
		$xml->startDocument('1.0', 'UTF-8');
670
		$xml->startElementNs(null, 'principal-search-property-set', 'DAV:');
671
672
		foreach($search_props as $name => $data)
673
		{
674
			$xml->startElement('principal-search-property');
675
			$xml->startElement('prop');
676
			if (is_array($data) && !empty($data['ns']))
677
			{
678
				$xml->writeElementNs(null, $name, $data['ns']);
679
			}
680
			else
681
			{
682
				$xml->writeElement($name);
683
			}
684
			$xml->endElement();	// prop
685
686
			$xml->startElement('description');
687
			$xml->writeAttribute('xml:lang', 'en');
688
			$xml->text(is_array($data) ? $data['description'] : $data);
689
			$xml->endElement();	// description
690
691
			$xml->endElement();	// principal-search-property
692
		}
693
		$xml->endElement();	// principal-search-property-set
694
		$xml->endDocument();
695
		echo $xml->outputMemory();
696
697
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
698
	}
699
700
	/**
701
	 * Handle principal-property-search report
702
	 *
703
	 * Current implementation runs a full (infinity) propfind, as we have principals only once in the principal collection.
704
	 *
705
	 * Example from WebDAV ACL rfc 3744:
706
	 * REPORT /index.html HTTP/1.1
707
	 * Host: www.example.com
708
	 * Content-Type: text/xml; charset="utf-8"
709
	 * Content-Length: xxxx
710
	 * Depth: 0
711
	 *
712
	 * <?xml version="1.0" encoding="utf-8" ?>
713
	 * <D:acl-principal-prop-set xmlns:D="DAV:">
714
	 *   <D:prop>
715
	 *     <D:displayname/>
716
	 *   </D:prop>
717
	 * </D:acl-principal-prop-set>
718
	 *
719
	 * Response is a multistatus as for a propfind. Seems the only diverence is, that prinipals are only returned once
720
	 * (even if they exists multiple times), only principals get returned (eg. not the collections they are in) AND the depth 0.
721
	 *
722
	 * @param string $path
723
	 * @param array $options
724
	 * @param array &$files
725
	 * @param int $user account_id
726
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
727
	 */
728
	function acl_principal_prop_set_report($path,&$options,&$files,$user)
729
	{
730
		//error_log(__METHOD__."('$path', ".array2string($options).",, $user)");
731
732
		// run "regular" propfind
733
		$options['root']['name'] = 'propfind';
734
		// search all principals, but not the proxys, rfc requires depth=0, but to search all principals
735
		$options['depth'] = 5 - count(explode('/', $path)); // /principals/ --> 3
736
737
		// we need the resourcetype to only return principals
738
		$options['props']['resourcetype'] = array(
739
			'name' => 'resourcetype',
740
			'xmlns' => 'DAV:',
741
		);
742
		if (($ret = $this->propfind($path, $options, $files, $user)) !== true)
743
		{
744
			return $ret;
745
		}
746
		// now filter out not matching "files"
747
		foreach($files['files'] as $n => $resource)
748
		{
749
			foreach($resource['props']['resourcetype']['val'] as $prop)
750
			{
751
				if ($prop['name'] == 'principal') continue 2;
752
			}
753
			unset($files['files'][$n]);	// not a principal --> do not return
754
		}
755
		// we should not return it
756
		unset($options['props']['resourcetype']);
757
758
		return $ret;
759
	}
760
761
	/**
762
	 * Do propfind in /pricipals/users
763
	 *
764
	 * @param string $name name of account or empty
765
	 * @param string $rest rest of path behind account-name
766
	 * @param array $options
767
	 * @return array|string array with files or HTTP error code
768
	 */
769
	protected function propfind_users($name,$rest,array $options)
770
	{
771
		//error_log(__METHOD__."($name,$rest,".array2string($options).')');
772
		if (empty($name))
773
		{
774
			$files = array();
775
			// add /pricipals/users/ entry
776
			$files[] = $this->add_collection('/principals/users/', array('displayname' => lang('Users')));
777
778
			if ($options['depth'])
779
			{
780
				if ($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'none' &&
781
					!isset($GLOBALS['egw_info']['user']['apps']['admin']))
782
				{
783
					if (($account = $this->accounts->read($GLOBALS['egw_info']['user']['account_id'])))
784
					{
785
						$files[] = $this->add_account($account);
786
					}
787
				}
788
				else
789
				{
790
					// add all users (account_selection == groupmembers is handled by accounts->search())
791
					foreach($this->accounts->search(array('type' => 'accounts','order' => 'account_lid')) as $account)
792
					{
793
						$files[] = $this->add_account($account);
794
					}
795
				}
796
			}
797
		}
798
		else
799
		{
800
			if (!($id = $this->accounts->name2id($name,'account_lid','u')) ||
801
				!($account = $this->accounts->read($id)) ||
802
				!$this->accounts->visible($name))
803
			{
804
				$this->caldav->log(__METHOD__."('$name', ...) account '$name' NOT found OR not visible to you (check account-selection preference)!");
805
				return '404 Not Found';
806
			}
807
			while (substr($rest,-1) == '/')
808
			{
809
				$rest = substr($rest,0,-1);
810
			}
811
			switch((string)$rest)
812
			{
813
				case '':
814
					$files[] = $this->add_account($account);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$files was never initialized. Although not strictly required by PHP, it is generally a good practice to add $files = array(); before regardless.
Loading history...
815
					if ($options['depth'])
816
					{
817
						$files[] = $this->add_proxys('users/'.$account['account_lid'], 'calendar-proxy-read');
818
						$files[] = $this->add_proxys('users/'.$account['account_lid'], 'calendar-proxy-write');
819
					}
820
					break;
821
				case 'calendar-proxy-read':
822
				case 'calendar-proxy-write':
823
					$files = array();
824
					$files[] = $this->add_proxys('users/'.$account['account_lid'], $rest);
825
					break;
826
				default:
827
					return '404 Not Found';
828
			}
829
		}
830
		return $files;
831
	}
832
833
	/**
834
	 * Do propfind in /pricipals/groups
835
	 *
836
	 * @param string $name name of group or empty
837
	 * @param string $rest rest of path behind account-name
838
	 * @param array $options
839
	 * @return array|string array with files or HTTP error code
840
	 */
841
	protected function propfind_groups($name,$rest,array $options)
842
	{
843
		//echo "<p>".__METHOD__."($name,$rest,".array2string($options).")</p>\n";
844
		if (empty($name))
845
		{
846
			$files = array();
847
			// add /pricipals/users/ entry
848
			$files[] = $this->add_collection('/principals/groups/', array('displayname' => lang('Groups')));
849
850
			if ($options['depth'])
851
			{
852
				// only show own groups, if account-selection is groupmembers or none
853
				$type = in_array($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'], array('groupmembers','none')) ?
854
					'owngroups' : 'groups';
855
856
				// add all groups or only membergroups
857
				foreach($this->accounts->search(array('type' => $type,'order' => 'account_lid')) as $account)
858
				{
859
					$files[] = $this->add_group($account);
860
				}
861
			}
862
		}
863
		else
864
		{
865
			if (!($id = $this->accounts->name2id($name,'account_lid','g')) ||
866
				!($account = $this->accounts->read($id)) ||
867
				// do NOT allow other groups, if account-selection is groupmembers or none
868
				in_array($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'], array('groupmembers','none')) &&
869
				!in_array($account['account_id'], $this->accounts->memberships($GLOBALS['egw_info']['user']['account_id'],true)))
870
			{
871
				return '404 Not Found';
872
			}
873
			while (substr($rest,-1) == '/')
874
			{
875
				$rest = substr($rest,0,-1);
876
			}
877
			switch((string)$rest)
878
			{
879
				case '':
880
					$files[] = $this->add_group($account);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$files was never initialized. Although not strictly required by PHP, it is generally a good practice to add $files = array(); before regardless.
Loading history...
881
					if ($options['depth'])
882
					{
883
						$files[] = $this->add_proxys('groups/'.$account['account_lid'], 'calendar-proxy-read');
884
						$files[] = $this->add_proxys('groups/'.$account['account_lid'], 'calendar-proxy-write');
885
					}
886
					break;
887
				case 'calendar-proxy-read':
888
				case 'calendar-proxy-write':
889
					$files = array();
890
					$files[] = $this->add_proxys('groups/'.$account['account_lid'], $rest);
891
					break;
892
				default:
893
					return '404 Not Found';
894
			}
895
		}
896
		return $files;
897
	}
898
899
	/**
900
	 * Get shared addressbooks of current user
901
	 *
902
	 * @return array with path relative to base URI (without addressbook postfix!)
903
	 */
904
	protected function get_shared_addressbooks()
905
	{
906
		$addressbooks = array();
907
		$ab_home_set = $GLOBALS['egw_info']['user']['preferences']['groupdav']['addressbook-home-set'];
908
		if (empty($ab_home_set)) $ab_home_set = 'P';	// personal addressbook
909
		$addressbook_home_set = explode(',', $ab_home_set);
910
		// replace symbolic id's with real nummeric id's
911
		foreach(array(
912
			'P' => $GLOBALS['egw_info']['user']['account_id'],
913
			'G' => $GLOBALS['egw_info']['user']['account_primary_group'],
914
			'U' => '0',
915
		) as $sym => $id)
916
		{
917
			if (($key = array_search($sym, $addressbook_home_set)) !== false)
918
			{
919
				$addressbook_home_set[$key] = $id;
920
			}
921
		}
922
		if (in_array('O',$addressbook_home_set))	// "all in one" from groupdav.php/addressbook/
923
		{
924
			$addressbooks[] = '/';
925
		}
926
		foreach(array_keys($GLOBALS['egw']->contacts->get_addressbooks(Api\Acl::READ)) as $id)
927
		{
928
			if ((in_array('A',$addressbook_home_set) || in_array((string)$id,$addressbook_home_set)) &&
929
				is_numeric($id) && ($owner = $this->accounts->id2name($id)))
930
			{
931
				$addressbooks[] = '/'.$owner.'/';
0 ignored issues
show
Bug introduced by
Are you sure $owner of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

931
				$addressbooks[] = '/'./** @scrutinizer ignore-type */ $owner.'/';
Loading history...
932
			}
933
		}
934
		return $addressbooks;
935
	}
936
937
	/**
938
	 * Add collection of a single account to a collection
939
	 *
940
	 * @param array $account
941
	 * @return array with values for keys 'path' and 'props'
942
	 */
943
	protected function add_account(array $account)
944
	{
945
		$addressbooks = $calendars = array();
946
		// since we "show" shared addressbooks and calendars in the user home, no need for individualiced homes
947
		$addressbooks[] = Api\CalDAV::mkprop('href',
948
			$this->base_uri.'/'.$account['account_lid'].'/');
949
		$calendars[] = Api\CalDAV::mkprop('href',
950
			$this->base_uri.'/'.$account['account_lid'].'/');
951
952
		$displayname = Api\Translation::convert($account['account_fullname'], Api\Translation::charset(),'utf-8');
953
954
		return $this->add_principal('users/'.$account['account_lid'], array(
955
			'getetag' => $this->get_etag($account),
956
			'displayname' => $displayname,
957
			// CalDAV
958
			'calendar-home-set' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-home-set',$calendars),
959
			// CalDAV scheduling
960
			'schedule-outbox-URL' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'schedule-outbox-URL',array(
961
				Api\CalDAV::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/outbox/'))),
962
			'schedule-inbox-URL' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'schedule-inbox-URL',array(
963
				Api\CalDAV::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/inbox/'))),
964
			'calendar-user-address-set' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-user-address-set',array(
965
				Api\CalDAV::mkprop('href','mailto:'.$account['account_email']),
966
				Api\CalDAV::mkprop('href',$this->base_uri(true).'/principals/users/'.$account['account_lid'].'/'),
967
				Api\CalDAV::mkprop('href',$this->base_uri(false).'/principals/users/'.$account['account_lid'].'/'),
968
				Api\CalDAV::mkprop('href','urn:uuid:'.Api\CalDAV::generate_uid('accounts', $account['account_id'])),
969
			)),
970
			'calendar-user-type' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-user-type','INDIVIDUAL'),
971
			// Calendarserver
972
			'email-address-set' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'email-address-set',array(
973
				Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'email-address',$account['account_email']))),
974
			'last-name' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'last-name',$account['account_lastname']),
975
			'first-name' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'first-name',$account['account_firstname']),
976
			'record-type' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'record-type','users'),
977
			// WebDAV ACL and CalDAV proxy
978
			'group-membership' => $this->principal_set('group-membership', $this->accounts->memberships($account['account_id']),
979
				array('calendar', 'resources'), $account['account_id']),	// add proxy-rights
980
			'alternate-URI-set' => array(
981
				Api\CalDAV::mkprop('href','mailto:'.$account['account_email'])),
982
			// CardDAV
983
			'addressbook-home-set' => Api\CalDAV::mkprop(Api\CalDAV::CARDDAV,'addressbook-home-set',$addressbooks),
984
			'principal-address' => Api\CalDAV::mkprop(Api\CalDAV::CARDDAV,'principal-address',
985
				$GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] === '1' ? '' : array(
986
				Api\CalDAV::mkprop('href',$this->base_uri.'/addressbook-accounts/'.$account['person_id'].'.vcf'))),
987
			// CardDAV directory
988
			'directory-gateway' => Api\CalDAV::mkprop(Api\CalDAV::CARDDAV, 'directory-gateway',array(
989
				Api\CalDAV::mkprop('href', $this->base_uri.'/addressbook/'))),
990
			'resource-id' => array(Api\CalDAV::mkprop('href','urn:uuid:'.Api\CalDAV::generate_uid('accounts', $account['account_id']))),
991
		));
992
	}
993
994
	/**
995
	 * Convert CalDAV principal URL to a calendar uid
996
	 *
997
	 * @param string $url
998
	 * @param string|array $only_type =null allowed types, return false for other (valid) types, eg. "users", "groups" or "resources", default all
999
	 * @param string $cn =null common name to be stored in case of an "e" uid
1000
	 * @return int|string|boolean integer account_id, string calendar uid or false if not a supported uid
1001
	 */
1002
	static public function url2uid($url, $only_type=null, $cn=null)
1003
	{
1004
		if (!$only_type) $only_type = array('users', 'groups', 'resources', 'locations', 'mailto');
1005
1006
		if ($url[0] == '/')
1007
		{
1008
			$schema = 'http';
1009
		}
1010
		else
1011
		{
1012
			list($schema, $rest) = explode(':', $url, 2);
1013
		}
1014
		if (empty($rest)) return false;
1015
1016
		switch(strtolower($schema))
1017
		{
1018
			case 'http':
1019
			case 'https':
1020
				list(,$rest) = explode('/groupdav.php/principals/', $url);
1021
				list($type, $name) = explode('/', $rest);
1022
				switch($type)
1023
				{
1024
					case 'users':
1025
					case 'groups':
1026
						$uid = $GLOBALS['egw']->accounts->name2id($name, 'account_lid', $type[0]);	// u=users, g=groups
1027
						break;
1028
					case 'resources':
1029
					case 'locations':
1030
						$uid = 'r'.(int)$name;
1031
						break;
1032
				}
1033
				break;
1034
1035
			case 'mailto':
1036
				if (($uid = $GLOBALS['egw']->accounts->name2id($rest, 'account_email')))
1037
				{
1038
					$type = $uid > 0 ? 'users' : 'groups';
1039
					break;
1040
				}
1041
				// search contacts for email
1042
				if ((list($data) = $GLOBALS['egw']->contacts->search(array('email' => $rest, 'email_home' => $rest),
1043
					array('id','egw_addressbook.account_id as account_id','n_fn'),
1044
					'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC',
1045
					'','',false,'OR')))
1046
				{
1047
					// found an addressbook entry
1048
					$uid = $data['account_id'] ? (int)$data['account_id'] : 'c'.$data['id'];
1049
					$type = 'users';
1050
				}
1051
				else	// just store email-address
1052
				{
1053
					$uid = $cn && $rest[0] != '<' && $cn != $rest ? 'e'.$cn.' <'.$rest.'>' : 'e'.$rest;
1054
					$type = 'users';
1055
				}
1056
				break;
1057
1058
			case 'urn':
1059
				list($urn_type, $uid) = explode(':', $rest, 2);
1060
				list($type, $id, $install_id) = explode('-', $uid);
1061
				if ($type == 'accounts' && empty($id))	// groups have a negative id, eg. "urn:uuid:accounts--1-..."
1062
				{
1063
					list($type, , $id_abs, $install_id) = explode('-', $uid);
1064
					$id = -$id_abs;
1065
				}
1066
				// own urn
1067
				if ($urn_type === 'uuid' && $install_id === $GLOBALS['egw_info']['server']['install_id'])
1068
				{
1069
					if ($type == 'accounts')
1070
					{
1071
						$type = $id > 0 ? 'users' : 'groups';
1072
						$uid = $id;
1073
					}
1074
					else
1075
					{
1076
						static $calendar_bo=null;
1077
						if (is_null($calendar_bo)) $calendar_bo = new calendar_bo();
1078
						foreach($calendar_bo->resources as $letter => $info)
1079
						{
1080
							if ($info['app'] == $type || $info['app'] == 'resources' && $type == 'location')
1081
							{
1082
								$uid = $letter.$id;
1083
							}
1084
						}
1085
					}
1086
				}
1087
				// todo: store urn's from other EGroupware / calendarservers like email addresses ("CN <urn>" or "urn", maybe with 'u' prefix)
1088
				break;
1089
1090
			default:
1091
				if (isset($GLOBALS['groupdav']) && is_a($GLOBALS['groupdav'],'groupdav'))
1092
				{
1093
					$GLOBALS['groupdav']->log(__METHOD__."('$url') unsupported principal URL '$url'!");
1094
				}
1095
				return false;
1096
		}
1097
		//error_log(__METHOD__."('$url', ".array2string($only_type).") uid='$uid', type='$type' --> returning ".array2string($uid && in_array($type, $only_type) ? $uid : false));
1098
		return $uid && in_array($type, $only_type) ? $uid : false;
1099
	}
1100
1101
	/**
1102
	 * Add collection of a single group to a collection
1103
	 *
1104
	 * @param array $account
1105
	 * @return array with values for keys 'path' and 'props'
1106
	 */
1107
	protected function add_group(array $account)
1108
	{
1109
		$displayname = Api\Translation::convert(lang('Group').' '.$account['account_lid'],	Api\Translation::charset(), 'utf-8');
1110
1111
		// only return current user, if account-selection == 'none'
1112
		if ($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] == 'none')
1113
		{
1114
			$groupmembers = array($GLOBALS['egw_info']['user']['account_id'] => $GLOBALS['egw_info']['user']['account_lid']);
1115
		}
1116
		else
1117
		{
1118
			$groupmembers = $this->accounts->members($account['account_id']);
1119
		}
1120
1121
		return $this->add_principal('groups/'.$account['account_lid'], array(
1122
			'getetag' => $this->get_etag($account),
1123
			'displayname' => $displayname,
1124
			'calendar-home-set' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-home-set',array(
1125
				Api\CalDAV::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/'))),
1126
			'addressbook-home-set' => Api\CalDAV::mkprop(Api\CalDAV::CARDDAV,'addressbook-home-set',array(
1127
				Api\CalDAV::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/'))),
1128
			'calendar-user-address-set' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-user-address-set',array(
1129
				Api\CalDAV::mkprop('href',$this->base_uri(true).'/principals/groups/'.$account['account_lid'].'/'),
1130
				Api\CalDAV::mkprop('href',$this->base_uri(false).'/principals/groups/'.$account['account_lid'].'/'),
1131
				Api\CalDAV::mkprop('href','urn:uuid:'.Api\CalDAV::generate_uid('accounts', $account['account_id'])),
1132
			)),
1133
			'record-type' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'record-type','groups'),
1134
			'calendar-user-type' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-user-type','GROUP'),
1135
			'group-member-set' => $this->principal_set('group-member-set', $groupmembers),
1136
			'resource-id' => array(Api\CalDAV::mkprop('href','urn:uuid:'.Api\CalDAV::generate_uid('accounts', $account['account_id']))),
1137
		));
1138
	}
1139
1140
	/**
1141
	 * Add collection of a single resource to a collection
1142
	 *
1143
	 * @param array $resource
1144
	 * @param boolean $is_location =null
1145
	 * @return array with values for keys 'path' and 'props'
1146
	 */
1147
	protected function add_principal_resource(array $resource, $is_location=null)
1148
	{
1149
		$displayname = null;
1150
		$name = $this->resource2name($resource, $is_location, $displayname);
1151
1152
		return $this->add_principal($name, array(
1153
			'getetag' => $this->get_resource_etag($resource),
1154
			'displayname' => $displayname,
1155
			'calendar-user-address-set' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-user-address-set',array(
1156
				Api\CalDAV::mkprop('href',$this->base_uri(true).'/principals/'.$name.'/'),
1157
				Api\CalDAV::mkprop('href',$this->base_uri(false).'/principals/'.$name.'/'),
1158
				Api\CalDAV::mkprop('href','urn:uuid:'.Api\CalDAV::generate_uid('resources', $resource['res_id'])),
1159
			)),
1160
			'record-type' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'record-type',$is_location ? 'locations' : 'resources'),
1161
			'calendar-user-type' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-user-type',$is_location ? 'ROOM' : 'RESOURCE'),
1162
			'resource-id' => array(Api\CalDAV::mkprop('href','urn:uuid:'.Api\CalDAV::generate_uid('resources', $resource['res_id']))),
1163
			// Calendarserver also reports empty email-address-set, thought iCal still does not show resources (only locations)
1164
			'email-address-set' => Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER,'email-address-set',''),
1165
			'calendar-home-set' => Api\CalDAV::mkprop(Api\CalDAV::CALDAV,'calendar-home-set',array(
1166
				Api\CalDAV::mkprop('href',$this->base_uri.'/'.$name.'/'))),
1167
		));
1168
	}
1169
1170
	/**
1171
	 * Get path of a resource-principal (relative to principal collection)
1172
	 *
1173
	 * @param array|int $resource
1174
	 * @param boolean &$is_location=null
1175
	 * @param string &$displayname=null on return displayname of resource
1176
	 * @return string eg. "locations/123-some-room" or "resouces/345-some-device"
1177
	 */
1178
	public static function resource2name($resource, &$is_location=null, &$displayname=null)
1179
	{
1180
		if (!is_array($resource) && !($resource = self::read_resource($resource)))
1181
		{
1182
			return null;
1183
		}
1184
		if (is_null($is_location)) $is_location = self::resource_is_location($resource);
1185
1186
		$displayname = Api\Translation::convert($resource['name'],	Api\Translation::charset(), 'utf-8');
1187
1188
		return ($is_location ? 'locations/' : 'resources/').$resource['res_id'].'-'.
1189
			preg_replace('/[^a-z0-9]+/i','-', Api\Translation::to_ascii($resource['name']));
1190
	}
1191
1192
	/**
1193
	 * Check if resource is a location
1194
	 *
1195
	 * @param array|int $resource
1196
	 * @return boolean
1197
	 */
1198
	public static function resource_is_location($resource)
1199
	{
1200
		static $location_cats=null;
1201
		if (is_null($location_cats))
1202
		{
1203
			$config = Api\Config::read('resources');
1204
			$location_cats = $config['location_cats'] ? explode(',', $config['location_cats']) : array();
1205
		}
1206
		if (!is_array($resource) && !($resource = self::read_resource($resource)))
1207
		{
1208
			return null;
1209
		}
1210
		return $resource['cat_id'] && in_array($resource['cat_id'], $location_cats);
1211
	}
1212
1213
	/**
1214
	 * Read a resource
1215
	 *
1216
	 * @param int $res_id resource-id
1217
	 * @return array with values for res_id, cat_id and name
1218
	 */
1219
	public static function read_resource($res_id)
1220
	{
1221
		static $cache=null;	// some per-request caching
1222
1223
		if (isset(self::$all_resources) && isset(self::$all_resources[$res_id]))
1224
		{
1225
			return self::$all_resources[$res_id];
1226
		}
1227
1228
		if (!isset($cache[$res_id]))
1229
		{
1230
			if (!isset(self::$resources)) self::$resources = new resources_bo();
1231
1232
			if (!($cache[$res_id] = self::$resources->read($res_id)))
1233
			{
1234
				return null;
1235
			}
1236
		}
1237
		return $cache[$res_id];
1238
	}
1239
1240
	/**
1241
	 * Get an etag for a resource
1242
	 *
1243
	 * @param array $resource
1244
	 * @return string
1245
	 */
1246
	protected function get_resource_etag(array $resource)
1247
	{
1248
		return md5(serialize($resource)).'-'.(self::resource_is_location($resource) ? 'l' : 'r');
1249
	}
1250
1251
	/**
1252
	 * Cache for get_resources
1253
	 *
1254
	 * @var array
1255
	 */
1256
	private static $all_resources;
1257
	/**
1258
	 * Get all resources (we cache the resources here, to only query them once per request)
1259
	 *
1260
	 * @param int $user =null account_if of user, or null for current user
1261
	 * @return array of array with values for res_id, cat_id and name (no other values1)
1262
	 */
1263
	public static function get_resources($user=null)
1264
	{
1265
		if (!isset(self::$all_resources))
1266
		{
1267
			if (!isset(self::$resources)) self::$resources = new resources_bo($user);
1268
1269
			self::$all_resources = array();
1270
			$query = array(
1271
				'show_bookable' => true,	// ignore non-bookable resources
1272
				'filter2' => -3,
1273
				'start' => 0,
1274
				'num_rows' => 10000,	// return all aka first 10000 entries
1275
			);
1276
			$rows = $readonlys = null;
1277
			if (self::$resources->get_rows($query, $rows, $readonlys))
1278
			{
1279
				//_debug_array($rows);
1280
				foreach($rows as $resource)
1281
				{
1282
					self::$all_resources[$resource['res_id']] = array_intersect_key($resource, array('res_id'=>true,'cat_id'=>true,'name'=>true));
1283
				}
1284
			}
1285
		}
1286
		return self::$all_resources;
1287
	}
1288
1289
	/**
1290
	 * Get category based ACL rights for resouces
1291
	 *
1292
	 * Cached to not query it multiple times per request
1293
	 *
1294
	 * @return array of 'L'.$cat_id => array($account_id => $rights) pairs
1295
	 */
1296
	protected function get_resource_rights()
1297
	{
1298
		static $grants=null;
1299
1300
		if (is_null($grants))
1301
		{
1302
			$grants = $this->acl->get_location_grants('L%', 'resources');
1303
		}
1304
		return $grants;
1305
	}
1306
1307
	/**
1308
	 * Add a collection
1309
	 *
1310
	 * @param string $path
1311
	 * @param array $props =array() extra properties 'resourcetype' is added anyway, name => value pairs or name => Api\CalDAV([namespace,]name,value)
1312
	 * @return array with values for keys 'path' and 'props'
1313
	 */
1314
	protected function add_collection($path, array $props = array())
1315
	{
1316
		if ($this->caldav->prop_requested('supported-report-set'))
1317
		{
1318
			$props['supported-report-set'] = $this->supported_report_set($path);
1319
		}
1320
		return $this->caldav->add_collection($path, $props);
1321
	}
1322
1323
	/**
1324
	 * Add a principal collection
1325
	 *
1326
	 * @param string $principal relative to principal-collection-set, eg. "users/username"
1327
	 * @param array $props =array() extra properties 'resourcetype' is added anyway
1328
	 * @param string $principal_url =null include given principal url, relative to principal-collection-set, default $principal
1329
	 * @return array with values for keys 'path' and 'props'
1330
	 */
1331
	protected function add_principal($principal, array $props = array(), $principal_url=null)
1332
	{
1333
		$props['resourcetype'][] = Api\CalDAV::mkprop('principal', '');
1334
1335
		// required props per WebDAV ACL
1336
		foreach(array('alternate-URI-set', 'group-membership') as $name)
1337
		{
1338
			if (!isset($props[$name])) $props[$name] = Api\CalDAV::mkprop($name,'');
1339
		}
1340
		if (!$principal_url) $principal_url = $principal;
0 ignored issues
show
Unused Code introduced by
The assignment to $principal_url is dead and can be removed.
Loading history...
1341
1342
		$props['principal-URL'] = array(
1343
			Api\CalDAV::mkprop('href',$this->base_uri.'/principals/'.$principal.'/'));
1344
1345
		return $this->add_collection('/principals/'.$principal.'/', $props);
1346
	}
1347
1348
	/**
1349
	 * Add a proxy collection for given principal and type
1350
	 *
1351
	 * A proxy is a user or group who has the right to act on behalf of the user
1352
	 *
1353
	 * @param string $principal relative to principal-collection-set, eg. "users/username"
1354
	 * @param string $type eg. 'calendar-proxy-read' or 'calendar-proxy-write'
1355
	 * @param array $proxys =array()
1356
	 * @param array $resource =null resource to use (to not query it multiple times from the database)
1357
	 * @return array with values for 'path' and 'props'
1358
	 */
1359
	protected function add_proxys($principal, $type, array $proxys=array(), array $resource=null)
1360
	{
1361
		list($app,,$what) = explode('-', $type);
1362
1363
		if (true) $proxys = array();	// ignore parameter!
1364
		list($account_type,$account) = explode('/', $principal);
1365
1366
		switch($account_type)
1367
		{
1368
			case 'users':
1369
			case 'groups':
1370
				$account = $location = $this->accounts->name2id($account, 'account_lid', $account_type[0]);
1371
				$right = $what == 'write' ? Api\Acl::EDIT : Api\Acl::READ;
1372
				$mask = $what == 'write' ? Api\Acl::EDIT : Api\Acl::EDIT|Api\Acl::READ;	// do NOT report write+read in read
1373
				break;
1374
1375
			case 'locations':
1376
			case 'resources':
1377
				$app = 'resources';
1378
				if (!is_array($resource) || $resource['res_id'] == (int)$account)
1379
				{
1380
					$resource = self::read_resource((int)$account);
1381
				}
1382
				$location = 'L'.$resource['cat_id'];
1383
				$right = $what == 'write' ? resources_acl_bo::DIRECT_BOOKING : resources_acl_bo::CAL_READ;
1384
				$mask = $what == 'write' ? resources_acl_bo::DIRECT_BOOKING : resources_acl_bo::DIRECT_BOOKING|resources_acl_bo::CAL_READ;	// do NOT report write+read in read
1385
				break;
1386
		}
1387
		static $principal2grants = array();
1388
		$grants =& $principal2grants[$principal];
1389
		if (!isset($grants))
1390
		{
1391
			switch($app)
1392
			{
1393
				case 'resources':
1394
					$res_grants = $this->get_resource_rights();
1395
					$grants = (array)$res_grants[$location];	// returns array($location => $grants)
1396
					break;
1397
1398
				case 'calendar':
1399
				default:
1400
					$grants = $this->acl->get_all_location_rights($account, $app, $app != 'addressbook');
1401
					break;
1402
			}
1403
			//echo "<p>type=$type --> app=$app, what=$what --> right=$right, mask=$mask, account=$account, location=$location --> grants=".array2string($grants)."</p>\n";
1404
		}
1405
		foreach($grants as $account_id => $rights)
1406
		{
1407
			if ($account_id !== 'run' && $account_id != $account && ($rights & $mask) == $right &&
1408
				($account_lid = $this->accounts->id2name($account_id)))
1409
			{
1410
				// ignore "broken" grants (eg. negative account_id for a user), as they lead to further errors (no members)
1411
				if (($t = $this->accounts->exists($account_id) == 1 ? 'u' : 'g') !== $this->accounts->get_type($account_id))
1412
				{
1413
					error_log(__METHOD__."('$principal', '$type') broken grants from #$account_id ($account_lid) type=$t ignored!");
1414
					continue;
1415
				}
1416
				$proxys[$account_id] = $account_lid;
1417
				// no need to add group-members, ACL grants to groups are understood by WebDAV ACL (tested with iCal)
1418
			}
1419
			//echo "<p>$account_id ($account_lid) type=$t: (rights=$rights & mask=$mask) == right=$right --> ".array2string(($rights & $mask) == $right)."</p>\n";
1420
		}
1421
		return $this->add_principal($principal.'/'.$type, array(
1422
				'displayname' => lang('%1 proxy of %2', lang($app).' '.lang($what), basename($principal)),
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with lang($app) . ' ' . lang($what). ( Ignorable by Annotation )

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

1422
				'displayname' => /** @scrutinizer ignore-call */ lang('%1 proxy of %2', lang($app).' '.lang($what), basename($principal)),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1423
				'group-member-set' => $this->principal_set('group-member-set', $proxys),
1424
				'getetag' => md5(serialize($proxys)),
1425
				'resourcetype' => array(Api\CalDAV::mkprop(Api\CalDAV::CALENDARSERVER, $type, '')),
1426
			));
1427
	}
1428
1429
	/**
1430
	 * Create a named property with set or principal-urls
1431
	 *
1432
	 * @param string $prop egw. 'group-member-set' or 'membership'
1433
	 * @param array $accounts =array() account_id => account_lid pairs
1434
	 * @param string|array $app_proxys =null applications for which proxys should be added
1435
	 * @param int $account who is the proxy
1436
	 * @return array with href props
1437
	 */
1438
	protected function principal_set($prop, array $accounts=array(), $app_proxys=null, $account=null)
1439
	{
1440
		unset($prop);	// not used, but required by function signature
1441
1442
		$set = array();
1443
		foreach($accounts as $account_id => $account_lid)
1444
		{
1445
			if ($this->accounts->visible($account_lid))	// only add visible accounts, gives error in iCal otherwise
1446
			{
1447
				$set[] = Api\CalDAV::mkprop('href', $this->base_uri.'/principals/'.($account_id < 0 ? 'groups/' : 'users/').$account_lid.'/');
1448
			}
1449
		}
1450
		if ($app_proxys)
1451
		{
1452
			foreach((array)$app_proxys as $app)
1453
			{
1454
				if (!isset($GLOBALS['egw_info']['user']['apps'][$app])) continue;
1455
1456
				switch($app)
1457
				{
1458
					case 'resources':
1459
						$proxy_groups = $this->get_resource_proxy_groups($account);
1460
						break;
1461
					default:
1462
						$proxy_groups = $this->get_calendar_proxy_groups($account, $app);
1463
						break;
1464
				}
1465
				$set = array_merge($set, $proxy_groups);
1466
			}
1467
		}
1468
		return $set;
1469
	}
1470
1471
	/**
1472
	 * Get proxy-groups for given user $account: users or groups who GRANT proxy rights to $account
1473
	 *
1474
	 * @param int $account who is the proxy
1475
	 * @param string|array $app_proxys =null applications for which proxys should be added
1476
	 * @return array with href props
1477
	 */
1478
	protected function get_resource_proxy_groups($account)
1479
	{
1480
		$set = array();
1481
		if (($resources = $this->get_resources()))
1482
		{
1483
			// location_grants = array(location => array(account_id => rights))
1484
			$all_location_grants = $this->get_resource_rights();
1485
			// get location grants for $account (incl. his memberships)
1486
			$memberships = $GLOBALS['egw']->accounts->memberships($account, true);
1487
			$location_grants = array();
1488
			foreach($all_location_grants as $location => $grants)
1489
			{
1490
				foreach($grants as $account_id => $rights)
1491
				{
1492
					if (($rights & (resources_acl_bo::CAL_READ|resources_acl_bo::DIRECT_BOOKING)) &&	// we only care for these rights
1493
						($account_id == $account || in_array($account_id, $memberships)))
1494
					{
1495
						if (!isset($location_grants[$location])) $location_grants[$location] = 0;
1496
						$location_grants[$location] |= $rights;
1497
					}
1498
				}
1499
			}
1500
			// now add proxy-groups for all resources user has rights to
1501
			foreach($resources as $resource)
1502
			{
1503
				$rights = $location_grants['L'.$resource['cat_id']];
1504
				if (isset($rights))
1505
				{
1506
					$set[] = Api\CalDAV::mkprop('href', $this->base_uri.'/principals/'.$this->resource2name($resource).
1507
						'/calendar-proxy-'.($rights & resources_acl_bo::DIRECT_BOOKING ? 'write' : 'read').'/');
1508
				}
1509
			}
1510
		}
1511
		//echo "get_resource_proxy_groups($account)"; _debug_array($set);
1512
		return $set;
1513
	}
1514
1515
	/**
1516
	 * Get proxy-groups for given user $account: users or groups who GRANT proxy rights to $account
1517
	 *
1518
	 * @param int $account who is the proxy
1519
	 * @param string $app ='calendar' applications for which proxys should be added
1520
	 * @return array with href props
1521
	 */
1522
	protected function get_calendar_proxy_groups($account, $app='calendar')
1523
	{
1524
		$set = array();
1525
		foreach($this->acl->get_grants($app, $app != 'addressbook', $account) as $account_id => $rights)
1526
		{
1527
			if ($account_id != $account && ($rights & Api\Acl::READ) &&
1528
				($account_lid = $this->accounts->id2name($account_id)) &&
1529
				$this->accounts->visible($account_lid))	// only add visible accounts, gives error in iCal otherwise
1530
			{
1531
				$set[] = Api\CalDAV::mkprop('href', $this->base_uri.'/principals/'.
1532
					($account_id < 0 ? 'groups/' : 'users/').
1533
					$account_lid.'/'.$app.'-proxy-'.($rights & Api\Acl::EDIT ? 'write' : 'read').'/');
0 ignored issues
show
Bug introduced by
Are you sure $account_lid of type string|true can be used in concatenation? ( Ignorable by Annotation )

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

1533
					/** @scrutinizer ignore-type */ $account_lid.'/'.$app.'-proxy-'.($rights & Api\Acl::EDIT ? 'write' : 'read').'/');
Loading history...
1534
			}
1535
		}
1536
		return $set;
1537
	}
1538
1539
	/**
1540
	 * Do propfind in /pricipals/(resources|locations)
1541
	 *
1542
	 * @param string $name name of group or empty
1543
	 * @param string $rest rest of path behind account-name
1544
	 * @param array $options
1545
	 * @param boolean $do_locations =false false: /principal/resources, true: /principals/locations
1546
	 * @return array|string array with files or HTTP error code
1547
	 */
1548
	protected function propfind_resources($name,$rest,array $options,$do_locations=false)
1549
	{
1550
		if (!isset($GLOBALS['egw_info']['user']['apps']['resources']))
1551
		{
1552
			return '404 Not Found';
1553
		}
1554
		//echo "<p>".__METHOD__."('$name', '$rest', ".array2string($options).', '.array2string($do_locations).")</p>\n";
1555
		if (empty($name))
1556
		{
1557
			$files = array();
1558
			// add /pricipals/users/ entry
1559
			$files[] = $this->add_collection('/principals/'.($do_locations ? 'locations/' : 'resources/'),
1560
				array('displayname' => $do_locations ? lang('Locations') : lang('Resources')));
1561
1562
			if ($options['depth'])
1563
			{
1564
				if (($resources = $this->get_resources()))
1565
				{
1566
					//_debug_array($resources);
1567
					foreach($resources as $resource)
1568
					{
1569
						if (($is_location = self::resource_is_location($resource)) == $do_locations)
1570
						{
1571
							$files[] = $this->add_principal_resource($resource, $is_location);
1572
						}
1573
					}
1574
				}
1575
			}
1576
		}
1577
		else
1578
		{
1579
			if (!($resource = self::read_resource((int)$name)) || ($is_location = self::resource_is_location($resource)) != $do_locations)
1580
			{
1581
				return '404 Not Found';
1582
			}
1583
			$path = ($is_location ? 'locations/' : 'resources/').$name;
1584
			while (substr($rest,-1) == '/')
1585
			{
1586
				$rest = substr($rest,0,-1);
1587
			}
1588
			switch((string)$rest)
1589
			{
1590
				case '':
1591
					$files[] = $this->add_principal_resource($resource);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$files was never initialized. Although not strictly required by PHP, it is generally a good practice to add $files = array(); before regardless.
Loading history...
1592
					if ($options['depth'])
1593
					{
1594
						$files[] = $this->add_proxys($path, 'calendar-proxy-read', array(), $resource);
1595
						$files[] = $this->add_proxys($path, 'calendar-proxy-write', array(), $resource);
1596
					}
1597
					break;
1598
				case 'calendar-proxy-read':
1599
				case 'calendar-proxy-write':
1600
					$files = array();
1601
					$files[] = $this->add_proxys($path, $rest, array(), $resource);
1602
					break;
1603
				default:
1604
					return '404 Not Found';
1605
			}
1606
		}
1607
		return $files;
1608
	}
1609
1610
	/**
1611
	 * Do propfind of /principals/
1612
	 *
1613
	 * @param string $name name of group or empty
1614
	 * @param string $rest name of rest of path behind group-name
1615
	 * @param array $options
1616
	 * @return array|string array with files or HTTP error code
1617
	 */
1618
	protected function propfind_principals(array $options)
1619
	{
1620
		//echo "<p>".__METHOD__."(".array($options).")</p>\n";
1621
		$files = array();
1622
		$files[] = $this->add_collection('/principals/');
1623
1624
		if ($options['depth'])
1625
		{
1626
			if (is_numeric($options['depth'])) --$options['depth'];
1627
			$files = array_merge($files, $this->propfind_users('','',$options),
1628
				$this->propfind_groups('','',$options));
1629
			if ($GLOBALS['egw_info']['user']['apps']['resources'])
1630
			{
1631
				$files = array_merge($files, $this->propfind_resources('','',$options,false),	// resources
1632
					$this->propfind_resources('','',$options,true));	// locations
1633
			}
1634
			//$files = array_merge($files,$this->propfind_uids('','',$options));
1635
		}
1636
		return $files;
1637
	}
1638
1639
	/**
1640
	 * Handle get request for an applications entry
1641
	 *
1642
	 * @param array &$options
1643
	 * @param int $id
1644
	 * @param int $user =null account_id
1645
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
1646
	 */
1647
	function get(&$options,$id,$user=null)
1648
	{
1649
		unset($options, $id, $user);	// not used, but required by function signature
1650
1651
		return false;
1652
	}
1653
1654
	/**
1655
	 * Handle get request for an applications entry
1656
	 *
1657
	 * @param array &$options
1658
	 * @param int $id
1659
	 * @param int $user =null account_id of owner, default null
1660
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
1661
	 */
1662
	function put(&$options,$id,$user=null)
1663
	{
1664
		unset($options, $id, $user);	// not used, but required by function signature
1665
1666
		return false;
1667
	}
1668
1669
	/**
1670
	 * Handle get request for an applications entry
1671
	 *
1672
	 * @param array &$options
1673
	 * @param int $id
1674
	 * @param int $user account_id of collection owner
1675
	 * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found')
1676
	 */
1677
	function delete(&$options,$id,$user)
1678
	{
1679
		unset($options, $id, $user);	// not used, but required by function signature
1680
1681
		return false;
1682
	}
1683
1684
	/**
1685
	 * Read an entry
1686
	 *
1687
	 * @param string|int $id
1688
	 * @return array/boolean array with entry, false if no read rights, null if $id does not exist
0 ignored issues
show
Documentation Bug introduced by
The doc comment array/boolean at position 0 could not be parsed: Unknown type name 'array/boolean' at position 0 in array/boolean.
Loading history...
1689
	 */
1690
	function read($id)
1691
	{
1692
		unset($id);	// not used, but required by function signature
1693
1694
		return false;
1695
	}
1696
1697
	/**
1698
	 * Check if user has the neccessary rights on an entry
1699
	 *
1700
	 * @param int $acl Api\Acl::READ, Api\Acl::EDIT or Api\Acl::DELETE
1701
	 * @param array|int $entry entry-array or id
1702
	 * @return boolean null if entry does not exist, false if no access, true if access permitted
1703
	 */
1704
	function check_access($acl,$entry)
1705
	{
1706
		if ($acl != Api\Acl::READ)
1707
		{
1708
			return false;
1709
		}
1710
		if (!is_array($entry) && !$this->accounts->name2id($entry,'account_lid','u'))
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->accounts->name2id...ry, 'account_lid', 'u') of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1711
		{
1712
			return null;
1713
		}
1714
		return true;
1715
	}
1716
1717
	/**
1718
	 * Get the etag for an entry, can be reimplemented for other algorithm or field names
1719
	 *
1720
	 * @param array|int $account array with event or cal_id
1721
	 * @return string/boolean string with etag or false
0 ignored issues
show
Documentation Bug introduced by
The doc comment string/boolean at position 0 could not be parsed: Unknown type name 'string/boolean' at position 0 in string/boolean.
Loading history...
1722
	 */
1723
	function get_etag($account)
1724
	{
1725
		if (!is_array($account))
1726
		{
1727
			$account = $this->read($account);
1728
		}
1729
		return $account['account_id'].':'.md5(serialize($account)).
1730
			// add md5 from calendar & resource grants, as they are listed as memberships
1731
			':'.md5(serialize($this->acl->get_grants('calendar', true, $account['account_id'])).
1732
				serialize($this->get_resource_rights())).
1733
			// as the principal of current user is influenced by GroupDAV prefs, we have to include them in the etag
1734
			($account['account_id'] == $GLOBALS['egw_info']['user']['account_id'] ?
1735
				':'.md5(serialize($GLOBALS['egw_info']['user']['preferences']['groupdav'])) : '');
1736
	}
1737
1738
	/**
1739
	 * Return privileges for current user, default is read and read-current-user-privilege-set
1740
	 *
1741
	 * Privileges are for the collection, not the resources / entries!
1742
	 *
1743
	 * @param string $path path of collection
1744
	 * @param int $user =null owner of the collection, default current user
1745
	 * @return array with privileges
1746
	 */
1747
	public function current_user_privileges($path, $user=null)
1748
	{
1749
		unset($path, $user);	// not used, but required by function signature
1750
1751
		return array('read', 'read-current-user-privilege-set');
1752
	}
1753
}
1754