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 $newUrl
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: $xml =
267: '<?xml version="1.0" encoding="UTF-8" ?>
268: <c:mkcalendar xmlns:c="' . \Sabre\CalDAV\Plugin::NS_CALDAV . '" xmlns:d="DAV:" xmlns:ic="http://apple.com/ns/ical/">
269: <d:set>
270: <d:prop>
271: <d:displayname>' . $sDisplayName . '</d:displayname>
272: <c:calendar-description>' . $sDescription . '</c:calendar-description>
273: <ic:calendar-order>' . $iOrder . '</ic:calendar-order>
274: <ic:calendar-color>' . $sColor . '</ic:calendar-color>
275: </d:prop>
276: </d:set>
277: </c:mkcalendar>';
278:
279: return $this->client->request(
280: 'MKCALENDAR',
281: $sCalendarHome . $sSystemName,
282: $xml,
283: array(
284: 'Content-Type' => 'application/xml',
285: 'Depth' => '1'
286: )
287: );
288: }
289:
290: /**
291: * @param string $sCalendarId
292: * @param string $sDisplayName
293: * @param string $sDescription
294: * @param int $iOrder
295: * @param string $sColor
296: *
297: * @return array
298: */
299: public function updateCalendar($sCalendarId, $sDisplayName, $sDescription, $iOrder, $sColor)
300: {
301: return $this->client->propPatch(
302: $sCalendarId,
303: array(
304: self::PROP_DISPLAYNAME => $sDisplayName,
305: self::PROP_CALENDAR_DESCRIPTION => $sDescription,
306: self::PROP_CALENDAR_ORDER => $iOrder,
307: self::PROP_CALENDAR_COLOR => $sColor
308: )
309: );
310: }
311:
312: /**
313: * @param string $sCalendarId
314: * @param string $sColor
315: *
316: * @return array
317: */
318: public function updateCalendarColor($sCalendarId, $sColor)
319: {
320: return $this->client->propPatch(
321: $sCalendarId,
322: array(
323: self::PROP_CALENDAR_COLOR => $sColor
324: )
325: );
326: }
327:
328: /**
329: * @param string $url
330: */
331: public function DeleteItem($url)
332: {
333: return $this->client->request('DELETE', $url);
334: }
335:
336: /**
337: * @param string $filter
338: * @param string $url
339: *
340: * @return array
341: */
342: public function QueryCal($filter, $url = '')
343: {
344: $xml =
345: '<?xml version="1.0" encoding="utf-8" ?>
346: <c:calendar-query xmlns:d="DAV:" xmlns:c="' . \Sabre\CalDAV\Plugin::NS_CALDAV . '">
347: <d:prop>
348: <d:getetag/>
349: <c:calendar-data/>
350: </d:prop>
351: ' . $filter . '
352: </c:calendar-query>';
353:
354: $res = array();
355: try {
356: $res = $this->client->request(
357: 'REPORT',
358: $url,
359: $xml,
360: array(
361: 'Content-Type' => 'application/xml',
362: 'Depth' => '1'
363: )
364: );
365: } catch(\Sabre\DAV\Exception $ex) {
366: return false;
367: }
368:
369: $aStatus = $this->client->parseMultiStatus($res['body']);
370:
371: $report = array();
372: foreach ($aStatus as $key => $props) {
373: $response = array();
374: if (count($props) > 0) {
375: $response['url'] = $url;
376: $response['href'] = basename($key);
377: $response['etag'] = isset($props[200]) ? preg_replace('/^"?([^"]+)"?/', '$1', $props[200][self::PROP_ETAG]) : '';
378: $response['data'] = isset($props[200]) ? $props[200][self::PROP_CALENDAR_DATA] : '';
379:
380: $report[] = $response;
381: }
382: }
383: return $report;
384: }
385:
386: /**
387: * @param string $filter
388: * @param string $url
389: *
390: * @return array
391: */
392: public function QueryCardsInfo($filter, $url = '')
393: {
394: $xml =
395: '<?xml version="1.0" encoding="utf-8" ?>
396: <c:addressbook-query xmlns:d="DAV:" xmlns:c="' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '">
397: <d:prop>
398: <d:getetag/>
399: </d:prop>
400: ' . $filter . '
401: </c:addressbook-query>';
402:
403: $res = array();
404: try {
405: $res = $this->client->request(
406: 'REPORT',
407: $url,
408: $xml,
409: array(
410: 'Content-Type' => 'application/xml',
411: 'Depth' => '1'
412: )
413: );
414: } catch(\Sabre\DAV\Exception $ex) {
415: return false;
416: }
417:
418: $aStatus = $this->client->parseMultiStatus($res['body']);
419:
420: $report = array();
421: foreach ($aStatus as $key => $props) {
422: $response = array();
423: $response['href'] = basename($key);
424: $response['etag'] = isset($props[200]) ? preg_replace('/^"?([^"]+)"?/', '$1', $props[200][self::PROP_ETAG]) : '';
425: $response['data'] = isset($props[200]) && isset($props[200][self::PROP_ADDRESSBOOK_DATA]) ? $props[200][self::PROP_ADDRESSBOOK_DATA] : '';
426:
427: $report[] = $response;
428: }
429: return $report;
430: }
431:
432: /**
433: * @param string $calendar_url
434: * @param array<string> $urls
435: *
436: * @return array
437: */
438: public function QueryCards($calendar_url, $urls = [])
439: {
440: $aHrefs = [];
441: foreach ($urls as $url) {
442: $aHrefs[] = ' <d:href>' . $url . '</d:href>';
443: }
444: $sHrefs = implode("\n", $aHrefs);
445: $xml =
446: '<?xml version="1.0" encoding="utf-8" ?>
447: <c:addressbook-multiget xmlns:d="DAV:" xmlns:c="' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '">
448: <d:prop>
449: <d:getetag />
450: <c:address-data />
451: </d:prop>
452: ' . $sHrefs . '
453: </c:addressbook-multiget>';
454:
455: $res = array();
456: try {
457: $res = $this->client->request(
458: 'REPORT',
459: $calendar_url,
460: $xml,
461: array(
462: 'Content-Type' => 'application/xml',
463: 'Depth' => '1'
464: )
465: );
466: } catch(\Sabre\DAV\Exception $ex) {
467: return false;
468: }
469:
470: $aStatus = $this->client->parseMultiStatus($res['body']);
471:
472: $report = array();
473: foreach ($aStatus as $key => $props) {
474: $response = array();
475: $response['href'] = basename($key);
476: $response['etag'] = isset($props[200]) ? preg_replace('/^"?([^"]+)"?/', '$1', $props[200][self::PROP_ETAG]) : '';
477: $response['data'] = isset($props[200]) && isset($props[200][self::PROP_ADDRESSBOOK_DATA]) ? $props[200][self::PROP_ADDRESSBOOK_DATA] : '';
478:
479: $report[] = $response;
480: }
481: return $report;
482: }
483:
484: public function GetVcardsInfo($url = '', $sSearch = '', $sGroupId = '')
485: {
486: $sFilter = '';
487: $sGroupFilter = '';
488: $sSearchFilter = '';
489:
490: if (!empty($sGroupId)) {
491: $sGroupFilter =
492: ' <c:prop-filter name="CATEGORIES">
493: <c:text-match icollation="i;octet" match-type="contains">' . $sGroupId . '</c:text-match>
494: </c:prop-filter>';
495: }
496:
497: if (!empty($sSearch)) {
498: $sSearchFilter =
499: ' <c:prop-filter name="FN">
500: <c:text-match icollation="i;octet" match-type="contains">' . $sSearch . '</c:text-match>
501: </c:prop-filter>
502: <c:prop-filter name="N">
503: <c:text-match icollation="i;octet" match-type="contains">' . $sSearch . '</c:text-match>
504: </c:prop-filter>
505: <c:prop-filter name="EMAIL">
506: <c:text-match icollation="i;octet" match-type="contains">' . $sSearch . '</c:text-match>
507: </c:prop-filter>
508: <c:prop-filter name="NICKNAME">
509: <c:text-match icollation="i;octet" match-type="contains">' . $sSearch . '</c:text-match>
510: </c:prop-filter>';
511: }
512:
513: if (!empty($sSearch) || !empty($sGroupId)) {
514: $sFilter =
515: ' <c:filter>
516: ' . $sSearchFilter
517: . $sGroupFilter . '
518: </c:filter>';
519: }
520:
521: return $this->QueryCardsInfo($sFilter, $url);
522: }
523:
524: public function GetVcards($url, $urls = [])
525: {
526: list($path, $name) = split($url);
527: $url = $path . '/' . rawurlencode($name);
528:
529: return $this->QueryCards($url, $urls);
530: }
531:
532: /**
533: * @param int|null $start
534: * @param int|null $end
535: *
536: * @return string
537: */
538: public function GetTimeRange($start = null, $end = null)
539: {
540: $timeRange = '';
541: $startRange = '';
542: $endRange = '';
543: if (isset($start) || isset($end)) {
544: if (isset($start)) {
545: $startRange = 'start="' . $start . '"';
546: }
547: if (isset($end)) {
548: $endRange = 'end="' . $end . '"';
549: }
550: $timeRange = sprintf('<c:time-range %s %s/>', $startRange, $endRange);
551: }
552: return $timeRange;
553: }
554:
555: /**
556: * @param string $url
557: * @param int|null $start
558: * @param int|null $finish
559: *
560: * @return array
561: */
562: public function getEvents($url = '', $start = null, $finish = null)
563: {
564: $timeRange = $this->GetTimeRange($start, $finish);
565: $url = rtrim($url, '/') . '/';
566: list($path, $name) = split($url);
567: $url = $path . '/' . rawurlencode($name);
568: $filter =
569: '<c:filter>
570: <c:comp-filter name="VCALENDAR">
571: <c:comp-filter name="VEVENT">
572: ' . $timeRange . '
573: </c:comp-filter>
574: </c:comp-filter>
575: </c:filter>';
576: return $this->QueryCal($filter, $url);
577: }
578:
579: /**
580: * @param string $sCalendarUrl
581: * @param string $sUid
582: *
583: * @return array
584: */
585: public function GetEventByUid($sCalendarUrl, $sUid)
586: {
587: $filter =
588: ' <c:filter>
589: <c:comp-filter name="VCALENDAR">
590: <c:comp-filter name="VEVENT">
591: <c:prop-filter name="UID">
592: <c:text-match icollation="i;octet">' . $sUid . '</c:text-match>
593: </c:prop-filter>
594: </c:comp-filter>
595: </c:comp-filter>
596: </c:filter>';
597: $result = $this->QueryCal($filter, $sCalendarUrl);
598: if ($result !== false) {
599: return current($result);
600: } else {
601: return false;
602: }
603: }
604:
605: /**
606: * @param string $url
607: *
608: * @return string
609: */
610: public function GetItem($url = '')
611: {
612: $res = $this->client->request('GET', $url);
613: if ($res !== false) {
614: return $res['body'];
615: }
616: return $res;
617: }
618:
619: /**
620: * @param string $url
621: * @param int|null $start
622: * @param int|null $finish
623: *
624: * @return array
625: */
626: public function GetAlarms($url = '', $start = null, $finish = null)
627: {
628: $timeRange = $this->GetTimeRange($start, $finish);
629: $url = rtrim($url, '/') . '/';
630:
631: $filter =
632: '<c:filter>
633: <c:comp-filter name="VCALENDAR">
634: <c:comp-filter name="VEVENT">
635: <c:comp-filter name="VALARM">
636: ' . $timeRange . '
637: </c:comp-filter>
638: </c:comp-filter>
639: </c:comp-filter>
640: </c:filter>';
641: // $recurrenceSet = '<c:limit-recurrence-set start="'.$start.'" end="'.$finish.'"/>';
642:
643: return $this->QueryCal($filter, $url);
644: }
645:
646:
647: /**
648: * @param int $start
649: * @param int $finish
650: * @param boolean $completed
651: * @param boolean $cancelled
652: * @param string $url
653: *
654: * @return array
655: */
656: public function GetTodos($start, $finish, $completed = false, $cancelled = false, $url = '')
657: {
658: $timeRange = $this->GetTimeRange($start, $finish);
659:
660: // Warning! May contain traces of double negatives...
661: $negateCancelled = ($cancelled === true ? 'no' : 'yes');
662: $negateCompleted = ($cancelled === true ? 'no' : 'yes');
663:
664: $filter =
665: '<c:filter>
666: <c:comp-filter name="VCALENDAR">
667: <c:comp-filter name="VTODO">
668: <c:prop-filter name="STATUS">
669: <c:text-match negate-condition="' . $negateCompleted . '">COMPLETED</c:text-match>
670: </c:prop-filter>
671: <c:prop-filter name="STATUS">
672: <C:text-match negate-condition="' . $negateCancelled . '">CANCELLED</c:text-match>
673: </c:prop-filter>
674: ' . $timeRange . '
675: </c:comp-filter>
676: </c:comp-filter>
677: </c:filter>';
678:
679: return $this->QueryCal($filter, $url);
680: }
681:
682: /**
683: * @param string $url
684: *
685: * @return array
686: */
687: public function getCalendar($url = '')
688: {
689: $filter =
690: '<c:filter>
691: <c:comp-filter name="VCALENDAR"></c:comp-filter>
692: </c:filter>';
693:
694: return $this->QueryCal($filter, $url);
695: }
696:
697: public function GetCurrentPrincipal()
698: {
699: $res = $this->client->propFind('', array(self::PROP_CURRENT_USER_PRINCIPAL));
700:
701: return $res[self::PROP_CURRENT_USER_PRINCIPAL];
702: }
703:
704: /**
705: * @param string $principal
706: */
707: public function GetPrincipalMembers($principal = '')
708: {
709: $res = array();
710: try {
711: $res = $this->client->propFind(
712: $principal,
713: array(
714: self::PROP_GROUP_MEMBERSHIP
715: ),
716: 1
717: );
718: } catch(\Exception $ex) {
719: return [];
720: }
721:
722: return $res[self::PROP_GROUP_MEMBERSHIP];
723: }
724:
725: /**
726: * @param $principalUrl string
727: */
728: public function GetCalendarHomeSet($principalUrl = '')
729: {
730: $props = $this->client->propFind(
731: $principalUrl,
732: array(
733: self::PROP_CALENDAR_HOME_SET
734: )
735: );
736: return $props[self::PROP_CALENDAR_HOME_SET];
737: }
738:
739: /**
740: * @param $principalUrl string
741: */
742: public function GetAddressBookHomeSet($principalUrl = '')
743: {
744: $props = $this->client->propFind(
745: $principalUrl,
746: array(
747: self::PROP_ADDRESSBOOK_HOME_SET
748: )
749: );
750: return $props[self::PROP_ADDRESSBOOK_HOME_SET];
751: }
752:
753: public function GetAddressBooks($url = '')
754: {
755: $aProps = $this->client->propFind(
756: $url,
757: array(
758: self::PROP_RESOURCETYPE,
759: self::PROP_DISPLAYNAME
760: ),
761: 1
762: );
763: return $aProps;
764: }
765:
766: public function getCalendars($url = '')
767: {
768: $calendars = array();
769:
770: if (class_exists('\Aurora\Modules\Calendar\Classes\Calendar')) {
771: $aProps = $this->client->propFind(
772: $url,
773: array(
774: self::PROP_RESOURCETYPE,
775: self::PROP_DISPLAYNAME,
776: self::PROP_OWNER,
777: self::PROP_CTAG,
778: self::PROP_CALENDAR_DESCRIPTION,
779: self::PROP_CALENDAR_COLOR,
780: self::PROP_CALENDAR_ORDER,
781: self::PROP_CALENDAR_INVITE
782: ),
783: 1
784: );
785:
786: foreach ($aProps as $key => $props) {
787: if ($props['{DAV:}resourcetype']->is('{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar') &&
788: !$props['{DAV:}resourcetype']->is('{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}shared')) {
789: $calendar = new \Aurora\Modules\Calendar\Classes\Calendar($key);
790:
791: $calendar->CTag = $props[self::PROP_CTAG];
792: $calendar->DisplayName = $props[self::PROP_DISPLAYNAME];
793: $calendar->Principals[] = isset($props[self::PROP_OWNER]) ? $props[self::PROP_OWNER] : '';
794: $calendar->Description = isset($props[self::PROP_CALENDAR_DESCRIPTION]) ? $props[self::PROP_CALENDAR_DESCRIPTION] : '';
795: $calendar->Color = isset($props[self::PROP_CALENDAR_COLOR]) ? $props[self::PROP_CALENDAR_COLOR] : '';
796: if (strlen($calendar->Color) > 7) {
797: $calendar->Color = substr($calendar->Color, 0, 7);
798: }
799:
800: if (isset($props[self::PROP_CALENDAR_ORDER])) {
801: $calendar->Order = $props[self::PROP_CALENDAR_ORDER];
802: }
803: if (isset($props[self::PROP_CALENDAR_INVITE])) {
804: $aInvitesProp = $props[self::PROP_CALENDAR_INVITE];
805:
806: foreach ($aInvitesProp as $aInviteProp) {
807: if ($aInviteProp['name'] === '{' . Plugin::NS_CALENDARSERVER . '}user') {
808: $aShare = [];
809:
810: foreach ($aInviteProp['value'] as $aValue) {
811: switch ($aValue['name']) {
812: case '{DAV:}href':
813: list(, $aShare['email']) = split($aValue['value']);
814:
815: break;
816: case '{http://calendarserver.org/ns/}access':
817: if (isset($aValue['value'][0])) {
818: $aShare['access'] = $aValue['value'][0]['name'] === '{' . Plugin::NS_CALENDARSERVER . '}read-write'
819: ? \Afterlogic\DAV\Permission::Write
820: : \Afterlogic\DAV\Permission::Read;
821: }
822: break;
823: }
824: }
825: if (!empty($aShare)) {
826: $calendar->Shares[] = $aShare;
827: }
828: }
829: }
830: }
831:
832: $calendars[$calendar->Id] = $calendar;
833: }
834: }
835: }
836: return $calendars;
837: }
838:
839: public function GetVcardByUid($uid, $url = '')
840: {
841: $filter = "";
842:
843: if ($uid) {
844: $filter =
845: '<c:filter>
846: <c:prop-filter name="UID">
847: <c:text-match collation="i;unicode-casemap" match-type="equals">' . $uid . '</c:text-match>
848: </c:prop-filter>
849: </c:filter>';
850: }
851:
852: return $this->QueryCardsInfo($filter, $url);
853: }
854:
855: public function GetProxies($sProxy)
856: {
857: $res = array();
858: try {
859: $res = $this->client->propFind(
860: $sProxy,
861: array(
862: self::PROP_GROUP_MEMBER_SET
863: ),
864: 1
865: );
866: } catch(\Exception $ex) {
867: return [];
868: }
869:
870: return $res[self::PROP_GROUP_MEMBER_SET];
871: }
872:
873: public function AddProxy($proxy, $to)
874: {
875: $sProxyStr = '';
876: $aCurrentProxies = array();
877: $duplicate = false;
878:
879: $aCurrentProxies = $this->GetProxies($proxy);
880:
881: if ($aCurrentProxies) {
882: foreach ($aCurrentProxies as $sCurrentProxy => $val) {
883: $sCurrentProxy = ltrim($sCurrentProxy, DAV_ROOT);
884: if ($sCurrentProxy == $proxy) {
885: $duplicate = true;
886: }
887: $sProxyStr .= '<d:href>' . $sCurrentProxy . '</d:href>';
888: }
889: }
890: if ($duplicate) {
891: return false;
892: }
893:
894: $sProxyStr .= '<d:href>' . \Afterlogic\DAV\Constants::PRINCIPALS_PREFIX . $to . '</d:href>';
895:
896: return $this->client->propPatch($proxy, array('group-member-set' => $sProxyStr));
897: }
898:
899: public function DeleteProxy($proxy, $to)
900: {
901: $aCurrentProxies = $this->GetProxies($proxy);
902:
903: $sProxyStr = "";
904:
905: if ($aCurrentProxies) {
906: foreach ($aCurrentProxies as $sCurrentProxy => $val) {
907: $sCurrentProxy = ltrim($sCurrentProxy, DAV_ROOT);
908: $sProxyStr .= '<d:href>' . $sCurrentProxy . '</d:href>';
909: }
910: }
911:
912: $sProxyStr = str_replace('<d:href>' . \Afterlogic\DAV\Constants::PRINCIPALS_PREFIX . $to . '</d:href>', '', $sProxyStr);
913:
914: return $this->client->propPatch(
915: $proxy,
916: array(
917: 'group-member-set' => $sProxyStr
918: )
919: );
920: }
921:
922: /**
923: * Returns a list of calendar homes for principals the current
924: * user has access to through the calendar-proxy functionality.
925: *
926: * @return array
927: */
928: public function GetCalendarProxiedFor($principalUrl)
929: {
930: $body =
931: '<?xml version="1.0"?>
932: <d:expand-property xmlns:d="DAV:">
933: <d:property name="calendar-proxy-read-for" namespace="' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '">
934: <d:property name="calendar-home-set" namespace="' . \Sabre\CalDAV\Plugin::NS_CALDAV . '" />
935: </d:property>
936: <d:property name="calendar-proxy-write-for" namespace="' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '">
937: <d:property name="calendar-home-set" namespace="' . \Sabre\CalDAV\Plugin::NS_CALDAV . '" />
938: </d:property>
939: </d:expand-property>';
940:
941: $res = $this->client->request(
942: 'REPORT',
943: $principalUrl,
944: $body,
945: array(
946: 'Content-Type' => 'application/xml',
947: 'Depth' => '1'
948: )
949: );
950:
951: if (isset($res['body'])) {
952: $data = new \DOMDocument();
953:
954: $data->loadXML($res['body'], LIBXML_NOERROR | LIBXML_NOWARNING | LIBXML_NSCLEAN);
955: $xp = new \DOMXPath($data);
956:
957: $xp->registerNamespace('c', \Sabre\CalDAV\Plugin::NS_CALDAV);
958: $xp->registerNamespace('cs', \Sabre\CalDAV\Plugin::NS_CALENDARSERVER);
959: $xp->registerNamespace('d', 'urn:dav');
960: $values = array();
961:
962: $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");
963: foreach ($result as $elem) {
964: $values[] = array(
965: 'href' => $elem->nodeValue,
966: 'mode' => 'read'
967: );
968: }
969:
970: $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");
971: foreach ($result as $elem) {
972: $values[] = array(
973: 'href' => $elem->nodeValue,
974: 'mode' => 'write'
975: );
976: }
977:
978: return $values;
979: }
980: return array();
981: }
982: }
983: