AddressBookQueryParser.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. <?php
  2. /**
  3. * Parses the addressbook-query report request body.
  4. *
  5. * Whoever designed this format, and the CalDAV equavalent even more so,
  6. * has no feel for design.
  7. *
  8. * @package Sabre
  9. * @subpackage CardDAV
  10. * @copyright Copyright (C) 2007-2011 Rooftop Solutions. All rights reserved.
  11. * @author Evert Pot (http://www.rooftopsolutions.nl/)
  12. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  13. */
  14. class Sabre_CardDAV_AddressBookQueryParser {
  15. const TEST_ANYOF = 'anyof';
  16. const TEST_ALLOF = 'allof';
  17. /**
  18. * List of requested properties the client wanted
  19. *
  20. * @var array
  21. */
  22. public $requestedProperties;
  23. /**
  24. * The number of results the client wants
  25. *
  26. * null means it wasn't specified, which in most cases means 'all results'.
  27. *
  28. * @var int|null
  29. */
  30. public $limit;
  31. /**
  32. * List of property filters.
  33. *
  34. * @var array
  35. */
  36. public $filters;
  37. /**
  38. * Either TEST_ANYOF or TEST_ALLOF
  39. *
  40. * @var string
  41. */
  42. public $test;
  43. /**
  44. * DOM Document
  45. *
  46. * @var DOMDocument
  47. */
  48. protected $dom;
  49. /**
  50. * DOM XPath object
  51. *
  52. * @var DOMXPath
  53. */
  54. protected $xpath;
  55. /**
  56. * Creates the parser
  57. *
  58. * @param DOMNode $dom
  59. * @return void
  60. */
  61. public function __construct(DOMDocument $dom) {
  62. $this->dom = $dom;
  63. $this->xpath = new DOMXPath($dom);
  64. $this->xpath->registerNameSpace('card',Sabre_CardDAV_Plugin::NS_CARDDAV);
  65. }
  66. /**
  67. * Parses the request.
  68. *
  69. * @param DOMNode $dom
  70. * @return void
  71. */
  72. public function parse() {
  73. $filterNode = null;
  74. $limit = $this->xpath->evaluate('number(/card:addressbook-query/card:limit/card:nresults)');
  75. if (is_nan($limit)) $limit = null;
  76. $filter = $this->xpath->query('/card:addressbook-query/card:filter');
  77. if ($filter->length !== 1) {
  78. throw new Sabre_DAV_Exception_BadRequest('Only one filter element is allowed');
  79. }
  80. $filter = $filter->item(0);
  81. $test = $this->xpath->evaluate('string(@test)', $filter);
  82. if (!$test) $test = self::TEST_ANYOF;
  83. if ($test !== self::TEST_ANYOF && $test !== self::TEST_ALLOF) {
  84. throw new Sabre_DAV_Exception_BadRequest('The test attribute must either hold "anyof" or "allof"');
  85. }
  86. $propFilters = array();
  87. $propFilterNodes = $this->xpath->query('card:prop-filter', $filter);
  88. for($ii=0; $ii < $propFilterNodes->length; $ii++) {
  89. $propFilters[] = $this->parsePropFilterNode($propFilterNodes->item($ii));
  90. }
  91. $this->filters = $propFilters;
  92. $this->limit = $limit;
  93. $this->requestedProperties = array_keys(Sabre_DAV_XMLUtil::parseProperties($this->dom->firstChild));
  94. $this->test = $test;
  95. }
  96. /**
  97. * Parses the prop-filter xml element
  98. *
  99. * @param DOMElement $propFilterNode
  100. * @return array
  101. */
  102. protected function parsePropFilterNode(DOMElement $propFilterNode) {
  103. $propFilter = array();
  104. $propFilter['name'] = $propFilterNode->getAttribute('name');
  105. $propFilter['test'] = $propFilterNode->getAttribute('test');
  106. if (!$propFilter['test']) $propFilter['test'] = 'anyof';
  107. $propFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $propFilterNode)->length>0;
  108. $paramFilterNodes = $this->xpath->query('card:param-filter', $propFilterNode);
  109. $propFilter['param-filters'] = array();
  110. for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
  111. $propFilter['param-filters'][] = $this->parseParamFilterNode($paramFilterNodes->item($ii));
  112. }
  113. $propFilter['text-matches'] = array();
  114. $textMatchNodes = $this->xpath->query('card:text-match', $propFilterNode);
  115. for($ii=0;$ii<$textMatchNodes->length;$ii++) {
  116. $propFilter['text-matches'][] = $this->parseTextMatchNode($textMatchNodes->item($ii));
  117. }
  118. return $propFilter;
  119. }
  120. /**
  121. * Parses the param-filter element
  122. *
  123. * @param DOMElement $paramFilterNode
  124. * @return array
  125. */
  126. public function parseParamFilterNode(DOMElement $paramFilterNode) {
  127. $paramFilter = array();
  128. $paramFilter['name'] = $paramFilterNode->getAttribute('name');
  129. $paramFilter['is-not-defined'] = $this->xpath->query('card:is-not-defined', $paramFilterNode)->length>0;
  130. $paramFilter['text-match'] = null;
  131. $textMatch = $this->xpath->query('card:text-match', $paramFilterNode);
  132. if ($textMatch->length>0) {
  133. $paramFilter['text-match'] = $this->parseTextMatchNode($textMatch->item(0));
  134. }
  135. return $paramFilter;
  136. }
  137. /**
  138. * Text match
  139. *
  140. * @param DOMElement $textMatchNode
  141. * @return void
  142. */
  143. public function parseTextMatchNode(DOMElement $textMatchNode) {
  144. $matchType = $textMatchNode->getAttribute('match-type');
  145. if (!$matchType) $matchType = 'contains';
  146. if (!in_array($matchType, array('contains', 'equals', 'starts-with', 'ends-with'))) {
  147. throw new Sabre_DAV_Exception_BadRequest('Unknown match-type: ' . $matchType);
  148. }
  149. $negateCondition = $textMatchNode->getAttribute('negate-condition');
  150. $negateCondition = $negateCondition==='yes';
  151. $collation = $textMatchNode->getAttribute('collation');
  152. if (!$collation) $collation = 'i;unicode-casemap';
  153. return array(
  154. 'negate-condition' => $negateCondition,
  155. 'collation' => $collation,
  156. 'match-type' => $matchType,
  157. 'value' => $textMatchNode->nodeValue
  158. );
  159. }
  160. }