PageRenderTime 61ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/webdav/vendors/SabreDAV/lib/Sabre/CardDAV/Plugin.php

https://github.com/phpnode/YiiBlocks
PHP | 466 lines | 217 code | 102 blank | 147 comment | 54 complexity | 1b8745bede55d1ba55ebf33aaa2aa669 MD5 | raw file
  1. <?php
  2. /**
  3. * CardDAV plugin
  4. *
  5. * @package Sabre
  6. * @subpackage CardDAV
  7. * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved.
  8. * @author Evert Pot (http://www.rooftopsolutions.nl/)
  9. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  10. */
  11. /**
  12. * The CardDAV plugin adds CardDAV functionality to the WebDAV server
  13. */
  14. class Sabre_CardDAV_Plugin extends Sabre_DAV_ServerPlugin {
  15. /**
  16. * Url to the addressbooks
  17. */
  18. const ADDRESSBOOK_ROOT = 'addressbooks';
  19. /**
  20. * xml namespace for CardDAV elements
  21. */
  22. const NS_CARDDAV = 'urn:ietf:params:xml:ns:carddav';
  23. /**
  24. * Add urls to this property to have them automatically exposed as
  25. * 'directories' to the user.
  26. *
  27. * @var array
  28. */
  29. public $directories = array();
  30. /**
  31. * Server class
  32. *
  33. * @var Sabre_DAV_Server
  34. */
  35. protected $server;
  36. /**
  37. * Initializes the plugin
  38. *
  39. * @param Sabre_DAV_Server $server
  40. * @return void
  41. */
  42. public function initialize(Sabre_DAV_Server $server) {
  43. /* Events */
  44. $server->subscribeEvent('beforeGetProperties', array($this, 'beforeGetProperties'));
  45. $server->subscribeEvent('report', array($this,'report'));
  46. /* Namespaces */
  47. $server->xmlNamespaces[self::NS_CARDDAV] = 'card';
  48. /* Mapping Interfaces to {DAV:}resourcetype values */
  49. $server->resourceTypeMapping['Sabre_CardDAV_IAddressBook'] = '{' . self::NS_CARDDAV . '}addressbook';
  50. $server->resourceTypeMapping['Sabre_CardDAV_IDirectory'] = '{' . self::NS_CARDDAV . '}directory';
  51. /* Adding properties that may never be changed */
  52. $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}supported-address-data';
  53. $server->protectedProperties[] = '{' . self::NS_CARDDAV . '}max-resource-size';
  54. $this->server = $server;
  55. }
  56. /**
  57. * Returns a list of supported features.
  58. *
  59. * This is used in the DAV: header in the OPTIONS and PROPFIND requests.
  60. *
  61. * @return array
  62. */
  63. public function getFeatures() {
  64. return array('addressbook');
  65. }
  66. /**
  67. * Returns a list of reports this plugin supports.
  68. *
  69. * This will be used in the {DAV:}supported-report-set property.
  70. * Note that you still need to subscribe to the 'report' event to actually
  71. * implement them
  72. *
  73. * @param string $uri
  74. * @return array
  75. */
  76. public function getSupportedReportSet($uri) {
  77. $node = $this->server->tree->getNodeForPath($uri);
  78. if ($node instanceof Sabre_CardDAV_AddressBook || $node instanceof Sabre_CardDAV_ICard) {
  79. return array(
  80. '{' . self::NS_CARDDAV . '}addressbook-multiget',
  81. '{' . self::NS_CARDDAV . '}addressbook-query',
  82. );
  83. }
  84. return array();
  85. }
  86. /**
  87. * Adds all CardDAV-specific properties
  88. *
  89. * @param string $path
  90. * @param Sabre_DAV_INode $node
  91. * @param array $requestedProperties
  92. * @param array $returnedProperties
  93. * @return void
  94. */
  95. public function beforeGetProperties($path, Sabre_DAV_INode $node, array &$requestedProperties, array &$returnedProperties) {
  96. if ($node instanceof Sabre_DAVACL_IPrincipal) {
  97. // calendar-home-set property
  98. $addHome = '{' . self::NS_CARDDAV . '}addressbook-home-set';
  99. if (in_array($addHome,$requestedProperties)) {
  100. $principalId = $node->getName();
  101. $addressbookHomePath = self::ADDRESSBOOK_ROOT . '/' . $principalId . '/';
  102. unset($requestedProperties[array_search($addHome, $requestedProperties)]);
  103. $returnedProperties[200][$addHome] = new Sabre_DAV_Property_Href($addressbookHomePath);
  104. }
  105. $directories = '{' . self::NS_CARDDAV . '}directory-gateway';
  106. if ($this->directories && in_array($directories, $requestedProperties)) {
  107. unset($requestedProperties[array_search($directories, $requestedProperties)]);
  108. $returnedProperties[200][$directories] = new Sabre_DAV_Property_HrefList($this->directories);
  109. }
  110. }
  111. if ($node instanceof Sabre_CardDAV_ICard) {
  112. // The address-data property is not supposed to be a 'real'
  113. // property, but in large chunks of the spec it does act as such.
  114. // Therefore we simply expose it as a property.
  115. $addressDataProp = '{' . self::NS_CARDDAV . '}address-data';
  116. if (in_array($addressDataProp, $requestedProperties)) {
  117. unset($requestedProperties[$addressDataProp]);
  118. $val = $node->get();
  119. if (is_resource($val))
  120. $val = stream_get_contents($val);
  121. // Taking out \r to not screw up the xml output
  122. $returnedProperties[200][$addressDataProp] = str_replace("\r","", $val);
  123. }
  124. }
  125. }
  126. /**
  127. * This functions handles REPORT requests specific to CardDAV
  128. *
  129. * @param string $reportName
  130. * @param DOMNode $dom
  131. * @return bool
  132. */
  133. public function report($reportName,$dom) {
  134. switch($reportName) {
  135. case '{'.self::NS_CARDDAV.'}addressbook-multiget' :
  136. $this->addressbookMultiGetReport($dom);
  137. return false;
  138. case '{'.self::NS_CARDDAV.'}addressbook-query' :
  139. $this->addressBookQueryReport($dom);
  140. return false;
  141. default :
  142. return;
  143. }
  144. }
  145. /**
  146. * This function handles the addressbook-multiget REPORT.
  147. *
  148. * This report is used by the client to fetch the content of a series
  149. * of urls. Effectively avoiding a lot of redundant requests.
  150. *
  151. * @param DOMNode $dom
  152. * @return void
  153. */
  154. public function addressbookMultiGetReport($dom) {
  155. $properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
  156. $hrefElems = $dom->getElementsByTagNameNS('urn:DAV','href');
  157. $propertyList = array();
  158. foreach($hrefElems as $elem) {
  159. $uri = $this->server->calculateUri($elem->nodeValue);
  160. list($propertyList[]) = $this->server->getPropertiesForPath($uri,$properties);
  161. }
  162. $this->server->httpResponse->sendStatus(207);
  163. $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
  164. $this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
  165. }
  166. /**
  167. * This function handles the addressbook-query REPORT
  168. *
  169. * This report is used by the client to filter an addressbook based on a
  170. * complex query.
  171. *
  172. * @param DOMNode $dom
  173. * @return void
  174. */
  175. protected function addressbookQueryReport($dom) {
  176. $query = new Sabre_CardDAV_AddressBookQueryParser($dom);
  177. $query->parse();
  178. $depth = $this->server->getHTTPDepth(0);
  179. if ($depth==0) {
  180. $candidateNodes = array(
  181. $this->server->tree->getNodeForPath($this->server->getRequestUri())
  182. );
  183. } else {
  184. $candidateNodes = $this->server->tree->getChildren($this->server->getRequestUri());
  185. }
  186. $validNodes = array();
  187. foreach($candidateNodes as $node) {
  188. if (!$node instanceof Sabre_CardDAV_ICard)
  189. continue;
  190. $blob = $node->get();
  191. if (is_resource($blob)) {
  192. $blob = stream_get_contents($blob);
  193. }
  194. if (!$this->validateFilters($blob, $query->filters, $query->test)) {
  195. continue;
  196. }
  197. $validNodes[] = $node;
  198. if ($query->limit && $query->limit <= count($validNodes)) {
  199. // We hit the maximum number of items, we can stop now.
  200. break;
  201. }
  202. }
  203. $result = array();
  204. foreach($validNodes as $validNode) {
  205. if ($depth==0) {
  206. $href = $this->server->getRequestUri();
  207. } else {
  208. $href = $this->server->getRequestUri() . '/' . $validNode->getName();
  209. }
  210. list($result[]) = $this->server->getPropertiesForPath($href, $query->requestedProperties, 0);
  211. }
  212. $this->server->httpResponse->sendStatus(207);
  213. $this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
  214. $this->server->httpResponse->sendBody($this->server->generateMultiStatus($result));
  215. }
  216. /**
  217. * Validates if a vcard makes it throught a list of filters.
  218. *
  219. * @param string $vcardData
  220. * @param array $filters
  221. * @param string $test anyof or allof (which means OR or AND)
  222. * @return bool
  223. */
  224. public function validateFilters($vcardData, array $filters, $test) {
  225. $vcard = Sabre_VObject_Reader::read($vcardData);
  226. $success = true;
  227. foreach($filters as $filter) {
  228. $isDefined = isset($vcard->{$filter['name']});
  229. if ($filter['is-not-defined']) {
  230. if ($isDefined) {
  231. $success = false;
  232. } else {
  233. $success = true;
  234. }
  235. } elseif ((!$filter['param-filters'] && !$filter['text-matches']) || !$isDefined) {
  236. // We only need to check for existence
  237. $success = $isDefined;
  238. } else {
  239. $vProperties = $vcard->select($filter['name']);
  240. $results = array();
  241. if ($filter['param-filters']) {
  242. $results[] = $this->validateParamFilters($vProperties, $filter['param-filters'], $filter['test']);
  243. }
  244. if ($filter['text-matches']) {
  245. $texts = array();
  246. foreach($vProperties as $vProperty)
  247. $texts[] = $vProperty->value;
  248. $results[] = $this->validateTextMatches($texts, $filter['text-matches'], $filter['test']);
  249. }
  250. if (count($results)===1) {
  251. $success = $results[0];
  252. } else {
  253. if ($filter['test'] === 'anyof') {
  254. $success = $results[0] || $results[1];
  255. } else {
  256. $success = $results[0] && $results[1];
  257. }
  258. }
  259. } // else
  260. // There are two conditions where we can already determine wether
  261. // or not this filter succeeds.
  262. if ($test==='anyof' && $success) {
  263. return true;
  264. }
  265. if ($test==='allof' && !$success) {
  266. return false;
  267. }
  268. } // foreach
  269. // If we got all the way here, it means we haven't been able to
  270. // determine early if the test failed or not.
  271. //
  272. // This implies for 'anyof' that the test failed, and for 'allof' that
  273. // we succeeded. Sounds weird, but makes sense.
  274. return $test==='allof';
  275. }
  276. /**
  277. * Validates if a param-filter can be applied to a specific property.
  278. *
  279. * @todo currently we're only validating the first parameter of the passed
  280. * property. Any subsequence parameters with the same name are
  281. * ignored.
  282. * @param Sabre_VObject_Property $vProperty
  283. * @param array $filters
  284. * @param string $test
  285. * @return bool
  286. */
  287. protected function validateParamFilters(array $vProperties, array $filters, $test) {
  288. $success = false;
  289. foreach($filters as $filter) {
  290. $isDefined = false;
  291. foreach($vProperties as $vProperty) {
  292. $isDefined = isset($vProperty[$filter['name']]);
  293. if ($isDefined) break;
  294. }
  295. if ($filter['is-not-defined']) {
  296. if ($isDefined) {
  297. $success = false;
  298. } else {
  299. $success = true;
  300. }
  301. // If there's no text-match, we can just check for existence
  302. } elseif (!$filter['text-match'] || !$isDefined) {
  303. $success = $isDefined;
  304. } else {
  305. $texts = array();
  306. $success = false;
  307. foreach($vProperties as $vProperty) {
  308. // If we got all the way here, we'll need to validate the
  309. // text-match filter.
  310. $success = Sabre_DAV_StringUtil::textMatch($vProperty[$filter['name']]->value, $filter['text-match']['value'], $filter['text-match']['collation'], $filter['text-match']['match-type']);
  311. if ($success) break;
  312. }
  313. if ($filter['text-match']['negate-condition']) {
  314. $success = !$success;
  315. }
  316. } // else
  317. // There are two conditions where we can already determine wether
  318. // or not this filter succeeds.
  319. if ($test==='anyof' && $success) {
  320. return true;
  321. }
  322. if ($test==='allof' && !$success) {
  323. return false;
  324. }
  325. }
  326. // If we got all the way here, it means we haven't been able to
  327. // determine early if the test failed or not.
  328. //
  329. // This implies for 'anyof' that the test failed, and for 'allof' that
  330. // we succeeded. Sounds weird, but makes sense.
  331. return $test==='allof';
  332. }
  333. /**
  334. * Validates if a text-filter can be applied to a specific property.
  335. *
  336. * @param array $texts
  337. * @param array $filters
  338. * @param string $test
  339. * @return bool
  340. */
  341. protected function validateTextMatches(array $texts, array $filters, $test) {
  342. foreach($filters as $filter) {
  343. $success = false;
  344. foreach($texts as $haystack) {
  345. $success = Sabre_DAV_StringUtil::textMatch($haystack, $filter['value'], $filter['collation'], $filter['match-type']);
  346. // Breaking on the first match
  347. if ($success) break;
  348. }
  349. if ($filter['negate-condition']) {
  350. $success = !$success;
  351. }
  352. if ($success && $test==='anyof')
  353. return true;
  354. if (!$success && $test=='allof')
  355. return false;
  356. }
  357. // If we got all the way here, it means we haven't been able to
  358. // determine early if the test failed or not.
  359. //
  360. // This implies for 'anyof' that the test failed, and for 'allof' that
  361. // we succeeded. Sounds weird, but makes sense.
  362. return $test==='allof';
  363. }
  364. }