Файловый менеджер - Редактировать - /home/avadvi5/calendar.aeronextgen.com/dav.zip
Ðазад
PK �n�\Y=m�1t 1t lib/CardDAV/Plugin.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV; use Sabre\DAV; use Sabre\DAV\Exception\ReportNotSupported; use Sabre\DAV\Xml\Property\LocalHref; use Sabre\DAVACL; use Sabre\HTTP; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\Uri; use Sabre\VObject; /** * CardDAV plugin. * * The CardDAV plugin adds CardDAV functionality to the WebDAV server * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class Plugin extends DAV\ServerPlugin { /** * Url to the addressbooks. */ const ADDRESSBOOK_ROOT = 'addressbooks'; /** * xml namespace for CardDAV elements. */ const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav'; /** * Add urls to this property to have them automatically exposed as * 'directories' to the user. * * @var array */ public $directories = []; /** * Server class. * * @var DAV\Server */ protected $server; /** * The default PDO storage uses a MySQL MEDIUMBLOB for iCalendar data, * which can hold up to 2^24 = 16777216 bytes. This is plenty. We're * capping it to 10M here. */ protected $maxResourceSize = 10000000; /** * Initializes the plugin. */ public function initialize(DAV\Server $server) { /* Events */ $server->on('propFind', [$this, 'propFindEarly']); $server->on('propFind', [$this, 'propFindLate'], 150); $server->on('report', [$this, 'report']); $server->on('onHTMLActionsPanel', [$this, 'htmlActionsPanel']); $server->on('beforeWriteContent', [$this, 'beforeWriteContent']); $server->on('beforeCreateFile', [$this, 'beforeCreateFile']); $server->on('afterMethod:GET', [$this, 'httpAfterGet']); $server->xml->namespaceMap[self::NS_CARDDAV] = 'card'; $server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-query'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookQueryReport'; $server->xml->elementMap['{'.self::NS_CARDDAV.'}addressbook-multiget'] = 'Sabre\\CardDAV\\Xml\\Request\\AddressBookMultiGetReport'; /* Mapping Interfaces to {DAV:}resourcetype values */ $server->resourceTypeMapping['Sabre\\CardDAV\\IAddressBook'] = '{'.self::NS_CARDDAV.'}addressbook'; $server->resourceTypeMapping['Sabre\\CardDAV\\IDirectory'] = '{'.self::NS_CARDDAV.'}directory'; /* Adding properties that may never be changed */ $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-address-data'; $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}max-resource-size'; $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}addressbook-home-set'; $server->protectedProperties[] = '{'.self::NS_CARDDAV.'}supported-collation-set'; $server->xml->elementMap['{http://calendarserver.org/ns/}me-card'] = 'Sabre\\DAV\\Xml\\Property\\Href'; $this->server = $server; } /** * Returns a list of supported features. * * This is used in the DAV: header in the OPTIONS and PROPFIND requests. * * @return array */ public function getFeatures() { return ['addressbook']; } /** * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. * Note that you still need to subscribe to the 'report' event to actually * implement them * * @param string $uri * * @return array */ public function getSupportedReportSet($uri) { $node = $this->server->tree->getNodeForPath($uri); if ($node instanceof IAddressBook || $node instanceof ICard) { return [ '{'.self::NS_CARDDAV.'}addressbook-multiget', '{'.self::NS_CARDDAV.'}addressbook-query', ]; } return []; } /** * Adds all CardDAV-specific properties. */ public function propFindEarly(DAV\PropFind $propFind, DAV\INode $node) { $ns = '{'.self::NS_CARDDAV.'}'; if ($node instanceof IAddressBook) { $propFind->handle($ns.'max-resource-size', $this->maxResourceSize); $propFind->handle($ns.'supported-address-data', function () { return new Xml\Property\SupportedAddressData(); }); $propFind->handle($ns.'supported-collation-set', function () { return new Xml\Property\SupportedCollationSet(); }); } if ($node instanceof DAVACL\IPrincipal) { $path = $propFind->getPath(); $propFind->handle('{'.self::NS_CARDDAV.'}addressbook-home-set', function () use ($path) { return new LocalHref($this->getAddressBookHomeForPrincipal($path).'/'); }); if ($this->directories) { $propFind->handle('{'.self::NS_CARDDAV.'}directory-gateway', function () { return new LocalHref($this->directories); }); } } if ($node instanceof ICard) { // The address-data property is not supposed to be a 'real' // property, but in large chunks of the spec it does act as such. // Therefore we simply expose it as a property. $propFind->handle('{'.self::NS_CARDDAV.'}address-data', function () use ($node) { $val = $node->get(); if (is_resource($val)) { $val = stream_get_contents($val); } return $val; }); } } /** * This functions handles REPORT requests specific to CardDAV. * * @param string $reportName * @param \DOMNode $dom * @param mixed $path * * @return bool */ public function report($reportName, $dom, $path) { switch ($reportName) { case '{'.self::NS_CARDDAV.'}addressbook-multiget': $this->server->transactionType = 'report-addressbook-multiget'; $this->addressbookMultiGetReport($dom); return false; case '{'.self::NS_CARDDAV.'}addressbook-query': $this->server->transactionType = 'report-addressbook-query'; $this->addressBookQueryReport($dom); return false; default: return; } } /** * Returns the addressbook home for a given principal. * * @param string $principal * * @return string */ protected function getAddressbookHomeForPrincipal($principal) { list(, $principalId) = Uri\split($principal); return self::ADDRESSBOOK_ROOT.'/'.$principalId; } /** * This function handles the addressbook-multiget REPORT. * * This report is used by the client to fetch the content of a series * of urls. Effectively avoiding a lot of redundant requests. * * @param Xml\Request\AddressBookMultiGetReport $report */ public function addressbookMultiGetReport($report) { $contentType = $report->contentType; $version = $report->version; if ($version) { $contentType .= '; version='.$version; } $vcardType = $this->negotiateVCard( $contentType ); $propertyList = []; $paths = array_map( [$this->server, 'calculateUri'], $report->hrefs ); foreach ($this->server->getPropertiesForMultiplePaths($paths, $report->properties) as $props) { if (isset($props['200']['{'.self::NS_CARDDAV.'}address-data'])) { $props['200']['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard( $props[200]['{'.self::NS_CARDDAV.'}address-data'], $vcardType ); } $propertyList[] = $props; } $prefer = $this->server->getHTTPPrefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($propertyList, 'minimal' === $prefer['return'])); } /** * This method is triggered before a file gets updated with new content. * * This plugin uses this method to ensure that Card nodes receive valid * vcard data. * * @param string $path * @param resource $data * @param bool $modified should be set to true, if this event handler * changed &$data */ public function beforeWriteContent($path, DAV\IFile $node, &$data, &$modified) { if (!$node instanceof ICard) { return; } $this->validateVCard($data, $modified); } /** * This method is triggered before a new file is created. * * This plugin uses this method to ensure that Card nodes receive valid * vcard data. * * @param string $path * @param resource $data * @param bool $modified should be set to true, if this event handler * changed &$data */ public function beforeCreateFile($path, &$data, DAV\ICollection $parentNode, &$modified) { if (!$parentNode instanceof IAddressBook) { return; } $this->validateVCard($data, $modified); } /** * Checks if the submitted iCalendar data is in fact, valid. * * An exception is thrown if it's not. * * @param resource|string $data * @param bool $modified should be set to true, if this event handler * changed &$data */ protected function validateVCard(&$data, &$modified) { // If it's a stream, we convert it to a string first. if (is_resource($data)) { $data = stream_get_contents($data); } $before = $data; try { // If the data starts with a [, we can reasonably assume we're dealing // with a jCal object. if ('[' === substr($data, 0, 1)) { $vobj = VObject\Reader::readJson($data); // Converting $data back to iCalendar, as that's what we // technically support everywhere. $data = $vobj->serialize(); $modified = true; } else { $vobj = VObject\Reader::read($data); } } catch (VObject\ParseException $e) { throw new DAV\Exception\UnsupportedMediaType('This resource only supports valid vCard or jCard data. Parse error: '.$e->getMessage()); } if ('VCARD' !== $vobj->name) { throw new DAV\Exception\UnsupportedMediaType('This collection can only support vcard objects.'); } $options = VObject\Node::PROFILE_CARDDAV; $prefer = $this->server->getHTTPPrefer(); if ('strict' !== $prefer['handling']) { $options |= VObject\Node::REPAIR; } $messages = $vobj->validate($options); $highestLevel = 0; $warningMessage = null; // $messages contains a list of problems with the vcard, along with // their severity. foreach ($messages as $message) { if ($message['level'] > $highestLevel) { // Recording the highest reported error level. $highestLevel = $message['level']; $warningMessage = $message['message']; } switch ($message['level']) { case 1: // Level 1 means that there was a problem, but it was repaired. $modified = true; break; case 2: // Level 2 means a warning, but not critical break; case 3: // Level 3 means a critical error throw new DAV\Exception\UnsupportedMediaType('Validation error in vCard: '.$message['message']); } } if ($warningMessage) { $this->server->httpResponse->setHeader( 'X-Sabre-Ew-Gross', 'vCard validation warning: '.$warningMessage ); // Re-serializing object. $data = $vobj->serialize(); if (!$modified && 0 !== strcmp($data, $before)) { // This ensures that the system does not send an ETag back. $modified = true; } } // Destroy circular references to PHP will GC the object. $vobj->destroy(); } /** * This function handles the addressbook-query REPORT. * * This report is used by the client to filter an addressbook based on a * complex query. * * @param Xml\Request\AddressBookQueryReport $report */ protected function addressbookQueryReport($report) { $depth = $this->server->getHTTPDepth(0); if (0 == $depth) { $candidateNodes = [ $this->server->tree->getNodeForPath($this->server->getRequestUri()), ]; if (!$candidateNodes[0] instanceof ICard) { throw new ReportNotSupported('The addressbook-query report is not supported on this url with Depth: 0'); } } else { $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri()); } $contentType = $report->contentType; if ($report->version) { $contentType .= '; version='.$report->version; } $vcardType = $this->negotiateVCard( $contentType ); $validNodes = []; foreach ($candidateNodes as $node) { if (!$node instanceof ICard) { continue; } $blob = $node->get(); if (is_resource($blob)) { $blob = stream_get_contents($blob); } if (!$this->validateFilters($blob, $report->filters, $report->test)) { continue; } $validNodes[] = $node; if ($report->limit && $report->limit <= count($validNodes)) { // We hit the maximum number of items, we can stop now. break; } } $result = []; foreach ($validNodes as $validNode) { if (0 == $depth) { $href = $this->server->getRequestUri(); } else { $href = $this->server->getRequestUri().'/'.$validNode->getName(); } list($props) = $this->server->getPropertiesForPath($href, $report->properties, 0); if (isset($props[200]['{'.self::NS_CARDDAV.'}address-data'])) { $props[200]['{'.self::NS_CARDDAV.'}address-data'] = $this->convertVCard( $props[200]['{'.self::NS_CARDDAV.'}address-data'], $vcardType, $report->addressDataProperties ); } $result[] = $props; } $prefer = $this->server->getHTTPPrefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody($this->server->generateMultiStatus($result, 'minimal' === $prefer['return'])); } /** * Validates if a vcard makes it throught a list of filters. * * @param string $vcardData * @param string $test anyof or allof (which means OR or AND) * * @return bool */ public function validateFilters($vcardData, array $filters, $test) { if (!$filters) { return true; } $vcard = VObject\Reader::read($vcardData); foreach ($filters as $filter) { $isDefined = isset($vcard->{$filter['name']}); if ($filter['is-not-defined']) { if ($isDefined) { $success = false; } else { $success = true; } } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) { // We only need to check for existence $success = $isDefined; } else { $vProperties = $vcard->select($filter['name']); $results = []; if ($filter['param-filters']) { $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']); } if ($filter['text-matches']) { $texts = []; foreach ($vProperties as $vProperty) { $texts[] = $vProperty->getValue(); } $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']); } if (1 === count($results)) { $success = $results[0]; } else { if ('anyof' === $filter['test']) { $success = $results[0] || $results[1]; } else { $success = $results[0] && $results[1]; } } } // else // There are two conditions where we can already determine whether // or not this filter succeeds. if ('anyof' === $test && $success) { // Destroy circular references to PHP will GC the object. $vcard->destroy(); return true; } if ('allof' === $test && !$success) { // Destroy circular references to PHP will GC the object. $vcard->destroy(); return false; } } // foreach // Destroy circular references to PHP will GC the object. $vcard->destroy(); // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return 'allof' === $test; } /** * Validates if a param-filter can be applied to a specific property. * * @todo currently we're only validating the first parameter of the passed * property. Any subsequence parameters with the same name are * ignored. * * @param string $test * * @return bool */ protected function validateParamFilters(array $vProperties, array $filters, $test) { foreach ($filters as $filter) { $isDefined = false; foreach ($vProperties as $vProperty) { $isDefined = isset($vProperty[$filter['name']]); if ($isDefined) { break; } } if ($filter['is-not-defined']) { if ($isDefined) { $success = false; } else { $success = true; } // If there's no text-match, we can just check for existence } elseif (!$filter['text-match'] || !$isDefined) { $success = $isDefined; } else { $success = false; foreach ($vProperties as $vProperty) { // If we got all the way here, we'll need to validate the // text-match filter. if (isset($vProperty[$filter['name']])) { $success = DAV\StringUtil::textMatch( $vProperty[$filter['name']]->getValue(), $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type'] ); if ($filter['text-match']['negate-condition']) { $success = !$success; } } if ($success) { break; } } } // else // There are two conditions where we can already determine whether // or not this filter succeeds. if ('anyof' === $test && $success) { return true; } if ('allof' === $test && !$success) { return false; } } // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return 'allof' === $test; } /** * Validates if a text-filter can be applied to a specific property. * * @param string $test * * @return bool */ protected function validateTextMatches(array $texts, array $filters, $test) { foreach ($filters as $filter) { $success = false; foreach ($texts as $haystack) { $success = DAV\StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']); if ($filter['negate-condition']) { $success = !$success; } // Breaking on the first match if ($success) { break; } } if ($success && 'anyof' === $test) { return true; } if (!$success && 'allof' == $test) { return false; } } // If we got all the way here, it means we haven't been able to // determine early if the test failed or not. // // This implies for 'anyof' that the test failed, and for 'allof' that // we succeeded. Sounds weird, but makes sense. return 'allof' === $test; } /** * This event is triggered when fetching properties. * * This event is scheduled late in the process, after most work for * propfind has been done. */ public function propFindLate(DAV\PropFind $propFind, DAV\INode $node) { // If the request was made using the SOGO connector, we must rewrite // the content-type property. By default SabreDAV will send back // text/x-vcard; charset=utf-8, but for SOGO we must strip that last // part. if (false === strpos((string) $this->server->httpRequest->getHeader('User-Agent'), 'Thunderbird')) { return; } $contentType = $propFind->get('{DAV:}getcontenttype'); if (null !== $contentType) { list($part) = explode(';', $contentType); if ('text/x-vcard' === $part || 'text/vcard' === $part) { $propFind->set('{DAV:}getcontenttype', 'text/x-vcard'); } } } /** * This method is used to generate HTML output for the * Sabre\DAV\Browser\Plugin. This allows us to generate an interface users * can use to create new addressbooks. * * @param string $output * * @return bool */ public function htmlActionsPanel(DAV\INode $node, &$output) { if (!$node instanceof AddressBookHome) { return; } $output .= '<tr><td colspan="2"><form method="post" action=""> <h3>Create new address book</h3> <input type="hidden" name="sabreAction" value="mkcol" /> <input type="hidden" name="resourceType" value="{DAV:}collection,{'.self::NS_CARDDAV.'}addressbook" /> <label>Name (uri):</label> <input type="text" name="name" /><br /> <label>Display name:</label> <input type="text" name="{DAV:}displayname" /><br /> <input type="submit" value="create" /> </form> </td></tr>'; return false; } /** * This event is triggered after GET requests. * * This is used to transform data into jCal, if this was requested. */ public function httpAfterGet(RequestInterface $request, ResponseInterface $response) { $contentType = $response->getHeader('Content-Type'); if (null === $contentType || false === strpos($contentType, 'text/vcard')) { return; } $target = $this->negotiateVCard($request->getHeader('Accept'), $mimeType); $newBody = $this->convertVCard( $response->getBody(), $target ); $response->setBody($newBody); $response->setHeader('Content-Type', $mimeType.'; charset=utf-8'); $response->setHeader('Content-Length', strlen($newBody)); } /** * This helper function performs the content-type negotiation for vcards. * * It will return one of the following strings: * 1. vcard3 * 2. vcard4 * 3. jcard * * It defaults to vcard3. * * @param string $input * @param string $mimeType * * @return string */ protected function negotiateVCard($input, &$mimeType = null) { $result = HTTP\negotiateContentType( $input, [ // Most often used mime-type. Version 3 'text/x-vcard', // The correct standard mime-type. Defaults to version 3 as // well. 'text/vcard', // vCard 4 'text/vcard; version=4.0', // vCard 3 'text/vcard; version=3.0', // jCard 'application/vcard+json', ] ); $mimeType = $result; switch ($result) { default: case 'text/x-vcard': case 'text/vcard': case 'text/vcard; version=3.0': $mimeType = 'text/vcard'; return 'vcard3'; case 'text/vcard; version=4.0': return 'vcard4'; case 'application/vcard+json': return 'jcard'; // @codeCoverageIgnoreStart } // @codeCoverageIgnoreEnd } /** * Converts a vcard blob to a different version, or jcard. * * @param string|resource $data * @param string $target * @param array $propertiesFilter * * @return string */ protected function convertVCard($data, $target, ?array $propertiesFilter = null) { if (is_resource($data)) { $data = stream_get_contents($data); } $input = VObject\Reader::read($data); if (!empty($propertiesFilter)) { $propertiesFilter = array_merge(['UID', 'VERSION', 'FN'], $propertiesFilter); $keys = array_unique(array_map(function ($child) { return $child->name; }, $input->children())); $keys = array_diff($keys, $propertiesFilter); foreach ($keys as $key) { unset($input->$key); } $data = $input->serialize(); } $output = null; try { switch ($target) { default: case 'vcard3': if (VObject\Document::VCARD30 === $input->getDocumentType()) { // Do nothing return $data; } $output = $input->convert(VObject\Document::VCARD30); return $output->serialize(); case 'vcard4': if (VObject\Document::VCARD40 === $input->getDocumentType()) { // Do nothing return $data; } $output = $input->convert(VObject\Document::VCARD40); return $output->serialize(); case 'jcard': $output = $input->convert(VObject\Document::VCARD40); return json_encode($output); } } finally { // Destroy circular references to PHP will GC the object. $input->destroy(); if (!is_null($output)) { $output->destroy(); } } } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using DAV\Server::getPlugin * * @return string */ public function getPluginName() { return 'carddav'; } /** * Returns a bunch of meta-data about the plugin. * * Providing this information is optional, and is mainly displayed by the * Browser plugin. * * The description key in the returned array may contain html and will not * be sanitized. * * @return array */ public function getPluginInfo() { return [ 'name' => $this->getPluginName(), 'description' => 'Adds support for CardDAV (rfc6352)', 'link' => 'http://sabre.io/dav/carddav/', ]; } } PK �n�\c�� lib/CardDAV/Card.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV; use Sabre\DAV; use Sabre\DAVACL; /** * The Card object represents a single Card from an addressbook. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class Card extends DAV\File implements ICard, DAVACL\IACL { use DAVACL\ACLTrait; /** * CardDAV backend. * * @var Backend\BackendInterface */ protected $carddavBackend; /** * Array with information about this Card. * * @var array */ protected $cardData; /** * Array with information about the containing addressbook. * * @var array */ protected $addressBookInfo; /** * Constructor. */ public function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo, array $cardData) { $this->carddavBackend = $carddavBackend; $this->addressBookInfo = $addressBookInfo; $this->cardData = $cardData; } /** * Returns the uri for this object. * * @return string */ public function getName() { return $this->cardData['uri']; } /** * Returns the VCard-formatted object. * * @return string */ public function get() { // Pre-populating 'carddata' is optional. If we don't yet have it // already, we fetch it from the backend. if (!isset($this->cardData['carddata'])) { $this->cardData = $this->carddavBackend->getCard($this->addressBookInfo['id'], $this->cardData['uri']); } return $this->cardData['carddata']; } /** * Updates the VCard-formatted object. * * @param string $cardData * * @return string|null */ public function put($cardData) { if (is_resource($cardData)) { $cardData = stream_get_contents($cardData); } // Converting to UTF-8, if needed $cardData = DAV\StringUtil::ensureUTF8($cardData); $etag = $this->carddavBackend->updateCard($this->addressBookInfo['id'], $this->cardData['uri'], $cardData); $this->cardData['carddata'] = $cardData; $this->cardData['etag'] = $etag; return $etag; } /** * Deletes the card. */ public function delete() { $this->carddavBackend->deleteCard($this->addressBookInfo['id'], $this->cardData['uri']); } /** * Returns the mime content-type. * * @return string */ public function getContentType() { return 'text/vcard; charset=utf-8'; } /** * Returns an ETag for this object. * * @return string */ public function getETag() { if (isset($this->cardData['etag'])) { return $this->cardData['etag']; } else { $data = $this->get(); if (is_string($data)) { return '"'.md5($data).'"'; } else { // We refuse to calculate the md5 if it's a stream. return null; } } } /** * Returns the last modification date as a unix timestamp. * * @return int */ public function getLastModified() { return isset($this->cardData['lastmodified']) ? $this->cardData['lastmodified'] : null; } /** * Returns the size of this object in bytes. * * @return int */ public function getSize() { if (array_key_exists('size', $this->cardData)) { return $this->cardData['size']; } else { return strlen($this->get()); } } /** * Returns the owner principal. * * This must be a url to a principal, or null if there's no owner * * @return string|null */ public function getOwner() { return $this->addressBookInfo['principaluri']; } /** * Returns a list of ACE's for this node. * * Each ACE has the following properties: * * 'privilege', a string such as {DAV:}read or {DAV:}write. These are * currently the only supported privileges * * 'principal', a url to the principal who owns the node * * 'protected' (optional), indicating that this ACE is not allowed to * be updated. * * @return array */ public function getACL() { // An alternative acl may be specified through the cardData array. if (isset($this->cardData['acl'])) { return $this->cardData['acl']; } return [ [ 'privilege' => '{DAV:}all', 'principal' => $this->addressBookInfo['principaluri'], 'protected' => true, ], ]; } } PK �n�\���Y / / lib/CardDAV/error_lognu �[��� [13-Apr-2025 15:16:36 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php on line 21 [13-Apr-2025 15:25:11 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php on line 19 [13-Apr-2025 17:33:58 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAVACL\AbstractPrincipalCollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php on line 18 [13-Apr-2025 21:33:00 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\IAddressBook" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php on line 20 [14-Apr-2025 01:02:25 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\File" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php:17 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php on line 17 [14-Apr-2025 02:50:09 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php on line 24 [14-Apr-2025 03:02:50 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php:26 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php on line 26 [14-Apr-2025 04:07:59 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\ICollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php on line 18 [14-Apr-2025 04:37:35 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\IFile" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php on line 19 [19-Apr-2025 06:29:05 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php on line 21 [19-Apr-2025 06:48:56 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php:26 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php on line 26 [19-Apr-2025 09:01:57 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAVACL\AbstractPrincipalCollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php on line 18 [19-Apr-2025 11:09:54 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php on line 19 [19-Apr-2025 12:35:14 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\ICollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php on line 18 [19-Apr-2025 12:51:10 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\IAddressBook" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php on line 20 [19-Apr-2025 12:52:22 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\File" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php:17 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php on line 17 [19-Apr-2025 13:21:17 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php on line 24 [19-Apr-2025 14:59:59 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\IFile" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php on line 19 [24-Apr-2025 19:40:43 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php on line 24 [24-Apr-2025 21:47:48 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\File" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php:17 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php on line 17 [24-Apr-2025 22:13:11 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\IAddressBook" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php on line 20 [24-Apr-2025 23:29:04 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\IFile" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php on line 19 [25-Apr-2025 01:35:45 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php:26 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php on line 26 [25-Apr-2025 01:47:17 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php on line 21 [25-Apr-2025 02:21:05 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\ICollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php on line 18 [25-Apr-2025 08:02:31 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAVACL\AbstractPrincipalCollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php on line 18 [25-Apr-2025 08:32:29 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php on line 19 [30-Apr-2025 08:08:03 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAVACL\AbstractPrincipalCollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookRoot.php on line 18 [30-Apr-2025 09:18:30 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\IFile" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/ICard.php on line 19 [30-Apr-2025 10:32:55 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php:26 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Plugin.php on line 26 [30-Apr-2025 11:22:07 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\IAddressBook" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IDirectory.php on line 20 [30-Apr-2025 13:30:08 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\DAV\ICollection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/IAddressBook.php on line 18 [30-Apr-2025 19:27:27 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\File" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php:17 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Card.php on line 17 [30-Apr-2025 19:36:17 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\ServerPlugin" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php:24 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/VCFExportPlugin.php on line 24 [30-Apr-2025 20:18:46 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php:21 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBookHome.php on line 21 [30-Apr-2025 20:38:39 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\DAV\Collection" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php:19 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/AddressBook.php on line 19 PK �n�\Kht� lib/CardDAV/IDirectory.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV; /** * IDirectory interface. * * Implement this interface to have an addressbook marked as a 'directory'. A * directory is an (often) global addressbook. * * A full description can be found in the IETF draft: * - draft-daboo-carddav-directory-gateway * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ interface IDirectory extends IAddressBook { } PK �n�\�{��� � lib/CardDAV/ICard.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV; use Sabre\DAV; /** * Card interface. * * Extend the ICard interface to allow your custom nodes to be picked up as * 'Cards'. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ interface ICard extends DAV\IFile { } PK �n�\�f��$ $ lib/CardDAV/Backend/error_lognu �[��� [16-Apr-2025 09:06:24 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php on line 18 [17-Apr-2025 07:11:59 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php:23 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php on line 23 [19-Apr-2025 16:51:17 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\CardDAV\Backend\AbstractBackend" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php on line 20 [21-Apr-2025 21:23:26 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php on line 18 [22-Apr-2025 08:05:43 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\CardDAV\Backend\AbstractBackend" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php on line 20 [22-Apr-2025 10:26:51 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php:23 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php on line 23 [28-Apr-2025 03:43:03 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php on line 18 [28-Apr-2025 07:56:22 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php:23 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php on line 23 [01-May-2025 14:30:22 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\CardDAV\Backend\AbstractBackend" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php on line 20 [03-May-2025 04:50:56 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php:18 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/AbstractBackend.php on line 18 [03-May-2025 06:00:08 UTC] PHP Fatal error: Uncaught Error: Class "Sabre\CardDAV\Backend\AbstractBackend" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php:20 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/PDO.php on line 20 [03-May-2025 14:49:59 UTC] PHP Fatal error: Uncaught Error: Interface "Sabre\CardDAV\Backend\BackendInterface" not found in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php:23 Stack trace: #0 {main} thrown in /home/avadvi5/calendar.aeronextgen.com/davis/vendor/sabre/dav/lib/CardDAV/Backend/SyncSupport.php on line 23 PK �n�\��e.A A ( lib/CardDAV/Backend/BackendInterface.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV\Backend; /** * CardDAV Backend Interface. * * Any CardDAV backend must implement this interface. * * Note that there are references to 'addressBookId' scattered throughout the * class. The value of the addressBookId is completely up to you, it can be any * arbitrary value you can use as an unique identifier. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ interface BackendInterface { /** * Returns the list of addressbooks for a specific user. * * Every addressbook should have the following properties: * id - an arbitrary unique id * uri - the 'basename' part of the url * principaluri - Same as the passed parameter * * Any additional clark-notation property may be passed besides this. Some * common ones are : * {DAV:}displayname * {urn:ietf:params:xml:ns:carddav}addressbook-description * {http://calendarserver.org/ns/}getctag * * @param string $principalUri * * @return array */ public function getAddressBooksForUser($principalUri); /** * Updates properties for an address book. * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param string $addressBookId */ public function updateAddressBook($addressBookId, \Sabre\DAV\PropPatch $propPatch); /** * Creates a new address book. * * This method should return the id of the new address book. The id can be * in any format, including ints, strings, arrays or objects. * * @param string $principalUri * @param string $url just the 'basename' of the url * * @return mixed */ public function createAddressBook($principalUri, $url, array $properties); /** * Deletes an entire addressbook and all its contents. * * @param mixed $addressBookId */ public function deleteAddressBook($addressBookId); /** * Returns all cards for a specific addressbook id. * * This method should return the following properties for each card: * * carddata - raw vcard data * * uri - Some unique url * * lastmodified - A unix timestamp * * It's recommended to also return the following properties: * * etag - A unique etag. This must change every time the card changes. * * size - The size of the card in bytes. * * If these last two properties are provided, less time will be spent * calculating them. If they are specified, you can also omit carddata. * This may speed up certain requests, especially with large cards. * * @param mixed $addressbookId * * @return array */ public function getCards($addressbookId); /** * Returns a specific card. * * The same set of properties must be returned as with getCards. The only * exception is that 'carddata' is absolutely required. * * If the card does not exist, you must return false. * * @param mixed $addressBookId * @param string $cardUri * * @return array */ public function getCard($addressBookId, $cardUri); /** * Returns a list of cards. * * This method should work identical to getCard, but instead return all the * cards in the list as an array. * * If the backend supports this, it may allow for some speed-ups. * * @param mixed $addressBookId * * @return array */ public function getMultipleCards($addressBookId, array $uris); /** * Creates a new card. * * The addressbook id will be passed as the first argument. This is the * same id as it is returned from the getAddressBooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. * * It is possible to return an ETag from this method. This ETag is for the * newly created resource, and must be enclosed with double quotes (that * is, the string itself must contain the double quotes). * * You should only return the ETag if you store the carddata as-is. If a * subsequent GET request on the same card does not have the same body, * byte-by-byte and you did return an ETag here, clients tend to get * confused. * * If you don't return an ETag, you can just return null. * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * * @return string|null */ public function createCard($addressBookId, $cardUri, $cardData); /** * Updates a card. * * The addressbook id will be passed as the first argument. This is the * same id as it is returned from the getAddressBooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. * * It is possible to return an ETag from this method. This ETag should * match that of the updated resource, and must be enclosed with double * quotes (that is: the string itself must contain the actual quotes). * * You should only return the ETag if you store the carddata as-is. If a * subsequent GET request on the same card does not have the same body, * byte-by-byte and you did return an ETag here, clients tend to get * confused. * * If you don't return an ETag, you can just return null. * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * * @return string|null */ public function updateCard($addressBookId, $cardUri, $cardData); /** * Deletes a card. * * @param mixed $addressBookId * @param string $cardUri * * @return bool */ public function deleteCard($addressBookId, $cardUri); } PK �n�\�K� # lib/CardDAV/Backend/SyncSupport.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV\Backend; /** * WebDAV-sync support for CardDAV backends. * * In order for backends to advertise support for WebDAV-sync, this interface * must be implemented. * * Implementing this can result in a significant reduction of bandwidth and CPU * time. * * For this to work, you _must_ return a {http://sabredav.org/ns}sync-token * property from getAddressBooksForUser. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ interface SyncSupport extends BackendInterface { /** * The getChanges method returns all the changes that have happened, since * the specified syncToken in the specified address book. * * This function should return an array, such as the following: * * [ * 'syncToken' => 'The current synctoken', * 'added' => [ * 'new.txt', * ], * 'modified' => [ * 'modified.txt', * ], * 'deleted' => [ * 'foo.php.bak', * 'old.txt' * ] * ]; * * The returned syncToken property should reflect the *current* syncToken * of the calendar, as reported in the {http://sabredav.org/ns}sync-token * property. This is needed here too, to ensure the operation is atomic. * * If the $syncToken argument is specified as null, this is an initial * sync, and all members should be reported. * * The modified property is an array of nodenames that have changed since * the last token. * * The deleted property is an array with nodenames, that have been deleted * from collection. * * The $syncLevel argument is basically the 'depth' of the report. If it's * 1, you only have to report changes that happened only directly in * immediate descendants. If it's 2, it should also include changes from * the nodes below the child collections. (grandchildren) * * The $limit argument allows a client to specify how many results should * be returned at most. If the limit is not specified, it should be treated * as infinite. * * If the limit (infinite or not) is higher than you're willing to return, * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. * * If the syncToken is expired (due to data cleanup) or unknown, you must * return null. * * The limit is 'suggestive'. You are free to ignore it. * * @param string $addressBookId * @param string $syncToken * @param int $syncLevel * @param int $limit * * @return array|null */ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null); } PK �n�\ƂyS:F :F lib/CardDAV/Backend/PDO.phpnu �[��� <?php declare(strict_types=1); namespace Sabre\CardDAV\Backend; use Sabre\CardDAV; use Sabre\DAV; use Sabre\DAV\PropPatch; /** * PDO CardDAV backend. * * This CardDAV backend uses PDO to store addressbooks * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class PDO extends AbstractBackend implements SyncSupport { /** * PDO connection. * * @var PDO */ protected $pdo; /** * The PDO table name used to store addressbooks. */ public $addressBooksTableName = 'addressbooks'; /** * The PDO table name used to store cards. */ public $cardsTableName = 'cards'; /** * The table name that will be used for tracking changes in address books. * * @var string */ public $addressBookChangesTableName = 'addressbookchanges'; /** * Sets up the object. */ public function __construct(\PDO $pdo) { $this->pdo = $pdo; } /** * Returns the list of addressbooks for a specific user. * * @param string $principalUri * * @return array */ public function getAddressBooksForUser($principalUri) { $stmt = $this->pdo->prepare('SELECT id, uri, displayname, principaluri, description, synctoken FROM '.$this->addressBooksTableName.' WHERE principaluri = ?'); $stmt->execute([$principalUri]); $addressBooks = []; foreach ($stmt->fetchAll() as $row) { $addressBooks[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $row['principaluri'], '{DAV:}displayname' => $row['displayname'], '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0', ]; } return $addressBooks; } /** * Updates properties for an address book. * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param string $addressBookId */ public function updateAddressBook($addressBookId, PropPatch $propPatch) { $supportedProperties = [ '{DAV:}displayname', '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description', ]; $propPatch->handle($supportedProperties, function ($mutations) use ($addressBookId) { $updates = []; foreach ($mutations as $property => $newValue) { switch ($property) { case '{DAV:}displayname': $updates['displayname'] = $newValue; break; case '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description': $updates['description'] = $newValue; break; } } $query = 'UPDATE '.$this->addressBooksTableName.' SET '; $first = true; foreach ($updates as $key => $value) { if ($first) { $first = false; } else { $query .= ', '; } $query .= ' '.$key.' = :'.$key.' '; } $query .= ' WHERE id = :addressbookid'; $stmt = $this->pdo->prepare($query); $updates['addressbookid'] = $addressBookId; $stmt->execute($updates); $this->addChange($addressBookId, '', 2); return true; }); } /** * Creates a new address book. * * @param string $principalUri * @param string $url just the 'basename' of the url * * @return int Last insert id */ public function createAddressBook($principalUri, $url, array $properties) { $values = [ 'displayname' => null, 'description' => null, 'principaluri' => $principalUri, 'uri' => $url, ]; foreach ($properties as $property => $newValue) { switch ($property) { case '{DAV:}displayname': $values['displayname'] = $newValue; break; case '{'.CardDAV\Plugin::NS_CARDDAV.'}addressbook-description': $values['description'] = $newValue; break; default: throw new DAV\Exception\BadRequest('Unknown property: '.$property); } } $query = 'INSERT INTO '.$this->addressBooksTableName.' (uri, displayname, description, principaluri, synctoken) VALUES (:uri, :displayname, :description, :principaluri, 1)'; $stmt = $this->pdo->prepare($query); $stmt->execute($values); return $this->pdo->lastInsertId( $this->addressBooksTableName.'_id_seq' ); } /** * Deletes an entire addressbook and all its contents. * * @param int $addressBookId */ public function deleteAddressBook($addressBookId) { $stmt = $this->pdo->prepare('DELETE FROM '.$this->cardsTableName.' WHERE addressbookid = ?'); $stmt->execute([$addressBookId]); $stmt = $this->pdo->prepare('DELETE FROM '.$this->addressBooksTableName.' WHERE id = ?'); $stmt->execute([$addressBookId]); $stmt = $this->pdo->prepare('DELETE FROM '.$this->addressBookChangesTableName.' WHERE addressbookid = ?'); $stmt->execute([$addressBookId]); } /** * Returns all cards for a specific addressbook id. * * This method should return the following properties for each card: * * carddata - raw vcard data * * uri - Some unique url * * lastmodified - A unix timestamp * * It's recommended to also return the following properties: * * etag - A unique etag. This must change every time the card changes. * * size - The size of the card in bytes. * * If these last two properties are provided, less time will be spent * calculating them. If they are specified, you can also omit carddata. * This may speed up certain requests, especially with large cards. * * @param mixed $addressbookId * * @return array */ public function getCards($addressbookId) { $stmt = $this->pdo->prepare('SELECT id, uri, lastmodified, etag, size FROM '.$this->cardsTableName.' WHERE addressbookid = ?'); $stmt->execute([$addressbookId]); $result = []; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $row['etag'] = '"'.$row['etag'].'"'; $row['lastmodified'] = (int) $row['lastmodified']; $result[] = $row; } return $result; } /** * Returns a specific card. * * The same set of properties must be returned as with getCards. The only * exception is that 'carddata' is absolutely required. * * If the card does not exist, you must return false. * * @param mixed $addressBookId * @param string $cardUri * * @return array */ public function getCard($addressBookId, $cardUri) { $stmt = $this->pdo->prepare('SELECT id, carddata, uri, lastmodified, etag, size FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri = ? LIMIT 1'); $stmt->execute([$addressBookId, $cardUri]); $result = $stmt->fetch(\PDO::FETCH_ASSOC); if (!$result) { return false; } $result['etag'] = '"'.$result['etag'].'"'; $result['lastmodified'] = (int) $result['lastmodified']; return $result; } /** * Returns a list of cards. * * This method should work identical to getCard, but instead return all the * cards in the list as an array. * * If the backend supports this, it may allow for some speed-ups. * * @param mixed $addressBookId * * @return array */ public function getMultipleCards($addressBookId, array $uris) { $query = 'SELECT id, uri, lastmodified, etag, size, carddata FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri IN ('; // Inserting a whole bunch of question marks $query .= implode(',', array_fill(0, count($uris), '?')); $query .= ')'; $stmt = $this->pdo->prepare($query); $stmt->execute(array_merge([$addressBookId], $uris)); $result = []; while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $row['etag'] = '"'.$row['etag'].'"'; $row['lastmodified'] = (int) $row['lastmodified']; $result[] = $row; } return $result; } /** * Creates a new card. * * The addressbook id will be passed as the first argument. This is the * same id as it is returned from the getAddressBooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. * * It is possible to return an ETag from this method. This ETag is for the * newly created resource, and must be enclosed with double quotes (that * is, the string itself must contain the double quotes). * * You should only return the ETag if you store the carddata as-is. If a * subsequent GET request on the same card does not have the same body, * byte-by-byte and you did return an ETag here, clients tend to get * confused. * * If you don't return an ETag, you can just return null. * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * * @return string|null */ public function createCard($addressBookId, $cardUri, $cardData) { $stmt = $this->pdo->prepare('INSERT INTO '.$this->cardsTableName.' (carddata, uri, lastmodified, addressbookid, size, etag) VALUES (?, ?, ?, ?, ?, ?)'); $etag = md5($cardData); $stmt->execute([ $cardData, $cardUri, time(), $addressBookId, strlen($cardData), $etag, ]); $this->addChange($addressBookId, $cardUri, 1); return '"'.$etag.'"'; } /** * Updates a card. * * The addressbook id will be passed as the first argument. This is the * same id as it is returned from the getAddressBooksForUser method. * * The cardUri is a base uri, and doesn't include the full path. The * cardData argument is the vcard body, and is passed as a string. * * It is possible to return an ETag from this method. This ETag should * match that of the updated resource, and must be enclosed with double * quotes (that is: the string itself must contain the actual quotes). * * You should only return the ETag if you store the carddata as-is. If a * subsequent GET request on the same card does not have the same body, * byte-by-byte and you did return an ETag here, clients tend to get * confused. * * If you don't return an ETag, you can just return null. * * @param mixed $addressBookId * @param string $cardUri * @param string $cardData * * @return string|null */ public function updateCard($addressBookId, $cardUri, $cardData) { $stmt = $this->pdo->prepare('UPDATE '.$this->cardsTableName.' SET carddata = ?, lastmodified = ?, size = ?, etag = ? WHERE uri = ? AND addressbookid =?'); $etag = md5($cardData); $stmt->execute([ $cardData, time(), strlen($cardData), $etag, $cardUri, $addressBookId, ]); $this->addChange($addressBookId, $cardUri, 2); return '"'.$etag.'"'; } /** * Deletes a card. * * @param mixed $addressBookId * @param string $cardUri * * @return bool */ public function deleteCard($addressBookId, $cardUri) { $stmt = $this->pdo->prepare('DELETE FROM '.$this->cardsTableName.' WHERE addressbookid = ? AND uri = ?'); $stmt->execute([$addressBookId, $cardUri]); $this->addChange($addressBookId, $cardUri, 3); return 1 === $stmt->rowCount(); } /** * The getChanges method returns all the changes that have happened, since * the specified syncToken in the specified address book. * * This function should return an array, such as the following: * * [ * 'syncToken' => 'The current synctoken', * 'added' => [ * 'new.txt', * ], * 'modified' => [ * 'updated.txt', * ], * 'deleted' => [ * 'foo.php.bak', * 'old.txt' * ] * ]; * * The returned syncToken property should reflect the *current* syncToken * of the addressbook, as reported in the {http://sabredav.org/ns}sync-token * property. This is needed here too, to ensure the operation is atomic. * * If the $syncToken argument is specified as null, this is an initial * sync, and all members should be reported. * * The modified property is an array of nodenames that have changed since * the last token. * * The deleted property is an array with nodenames, that have been deleted * from collection. * * The $syncLevel argument is basically the 'depth' of the report. If it's * 1, you only have to report changes that happened only directly in * immediate descendants. If it's 2, it should also include changes from * the nodes below the child collections. (grandchildren) * * The $limit argument allows a client to specify how many results should * be returned at most. If the limit is not specified, it should be treated * as infinite. * * If the limit (infinite or not) is higher than you're willing to return, * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. * * If the syncToken is expired (due to data cleanup) or unknown, you must * return null. * * The limit is 'suggestive'. You are free to ignore it. * * @param string $addressBookId * @param string $syncToken * @param int $syncLevel * @param int $limit * * @return array|null */ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { // Current synctoken $stmt = $this->pdo->prepare('SELECT synctoken FROM '.$this->addressBooksTableName.' WHERE id = ?'); $stmt->execute([$addressBookId]); $currentToken = $stmt->fetchColumn(0); if (is_null($currentToken)) { return null; } $result = [ 'syncToken' => $currentToken, 'added' => [], 'modified' => [], 'deleted' => [], ]; if ($syncToken) { $query = 'SELECT uri, operation FROM '.$this->addressBookChangesTableName.' WHERE synctoken >= ? AND synctoken < ? AND addressbookid = ? ORDER BY synctoken'; if ($limit > 0) { $query .= ' LIMIT '.(int) $limit; } // Fetching all changes $stmt = $this->pdo->prepare($query); $stmt->execute([$syncToken, $currentToken, $addressBookId]); $changes = []; // This loop ensures that any duplicates are overwritten, only the // last change on a node is relevant. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; } foreach ($changes as $uri => $operation) { switch ($operation) { case 1: $result['added'][] = $uri; break; case 2: $result['modified'][] = $uri; break; case 3: $result['deleted'][] = $uri; break; } } } else { // No synctoken supplied, this is the initial sync. $query = 'SELECT uri FROM '.$this->cardsTableName.' WHERE addressbookid = ?'; $stmt = $this->pdo->prepare($query); $stmt->execute([$addressBookId]); $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); } return $result; } /** * Adds a change record to the addressbookchanges table. * * @param mixed $addressBookId * @param string $objectUri * @param int $operation 1 = add, 2 = modify, 3 = delete */ protected function addChange($addressBookId, $objectUri, $operation) { $stmt = $this->pdo->prepare('INSERT INTO '.$this->addressBookChangesTableName.' (uri, synctoken, addressbookid, operation) SELECT ?, synctoken, ?, ? FROM '.$this->addressBooksTableName.' WHERE id = ?'); $stmt->execute([ $objectUri, $addressBookId, $operation, $addressBookId, ]); $stmt = $this->pdo->prepare('UPDATE '.$this->addressBooksTableName.' SET synctoken = synctoken + 1 WHERE id = ?'); $stmt->execute([ $addressBookId, ]); } } PK �n�\9��� � '