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']);
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';
80 $server->resourceTypeMapping[
'Sabre\\CardDAV\\IAddressBook'] =
'{' . self::NS_CARDDAV .
'}addressbook';
81 $server->resourceTypeMapping[
'Sabre\\CardDAV\\IDirectory'] =
'{' . self::NS_CARDDAV .
'}directory';
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';
89 $server->xml->elementMap[
'{http://calendarserver.org/ns/}me-card'] =
'Sabre\\DAV\\Xml\\Property\\Href';
104 return [
'addressbook'];
120 $node = $this->server->tree->getNodeForPath($uri);
123 '{' . self::NS_CARDDAV .
'}addressbook-multiget',
124 '{' . self::NS_CARDDAV .
'}addressbook-query',
141 $ns =
'{' . self::NS_CARDDAV .
'}';
145 $propFind->handle($ns .
'max-resource-size', $this->maxResourceSize);
146 $propFind->handle($ns .
'supported-address-data',
function() {
149 $propFind->handle($ns .
'supported-collation-set',
function() {
156 $path = $propFind->getPath();
158 $propFind->handle(
'{' . self::NS_CARDDAV .
'}addressbook-home-set',
function() use (
$path) {
159 return new LocalHref($this->getAddressBookHomeForPrincipal(
$path) .
'/');
162 if ($this->directories) $propFind->handle(
'{' . self::NS_CARDDAV .
'}directory-gateway',
function() {
163 return new LocalHref($this->directories);
168 if ($node instanceof
ICard) {
173 $propFind->handle(
'{' . self::NS_CARDDAV .
'}address-data',
function() use ($node) {
175 if (is_resource($val))
176 $val = stream_get_contents($val);
196 switch ($reportName) {
197 case '{' . self::NS_CARDDAV .
'}addressbook-multiget' :
198 $this->server->transactionType =
'report-addressbook-multiget';
201 case '{' . self::NS_CARDDAV .
'}addressbook-query' :
202 $this->server->transactionType =
'report-addressbook-query';
203 $this->addressBookQueryReport($dom);
222 return self::ADDRESSBOOK_ROOT .
'/' . $principalId;
250 [$this->server,
'calculateUri'],
253 foreach ($this->server->getPropertiesForMultiplePaths(
$paths, $report->properties) as $props) {
255 if (isset($props[
'200'][
'{' . self::NS_CARDDAV .
'}address-data'])) {
257 $props[
'200'][
'{' . self::NS_CARDDAV .
'}address-data'] = $this->
convertVCard(
258 $props[200][
'{' . self::NS_CARDDAV .
'}address-data'],
263 $propertyList[] = $props;
267 $prefer = $this->server->getHTTPPrefer();
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'));
291 if (!$node instanceof
ICard)
333 if (is_resource(
$data)) {
343 if (substr(
$data, 0, 1) ===
'[') {
360 if (
$vobj->name !==
'VCARD') {
365 $prefer = $this->server->getHTTPPrefer();
367 if ($prefer[
'handling'] !==
'strict') {
374 $warningMessage =
null;
380 if (
$message[
'level'] > $highestLevel) {
383 $warningMessage =
$message[
'message'];
402 if ($warningMessage) {
403 $this->server->httpResponse->setHeader(
405 'vCard validation warning: ' . $warningMessage
410 if (!$modified && strcmp(
$data, $before) !== 0) {
432 $depth = $this->server->getHTTPDepth(0);
436 $this->server->tree->getNodeForPath($this->server->getRequestUri())
438 if (!$candidateNodes[0] instanceof
ICard) {
439 throw new ReportNotSupported(
'The addressbook-query report is not supported on this url with Depth: 0');
442 $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
446 if ($report->version) {
455 foreach ($candidateNodes as $node) {
457 if (!$node instanceof
ICard)
460 $blob = $node->get();
461 if (is_resource($blob)) {
462 $blob = stream_get_contents($blob);
469 $validNodes[] = $node;
471 if ($report->limit && $report->limit <= count($validNodes)) {
479 foreach ($validNodes as $validNode) {
482 $href = $this->server->getRequestUri();
484 $href = $this->server->getRequestUri() .
'/' . $validNode->getName();
487 list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0);
489 if (isset($props[200][
'{' . self::NS_CARDDAV .
'}address-data'])) {
491 $props[200][
'{' . self::NS_CARDDAV .
'}address-data'] = $this->
convertVCard(
492 $props[200][
'{' . self::NS_CARDDAV .
'}address-data'],
494 $report->addressDataProperties
502 $prefer = $this->server->getHTTPPrefer();
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'));
522 if (!$filters)
return true;
525 foreach ($filters as $filter) {
527 $isDefined = isset($vcard->{$filter[
'name']});
528 if ($filter[
'is-not-defined']) {
534 } elseif ((!$filter[
'param-filters'] && !$filter[
'text-matches']) || !$isDefined) {
541 $vProperties = $vcard->select($filter[
'name']);
544 if ($filter[
'param-filters']) {
547 if ($filter[
'text-matches']) {
549 foreach ($vProperties as $vProperty)
550 $texts[] = $vProperty->getValue();
558 if ($filter[
'test'] ===
'anyof') {
595 return $test ===
'allof';
612 foreach ($filters as $filter) {
615 foreach ($vProperties as $vProperty) {
616 $isDefined = isset($vProperty[$filter[
'name']]);
617 if ($isDefined)
break;
620 if ($filter[
'is-not-defined']) {
628 } elseif (!$filter[
'text-match'] || !$isDefined) {
635 foreach ($vProperties as $vProperty) {
638 $success =
DAV\StringUtil::textMatch($vProperty[$filter[
'name']]->getValue(), $filter[
'text-match'][
'value'], $filter[
'text-match'][
'collation'], $filter[
'text-match'][
'match-type']);
641 if ($filter[
'text-match'][
'negate-condition']) {
663 return $test ===
'allof';
677 foreach ($filters as $filter) {
680 foreach ($texts as $haystack) {
686 if ($filter[
'negate-condition']) {
704 return $test ===
'allof';
724 if (strpos($this->server->httpRequest->getHeader(
'User-Agent'),
'Thunderbird') ===
false) {
729 if ($part ===
'text/x-vcard' || $part ===
'text/vcard') {
730 $propFind->set(
'{DAV:}getcontenttype',
'text/x-vcard');
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" />
774 if (strpos(
$response->getHeader(
'Content-Type'),
'text/vcard') ===
false) {
778 $target = $this->negotiateVCard(
$request->getHeader(
'Accept'), $mimeType);
780 $newBody = $this->convertVCard(
786 $response->setHeader(
'Content-Type', $mimeType .
'; charset=utf-8');
787 $response->setHeader(
'Content-Length', strlen($newBody));
816 'text/vcard; version=4.0',
818 'text/vcard; version=3.0',
820 'application/vcard+json',
828 case 'text/x-vcard' :
830 case 'text/vcard; version=3.0' :
831 $mimeType =
'text/vcard';
833 case 'text/vcard; version=4.0' :
835 case 'application/vcard+json' :
854 if (is_resource(
$data)) {
858 if (!empty($propertiesFilter)) {
859 $propertiesFilter = array_merge([
'UID',
'VERSION',
'FN'], $propertiesFilter);
860 $keys = array_unique(array_map(
function($child) {
933 'name' => $this->getPluginName(),
934 'description' =>
'Adds support for CardDAV (rfc6352)',
935 'link' =>
'http://sabre.io/dav/carddav/',
foreach($paths as $path) $request
An exception for terminatinating execution or to throw for unit testing.
validateParamFilters(array $vProperties, array $filters, $test)
Validates if a param-filter can be applied to a specific property.
getSupportedReportSet($uri)
Returns a list of reports this plugin supports.
const ADDRESSBOOK_ROOT
Url to the addressbooks.
getAddressbookHomeForPrincipal($principal)
Returns the addressbook home for a given principal.
convertVCard($data, $target, array $propertiesFilter=null)
Converts a vcard blob to a different version, or jcard.
negotiateVCard($input, &$mimeType=null)
This helper function performs the content-type negotiation for vcards.
addressbookQueryReport($report)
This function handles the addressbook-query REPORT.
validateVCard(&$data, &$modified)
Checks if the submitted iCalendar data is in fact, valid.
htmlActionsPanel(DAV\INode $node, &$output)
This method is used to generate HTML output for the Sabre\DAV\Browser\Plugin.
validateFilters($vcardData, array $filters, $test)
Validates if a vcard makes it throught a list of filters.
propFindLate(DAV\PropFind $propFind, DAV\INode $node)
This event is triggered when fetching properties.
getPluginName()
Returns a plugin name.
beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified)
This method is triggered before a new file is created.
report($reportName, $dom, $path)
This functions handles REPORT requests specific to CardDAV.
const NS_CARDDAV
xml namespace for CardDAV elements
$maxResourceSize
The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, which can hold up to 2^24 = 16777...
getPluginInfo()
Returns a bunch of meta-data about the plugin.
httpAfterGet(RequestInterface $request, ResponseInterface $response)
This event is triggered after GET requests.
beforeWriteContent($path, DAV\IFile $node, &$data, &$modified)
This method is triggered before a file gets updated with new content.
addressbookMultiGetReport($report)
This function handles the addressbook-multiget REPORT.
validateTextMatches(array $texts, array $filters, $test)
Validates if a text-filter can be applied to a specific property.
initialize(DAV\Server $server)
Initializes the plugin.
getFeatures()
Returns a list of supported features.
propFindEarly(DAV\PropFind $propFind, DAV\INode $node)
Adds all CardDAV-specific properties.
Supported-address-data property.
supported-collation-set property
This class holds all the information about a PROPFIND request.
The baseclass for all server plugins.
static textMatch($haystack, $needle, $collation, $matchType='contains')
Checks if a needle occurs in a haystack ;)
static splitPath($path)
Returns the 'dirname' and 'basename' for a path.
static negotiate($acceptHeaderValue, array $availableOptions)
Deprecated! Use negotiateContentType.
const PROFILE_CARDDAV
If this option is set, the validator will operate on the vcards on the assumption that the vcards nee...
const REPAIR
The following constants are used by the validate() method.
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.
static read($data, $options=0, $charset='UTF-8')
Parses a vCard or iCalendar object, and returns the top component.
The ICollection Interface.
This interface represents a file in the directory tree.
The INode interface is the base interface, and the parent class of both ICollection and IFile.
The RequestInterface represents a HTTP request.
This interface represents a HTTP response.
catch(Exception $e) $message
if( $path[strlen( $path) - 1]==='/') if(is_dir($path)) if(!file_exists( $path)) if(preg_match('#\.php$#D', mb_strtolower($path, 'UTF-8'))) $contentType