1: <?php
2: /**
3: * This code is licensed under AGPLv3 license or Afterlogic Software License
4: * if commercial version of the product was purchased.
5: * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
6: */
7:
8: namespace Aurora\Modules\Dav;
9:
10: use Afterlogic\DAV\CalDAV\Plugin;
11: use Aurora\Modules\Calendar\Enums\Permission;
12:
13: use function Sabre\Uri\split;
14:
15: /**
16: * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
17: * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
18: * @copyright Copyright (c) 2023, Afterlogic Corp.
19: *
20: * @package Classes
21: * @subpackage Dav
22: */
23: if (!defined('DAV_ROOT')) {
24: define('DAV_ROOT', 'dav/server.php/');
25: }
26: if (!defined('DAV_EMAIL_DEV')) {
27: define('DAV_EMAIL_DEV', '@');
28: }
29:
30: class Client
31: {
32: public const PROP_RESOURCETYPE = '{DAV:}resourcetype';
33: public const PROP_DISPLAYNAME = '{DAV:}displayname';
34: public const PROP_CALENDAR_DESCRIPTION = '{urn:ietf:params:xml:ns:caldav}calendar-description';
35: public const PROP_CALENDAR_ORDER = '{http://apple.com/ns/ical/}calendar-order';
36: public const PROP_CALENDAR_COLOR = '{http://apple.com/ns/ical/}calendar-color';
37: public const PROP_CALENDAR_DATA = '{urn:ietf:params:xml:ns:caldav}calendar-data';
38: public const PROP_ADDRESSBOOK_DATA = '{urn:ietf:params:xml:ns:carddav}address-data';
39: public const PROP_OWNER = '{DAV:}owner';
40: public const PROP_CTAG = '{http://calendarserver.org/ns/}getctag';
41: public const PROP_ETAG = '{DAV:}getetag';
42: public const PROP_CURRENT_USER_PRINCIPAL = '{DAV:}current-user-principal';
43: public const PROP_CALENDAR_HOME_SET = '{urn:ietf:params:xml:ns:caldav}calendar-home-set';
44: public const PROP_CALENDAR_INVITE = '{' . Plugin::NS_CALENDARSERVER . '}invite';
45: public const PROP_ADDRESSBOOK_HOME_SET = '{urn:ietf:params:xml:ns:carddav}addressbook-home-set';
46: public const PROP_GROUP_MEMBERSHIP = '{DAV:}group-membership';
47: public const PROP_GROUP_MEMBER_SET = '{DAV:}group-member-set';
48:
49:
50: /**
51: * @var string
52: */
53: public $baseUrl;
54:
55: /**
56: * @var string
57: */
58: protected $user;
59:
60: /**
61: * @var bool
62: */
63: protected $connected;
64:
65: /**
66: * @var string
67: */
68: protected $protocol = 'http';
69:
70: /**
71: * @var string
72: */
73: protected $port = 80;
74:
75: /**
76: * @var string
77: */
78: protected $server;
79:
80: /**
81: * @var bool
82: */
83: public $isCustomServer = false;
84:
85: /**
86: * @var \Afterlogic\DAV\Client
87: */
88: public $client;
89:
90: /**
91: * @param string $baseUrl
92: * @param string $user
93: * @param string $pass
94: */
95: public function __construct($baseUrl, $user, $pass)
96: {
97: $this->client = new \Afterlogic\DAV\Client(
98: array(
99: 'baseUri' => $baseUrl,
100: 'userName' => $user,
101: 'password' => $pass
102: )
103: );
104:
105: $this->user = $user;
106:
107: $aUrlParts = parse_url($baseUrl);
108:
109: if (isset($aUrlParts['port'])) {
110: $this->port = $aUrlParts['port'];
111: }
112:
113: $matches = array();
114: if (preg_match('#^(https?)://([a-z0-9.-]+)(:([0-9]+))?(/.*)$#', $baseUrl, $matches)) {
115: $this->protocol = $matches[1];
116: $this->server = $matches[2];
117: $this->baseUrl = $matches[5];
118: }
119:
120: $this->connected = false;
121: }
122:
123: /**
124: * @return bool
125: */
126: public function Connect()
127: {
128: if ($this->connected === false) {
129: $this->connected = $this->testConnection();
130: }
131: return $this->connected;
132: }
133:
134: /**
135: * @return bool
136: */
137: public function testConnection()
138: {
139: $res = $this->client->options_ex();
140: $this->isCustomServer = $res['custom-server'];
141:
142: return true;
143: }
144:
145: /**
146: * @return string
147: */
148: public function GetBaseUrl()
149: {
150: return $this->baseUrl;
151: }
152:
153: /**
154: * @return string
155: */
156: public function GetRootUrl($url)
157: {
158: $urlParts = parse_url($url);
159: $path = '/';
160: if (isset($urlParts['path'])) {
161: $path = $urlParts['path'];
162: }
163: return $path;
164: }
165:
166: /**
167: * @return string
168: */
169: public function GetUser()
170: {
171: return $this->user;
172: }
173:
174: /**
175: * @return string
176: */
177: public function GetServer()
178: {
179: return $this->server;
180: }
181:
182: /**
183: * @return string
184: */
185: public function GetServerUrl()
186: {
187: $sPort = '';
188: if ((int)$this->port != 80) {
189: $sPort = ':' . $this->port;
190: }
191: return $this->protocol . '://' . $_SERVER['SERVER_NAME'] . $sPort;
192: }
193:
194: /**
195: * @param string $sUrl
196: * @param string $sData
197: *
198: * @return string
199: */
200: public function CreateItem($sUrl, $sData)
201: {
202: return $this->client->request(
203: 'PUT',
204: $sUrl,
205: $sData,
206: array(
207: 'If-None-Match' => '*'
208: )
209: );
210: }
211:
212: /**
213: * @param string $sUrl
214: * @param string $sData
215: *
216: * @return string
217: */
218: public function UpdateItem($sUrl, $sData, $sEtag = '*')
219: {
220: return $this->client->request(
221: 'PUT',
222: $sUrl,
223: $sData,
224: array(
225: // 'If-Match' => '"'.$sEtag.'"'
226: )
227: );
228: }
229:
230: /**
231: * @param string $url
232: * @param string $icalData
233: *
234: * @return string
235: */
236: public function MoveItem($url, $newUrl)
237: {
238: return $this->client->request(
239: 'MOVE',
240: $url,
241: '',
242: array(
243: 'destination' => $newUrl
244: )
245: );
246: }
247:
248: /**
249: * @param string $sSystemName
250: * @param string $sDisplayName
251: * @param string $sDescription
252: * @param int $iOrder
253: * @param string $sColor
254: * @param string $sCalendarHome
255: *
256: * @return string
257: */
258: public function createCalendar(
259: $sSystemName,
260: $sDisplayName,
261: $sDescription,
262: $iOrder,
263: $sColor,
264: $sCalendarHome
265: )
266: {
267: $xml =
268: '<?xml version="1.0" encoding="UTF-8" ?>
269: <c:mkcalendar xmlns:c="'.\Sabre\CalDAV\Plugin::NS_CALDAV.'" xmlns:d="DAV:" xmlns:ic="http://apple.com/ns/ical/">
270: <d:set>
271: <d:prop>
272: <d:displayname>'.$sDisplayName.'</d:displayname>
273: <c:calendar-description>'.$sDescription.'</c:calendar-description>
274: <ic:calendar-order>'.$iOrder.'</ic:calendar-order>
275: <ic:calendar-color>'.$sColor.'</ic:calendar-color>
276: </d:prop>
277: </d:set>
278: </c:mkcalendar>';
279:
280: return $this->client->request(
281: 'MKCALENDAR',
282: $sCalendarHome.$sSystemName,
283: $xml,
284: array(
285: 'Content-Type' => 'application/xml',
286: 'Depth' => '1'
287: )
288: );
289: }
290:
291: /**
292: * @param string $sCalendarId
293: * @param string $sDisplayName
294: * @param string $sDescription
295: * @param int $iOrder
296: * @param string $sColor
297: *
298: * @return array
299: */
300: public function updateCalendar($sCalendarId, $sDisplayName, $sDescription, $iOrder, $sColor)
301: {
302: return $this->client->propPatch(
303: $sCalendarId,
304: array(
305: self::PROP_DISPLAYNAME => $sDisplayName,
306: self::PROP_CALENDAR_DESCRIPTION => $sDescription,
307: self::PROP_CALENDAR_ORDER => $iOrder,
308: self::PROP_CALENDAR_COLOR => $sColor
309: )
310: );
311: }
312:
313: /**
314: * @param string $sCalendarId
315: * @param string $sColor
316: *
317: * @return array
318: */
319: public function updateCalendarColor($sCalendarId, $sColor)
320: {
321: return $this->client->propPatch(
322: $sCalendarId,
323: array(
324: self::PROP_CALENDAR_COLOR => $sColor
325: )
326: );
327: }
328:
329: /**
330: * @param string $url
331: */
332: public function DeleteItem($url)
333: {
334: return $this->client->request('DELETE', $url);
335: }
336:
337: /**
338: * @param string $filter
339: * @param string $url
340: *
341: * @return array
342: */
343: public function QueryCal($filter, $url = '')
344: {
345: $xml =
346: '<?xml version="1.0" encoding="utf-8" ?>
347: <c:calendar-query xmlns:d="DAV:" xmlns:c="'.\Sabre\CalDAV\Plugin::NS_CALDAV.'">
348: <d:prop>
349: <d:getetag/>
350: <c:calendar-data/>
351: </d:prop>
352: '.$filter.'
353: </c:calendar-query>';
354:
355: $res = array();
356: try {
357: $res = $this->client->request(
358: 'REPORT',
359: $url,
360: $xml,
361: array(
362: 'Content-Type' => 'application/xml',
363: 'Depth' => '1'
364: )
365: );
366: } catch(\Sabre\DAV\Exception $ex) {
367: return false;
368: }
369:
370: $aStatus = $this->client->parseMultiStatus($res['body']);
371:
372: $report = array();
373: foreach ($aStatus as $key => $props) {
374: $response = array();
375: if (count($props) > 0) {
376: $response['url'] = $url;
377: $response['href'] = basename($key);
378: $response['etag'] = isset($props[200]) ? preg_replace('/^"?([^"]+)"?/', '$1', $props[200][self::PROP_ETAG]) : '';
379: $response['data'] = isset($props[200]) ? $props[200][self::PROP_CALENDAR_DATA] : '';
380:
381: $report[] = $response;
382: }
383: }
384: return $report;
385: }
386:
387: /**
388: * @param string $filter
389: * @param string $url
390: *
391: * @return array
392: */
393: public function QueryCardsInfo($filter, $url = '')
394: {
395: $xml =
396: '<?xml version="1.0" encoding="utf-8" ?>
397: <c:addressbook-query xmlns:d="DAV:" xmlns:c="'.\Sabre\CardDAV\Plugin::NS_CARDDAV.'">
398: <d:prop>
399: <d:getetag/>
400: </d:prop>
401: '.$filter.'
402: </c:addressbook-query>';
403:
404: $res = array();
405: try {
406: $res = $this->client->request(
407: 'REPORT',
408: $url,
409: $xml,
410: array(
411: 'Content-Type' => 'application/xml',
412: 'Depth' => '1'
413: )
414: );
415: } catch(\Sabre\DAV\Exception $ex) {
416: return false;
417: }
418:
419: $aStatus = $this->client->parseMultiStatus($res['body']);
420:
421: $report = array();
422: foreach ($aStatus as $key => $props) {
423: $response = array();
424: $response['href'] = basename($key);
425: $response['etag'] = isset($props[200]) ? preg_replace('/^"?([^"]+)"?/', '$1', $props[200][self::PROP_ETAG]) : '';
426: $response['data'] = isset($props[200]) && isset($props[200][self::PROP_ADDRESSBOOK_DATA]) ? $props[200][self::PROP_ADDRESSBOOK_DATA] : '';
427:
428: $report[] = $response;
429: }
430: return $report;
431: }
432:
433: /**
434: * @param string $filter
435: * @param string $url
436: *
437: * @return array
438: */
439: public function QueryCards($calendar_url, $urls = [])
440: {
441: $aHrefs = [];
442: foreach ($urls as $url) {
443: $aHrefs[] = ' <d:href>' . $url . '</d:href>';
444: }
445: $sHrefs = implode("\n", $aHrefs);
446: $xml =
447: '<?xml version="1.0" encoding="utf-8" ?>
448: <c:addressbook-multiget xmlns:d="DAV:" xmlns:c="'.\Sabre\CardDAV\Plugin::NS_CARDDAV.'">
449: <d:prop>
450: <d:getetag />
451: <c:address-data />
452: </d:prop>
453: ' . $sHrefs . '
454: </c:addressbook-multiget>';
455:
456: $res = array();
457: try {
458: $res = $this->client->request(
459: 'REPORT',
460: $calendar_url,
461: $xml,
462: array(
463: 'Content-Type' => 'application/xml',
464: 'Depth' => '1'
465: )
466: );
467: } catch(\Sabre\DAV\Exception $ex) {
468: return false;
469: }
470:
471: $aStatus = $this->client->parseMultiStatus($res['body']);
472:
473: $report = array();
474: foreach ($aStatus as $key => $props) {
475: $response = array();
476: $response['href'] = basename($key);
477: $response['etag'] = isset($props[200]) ? preg_replace('/^"?([^"]+)"?/', '$1', $props[200][self::PROP_ETAG]) : '';
478: $response['data'] = isset($props[200]) && isset($props[200][self::PROP_ADDRESSBOOK_DATA]) ? $props[200][self::PROP_ADDRESSBOOK_DATA] : '';
479:
480: $report[] = $response;
481: }
482: return $report;
483: }
484:
485: public function GetVcardsInfo($url = '', $sSearch = '', $sGroupId = '')
486: {
487: $sFilter = '';
488: $sGroupFilter = '';
489: $sSearchFilter = '';
490:
491: if (!empty($sGroupId)) {
492: $sGroupFilter =
493: ' <c:prop-filter name="CATEGORIES">
494: <c:text-match icollation="i;octet" match-type="contains">'.$sGroupId.'</c:text-match>
495: </c:prop-filter>';
496: }
497:
498: if (!empty($sSearch)) {
499: $sSearchFilter =
500: ' <c:prop-filter name="FN">
501: <c:text-match icollation="i;octet" match-type="contains">'.$sSearch.'</c:text-match>
502: </c:prop-filter>
503: <c:prop-filter name="N">
504: <c:text-match icollation="i;octet" match-type="contains">'.$sSearch.'</c:text-match>
505: </c:prop-filter>
506: <c:prop-filter name="EMAIL">
507: <c:text-match icollation="i;octet" match-type="contains">'.$sSearch.'</c:text-match>
508: </c:prop-filter>
509: <c:prop-filter name="NICKNAME">
510: <c:text-match icollation="i;octet" match-type="contains">'.$sSearch.'</c:text-match>
511: </c:prop-filter>';
512: }
513:
514: if (!empty($sSearch) || !empty($sGroupId)) {
515: $sFilter =
516: ' <c:filter>
517: ' . $sSearchFilter
518: . $sGroupFilter . '
519: </c:filter>';
520: }
521:
522: return $this->QueryCardsInfo($sFilter, $url);
523: }
524:
525: public function GetVcards($url, $urls = [])
526: {
527: list($path, $name) = split($url);
528: $url = $path . '/' . rawurlencode($name);
529:
530: return $this->QueryCards($url, $urls);
531: }
532:
533: /**
534: * @param timestamp $start
535: * @param timestamp $end
536: *
537: * @return string
538: */
539: public function GetTimeRange($start = null, $end = null)
540: {
541: $timeRange = '';
542: $startRange = '';
543: $endRange = '';
544: if (isset($start) || isset($end)) {
545: if (isset($start)) {
546: $startRange = 'start="'.$start.'"';
547: }
548: if (isset($end)) {
549: $endRange = 'end="'.$end.'"';
550: }
551: $timeRange = sprintf('<c:time-range %s %s/>', $startRange, $endRange);
552: }
553: return $timeRange;
554: }
555:
556: /**
557: * @param string $url
558: * @param timestamp $start
559: * @param timestamp $finish
560: *
561: * @return array
562: */
563: public function getEvents($url = '', $start = null, $finish = null)
564: {
565: $timeRange = $this->GetTimeRange($start, $finish);
566: $url = rtrim($url, '/') . '/';
567: list($path, $name) = split($url);
568: $url = $path . '/' . rawurlencode($name);
569: $filter =
570: '<c:filter>
571: <c:comp-filter name="VCALENDAR">
572: <c:comp-filter name="VEVENT">
573: '.$timeRange.'
574: </c:comp-filter>
575: </c:comp-filter>
576: </c:filter>';
577: return $this->QueryCal($filter, $url);
578: }
579:
580: /**
581: * @param string $sCalendarUrl
582: * @param string $sUid
583: *
584: * @return array
585: */
586: public function GetEventByUid($sCalendarUrl, $sUid)
587: {
588: $filter =
589: ' <c:filter>
590: <c:comp-filter name="VCALENDAR">
591: <c:comp-filter name="VEVENT">
592: <c:prop-filter name="UID">
593: <c:text-match icollation="i;octet">'.$sUid.'</c:text-match>
594: </c:prop-filter>
595: </c:comp-filter>
596: </c:comp-filter>
597: </c:filter>';
598: $result = $this->QueryCal($filter, $sCalendarUrl);
599: if ($result !== false) {
600: return current($result);
601: } else {
602: return false;
603: }
604: }
605:
606: /**
607: * @param string $url
608: *
609: * @return string
610: */
611: public function GetItem($url = '')
612: {
613: $res = $this->client->request('GET', $url);
614: if ($res !== false) {
615: return $res['body'];
616: }
617: return $res;
618: }
619:
620: /**
621: * @param string $url
622: * @param timestamp $start
623: * @param timestamp $finish
624: *
625: * @return array
626: */
627: public function GetAlarms($url = '', $start = null, $finish = null)
628: {
629: $timeRange = $this->GetTimeRange($start, $finish);
630: $url = rtrim($url, '/') . '/';
631:
632: $filter =
633: '<c:filter>
634: <c:comp-filter name="VCALENDAR">
635: <c:comp-filter name="VEVENT">
636: <c:comp-filter name="VALARM">
637: '.$timeRange.'
638: </c:comp-filter>
639: </c:comp-filter>
640: </c:comp-filter>
641: </c:filter>';
642: // $recurrenceSet = '<c:limit-recurrence-set start="'.$start.'" end="'.$finish.'"/>';
643:
644: return $this->QueryCal($filter, $url);
645: }
646:
647:
648: /**
649: * @param timestamp $start
650: * @param timestamp $finish
651: * @param boolean $completed
652: * @param boolean $cancelled
653: * @param string $url
654: *
655: * @return array
656: */
657: public function GetTodos($start, $finish, $completed = false, $cancelled = false, $url = '')
658: {
659: $timeRange = $this->GetTimeRange($start, $finish);
660:
661: // Warning! May contain traces of double negatives...
662: $negateCancelled = ($cancelled === true ? 'no' : 'yes');
663: $negateCompleted = ($cancelled === true ? 'no' : 'yes');
664:
665: $filter =
666: '<c:filter>
667: <c:comp-filter name="VCALENDAR">
668: <c:comp-filter name="VTODO">
669: <c:prop-filter name="STATUS">
670: <c:text-match negate-condition="'.$negateCompleted.'">COMPLETED</c:text-match>
671: </c:prop-filter>
672: <c:prop-filter name="STATUS">
673: <C:text-match negate-condition="'.$negateCancelled.'">CANCELLED</c:text-match>
674: </c:prop-filter>
675: ' . $timeRange . '
676: </c:comp-filter>
677: </c:comp-filter>
678: </c:filter>';
679:
680: return $this->QueryCal($filter, $url);
681: }
682:
683: /**
684: * @param string url
685: *
686: * @return array
687: */
688: public function getCalendar($url = '')
689: {
690: $filter =
691: '<c:filter>
692: <c:comp-filter name="VCALENDAR"></c:comp-filter>
693: </c:filter>';
694:
695: return $this->QueryCal($filter, $url);
696: }
697:
698: public function GetCurrentPrincipal()
699: {
700: $res = $this->client->propFind('', array(self::PROP_CURRENT_USER_PRINCIPAL));
701:
702: return $res[self::PROP_CURRENT_USER_PRINCIPAL];
703: }
704:
705: /**
706: * @param string $principal
707: */
708: public function GetPrincipalMembers($principal = '')
709: {
710: $res = array();
711: try {
712: $res = $this->client->propFind(
713: $principal,
714: array(
715: self::PROP_GROUP_MEMBERSHIP
716: ),
717: 1
718: );
719: } catch(\Exception $ex) {
720: return [];
721: }
722:
723: return $res[self::PROP_GROUP_MEMBERSHIP];
724: }
725:
726: /**
727: * @param $principalUrl string
728: */
729: public function GetCalendarHomeSet($principalUrl = '')
730: {
731: $props = $this->client->propFind(
732: $principalUrl,
733: array(
734: self::PROP_CALENDAR_HOME_SET
735: )
736: );
737: return $props[self::PROP_CALENDAR_HOME_SET];
738: }
739:
740: /**
741: * @param $principalUrl string
742: */
743: public function GetAddressBookHomeSet($principalUrl = '')
744: {
745: $props = $this->client->propFind(
746: $principalUrl,
747: array(
748: self::PROP_ADDRESSBOOK_HOME_SET
749: )
750: );
751: return $props[self::PROP_ADDRESSBOOK_HOME_SET];
752: }
753:
754: public function GetAddressBooks($url = '')
755: {
756: $aProps = $this->client->propFind(
757: $url,
758: array(
759: self::PROP_RESOURCETYPE,
760: self::PROP_DISPLAYNAME
761: ),
762: 1
763: );
764: return $aProps;
765: }
766:
767: public function getCalendars($url = '')
768: {
769: $calendars = array();
770:
771: if (class_exists('\Aurora\Modules\Calendar\Classes\Calendar')) {
772: $aProps = $this->client->propFind(
773: $url,
774: array(
775: self::PROP_RESOURCETYPE,
776: self::PROP_DISPLAYNAME,
777: self::PROP_OWNER,
778: self::PROP_CTAG,
779: self::PROP_CALENDAR_DESCRIPTION,
780: self::PROP_CALENDAR_COLOR,
781: self::PROP_CALENDAR_ORDER,
782: self::PROP_CALENDAR_INVITE
783: ),
784: 1
785: );
786:
787: foreach ($aProps as $key => $props) {
788: if ($props['{DAV:}resourcetype']->is('{'.\Sabre\CalDAV\Plugin::NS_CALDAV.'}calendar') &&
789: !$props['{DAV:}resourcetype']->is('{'.\Sabre\CalDAV\Plugin::NS_CALENDARSERVER.'}shared')) {
790: $calendar = new \Aurora\Modules\Calendar\Classes\Calendar($key);
791:
792: $calendar->CTag = $props[self::PROP_CTAG];
793: $calendar->DisplayName = $props[self::PROP_DISPLAYNAME];
794: $calendar->Principals[] = isset($props[self::PROP_OWNER]) ? $props[self::PROP_OWNER] : '';
795: $calendar->Description = isset($props[self::PROP_CALENDAR_DESCRIPTION]) ? $props[self::PROP_CALENDAR_DESCRIPTION] : '';
796: $calendar->Color = isset($props[self::PROP_CALENDAR_COLOR]) ? $props[self::PROP_CALENDAR_COLOR] : '';
797: if (strlen($calendar->Color) > 7) {
798: $calendar->Color = substr($calendar->Color, 0, 7);
799: }
800:
801: if (isset($props[self::PROP_CALENDAR_ORDER])) {
802: $calendar->Order = $props[self::PROP_CALENDAR_ORDER];
803: }
804: if (isset($props[self::PROP_CALENDAR_INVITE])) {
805: $aInvitesProp = $props[self::PROP_CALENDAR_INVITE];
806:
807: foreach ($aInvitesProp as $aInviteProp) {
808: if ($aInviteProp['name'] === '{' . Plugin::NS_CALENDARSERVER . '}user') {
809: $aShare = [];
810:
811: foreach ($aInviteProp['value'] as $aValue) {
812: switch ($aValue['name']) {
813: case '{DAV:}href':
814: list(, $aShare['email']) = split($aValue['value']);
815:
816: break;
817: case '{http://calendarserver.org/ns/}access':
818: if (isset($aValue['value'][0])) {
819: $aShare['access'] = $aValue['value'][0]['name'] === '{' . Plugin::NS_CALENDARSERVER . '}read-write'
820: ? \Afterlogic\DAV\Permission::Write
821: : \Afterlogic\DAV\Permission::Read;
822: }
823: break;
824: }
825: }
826: if (!empty($aShare)) {
827: $calendar->Shares[] = $aShare;
828: }
829: }
830: }
831: }
832:
833: $calendars[$calendar->Id] = $calendar;
834: }
835: }
836: }
837: return $calendars;
838: }
839:
840: public function GetVcardByUid($uid, $url = '')
841: {
842: $filter = "";
843:
844: if ($uid) {
845: $filter =
846: '<c:filter>
847: <c:prop-filter name="UID">
848: <c:text-match collation="i;unicode-casemap" match-type="equals">'.$uid.'</c:text-match>
849: </c:prop-filter>
850: </c:filter>';
851: }
852:
853: return $this->QueryCardsInfo($filter, $url);
854: }
855:
856: public function GetProxies($sProxy)
857: {
858: $res = array();
859: try {
860: $res = $this->client->propFind(
861: $sProxy,
862: array(
863: self::PROP_GROUP_MEMBER_SET
864: ),
865: 1
866: );
867: } catch(\Exception $ex) {
868: return [];
869: }
870:
871: return $res[self::PROP_GROUP_MEMBER_SET];
872: }
873:
874: public function AddProxy($proxy, $to)
875: {
876: $sProxyStr = '';
877: $aCurrentProxies = array();
878: $duplicate = false;
879:
880: $aCurrentProxies = $this->GetProxies($proxy);
881:
882: if ($aCurrentProxies) {
883: foreach ($aCurrentProxies as $sCurrentProxy => $val) {
884: $sCurrentProxy = ltrim($sCurrentProxy, DAV_ROOT);
885: if ($sCurrentProxy == $proxy) {
886: $duplicate = true;
887: }
888: $sProxyStr .= '<d:href>' . $sCurrentProxy . '</d:href>';
889: }
890: }
891: if ($duplicate) {
892: return false;
893: }
894:
895: $sProxyStr .= '<d:href>' . \Afterlogic\DAV\Constants::PRINCIPALS_PREFIX . $to . '</d:href>';
896:
897: return $this->client->propPatch($proxy, array('group-member-set'=>$sProxyStr));
898: }
899:
900: public function DeleteProxy($proxy, $to)
901: {
902: $aCurrentProxies = $this->GetProxies($proxy);
903:
904: $sProxyStr = "";
905:
906: if ($aCurrentProxies) {
907: foreach ($aCurrentProxies as $sCurrentProxy => $val) {
908: $sCurrentProxy = ltrim($sCurrentProxy, DAV_ROOT);
909: $sProxyStr .= '<d:href>' . $sCurrentProxy . '</d:href>';
910: }
911: }
912:
913: $sProxyStr = str_replace('<d:href>' . \Afterlogic\DAV\Constants::PRINCIPALS_PREFIX . $to . '</d:href>', '', $sProxyStr);
914:
915: return $this->client->propPatch(
916: $proxy,
917: array(
918: 'group-member-set'=>$sProxyStr
919: )
920: );
921: }
922:
923: /**
924: * Returns a list of calendar homes for principals the current
925: * user has access to through the calendar-proxy functionality.
926: *
927: * @return array
928: */
929: public function GetCalendarProxiedFor($principalUrl)
930: {
931: $body =
932: '<?xml version="1.0"?>
933: <d:expand-property xmlns:d="DAV:">
934: <d:property name="calendar-proxy-read-for" namespace="'.\Sabre\CalDAV\Plugin::NS_CALENDARSERVER.'">
935: <d:property name="calendar-home-set" namespace="'.\Sabre\CalDAV\Plugin::NS_CALDAV.'" />
936: </d:property>
937: <d:property name="calendar-proxy-write-for" namespace="'.\Sabre\CalDAV\Plugin::NS_CALENDARSERVER.'">
938: <d:property name="calendar-home-set" namespace="'.\Sabre\CalDAV\Plugin::NS_CALDAV.'" />
939: </d:property>
940: </d:expand-property>';
941:
942: $res = $this->client->request(
943: 'REPORT',
944: $principalUrl,
945: $body,
946: array(
947: 'Content-Type' => 'application/xml',
948: 'Depth' => '1'
949: )
950: );
951:
952: if (isset($res['body'])) {
953: $data = new \DOMDocument();
954:
955: $data->loadXML($res['body'], LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NSCLEAN);
956: $xp = new \DOMXPath($data);
957:
958: $xp->registerNamespace('c', \Sabre\CalDAV\Plugin::NS_CALDAV);
959: $xp->registerNamespace('cs', \Sabre\CalDAV\Plugin::NS_CALENDARSERVER);
960: $xp->registerNamespace('d', 'urn:dav');
961: $values = array();
962:
963: $result = $xp->query("/d:multistatus/d:response/d:propstat/d:prop/cs:calendar-proxy-read-for/d:response/d:propstat/d:prop/c:calendar-home-set/d:href");
964: foreach ($result as $elem) {
965: $values[] = array(
966: 'href' => $elem->nodeValue,
967: 'mode' => 'read'
968: );
969: }
970:
971: $result = $xp->query("/d:multistatus/d:response/d:propstat/d:prop/cs:calendar-proxy-write-for/d:response/d:propstat/d:prop/c:calendar-home-set/d:href");
972: foreach ($result as $elem) {
973: $values[] = array(
974: 'href' => $elem->nodeValue,
975: 'mode' => 'write'
976: );
977: }
978:
979: return $values;
980: }
981: return array();
982: }
983: }
984: