PageRenderTime 24ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/tine20/Tinebase/Convert/VCalendar/Abstract.php

https://github.com/corneliusweiss/Tine-2.0-Open-Source-Groupware-and-CRM
PHP | 263 lines | 145 code | 40 blank | 78 comment | 33 complexity | d6d2de70c8a2273f60be23d44aa7eec8 MD5 | raw file
  1. <?php
  2. /**
  3. * Tine 2.0
  4. *
  5. * @package Tinebase
  6. * @subpackage Convert
  7. * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
  8. * @author Philipp Schüle <p.schuele@metaways.de>
  9. * @copyright Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
  10. *
  11. */
  12. /**
  13. * abstract class for VCALENDAR/VTODO/VCARD/... conversion
  14. *
  15. * @package Tinebase
  16. * @subpackage Convert
  17. */
  18. abstract class Tinebase_Convert_VCalendar_Abstract
  19. {
  20. /**
  21. * use servers modlogProperties instead of given DTSTAMP & SEQUENCE
  22. * use this if the concurrency checks are done differntly like in CalDAV
  23. * where the etag is checked
  24. */
  25. const OPTION_USE_SERVER_MODLOG = 'useServerModlog';
  26. protected $_supportedFields = array();
  27. protected $_version;
  28. protected $_modelName = null;
  29. /**
  30. * @param string $version the version of the client
  31. * @throws Tinebase_Exception
  32. */
  33. public function __construct($version = null)
  34. {
  35. if (! $this->_modelName) {
  36. throw new Tinebase_Exception('modelName is required');
  37. }
  38. $this->_version = $version;
  39. }
  40. /**
  41. * returns VObject of input data
  42. *
  43. * @param mixed $blob
  44. * @return \Sabre\VObject\Component\VCalendar
  45. */
  46. public static function getVObject($blob)
  47. {
  48. if ($blob instanceof \Sabre\VObject\Component\VCalendar) {
  49. return $blob;
  50. }
  51. if (is_resource($blob)) {
  52. $blob = stream_get_contents($blob);
  53. }
  54. $blob = Tinebase_Core::filterInputForDatabase($blob);
  55. $vcalendar = self::readVCalBlob($blob);
  56. return $vcalendar;
  57. }
  58. /**
  59. * reads vcal blob and tries to repair some parsing problems that Sabre has
  60. *
  61. * @param string $blob
  62. * @param integer $failcount
  63. * @param integer $spacecount
  64. * @param integer $lastBrokenLineNumber
  65. * @param array $lastLines
  66. * @throws Sabre\VObject\ParseException
  67. * @return Sabre\VObject\Component\VCalendar
  68. *
  69. * @see 0006110: handle iMIP messages from outlook
  70. *
  71. * @todo maybe we can remove this when #7438 is resolved
  72. */
  73. public static function readVCalBlob($blob, $failcount = 0, $spacecount = 0, $lastBrokenLineNumber = 0, $lastLines = array())
  74. {
  75. // convert to utf-8
  76. $blob = Tinebase_Helper::mbConvertTo($blob);
  77. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
  78. ' ' . $blob);
  79. try {
  80. $vcalendar = \Sabre\VObject\Reader::read($blob);
  81. } catch (Sabre\VObject\ParseException $svpe) {
  82. // NOTE: we try to repair\Sabre\VObject\Reader as it fails to detect followup lines that do not begin with a space or tab
  83. if ($failcount < 10 && preg_match(
  84. '/Invalid VObject, line ([0-9]+) did not follow the icalendar\/vcard format/', $svpe->getMessage(), $matches
  85. )) {
  86. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  87. ' ' . $svpe->getMessage() .
  88. ' lastBrokenLineNumber: ' . $lastBrokenLineNumber);
  89. $brokenLineNumber = $matches[1] - 1 + $spacecount;
  90. if ($lastBrokenLineNumber === $brokenLineNumber) {
  91. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  92. ' Try again: concat this line to previous line.');
  93. $lines = $lastLines;
  94. $brokenLineNumber--;
  95. // increase spacecount because one line got removed
  96. $spacecount++;
  97. } else {
  98. $lines = preg_split('/[\r\n]*\n/', $blob);
  99. if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
  100. ' Concat next line to this one.');
  101. $lastLines = $lines; // for retry
  102. }
  103. $lines[$brokenLineNumber] .= $lines[$brokenLineNumber + 1];
  104. unset($lines[$brokenLineNumber + 1]);
  105. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ .
  106. ' failcount: ' . $failcount .
  107. ' brokenLineNumber: ' . $brokenLineNumber .
  108. ' spacecount: ' . $spacecount);
  109. $vcalendar = self::readVCalBlob(implode("\n", $lines), $failcount + 1, $spacecount, $brokenLineNumber, $lastLines);
  110. } else {
  111. throw $svpe;
  112. }
  113. }
  114. return $vcalendar;
  115. }
  116. /**
  117. * to be overwriten in extended classes to modify/cleanup $_vcalendar
  118. *
  119. * @param \Sabre\VObject\Component\VCalendar $vcalendar
  120. */
  121. protected function _afterFromTine20Model(\Sabre\VObject\Component\VCalendar $vcalendar)
  122. {
  123. }
  124. /**
  125. * parse valarm properties
  126. *
  127. * @param Tinebase_Record_Abstract $record
  128. * @param Traversable $valarms
  129. * @param \Sabre\VObject\Component $vcomponent
  130. */
  131. protected function _parseAlarm(Tinebase_Record_Abstract $record, $valarms, \Sabre\VObject\Component $vcomponent)
  132. {
  133. foreach ($valarms as $valarm) {
  134. if ($valarm->ACTION == 'NONE') {
  135. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  136. . ' We can\'t cope with action NONE: iCal 6.0 sends default alarms in the year 1976 with action NONE. Skipping alarm.');
  137. continue;
  138. }
  139. if (! is_object($valarm->TRIGGER)) {
  140. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  141. . ' Alarm has no TRIGGER value. Skipping it.');
  142. continue;
  143. }
  144. # TRIGGER:-PT15M
  145. if (is_string($valarm->TRIGGER->getValue()) && $valarm->TRIGGER instanceof Sabre\VObject\Property\ICalendar\Duration) {
  146. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  147. . ' Adding DURATION trigger value for ' . $valarm->TRIGGER->getValue());
  148. $valarm->TRIGGER->add('VALUE', 'DURATION');
  149. }
  150. $trigger = is_object($valarm->TRIGGER['VALUE']) ? $valarm->TRIGGER['VALUE'] : (is_object($valarm->TRIGGER['RELATED']) ? $valarm->TRIGGER['RELATED'] : NULL);
  151. if ($trigger === NULL) {
  152. // added Trigger/Related for eM Client alarms
  153. // 2014-01-03 - Bullshit, why don't we have testdata for emclient alarms?
  154. // this alarm handling should be refactored, the logic is scrambled
  155. // @see 0006110: handle iMIP messages from outlook
  156. // @todo fix 0007446: handle broken alarm in outlook invitation message
  157. if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
  158. . ' Alarm has no TRIGGER value. Skipping it.');
  159. continue;
  160. }
  161. switch (strtoupper($trigger->getValue())) {
  162. # TRIGGER;VALUE=DATE-TIME:20111031T130000Z
  163. case 'DATE-TIME':
  164. $alarmTime = new Tinebase_DateTime($valarm->TRIGGER->getValue());
  165. $alarmTime->setTimezone('UTC');
  166. $alarm = new Tinebase_Model_Alarm(array(
  167. 'alarm_time' => $alarmTime,
  168. 'minutes_before' => 'custom',
  169. 'model' => $this->_modelName,
  170. ));
  171. break;
  172. # TRIGGER;VALUE=DURATION:-PT1H15M
  173. case 'DURATION':
  174. default:
  175. $durationBaseTime = isset($vcomponent->DTSTART) ? $vcomponent->DTSTART : $vcomponent->DUE;
  176. $alarmTime = $this->_convertToTinebaseDateTime($durationBaseTime);
  177. $alarmTime->setTimezone('UTC');
  178. preg_match('/(?P<invert>[+-]?)(?P<spec>P.*)/', $valarm->TRIGGER->getValue(), $matches);
  179. $duration = new DateInterval($matches['spec']);
  180. $duration->invert = !!($matches['invert'] === '-');
  181. $alarm = new Tinebase_Model_Alarm(array(
  182. 'alarm_time' => $alarmTime->add($duration),
  183. 'minutes_before' => ($duration->format('%d') * 60 * 24) + ($duration->format('%h') * 60) + ($duration->format('%i')),
  184. 'model' => $this->_modelName,
  185. ));
  186. if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
  187. . ' Adding DURATION alarm ' . print_r($alarm->toArray(), true));
  188. }
  189. if ($valarm->ACKNOWLEDGED) {
  190. $dtack = $valarm->ACKNOWLEDGED->getDateTime();
  191. Calendar_Controller_Alarm::setAcknowledgeTime($alarm, $dtack);
  192. }
  193. $record->alarms->addRecord($alarm);
  194. }
  195. }
  196. /**
  197. * get datetime from sabredav datetime property (user TZ is fallback)
  198. *
  199. * @param Sabre\VObject\Property $dateTimeProperty
  200. * @param boolean $_useUserTZ
  201. * @return Tinebase_DateTime
  202. *
  203. * @todo try to guess some common timezones
  204. */
  205. protected function _convertToTinebaseDateTime(\Sabre\VObject\Property $dateTimeProperty, $_useUserTZ = FALSE)
  206. {
  207. $defaultTimezone = date_default_timezone_get();
  208. date_default_timezone_set((string) Tinebase_Core::getUserTimezone());
  209. if ($dateTimeProperty instanceof Sabre\VObject\Property\ICalendar\DateTime) {
  210. $dateTime = $dateTimeProperty->getDateTime();
  211. $isFloatingTime = !$dateTimeProperty['TZID'] && !preg_match('/Z$/i', $dateTimeProperty->getValue());
  212. $isDate = (isset($dateTimeProperty['VALUE']) && strtoupper($dateTimeProperty['VALUE']) == 'DATE');
  213. $tz = ($_useUserTZ || $isFloatingTime || $isDate) ?
  214. (string) Tinebase_Core::getUserTimezone() :
  215. $dateTime->getTimezone();
  216. $result = new Tinebase_DateTime($dateTime->format(Tinebase_Record_Abstract::ISO8601LONG), $tz);
  217. } else {
  218. $result = new Tinebase_DateTime($dateTimeProperty->getValue());
  219. }
  220. date_default_timezone_set($defaultTimezone);
  221. return $result;
  222. }
  223. }