PageRenderTime 40ms CodeModel.GetById 5ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Sabre/CalDAV/CalendarQueryParser.php

https://github.com/KOLANICH/SabreDAV
PHP | 298 lines | 143 code | 66 blank | 89 comment | 21 complexity | 873377ccf212468953cc07ff5a667073 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. namespace Sabre\CalDAV;
  3. use Sabre\VObject;
  4. /**
  5. * Parses the calendar-query report request body.
  6. *
  7. * Whoever designed this format, and the CalDAV equivalent even more so,
  8. * has no feel for design.
  9. *
  10. * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
  11. * @author Evert Pot (http://evertpot.com/)
  12. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  13. */
  14. class CalendarQueryParser {
  15. /**
  16. * List of requested properties the client wanted
  17. *
  18. * @var array
  19. */
  20. public $requestedProperties;
  21. /**
  22. * List of property/component filters.
  23. *
  24. * @var array
  25. */
  26. public $filters;
  27. /**
  28. * This property will contain null if CALDAV:expand was not specified,
  29. * otherwise it will contain an array with 2 elements (start, end). Each
  30. * contain a DateTime object.
  31. *
  32. * If expand is specified, recurring calendar objects are to be expanded
  33. * into their individual components, and only the components that fall
  34. * within the specified time-range are to be returned.
  35. *
  36. * For more details, see rfc4791, section 9.6.5.
  37. *
  38. * @var null|array
  39. */
  40. public $expand;
  41. /**
  42. * DOM Document
  43. *
  44. * @var DOMDocument
  45. */
  46. protected $dom;
  47. /**
  48. * DOM XPath object
  49. *
  50. * @var DOMXPath
  51. */
  52. protected $xpath;
  53. /**
  54. * Creates the parser
  55. *
  56. * @param \DOMDocument $dom
  57. */
  58. public function __construct(\DOMDocument $dom) {
  59. $this->dom = $dom;
  60. $this->xpath = new \DOMXPath($dom);
  61. $this->xpath->registerNameSpace('cal',Plugin::NS_CALDAV);
  62. $this->xpath->registerNameSpace('dav','urn:DAV');
  63. }
  64. /**
  65. * Parses the request.
  66. *
  67. * @return void
  68. */
  69. public function parse() {
  70. $filterNode = null;
  71. $filter = $this->xpath->query('/cal:calendar-query/cal:filter');
  72. if ($filter->length !== 1) {
  73. throw new \Sabre\DAV\Exception\BadRequest('Only one filter element is allowed');
  74. }
  75. $compFilters = $this->parseCompFilters($filter->item(0));
  76. if (count($compFilters)!==1) {
  77. throw new \Sabre\DAV\Exception\BadRequest('There must be exactly 1 top-level comp-filter.');
  78. }
  79. $this->filters = $compFilters[0];
  80. $this->requestedProperties = array_keys(\Sabre\DAV\XMLUtil::parseProperties($this->dom->firstChild));
  81. $expand = $this->xpath->query('/cal:calendar-query/dav:prop/cal:calendar-data/cal:expand');
  82. if ($expand->length>0) {
  83. $this->expand = $this->parseExpand($expand->item(0));
  84. }
  85. }
  86. /**
  87. * Parses all the 'comp-filter' elements from a node
  88. *
  89. * @param \DOMElement $parentNode
  90. * @return array
  91. */
  92. protected function parseCompFilters(\DOMElement $parentNode) {
  93. $compFilterNodes = $this->xpath->query('cal:comp-filter', $parentNode);
  94. $result = array();
  95. for($ii=0; $ii < $compFilterNodes->length; $ii++) {
  96. $compFilterNode = $compFilterNodes->item($ii);
  97. $compFilter = array();
  98. $compFilter['name'] = $compFilterNode->getAttribute('name');
  99. $compFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $compFilterNode)->length>0;
  100. $compFilter['comp-filters'] = $this->parseCompFilters($compFilterNode);
  101. $compFilter['prop-filters'] = $this->parsePropFilters($compFilterNode);
  102. $compFilter['time-range'] = $this->parseTimeRange($compFilterNode);
  103. if ($compFilter['time-range'] && !in_array($compFilter['name'],array(
  104. 'VEVENT',
  105. 'VTODO',
  106. 'VJOURNAL',
  107. 'VFREEBUSY',
  108. 'VALARM',
  109. ))) {
  110. throw new \Sabre\DAV\Exception\BadRequest('The time-range filter is not defined for the ' . $compFilter['name'] . ' component');
  111. };
  112. $result[] = $compFilter;
  113. }
  114. return $result;
  115. }
  116. /**
  117. * Parses all the prop-filter elements from a node
  118. *
  119. * @param \DOMElement $parentNode
  120. * @return array
  121. */
  122. protected function parsePropFilters(\DOMElement $parentNode) {
  123. $propFilterNodes = $this->xpath->query('cal:prop-filter', $parentNode);
  124. $result = array();
  125. for ($ii=0; $ii < $propFilterNodes->length; $ii++) {
  126. $propFilterNode = $propFilterNodes->item($ii);
  127. $propFilter = array();
  128. $propFilter['name'] = $propFilterNode->getAttribute('name');
  129. $propFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $propFilterNode)->length>0;
  130. $propFilter['param-filters'] = $this->parseParamFilters($propFilterNode);
  131. $propFilter['text-match'] = $this->parseTextMatch($propFilterNode);
  132. $propFilter['time-range'] = $this->parseTimeRange($propFilterNode);
  133. $result[] = $propFilter;
  134. }
  135. return $result;
  136. }
  137. /**
  138. * Parses the param-filter element
  139. *
  140. * @param \DOMElement $parentNode
  141. * @return array
  142. */
  143. protected function parseParamFilters(\DOMElement $parentNode) {
  144. $paramFilterNodes = $this->xpath->query('cal:param-filter', $parentNode);
  145. $result = array();
  146. for($ii=0;$ii<$paramFilterNodes->length;$ii++) {
  147. $paramFilterNode = $paramFilterNodes->item($ii);
  148. $paramFilter = array();
  149. $paramFilter['name'] = $paramFilterNode->getAttribute('name');
  150. $paramFilter['is-not-defined'] = $this->xpath->query('cal:is-not-defined', $paramFilterNode)->length>0;
  151. $paramFilter['text-match'] = $this->parseTextMatch($paramFilterNode);
  152. $result[] = $paramFilter;
  153. }
  154. return $result;
  155. }
  156. /**
  157. * Parses the text-match element
  158. *
  159. * @param \DOMElement $parentNode
  160. * @return array|null
  161. */
  162. protected function parseTextMatch(\DOMElement $parentNode) {
  163. $textMatchNodes = $this->xpath->query('cal:text-match', $parentNode);
  164. if ($textMatchNodes->length === 0)
  165. return null;
  166. $textMatchNode = $textMatchNodes->item(0);
  167. $negateCondition = $textMatchNode->getAttribute('negate-condition');
  168. $negateCondition = $negateCondition==='yes';
  169. $collation = $textMatchNode->getAttribute('collation');
  170. if (!$collation) $collation = 'i;ascii-casemap';
  171. return array(
  172. 'negate-condition' => $negateCondition,
  173. 'collation' => $collation,
  174. 'value' => $textMatchNode->nodeValue
  175. );
  176. }
  177. /**
  178. * Parses the time-range element
  179. *
  180. * @param \DOMElement $parentNode
  181. * @return array|null
  182. */
  183. protected function parseTimeRange(\DOMElement $parentNode) {
  184. $timeRangeNodes = $this->xpath->query('cal:time-range', $parentNode);
  185. if ($timeRangeNodes->length === 0) {
  186. return null;
  187. }
  188. $timeRangeNode = $timeRangeNodes->item(0);
  189. if ($start = $timeRangeNode->getAttribute('start')) {
  190. $start = VObject\DateTimeParser::parseDateTime($start);
  191. } else {
  192. $start = null;
  193. }
  194. if ($end = $timeRangeNode->getAttribute('end')) {
  195. $end = VObject\DateTimeParser::parseDateTime($end);
  196. } else {
  197. $end = null;
  198. }
  199. if (!is_null($start) && !is_null($end) && $end <= $start) {
  200. throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the time-range filter');
  201. }
  202. return array(
  203. 'start' => $start,
  204. 'end' => $end,
  205. );
  206. }
  207. /**
  208. * Parses the CALDAV:expand element
  209. *
  210. * @param \DOMElement $parentNode
  211. * @return void
  212. */
  213. protected function parseExpand(\DOMElement $parentNode) {
  214. $start = $parentNode->getAttribute('start');
  215. if(!$start) {
  216. throw new \Sabre\DAV\Exception\BadRequest('The "start" attribute is required for the CALDAV:expand element');
  217. }
  218. $start = VObject\DateTimeParser::parseDateTime($start);
  219. $end = $parentNode->getAttribute('end');
  220. if(!$end) {
  221. throw new \Sabre\DAV\Exception\BadRequest('The "end" attribute is required for the CALDAV:expand element');
  222. }
  223. $end = VObject\DateTimeParser::parseDateTime($end);
  224. if ($end <= $start) {
  225. throw new \Sabre\DAV\Exception\BadRequest('The end-date must be larger than the start-date in the expand element.');
  226. }
  227. return array(
  228. 'start' => $start,
  229. 'end' => $end,
  230. );
  231. }
  232. }