ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Plugin.php
Go to the documentation of this file.
1<?php
2
3namespace Sabre\CardDAV;
4
5use Sabre\DAV;
9use Sabre\HTTP;
13
23class Plugin extends DAV\ServerPlugin {
24
28 const ADDRESSBOOK_ROOT = 'addressbooks';
29
33 const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
34
41 public $directories = [];
42
48 protected $server;
49
55 protected $maxResourceSize = 10000000;
56
63 function initialize(DAV\Server $server) {
64
65 /* Events */
66 $server->on('propFind', [$this, 'propFindEarly']);
67 $server->on('propFind', [$this, 'propFindLate'], 150);
68 $server->on('report', [$this, 'report']);
69 $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']);
70 $server->on('beforeWriteContent', [$this, 'beforeWriteContent']);
71 $server->on('beforeCreateFile', [$this, 'beforeCreateFile']);
72 $server->on('afterMethod:GET', [$this, 'httpAfterGet']);
73
74 $server->xml->namespaceMap[self::NS_CARDDAV] = 'card';
75
76 $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport';
77 $server->xml->elementMap['{' . self::NS_CARDDAV . '}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport';
78
79 /* Mapping Interfaces to {DAV:}resourcetype values */
80 $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook';
81 $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{' . self::NS_CARDDAV . '}directory';
82
83 /* Adding properties that may never be changed */
84 $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data';
85 $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size';
86 $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}addressbook-home-set';
87 $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-collation-set';
88
89 $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href';
90
91 $this->server = $server;
92
93 }
94
102 function getFeatures() {
103
104 return ['addressbook'];
105
106 }
107
118 function getSupportedReportSet($uri) {
119
120 $node = $this->server->tree->getNodeForPath($uri);
121 if ($node instanceof IAddressBook || $node instanceof ICard) {
122 return [
123 '{' . self::NS_CARDDAV . '}addressbook-multiget',
124 '{' . self::NS_CARDDAV . '}addressbook-query',
125 ];
126 }
127 return [];
128
129 }
130
131
139 function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) {
140
141 $ns = '{' . self::NS_CARDDAV . '}';
142
143 if ($node instanceof IAddressBook) {
144
145 $propFind->handle($ns . 'max-resource-size', $this->maxResourceSize);
146 $propFind->handle($ns . 'supported-address-data', function() {
148 });
149 $propFind->handle($ns . 'supported-collation-set', function() {
151 });
152
153 }
154 if ($node instanceof DAVACL\IPrincipal) {
155
156 $path = $propFind->getPath();
157
158 $propFind->handle('{' . self::NS_CARDDAV . '}addressbook-home-set', function() use ($path) {
159 return new LocalHref($this->getAddressBookHomeForPrincipal($path) . '/');
160 });
161
162 if ($this->directories) $propFind->handle('{' . self::NS_CARDDAV . '}directory-gateway', function() {
163 return new LocalHref($this->directories);
164 });
165
166 }
167
168 if ($node instanceof ICard) {
169
170 // The address-data property is not supposed to be a 'real'
171 // property, but in large chunks of the spec it does act as such.
172 // Therefore we simply expose it as a property.
173 $propFind->handle('{' . self::NS_CARDDAV . '}address-data', function() use ($node) {
174 $val = $node->get();
175 if (is_resource($val))
176 $val = stream_get_contents($val);
177
178 return $val;
179
180 });
181
182 }
183
184 }
185
194 function report($reportName, $dom, $path) {
195
196 switch ($reportName) {
197 case '{' . self::NS_CARDDAV . '}addressbook-multiget' :
198 $this->server->transactionType = 'report-addressbook-multiget';
199 $this->addressbookMultiGetReport($dom);
200 return false;
201 case '{' . self::NS_CARDDAV . '}addressbook-query' :
202 $this->server->transactionType = 'report-addressbook-query';
203 $this->addressBookQueryReport($dom);
204 return false;
205 default :
206 return;
207
208 }
209
210
211 }
212
219 protected function getAddressbookHomeForPrincipal($principal) {
220
221 list(, $principalId) = \Sabre\HTTP\URLUtil::splitPath($principal);
222 return self::ADDRESSBOOK_ROOT . '/' . $principalId;
223
224 }
225
226
236 function addressbookMultiGetReport($report) {
237
238 $contentType = $report->contentType;
239 $version = $report->version;
240 if ($version) {
241 $contentType .= '; version=' . $version;
242 }
243
244 $vcardType = $this->negotiateVCard(
246 );
247
248 $propertyList = [];
249 $paths = array_map(
250 [$this->server, 'calculateUri'],
251 $report->hrefs
252 );
253 foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) {
254
255 if (isset($props['200']['{' . self::NS_CARDDAV . '}address-data'])) {
256
257 $props['200']['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
258 $props[200]['{' . self::NS_CARDDAV . '}address-data'],
259 $vcardType
260 );
261
262 }
263 $propertyList[] = $props;
264
265 }
266
267 $prefer = $this->server->getHTTPPrefer();
268
269 $this->server->httpResponse->setStatus(207);
270 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
271 $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
272 $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, $prefer['return'] === 'minimal'));
273
274 }
275
289 function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) {
290
291 if (!$node instanceof ICard)
292 return;
293
294 $this->validateVCard($data, $modified);
295
296 }
297
311 function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) {
312
313 if (!$parentNode instanceof IAddressBook)
314 return;
315
316 $this->validateVCard($data, $modified);
317
318 }
319
330 protected function validateVCard(&$data, &$modified) {
331
332 // If it's a stream, we convert it to a string first.
333 if (is_resource($data)) {
334 $data = stream_get_contents($data);
335 }
336
337 $before = $data;
338
339 try {
340
341 // If the data starts with a [, we can reasonably assume we're dealing
342 // with a jCal object.
343 if (substr($data, 0, 1) === '[') {
345
346 // Converting $data back to iCalendar, as that's what we
347 // technically support everywhere.
348 $data = $vobj->serialize();
349 $modified = true;
350 } else {
352 }
353
354 } catch (VObject\ParseException $e) {
355
356 throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: ' . $e->getMessage());
357
358 }
359
360 if ($vobj->name !== 'VCARD') {
361 throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.');
362 }
363
365 $prefer = $this->server->getHTTPPrefer();
366
367 if ($prefer['handling'] !== 'strict') {
369 }
370
371 $messages = $vobj->validate($options);
372
373 $highestLevel = 0;
374 $warningMessage = null;
375
376 // $messages contains a list of problems with the vcard, along with
377 // their severity.
378 foreach ($messages as $message) {
379
380 if ($message['level'] > $highestLevel) {
381 // Recording the highest reported error level.
382 $highestLevel = $message['level'];
383 $warningMessage = $message['message'];
384 }
385
386 switch ($message['level']) {
387
388 case 1 :
389 // Level 1 means that there was a problem, but it was repaired.
390 $modified = true;
391 break;
392 case 2 :
393 // Level 2 means a warning, but not critical
394 break;
395 case 3 :
396 // Level 3 means a critical error
397 throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: ' . $message['message']);
398
399 }
400
401 }
402 if ($warningMessage) {
403 $this->server->httpResponse->setHeader(
404 'X-Sabre-Ew-Gross',
405 'vCard validation warning: ' . $warningMessage
406 );
407
408 // Re-serializing object.
409 $data = $vobj->serialize();
410 if (!$modified && strcmp($data, $before) !== 0) {
411 // This ensures that the system does not send an ETag back.
412 $modified = true;
413 }
414 }
415
416 // Destroy circular references to PHP will GC the object.
417 $vobj->destroy();
418 }
419
420
430 protected function addressbookQueryReport($report) {
431
432 $depth = $this->server->getHTTPDepth(0);
433
434 if ($depth == 0) {
435 $candidateNodes = [
436 $this->server->tree->getNodeForPath($this->server->getRequestUri())
437 ];
438 if (!$candidateNodes[0] instanceof ICard) {
439 throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0');
440 }
441 } else {
442 $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
443 }
444
445 $contentType = $report->contentType;
446 if ($report->version) {
447 $contentType .= '; version=' . $report->version;
448 }
449
450 $vcardType = $this->negotiateVCard(
452 );
453
454 $validNodes = [];
455 foreach ($candidateNodes as $node) {
456
457 if (!$node instanceof ICard)
458 continue;
459
460 $blob = $node->get();
461 if (is_resource($blob)) {
462 $blob = stream_get_contents($blob);
463 }
464
465 if (!$this->validateFilters($blob, $report->filters, $report->test)) {
466 continue;
467 }
468
469 $validNodes[] = $node;
470
471 if ($report->limit && $report->limit <= count($validNodes)) {
472 // We hit the maximum number of items, we can stop now.
473 break;
474 }
475
476 }
477
478 $result = [];
479 foreach ($validNodes as $validNode) {
480
481 if ($depth == 0) {
482 $href = $this->server->getRequestUri();
483 } else {
484 $href = $this->server->getRequestUri() . '/' . $validNode->getName();
485 }
486
487 list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
488
489 if (isset($props[200]['{' . self::NS_CARDDAV . '}address-data'])) {
490
491 $props[200]['{' . self::NS_CARDDAV . '}address-data'] = $this->convertVCard(
492 $props[200]['{' . self::NS_CARDDAV . '}address-data'],
493 $vcardType,
494 $report->addressDataProperties
495 );
496
497 }
498 $result[] = $props;
499
500 }
501
502 $prefer = $this->server->getHTTPPrefer();
503
504 $this->server->httpResponse->setStatus(207);
505 $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
506 $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer');
507 $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, $prefer['return'] === 'minimal'));
508
509 }
510
519 function validateFilters($vcardData, array $filters, $test) {
520
521
522 if (!$filters) return true;
523 $vcard = VObject\Reader::read($vcardData);
524
525 foreach ($filters as $filter) {
526
527 $isDefined = isset($vcard->{$filter['name']});
528 if ($filter['is-not-defined']) {
529 if ($isDefined) {
530 $success = false;
531 } else {
532 $success = true;
533 }
534 } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
535
536 // We only need to check for existence
537 $success = $isDefined;
538
539 } else {
540
541 $vProperties = $vcard->select($filter['name']);
542
543 $results = [];
544 if ($filter['param-filters']) {
545 $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
546 }
547 if ($filter['text-matches']) {
548 $texts = [];
549 foreach ($vProperties as $vProperty)
550 $texts[] = $vProperty->getValue();
551
552 $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
553 }
554
555 if (count($results) === 1) {
556 $success = $results[0];
557 } else {
558 if ($filter['test'] === 'anyof') {
559 $success = $results[0] || $results[1];
560 } else {
561 $success = $results[0] && $results[1];
562 }
563 }
564
565 } // else
566
567 // There are two conditions where we can already determine whether
568 // or not this filter succeeds.
569 if ($test === 'anyof' && $success) {
570
571 // Destroy circular references to PHP will GC the object.
572 $vcard->destroy();
573
574 return true;
575 }
576 if ($test === 'allof' && !$success) {
577
578 // Destroy circular references to PHP will GC the object.
579 $vcard->destroy();
580
581 return false;
582 }
583
584 } // foreach
585
586
587 // Destroy circular references to PHP will GC the object.
588 $vcard->destroy();
589
590 // If we got all the way here, it means we haven't been able to
591 // determine early if the test failed or not.
592 //
593 // This implies for 'anyof' that the test failed, and for 'allof' that
594 // we succeeded. Sounds weird, but makes sense.
595 return $test === 'allof';
596
597 }
598
610 protected function validateParamFilters(array $vProperties, array $filters, $test) {
611
612 foreach ($filters as $filter) {
613
614 $isDefined = false;
615 foreach ($vProperties as $vProperty) {
616 $isDefined = isset($vProperty[$filter['name']]);
617 if ($isDefined) break;
618 }
619
620 if ($filter['is-not-defined']) {
621 if ($isDefined) {
622 $success = false;
623 } else {
624 $success = true;
625 }
626
627 // If there's no text-match, we can just check for existence
628 } elseif (!$filter['text-match'] || !$isDefined) {
629
630 $success = $isDefined;
631
632 } else {
633
634 $success = false;
635 foreach ($vProperties as $vProperty) {
636 // If we got all the way here, we'll need to validate the
637 // text-match filter.
638 $success = DAV\StringUtil::textMatch($vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
639 if ($success) break;
640 }
641 if ($filter['text-match']['negate-condition']) {
643 }
644
645 } // else
646
647 // There are two conditions where we can already determine whether
648 // or not this filter succeeds.
649 if ($test === 'anyof' && $success) {
650 return true;
651 }
652 if ($test === 'allof' && !$success) {
653 return false;
654 }
655
656 }
657
658 // If we got all the way here, it means we haven't been able to
659 // determine early if the test failed or not.
660 //
661 // This implies for 'anyof' that the test failed, and for 'allof' that
662 // we succeeded. Sounds weird, but makes sense.
663 return $test === 'allof';
664
665 }
666
675 protected function validateTextMatches(array $texts, array $filters, $test) {
676
677 foreach ($filters as $filter) {
678
679 $success = false;
680 foreach ($texts as $haystack) {
681 $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
682
683 // Breaking on the first match
684 if ($success) break;
685 }
686 if ($filter['negate-condition']) {
688 }
689
690 if ($success && $test === 'anyof')
691 return true;
692
693 if (!$success && $test == 'allof')
694 return false;
695
696
697 }
698
699 // If we got all the way here, it means we haven't been able to
700 // determine early if the test failed or not.
701 //
702 // This implies for 'anyof' that the test failed, and for 'allof' that
703 // we succeeded. Sounds weird, but makes sense.
704 return $test === 'allof';
705
706 }
707
718 function propFindLate(DAV\PropFind $propFind, DAV\INode $node) {
719
720 // If the request was made using the SOGO connector, we must rewrite
721 // the content-type property. By default SabreDAV will send back
722 // text/x-vcard; charset=utf-8, but for SOGO we must strip that last
723 // part.
724 if (strpos($this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird') === false) {
725 return;
726 }
727 $contentType = $propFind->get('{DAV:}getcontenttype');
728 list($part) = explode(';', $contentType);
729 if ($part === 'text/x-vcard' || $part === 'text/vcard') {
730 $propFind->set('{DAV:}getcontenttype', 'text/x-vcard');
731 }
732
733 }
734
744 function htmlActionsPanel(DAV\INode $node, &$output) {
745
746 if (!$node instanceof AddressBookHome)
747 return;
748
749 $output .= '<tr><td colspan="2"><form method="post" action="">
750 <h3>Create new address book</h3>
751 <input type="hidden" name="sabreAction" value="mkcol" />
752 <input type="hidden" name="resourceType" value="{DAV:}collection,{' . self::NS_CARDDAV . '}addressbook" />
753 <label>Name (uri):</label> <input type="text" name="name" /><br />
754 <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br />
755 <input type="submit" value="create" />
756 </form>
757 </td></tr>';
758
759 return false;
760
761 }
762
773
774 if (strpos($response->getHeader('Content-Type'), 'text/vcard') === false) {
775 return;
776 }
777
778 $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType);
779
780 $newBody = $this->convertVCard(
781 $response->getBody(),
782 $target
783 );
784
785 $response->setBody($newBody);
786 $response->setHeader('Content-Type', $mimeType . '; charset=utf-8');
787 $response->setHeader('Content-Length', strlen($newBody));
788
789 }
790
805 protected function negotiateVCard($input, &$mimeType = null) {
806
808 $input,
809 [
810 // Most often used mime-type. Version 3
811 'text/x-vcard',
812 // The correct standard mime-type. Defaults to version 3 as
813 // well.
814 'text/vcard',
815 // vCard 4
816 'text/vcard; version=4.0',
817 // vCard 3
818 'text/vcard; version=3.0',
819 // jCard
820 'application/vcard+json',
821 ]
822 );
823
824 $mimeType = $result;
825 switch ($result) {
826
827 default :
828 case 'text/x-vcard' :
829 case 'text/vcard' :
830 case 'text/vcard; version=3.0' :
831 $mimeType = 'text/vcard';
832 return 'vcard3';
833 case 'text/vcard; version=4.0' :
834 return 'vcard4';
835 case 'application/vcard+json' :
836 return 'jcard';
837
838 // @codeCoverageIgnoreStart
839 }
840 // @codeCoverageIgnoreEnd
841
842 }
843
852 protected function convertVCard($data, $target, array $propertiesFilter = null) {
853
854 if (is_resource($data)) {
855 $data = stream_get_contents($data);
856 }
858 if (!empty($propertiesFilter)) {
859 $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter);
860 $keys = array_unique(array_map(function($child) {
861 return $child->name;
862 }, $input->children()));
863 $keys = array_diff($keys, $propertiesFilter);
864 foreach ($keys as $key) {
865 unset($input->$key);
866 }
867 $data = $input->serialize();
868 }
869 $output = null;
870 try {
871
872 switch ($target) {
873 default :
874 case 'vcard3' :
875 if ($input->getDocumentType() === VObject\Document::VCARD30) {
876 // Do nothing
877 return $data;
878 }
879 $output = $input->convert(VObject\Document::VCARD30);
880 return $output->serialize();
881 case 'vcard4' :
882 if ($input->getDocumentType() === VObject\Document::VCARD40) {
883 // Do nothing
884 return $data;
885 }
886 $output = $input->convert(VObject\Document::VCARD40);
887 return $output->serialize();
888 case 'jcard' :
889 $output = $input->convert(VObject\Document::VCARD40);
890 return json_encode($output);
891
892 }
893
894 } finally {
895
896 // Destroy circular references to PHP will GC the object.
897 $input->destroy();
898 if (!is_null($output)) {
899 $output->destroy();
900 }
901 }
902
903 }
904
913 function getPluginName() {
914
915 return 'carddav';
916
917 }
918
930 function getPluginInfo() {
931
932 return [
933 'name' => $this->getPluginName(),
934 'description' => 'Adds support for CardDAV (rfc6352)',
935 'link' => 'http://sabre.io/dav/carddav/',
936 ];
937
938 }
939
940}
$result
$test
Definition: Utf8Test.php:84
$success
Definition: Utf8Test.php:86
$path
Definition: aliased.php:25
foreach($paths as $path) $request
Definition: asyncclient.php:32
$version
Definition: build.php:27
An exception for terminatinating execution or to throw for unit testing.
AddressBook Home class.
CardDAV plugin.
Definition: Plugin.php:23
validateParamFilters(array $vProperties, array $filters, $test)
Validates if a param-filter can be applied to a specific property.
Definition: Plugin.php:610
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
Definition: Plugin.php:118
const ADDRESSBOOK_ROOT
Url to the addressbooks.
Definition: Plugin.php:28
getAddressbookHomeForPrincipal($principal)
Returns the addressbook home for a given principal.
Definition: Plugin.php:219
convertVCard($data, $target, array $propertiesFilter=null)
Converts a vcard blob to a different version, or jcard.
Definition: Plugin.php:852
negotiateVCard($input, &$mimeType=null)
This helper function performs the content-type negotiation for vcards.
Definition: Plugin.php:805
addressbookQueryReport($report)
This function handles the addressbook-query REPORT.
Definition: Plugin.php:430
validateVCard(&$data, &$modified)
Checks if the submitted iCalendar data is in fact, valid.
Definition: Plugin.php:330
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the Sabre\DAV\Browser\Plugin.
Definition: Plugin.php:744
validateFilters($vcardData, array $filters, $test)
Validates if a vcard makes it throught a list of filters.
Definition: Plugin.php:519
propFindLate(DAV\PropFind $propFind, DAV\INode $node)
This event is triggered when fetching properties.
Definition: Plugin.php:718
getPluginName()
Returns a plugin name.
Definition: Plugin.php:913
beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
This method is triggered before a new file is created.
Definition: Plugin.php:311
report($reportName, $dom, $path)
This functions handles REPORT requests specific to CardDAV.
Definition: Plugin.php:194
const NS_CARDDAV
xml namespace for CardDAV elements
Definition: Plugin.php:33
$maxResourceSize
The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, which can hold up to 2^24 = 16777...
Definition: Plugin.php:55
getPluginInfo()
Returns a bunch of meta-data about the plugin.
Definition: Plugin.php:930
httpAfterGet(RequestInterface $request, ResponseInterface $response)
This event is triggered after GET requests.
Definition: Plugin.php:772
beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
This method is triggered before a file gets updated with new content.
Definition: Plugin.php:289
addressbookMultiGetReport($report)
This function handles the addressbook-multiget REPORT.
Definition: Plugin.php:236
validateTextMatches(array $texts, array $filters, $test)
Validates if a text-filter can be applied to a specific property.
Definition: Plugin.php:675
initialize(DAV\Server $server)
Initializes the plugin.
Definition: Plugin.php:63
getFeatures()
Returns a list of supported features.
Definition: Plugin.php:102
propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
Adds all CardDAV-specific properties.
Definition: Plugin.php:139
This class holds all the information about a PROPFIND request.
Definition: PropFind.php:11
The baseclass for all server plugins.
Main DAV server class.
Definition: Server.php:23
static textMatch($haystack, $needle, $collation, $matchType='contains')
Checks if a needle occurs in a haystack ;)
Definition: StringUtil.php:27
static splitPath($path)
Returns the 'dirname' and 'basename' for a path.
Definition: URLUtil.php:83
static negotiate($acceptHeaderValue, array $availableOptions)
Deprecated! Use negotiateContentType.
Definition: Util.php:38
const VCARD30
vCard 3.0.
Definition: Document.php:44
const VCARD40
vCard 4.0.
Definition: Document.php:49
const PROFILE_CARDDAV
If this option is set, the validator will operate on the vcards on the assumption that the vcards nee...
Definition: Node.php:36
const REPAIR
The following constants are used by the validate() method.
Definition: Node.php:27
Exception thrown by Reader if an invalid object was attempted to be parsed.
static readJson($data, $options=0)
Parses a jCard or jCal object, and returns the top component.
Definition: Reader.php:67
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
Definition: Reader.php:42
$key
Definition: croninfo.php:18
$messages
Definition: en.php:5
AddressBook interface.
Card interface.
Definition: ICard.php:17
IPrincipal interface.
Definition: IPrincipal.php:16
The ICollection Interface.
Definition: ICollection.php:14
This interface represents a file in the directory tree.
Definition: IFile.php:16
The INode interface is the base interface, and the parent class of both ICollection and IFile.
Definition: INode.php:12
The RequestInterface represents a HTTP request.
This interface represents a HTTP response.
catch(Exception $e) $message
$target
Definition: test.php:19
$keys
if($argc< 2) $paths
Definition: migrateto20.php:44
if( $path[strlen( $path) - 1]==='/') if(is_dir($path)) if(!file_exists( $path)) if(preg_match('#\.php$#D', mb_strtolower($path, 'UTF-8'))) $contentType
Definition: module.php:144
$response
$vobj
Definition: rrulebench.php:21
$results
Definition: svg-scanner.php:47
$data
Definition: bench.php:6