<?php namespace Sabre\CardDAV; use Sabre\DAV; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\VObject; /** * VCF Exporter * * This plugin adds the ability to export entire address books as .vcf files. * This is useful for clients that don't support CardDAV yet. They often do * support vcf files. * * @copyright Copyright (C) fruux GmbH (https://fruux.com/) * @author Evert Pot (http://evertpot.com/) * @author Thomas Tanghus (http://tanghus.net/) * @license http://sabre.io/license/ Modified BSD License */ class VCFExportPlugin extends DAV\ServerPlugin { /** * Reference to Server class * * @var DAV\Server */ protected $server; /** * Initializes the plugin and registers event handlers * * @param DAV\Server $server * @return void */ function initialize(DAV\Server $server) { $this->server = $server; $this->server->on('method:GET', [$this, 'httpGet'], 90); $server->on('browserButtonActions', function($path, $node, &$actions) { if ($node instanceof IAddressBook) { $actions .= '<a href="' . htmlspecialchars($path, ENT_QUOTES, 'UTF-8') . '?export"><span class="oi" data-glyph="book"></span></a>'; } }); } /** * Intercepts GET requests on addressbook urls ending with ?export. * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ 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 */ 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 */ 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 */ 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/', ]; } }