server = $server; $this->server->on('method:GET', [$this, 'httpGet'], 90); $server->on('browserButtonActions', function ($path, $node, &$actions) { if ($node instanceof IAddressBook) { $actions .= ''; } }); } /** * Intercepts GET requests on addressbook urls ending with ?export. * * @param RequestInterface $request * @param ResponseInterface $response * * @return bool */ public function httpGet(RequestInterface $request, ResponseInterface $response) { $queryParams = $request->getQueryParameters(); if (!array_key_exists('export', $queryParams)) { return; } $path = $request->getPath(); $node = $this->server->tree->getNodeForPath($path); if (!($node instanceof IAddressBook)) { return; } $this->server->transactionType = 'get-addressbook-export'; // Checking ACL, if available. if ($aclPlugin = $this->server->getPlugin('acl')) { $aclPlugin->checkPrivileges($path, '{DAV:}read'); } $nodes = $this->server->getPropertiesForPath($path, [ '{'.Plugin::NS_CARDDAV.'}address-data', ], 1); $format = 'text/directory'; $output = null; $filenameExtension = null; switch ($format) { case 'text/directory': $output = $this->generateVCF($nodes); $filenameExtension = '.vcf'; break; } $filename = preg_replace( '/[^a-zA-Z0-9-_ ]/um', '', $node->getName() ); $filename .= '-'.date('Y-m-d').$filenameExtension; $response->setHeader('Content-Disposition', 'attachment; filename="'.$filename.'"'); $response->setHeader('Content-Type', $format); $response->setStatus(200); $response->setBody($output); // Returning false to break the event chain return false; } /** * Merges all vcard objects, and builds one big vcf export. * * @param array $nodes * * @return string */ public function generateVCF(array $nodes) { $output = ''; foreach ($nodes as $node) { if (!isset($node[200]['{'.Plugin::NS_CARDDAV.'}address-data'])) { continue; } $nodeData = $node[200]['{'.Plugin::NS_CARDDAV.'}address-data']; // Parsing this node so VObject can clean up the output. $vcard = VObject\Reader::read($nodeData); $output .= $vcard->serialize(); // Destroy circular references to PHP will GC the object. $vcard->destroy(); } return $output; } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using \Sabre\DAV\Server::getPlugin * * @return string */ public function getPluginName() { return 'vcf-export'; } /** * 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 the ability to export CardDAV addressbooks as a single vCard file.', 'link' => 'http://sabre.io/dav/vcf-export-plugin/', ]; } }