PageRenderTime 62ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/backend/caldav.php

https://github.com/MikeEvans/PHP-Push-2
PHP | 1452 lines | 1162 code | 98 blank | 192 comment | 171 complexity | 99e446744dfa62ba6291b3ddcf89786f MD5 | raw file
Possible License(s): AGPL-3.0
  1. <?php
  2. /***********************************************
  3. * File : caldav.php
  4. * Project : PHP-Push
  5. * Descr : This backend is based on
  6. * 'BackendDiff' and implements an
  7. * CalDAV interface
  8. *
  9. * Created : 29.03.2012
  10. *
  11. * Copyright 2012 Jean-Louis Dupond
  12. ************************************************/
  13. include_once('lib/default/diffbackend/diffbackend.php');
  14. include_once('include/caldav-client-v2.php');
  15. include_once('include/z_RTF.php');
  16. include_once('iCalendar.php');
  17. class BackendCalDAV extends BackendDiff {
  18. private $_caldav;
  19. private $_caldav_path;
  20. private $_collection = array();
  21. private $_username;
  22. /**
  23. * Login to the CalDAV backend
  24. * @see IBackend::Logon()
  25. */
  26. public function Logon($username, $domain, $password)
  27. {
  28. $this->_username = $username;
  29. $this->_caldav_path = str_replace('%u', $username, CALDAV_PATH);
  30. $this->_caldav = new CalDAVClient(CALDAV_SERVER . ":" . CALDAV_PORT . $this->_caldav_path, $username, $password);
  31. $options = $this->_caldav->DoOptionsRequest();
  32. if (isset($options["PROPFIND"]))
  33. {
  34. ZLog::Write(LOGLEVEL_INFO, sprintf("BackendCalDAV->Logon(): User '%s' is authenticated on CalDAV", $username));
  35. return true;
  36. }
  37. else
  38. {
  39. ZLog::Write(LOGLEVEL_INFO, sprintf("BackendCalDAV->Logon(): User '%s' is not authenticated on CalDAV", $username));
  40. return false;
  41. }
  42. }
  43. /**
  44. * The connections to CalDAV are always directly closed. So nothing special needs to happen here.
  45. * @see IBackend::Logoff()
  46. */
  47. public function Logoff()
  48. {
  49. return true;
  50. }
  51. /**
  52. * CalDAV doesn't need to handle SendMail
  53. * @see IBackend::SendMail()
  54. */
  55. public function SendMail($sm)
  56. {
  57. return false;
  58. }
  59. /**
  60. * No attachments in CalDAV
  61. * @see IBackend::GetAttachmentData()
  62. */
  63. public function GetAttachmentData($attname)
  64. {
  65. return false;
  66. }
  67. /**
  68. * Deletes are always permanent deletes. Messages doesn't get moved.
  69. * @see IBackend::GetWasteBasket()
  70. */
  71. public function GetWasteBasket()
  72. {
  73. return false;
  74. }
  75. /**
  76. * Get a list of all the folders we are going to sync.
  77. * Each caldav calendar can contain tasks (prefix T) and events (prefix C), so duplicate each calendar found.
  78. * @see BackendDiff::GetFolderList()
  79. */
  80. public function GetFolderList()
  81. {
  82. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->GetFolderList(): Getting all folders."));
  83. $folders = array();
  84. $calendars = $this->_caldav->FindCalendars();
  85. foreach ($calendars as $val)
  86. {
  87. $folder = array();
  88. $fpath = explode("/", $val->url, -1);
  89. if (is_array($fpath))
  90. {
  91. $folderid = array_pop($fpath);
  92. $id = "C" . $folderid;
  93. $folders[] = $this->StatFolder($id);
  94. $id = "T" . $folderid;
  95. $folders[] = $this->StatFolder($id);
  96. }
  97. }
  98. return $folders;
  99. }
  100. /**
  101. * Returning a SyncFolder
  102. * @see BackendDiff::GetFolder()
  103. */
  104. public function GetFolder($id)
  105. {
  106. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->GetFolder('%s')", $id));
  107. $val = $this->_caldav->GetCalendarDetails($this->_caldav_path . substr($id, 1) . "/");
  108. $folder = new SyncFolder();
  109. $folder->parentid = "0";
  110. $folder->displayname = $val->displayname;
  111. $folder->serverid = $id;
  112. if ($id[0] == "C")
  113. {
  114. if (defined(CALDAV_PERSONAL) && strtolower(substr($id, 1) == CALDAV_PERSONAL))
  115. {
  116. $folder->type = SYNC_FOLDER_TYPE_USER_APPOINTMENT;
  117. }
  118. else
  119. {
  120. $folder->type = SYNC_FOLDER_TYPE_APPOINTMENT;
  121. }
  122. }
  123. else
  124. {
  125. if (defined(CALDAV_PERSONAL) && strtolower(substr($id, 1) == CALDAV_PERSONAL))
  126. {
  127. $folder->type = SYNC_FOLDER_TYPE_USER_TASK;
  128. }
  129. else
  130. {
  131. $folder->type = SYNC_FOLDER_TYPE_TASK;
  132. }
  133. }
  134. return $folder;
  135. }
  136. /**
  137. * Returns information on the folder.
  138. * @see BackendDiff::StatFolder()
  139. */
  140. public function StatFolder($id)
  141. {
  142. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->StatFolder('%s')", $id));
  143. $val = $this->GetFolder($id);
  144. $folder = array();
  145. $folder["id"] = $id;
  146. $folder["parent"] = $val->parentid;
  147. $folder["mod"] = $val->serverid;
  148. return $folder;
  149. }
  150. /**
  151. * ChangeFolder is not supported under CalDAV
  152. * @see BackendDiff::ChangeFolder()
  153. */
  154. public function ChangeFolder($folderid, $oldid, $displayname, $type)
  155. {
  156. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type));
  157. return false;
  158. }
  159. /**
  160. * DeleteFolder is not supported under CalDAV
  161. * @see BackendDiff::DeleteFolder()
  162. */
  163. public function DeleteFolder($id, $parentid)
  164. {
  165. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->DeleteFolder('%s','%s')", $id, $parentid));
  166. return false;
  167. }
  168. /**
  169. * Get a list of all the messages.
  170. * @see BackendDiff::GetMessageList()
  171. */
  172. public function GetMessageList($folderid, $cutoffdate)
  173. {
  174. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->GetMessageList('%s','%s')", $folderid, $cutoffdate));
  175. /* Calculating the range of events we want to sync */
  176. $begin = gmdate("Ymd\THis\Z", $cutoffdate);
  177. $finish = gmdate("Ymd\THis\Z", 2147483647);
  178. $path = $this->_caldav_path . substr($folderid, 1) . "/";
  179. if ($folderid[0] == "C")
  180. {
  181. $msgs = $this->_caldav->GetEvents($begin, $finish, $path);
  182. }
  183. else
  184. {
  185. $msgs = $this->_caldav->GetTodos($begin, $finish, false, false, $path);
  186. }
  187. $messages = array();
  188. foreach ($msgs as $e)
  189. {
  190. $id = $e['href'];
  191. $this->_collection[$id] = $e;
  192. $messages[] = $this->StatMessage($folderid, $id);
  193. }
  194. return $messages;
  195. }
  196. /**
  197. * Get a SyncObject by its ID
  198. * @see BackendDiff::GetMessage()
  199. */
  200. public function GetMessage($folderid, $id, $contentparameters)
  201. {
  202. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->GetMessage('%s','%s')", $folderid, $id));
  203. $data = $this->_collection[$id]['data'];
  204. if ($folderid[0] == "C")
  205. {
  206. return $this->_ParseVEventToAS($data, $contentparameters);
  207. }
  208. if ($folderid[0] == "T")
  209. {
  210. return $this->_ParseVTodoToAS($data, $contentparameters);
  211. }
  212. return false;
  213. }
  214. /**
  215. * Return id, flags and mod of a messageid
  216. * @see BackendDiff::StatMessage()
  217. */
  218. public function StatMessage($folderid, $id)
  219. {
  220. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->StatMessage('%s','%s')", $folderid, $id));
  221. $type = "VEVENT";
  222. if ($folderid[0] == "T")
  223. {
  224. $type = "VTODO";
  225. }
  226. $data = null;
  227. if (array_key_exists($id, $this->_collection))
  228. {
  229. $data = $this->_collection[$id];
  230. }
  231. else
  232. {
  233. $path = $this->_caldav_path . substr($folderid, 1) . "/";
  234. $e = $this->_caldav->GetEntryByUid(substr($id, 0, strlen($id)-4), $path, $type);
  235. if ($e == null && count($e) <= 0)
  236. return;
  237. $data = $e[0];
  238. }
  239. $message = array();
  240. $message['id'] = $data['href'];
  241. $message['flags'] = "1";
  242. $message['mod'] = $data['etag'];
  243. return $message;
  244. }
  245. /**
  246. * Change/Add a message with contents received from ActiveSync
  247. * @see BackendDiff::ChangeMessage()
  248. */
  249. public function ChangeMessage($folderid, $id, $message)
  250. {
  251. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->ChangeMessage('%s','%s')", $folderid, $id));
  252. if ($id)
  253. {
  254. $mod = $this->StatMessage($folderid, $id);
  255. $etag = $mod['mod'];
  256. }
  257. else
  258. {
  259. $etag = "*";
  260. $date = gmdate("Ymd\THis\Z");
  261. $random = hash("md5", microtime());
  262. $id = $date . "-" . $random . ".ics";
  263. }
  264. $data = $this->_ParseASToVCalendar($message, $folderid, substr($id, 0, strlen($id)-4));
  265. $url = $this->_caldav_path . substr($folderid, 1) . "/" . $id;
  266. $etag_new = $this->_caldav->DoPUTRequest($url, $data, $etag);
  267. $item = array();
  268. $item['href'] = $id;
  269. $item['etag'] = $etag_new;
  270. $item['data'] = $data;
  271. $this->_collection[$id] = $item;
  272. return $this->StatMessage($folderid, $id);
  273. }
  274. /**
  275. * Change the read flag is not supported.
  276. * @see BackendDiff::SetReadFlag()
  277. */
  278. public function SetReadFlag($folderid, $id, $flags)
  279. {
  280. return false;
  281. }
  282. /**
  283. * Delete a message from the CalDAV server.
  284. * @see BackendDiff::DeleteMessage()
  285. */
  286. public function DeleteMessage($folderid, $id)
  287. {
  288. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->DeleteMessage('%s','%s')", $folderid, $id));
  289. $url = $this->_caldav_path . substr($folderid, 1) . "/" . $id;
  290. $http_status_code = $this->_caldav->DoDELETERequest($url);
  291. if ($http_status_code == "204") {
  292. return true;
  293. }
  294. return false;
  295. }
  296. /**
  297. * Move a message is not supported by CalDAV.
  298. * @see BackendDiff::MoveMessage()
  299. */
  300. public function MoveMessage($folderid, $id, $newfolderid)
  301. {
  302. return false;
  303. }
  304. /**
  305. * Convert a iCAL VEvent to ActiveSync format
  306. * @param ical_vevent $data
  307. * @param ContentParameters $contentparameters
  308. * @return SyncAppointment
  309. */
  310. private function _ParseVEventToAS($data, $contentparameters)
  311. {
  312. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToAS(): Parsing VEvent"));
  313. $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
  314. $message = new SyncAppointment();
  315. $ical = new iCalComponent($data);
  316. $timezones = $ical->GetComponents("VTIMEZONE");
  317. $timezone = "";
  318. if (count($timezones) > 0)
  319. {
  320. $timezone = $this->_ParseTimezone($timezones[0]->GetPValue("TZID"));
  321. }
  322. if (!$timezone)
  323. {
  324. $timezone = date_default_timezone_get();
  325. }
  326. $message->timezone = $this->_GetTimezoneString($timezone);
  327. $vevents = $ical->GetComponents("VTIMEZONE", false);
  328. foreach ($vevents as $event)
  329. {
  330. $rec = $event->GetProperties("RECURRENCE-ID");
  331. if (count($rec) > 0)
  332. {
  333. $recurrence_id = reset($rec);
  334. $exception = new SyncAppointmentException();
  335. $tzid = $this->_ParseTimezone($recurrence_id->GetParameterValue("TZID"));
  336. if (!$tzid)
  337. {
  338. $tzid = $timezone;
  339. }
  340. $exception->exceptionstarttime = $this->_MakeUTCDate($recurrence_id->Value(), $tzid);
  341. $exception->deleted = "0";
  342. $exception = $this->_ParseVEventToSyncObject($event, $exception, $truncsize);
  343. if (!isset($message->exceptions))
  344. {
  345. $message->exceptions = array();
  346. }
  347. $message->exceptions[] = $exception;
  348. }
  349. else
  350. {
  351. $message = $this->_ParseVEventToSyncObject($event, $message, $truncsize);
  352. }
  353. }
  354. return $message;
  355. }
  356. /**
  357. * Parse 1 VEvent
  358. * @param ical_vevent $event
  359. * @param SyncAppointment(Exception) $message
  360. * @param int $truncsize
  361. */
  362. private function _ParseVEventToSyncObject($event, $message, $truncsize)
  363. {
  364. //Defaults
  365. $message->busystatus = "2";
  366. $properties = $event->GetProperties();
  367. // Find the start date first, to use as default for recurrences
  368. foreach ($properties as $property)
  369. {
  370. if ($property->Name()=="DTSTART")
  371. {
  372. $message->starttime = $this->_MakeUTCDate($property->Value(), $this->_ParseTimezone($property->GetParameterValue("TZID"))); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToSyncObject(): starttime ='%s'.",$message->starttime));
  373. }
  374. }
  375. foreach ($properties as $property)
  376. {
  377. switch ($property->Name())
  378. {
  379. case "LAST-MODIFIED":
  380. $message->dtstamp = $this->_MakeUTCDate($property->Value());
  381. break;
  382. case "DTSTART":
  383. $message->starttime = $this->_MakeUTCDate($property->Value(), $this->_ParseTimezone($property->GetParameterValue("TZID")));
  384. if (strlen($property->Value()) == 8)
  385. {
  386. $message->alldayevent = "1";
  387. }
  388. break;
  389. case "SUMMARY":
  390. $message->subject = $property->Value();
  391. break;
  392. case "UID":
  393. $message->uid = $property->Value();
  394. break;
  395. case "ORGANIZER":
  396. $org_mail = str_ireplace("MAILTO:", "", $property->Value());
  397. $message->organizeremail = $org_mail;
  398. $org_cn = $property->GetParameterValue("CN");
  399. if ($org_cn)
  400. {
  401. $message->organizername = $org_cn;
  402. }
  403. break;
  404. case "LOCATION":
  405. $message->location = $property->Value();
  406. break;
  407. case "DTEND":
  408. $message->endtime = $this->_MakeUTCDate($property->Value(), $this->_ParseTimezone($property->GetParameterValue("TZID")));
  409. if (strlen($property->Value()) == 8)
  410. {
  411. $message->alldayevent = "1";
  412. }
  413. break;
  414. case "RRULE":
  415. $message->recurrence = $this->_ParseRecurrence($property->Value(), "vevent", $message->starttime);
  416. break;
  417. case "CLASS":
  418. switch ($property->Value())
  419. {
  420. case "PUBLIC":
  421. $message->sensitivity = "0";
  422. break;
  423. case "PRIVATE":
  424. $message->sensitivity = "2";
  425. break;
  426. case "CONFIDENTIAL":
  427. $message->sensitivity = "3";
  428. break;
  429. }
  430. break;
  431. case "TRANSP":
  432. switch ($property->Value())
  433. {
  434. case "TRANSPARENT":
  435. $message->busystatus = "0";
  436. break;
  437. case "OPAQUE":
  438. $message->busystatus = "2";
  439. break;
  440. }
  441. break;
  442. case "STATUS":
  443. switch ($property->Value())
  444. {
  445. case "TENTATIVE":
  446. $message->meetingstatus = "1";
  447. break;
  448. case "CONFIRMED":
  449. $message->meetingstatus = "3";
  450. break;
  451. case "CANCELLED":
  452. $message->meetingstatus = "5";
  453. break;
  454. }
  455. break;
  456. case "ATTENDEE":
  457. $attendee = new SyncAttendee();
  458. $att_email = str_ireplace("MAILTO:", "", $property->Value());
  459. $attendee->email = $att_email;
  460. $att_cn = $property->GetParameterValue("CN");
  461. if ($att_cn)
  462. {
  463. $attendee->name = $att_cn;
  464. }
  465. if (isset($message->attendees) && is_array($message->attendees))
  466. {
  467. $message->attendees[] = $attendee;
  468. }
  469. else
  470. {
  471. $message->attendees = array($attendee);
  472. }
  473. break;
  474. case "DESCRIPTION":
  475. $body = $property->Value();
  476. // truncate body, if requested
  477. if(strlen($body) > $truncsize) {
  478. $body = Utils::Utf8_truncate($body, $truncsize);
  479. $message->bodytruncated = 1;
  480. } else {
  481. $body = $body;
  482. $message->bodytruncated = 0;
  483. }
  484. $body = str_replace("\n","\r\n", str_replace("\r","",$body));
  485. $message->body = $body;
  486. break;
  487. case "CATEGORIES":
  488. $categories = explode(",", $property->Value());
  489. $message->categories = $categories;
  490. break;
  491. case "EXDATE":
  492. $exception = new SyncAppointmentException();
  493. $exception->deleted = "1";
  494. $exception->exceptionstarttime = $this->_MakeUTCDate($property->Value());
  495. if (!isset($message->exceptions))
  496. {
  497. $message->exceptions = array();
  498. }
  499. $message->exceptions[] = $exception;
  500. break;
  501. //We can ignore the following
  502. case "PRIORITY":
  503. case "SEQUENCE":
  504. case "CREATED":
  505. case "DTSTAMP":
  506. case "X-MOZ-GENERATION":
  507. case "X-MOZ-LASTACK":
  508. case "X-LIC-ERROR":
  509. case "RECURRENCE-ID":
  510. break;
  511. default:
  512. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToSyncObject(): '%s' is not yet supported.", $property->Name()));
  513. }
  514. }
  515. $valarm = current($event->GetComponents("VALARM"));
  516. if ($valarm)
  517. {
  518. $properties = $valarm->GetProperties();
  519. foreach ($properties as $property)
  520. {
  521. if ($property->Name() == "TRIGGER")
  522. {
  523. $parameters = $property->Parameters();
  524. if (array_key_exists("VALUE", $parameters) && $parameters["VALUE"] == "DATE-TIME")
  525. {
  526. $trigger = date_create("@" . $this->_MakeUTCDate($property->Value()));
  527. $begin = date_create("@" . $message->starttime);
  528. $interval = date_diff($begin, $trigger);
  529. $message->reminder = $interval->format("%i") + $interval->format("%h") * 60 + $interval->format("%a") * 60 * 24;
  530. }
  531. elseif (!array_key_exists("VALUE", $parameters) || $parameters["VALUE"] == "DURATION")
  532. {
  533. $val = str_replace("-", "", $property->Value());
  534. $interval = new DateInterval($val);
  535. $message->reminder = $interval->format("%i") + $interval->format("%h") * 60 + $interval->format("%a") * 60 * 24;
  536. }
  537. }
  538. }
  539. }
  540. return $message;
  541. }
  542. /**
  543. * Parse a RRULE
  544. * @param string $rrulestr
  545. */
  546. private function _ParseRecurrence($rrulestr, $type, $startdate)
  547. {
  548. $recurrence = new SyncRecurrence();
  549. if ($type == "vtodo")
  550. {
  551. $recurrence = new SyncTaskRecurrence();
  552. }
  553. $rrules = explode(";", $rrulestr);
  554. foreach ($rrules as $rrule)
  555. {
  556. $rule = explode("=", $rrule);
  557. switch ($rule[0])
  558. {
  559. case "FREQ":
  560. switch ($rule[1])
  561. {
  562. case "DAILY":
  563. $recurrence->type = "0";
  564. break;
  565. case "WEEKLY":
  566. $recurrence->type = "1";
  567. // Set Defaults to avoid "Malformed Recurrence" errors in some clients where the
  568. // INTERVAL, UNTIL, and BYDAY values are required
  569. $recurrence->interval = "1";
  570. $recurrence->until = $this->_MakeUTCDate("20380119");
  571. $day = strtoupper(substr(DateTime::createFromFormat("U",$startdate)->Format("D"),0,2));
  572. $dval = 0;
  573. switch ($day)
  574. {
  575. // 1 = Sunday
  576. // 2 = Monday
  577. // 4 = Tuesday
  578. // 8 = Wednesday
  579. // 16 = Thursday
  580. // 32 = Friday
  581. // 64 = Saturday
  582. case "SU":
  583. $dval += 1;
  584. break;
  585. case "MO":
  586. $dval += 2;
  587. break;
  588. case "TU":
  589. $dval += 4;
  590. break;
  591. case "WE":
  592. $dval += 8;
  593. break;
  594. case "TH":
  595. $dval += 16;
  596. break;
  597. case "FR":
  598. $dval += 32;
  599. break;
  600. case "SA":
  601. $dval += 64;
  602. break;
  603. }
  604. $recurrence->dayofweek = $dval;
  605. break;
  606. case "MONTHLY":
  607. $recurrence->type = "2";
  608. // Set Defaults to avoid "Malformed Recurrence" errors in some clients where the
  609. // INTERVAL, UNTIL, and BYMONTHDAY values are required
  610. $recurrence->interval = "1";
  611. $recurrence->until = $this->_MakeUTCDate("20380119");
  612. $day = DateTime::createFromFormat("U",$startdate)->Format("j");
  613. $recurrence->dayofmonth = $day;
  614. break;
  615. case "YEARLY":
  616. $recurrence->type = "5";
  617. // Set Defaults to avoid "Malformed Recurrence" errors in some clients where the
  618. // INTERVAL, UNTIL, BYMONTHDAY, and BYMONTH values are required
  619. $recurrence->interval = "1";
  620. $recurrence->until = $this->_MakeUTCDate("20380119");
  621. $day = DateTime::createFromFormat("U",$startdate)->Format("j");
  622. $recurrence->dayofmonth = $day;
  623. $day = DateTime::createFromFormat("U",$startdate)->Format("n");
  624. $recurrence->monthofyear = $day;
  625. }
  626. break;
  627. case "UNTIL":
  628. $recurrence->until = $this->_MakeUTCDate($rule[1]);
  629. break;
  630. case "COUNT":
  631. $recurrence->occurrences = $rule[1];
  632. break;
  633. case "INTERVAL":
  634. $recurrence->interval = $rule[1];
  635. break;
  636. case "BYDAY":
  637. $dval = 0;
  638. $days = explode(",", $rule[1]);
  639. foreach ($days as $day)
  640. {
  641. switch ($day)
  642. {
  643. // 1 = Sunday
  644. // 2 = Monday
  645. // 4 = Tuesday
  646. // 8 = Wednesday
  647. // 16 = Thursday
  648. // 32 = Friday
  649. // 62 = Weekdays // not in spec: daily weekday recurrence
  650. // 64 = Saturday
  651. case "SU":
  652. $dval += 1;
  653. break;
  654. case "MO":
  655. $dval += 2;
  656. break;
  657. case "TU":
  658. $dval += 4;
  659. break;
  660. case "WE":
  661. $dval += 8;
  662. break;
  663. case "TH":
  664. $dval += 16;
  665. break;
  666. case "FR":
  667. $dval += 32;
  668. break;
  669. case "SA":
  670. $dval += 64;
  671. break;
  672. }
  673. }
  674. $recurrence->dayofweek = $dval;
  675. break;
  676. //Only 1 BYMONTHDAY is supported, so BYMONTHDAY=2,3 will only include 2
  677. case "BYMONTHDAY":
  678. $days = explode(",", $rule[1]);
  679. $recurrence->dayofmonth = $days[0];
  680. break;
  681. case "BYMONTH":
  682. $recurrence->monthofyear = $rule[1];
  683. break;
  684. default:
  685. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseRecurrence(): '%s' is not yet supported.", $rule[0]));
  686. }
  687. }
  688. return $recurrence;
  689. }
  690. /**
  691. * Generate a iCAL VCalendar from ActiveSync object.
  692. * @param string $data
  693. * @param string $folderid
  694. * @param string $id
  695. */
  696. private function _ParseASToVCalendar($data, $folderid, $id)
  697. {
  698. $ical = new iCalComponent();
  699. $ical->SetType("VCALENDAR");
  700. $ical->AddProperty("VERSION", "2.0");
  701. $ical->AddProperty("PRODID", "-//php-push//NONSGML PHP-Push Calendar//EN");
  702. $ical->AddProperty("CALSCALE", "GREGORIAN");
  703. if ($folderid[0] == "C")
  704. {
  705. $vevent = $this->_ParseASEventToVEvent($data, $id);
  706. $vevent->AddProperty("UID", $id);
  707. $ical->AddComponent($vevent);
  708. if (isset($data->exceptions) && is_array($data->exceptions))
  709. {
  710. foreach ($data->exceptions as $ex)
  711. {
  712. $exception = $this->_ParseASEventToVEvent($ex, $id);
  713. if ($data->alldayevent == 1)
  714. {
  715. $exception->AddProperty("RECURRENCE-ID", $this->_GetDateFromUTC("Ymd", $ex->exceptionstarttime, $data->timezone), array("VALUE" => "DATE"));
  716. }
  717. else
  718. {
  719. $exception->AddProperty("RECURRENCE-ID", gmdate("Ymd\THis\Z", $ex->exceptionstarttime));
  720. }
  721. $exception->AddProperty("UID", $id);
  722. $ical->AddComponent($exception);
  723. }
  724. }
  725. }
  726. if ($folderid[0] == "T")
  727. {
  728. $vtodo = $this->_ParseASTaskToVTodo($data, $id);
  729. $vtodo->AddProperty("UID", $id);
  730. $vtodo->AddProperty("DTSTAMP", gmdate("Ymd\THis\Z"));
  731. $ical->AddComponent($vtodo);
  732. }
  733. return $ical->Render();
  734. }
  735. /**
  736. * Generate a VEVENT from a SyncAppointment(Exception).
  737. * @param string $data
  738. * @param string $id
  739. * @return iCalComponent
  740. */
  741. private function _ParseASEventToVEvent($data, $id)
  742. {
  743. $vevent = new iCalComponent();
  744. $vevent->SetType("VEVENT");
  745. if (isset($data->dtstamp))
  746. {
  747. $vevent->AddProperty("DTSTAMP", gmdate("Ymd\THis\Z", $data->dtstamp));
  748. $vevent->AddProperty("LAST-MODIFIED", gmdate("Ymd\THis\Z", $data->dtstamp));
  749. }
  750. if (isset($data->starttime))
  751. {
  752. if ($data->alldayevent == 1)
  753. {
  754. $vevent->AddProperty("DTSTART", $this->_GetDateFromUTC("Ymd", $data->starttime, $data->timezone), array("VALUE" => "DATE"));
  755. }
  756. else
  757. {
  758. $vevent->AddProperty("DTSTART", gmdate("Ymd\THis\Z", $data->starttime));
  759. }
  760. }
  761. if (isset($data->subject))
  762. {
  763. $vevent->AddProperty("SUMMARY", $data->subject);
  764. }
  765. if (isset($data->organizeremail))
  766. {
  767. if (isset($data->organizername))
  768. {
  769. $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $data->organizeremail), array("CN" => $data->organizername));
  770. }
  771. else
  772. {
  773. $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $data->organizeremail));
  774. }
  775. }
  776. if (isset($data->location))
  777. {
  778. $vevent->AddProperty("LOCATION", $data->location);
  779. }
  780. if (isset($data->endtime))
  781. {
  782. if ($data->alldayevent == 1)
  783. {
  784. $vevent->AddProperty("DTEND", $this->_GetDateFromUTC("Ymd", $data->endtime, $data->timezone), array("VALUE" => "DATE"));
  785. }
  786. else
  787. {
  788. $vevent->AddProperty("DTEND", gmdate("Ymd\THis\Z", $data->endtime));
  789. }
  790. }
  791. if (isset($data->recurrence))
  792. {
  793. $vevent->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence));
  794. }
  795. if (isset($data->sensitivity))
  796. {
  797. switch ($data->sensitivity)
  798. {
  799. case "0":
  800. $vevent->AddProperty("CLASS", "PUBLIC");
  801. break;
  802. case "2":
  803. $vevent->AddProperty("CLASS", "PRIVATE");
  804. break;
  805. case "3":
  806. $vevent->AddProperty("CLASS", "CONFIDENTIAL");
  807. break;
  808. }
  809. }
  810. if (isset($data->busystatus))
  811. {
  812. switch ($data->busystatus)
  813. {
  814. case "0":
  815. case "1":
  816. $vevent->AddProperty("TRANSP", "TRANSPARENT");
  817. break;
  818. case "2":
  819. case "3":
  820. $vevent->AddProperty("TRANSP", "OPAQUE");
  821. break;
  822. }
  823. }
  824. if (isset($data->reminder))
  825. {
  826. $valarm = new iCalComponent();
  827. $valarm->SetType("VALARM");
  828. $valarm->AddProperty("ACTION", "DISPLAY");
  829. $trigger = "-PT" . $data->reminder . "M";
  830. $valarm->AddProperty("TRIGGER", $trigger);
  831. $vevent->AddComponent($valarm);
  832. }
  833. if (isset($data->rtf))
  834. {
  835. $rtfparser = new rtf();
  836. $rtfparser->loadrtf(base64_decode($data->rtf));
  837. $rtfparser->output("ascii");
  838. $rtfparser->parse();
  839. $vevent->AddProperty("DESCRIPTION", $rtfparser->out);
  840. }
  841. if (isset($data->meetingstatus))
  842. {
  843. switch ($data->meetingstatus)
  844. {
  845. case "1":
  846. $vevent->AddProperty("STATUS", "TENTATIVE");
  847. break;
  848. case "3":
  849. $vevent->AddProperty("STATUS", "CONFIRMED");
  850. break;
  851. case "5":
  852. case "7":
  853. $vevent->AddProperty("STATUS", "CANCELLED");
  854. break;
  855. }
  856. }
  857. if (isset($data->attendees) && is_array($data->attendees))
  858. {
  859. //If there are attendees, we need to set ORGANIZER
  860. //Some phones doesn't send the organizeremail, so we gotto get it somewhere else.
  861. //Lets use the login here ($username)
  862. if (!isset($data->organizeremail))
  863. {
  864. $vevent->AddProperty("ORGANIZER", sprintf("MAILTO:%s", $this->_username));
  865. }
  866. foreach ($data->attendees as $att)
  867. {
  868. $att_str = sprintf("MAILTO:%s", $att->email);
  869. $vevent->AddProperty("ATTENDEE", $att_str, array("CN" => $att->name));
  870. }
  871. }
  872. if (isset($data->body))
  873. {
  874. $vevent->AddProperty("DESCRIPTION", $data->body);
  875. }
  876. if (isset($data->categories) && is_array($data->categories))
  877. {
  878. $vevent->AddProperty("CATEGORIES", implode(",", $data->categories));
  879. }
  880. return $vevent;
  881. }
  882. /**
  883. * Generate Recurrence
  884. * @param string $rec
  885. */
  886. private function _GenerateRecurrence($rec)
  887. {
  888. $rrule = array();
  889. if (isset($rec->type))
  890. {
  891. $freq = "";
  892. switch ($rec->type)
  893. {
  894. case "0":
  895. $freq = "DAILY";
  896. break;
  897. case "1":
  898. $freq = "WEEKLY";
  899. break;
  900. case "2":
  901. $freq = "MONTHLY";
  902. break;
  903. case "5":
  904. $freq = "YEARLY";
  905. break;
  906. }
  907. $rrule[] = "FREQ=" . $freq;
  908. }
  909. if (isset($rec->until))
  910. {
  911. $rrule[] = "UNTIL=" . gmdate("Ymd\THis\Z", $rec->until);
  912. }
  913. if (isset($rec->occurrences))
  914. {
  915. $rrule[] = "COUNT=" . $rec->occurrences;
  916. }
  917. if (isset($rec->interval))
  918. {
  919. $rrule[] = "INTERVAL=" . $rec->interval;
  920. }
  921. if (isset($rec->dayofweek))
  922. {
  923. $days = array();
  924. if (($rec->dayofweek & 1) == 1)
  925. {
  926. $days[] = "SU";
  927. }
  928. if (($rec->dayofweek & 2) == 2)
  929. {
  930. $days[] = "MO";
  931. }
  932. if (($rec->dayofweek & 4) == 4)
  933. {
  934. $days[] = "TU";
  935. }
  936. if (($rec->dayofweek & 8) == 8)
  937. {
  938. $days[] = "WE";
  939. }
  940. if (($rec->dayofweek & 16) == 16)
  941. {
  942. $days[] = "TH";
  943. }
  944. if (($rec->dayofweek & 32) == 32)
  945. {
  946. $days[] = "FR";
  947. }
  948. if (($rec->dayofweek & 64) == 64)
  949. {
  950. $days[] = "SA";
  951. }
  952. $rrule[] = "BYDAY=" . implode(",", $days);
  953. }
  954. if (isset($rec->dayofmonth))
  955. {
  956. $rrule[] = "BYMONTHDAY=" . $rec->dayofmonth;
  957. }
  958. if (isset($rec->monthofyear))
  959. {
  960. $rrule[] = "BYMONTH=" . $rec->monthofyear;
  961. }
  962. return implode(";", $rrule);
  963. }
  964. /**
  965. * Convert a iCAL VTodo to ActiveSync format
  966. * @param string $data
  967. * @param ContentParameters $contentparameters
  968. */
  969. private function _ParseVTodoToAS($data, $contentparameters)
  970. {
  971. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVTodoToAS(): Parsing VTodo"));
  972. $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation());
  973. $message = new SyncTask();
  974. $ical = new iCalComponent($data);
  975. $vtodos = $ical->GetComponents("VTODO");
  976. //Should only loop once
  977. foreach ($vtodos as $vtodo)
  978. {
  979. $message = $this->_ParseVTodoToSyncObject($vtodo, $message, $truncsize);
  980. }
  981. return $message;
  982. }
  983. /**
  984. * Parse 1 VEvent
  985. * @param ical_vtodo $vtodo
  986. * @param SyncAppointment(Exception) $message
  987. * @param int $truncsize
  988. */
  989. private function _ParseVTodoToSyncObject($vtodo, $message, $truncsize)
  990. {
  991. //Default
  992. $message->reminderset = "0";
  993. $message->importance = "1";
  994. $message->complete = "0";
  995. $properties = $vtodo->GetProperties();
  996. // Find the start date first, to use as default for recurrences
  997. foreach ($properties as $property)
  998. {
  999. if ($property->Name()=="DTSTART")
  1000. {
  1001. $message->starttime = $this->_MakeUTCDate($property->Value(), $this->_ParseTimezone($property->GetParameterValue("TZID"))); ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_ParseVEventToSyncObject(): starttime ='%s'.",$message->starttime));
  1002. }
  1003. }
  1004. foreach ($properties as $property)
  1005. {
  1006. switch ($property->Name())
  1007. {
  1008. case "SUMMARY":
  1009. $message->subject = $property->Value();
  1010. break;
  1011. case "STATUS":
  1012. switch ($property->Value())
  1013. {
  1014. case "NEEDS-ACTION":
  1015. case "IN-PROCESS":
  1016. $message->complete = "0";
  1017. break;
  1018. case "COMPLETED":
  1019. case "CANCELLED":
  1020. $message->complete = "1";
  1021. break;
  1022. }
  1023. break;
  1024. case "COMPLETED":
  1025. $message->datecompleted = $this->_MakeUTCDate($property->Value());
  1026. break;
  1027. case "DUE":
  1028. $message->utcduedate = $this->_MakeUTCDate($property->Value());
  1029. break;
  1030. case "PRIORITY":
  1031. $priority = $property->Value();
  1032. if ($priority <= 3)
  1033. $message->importance = "0";
  1034. if ($priority <= 6)
  1035. $message->importance = "1";
  1036. if ($priority > 6)
  1037. $message->importance = "2";
  1038. break;
  1039. case "RRULE":
  1040. $message->recurrence = $this->_ParseRecurrence($property->Value(), "vtodo",$message->starttime);
  1041. break;
  1042. case "CLASS":
  1043. switch ($property->Value())
  1044. {
  1045. case "PUBLIC":
  1046. $message->sensitivity = "0";
  1047. break;
  1048. case "PRIVATE":
  1049. $message->sensitivity = "2";
  1050. break;
  1051. case "CONFIDENTIAL":
  1052. $message->sensitivity = "3";
  1053. break;
  1054. }
  1055. break;
  1056. case "DTSTART":
  1057. $message->utcstartdate = $this->_MakeUTCDate($property->Value());
  1058. break;
  1059. case "SUMMARY":
  1060. $message->subject = $property->Value();
  1061. break;
  1062. case "CATEGORIES":
  1063. $categories = explode(",", $property->Value());
  1064. $message->categories = $categories;
  1065. break;
  1066. }
  1067. }
  1068. if (isset($message->recurrence))
  1069. {
  1070. $message->recurrence->start = $message->utcstartdate;
  1071. }
  1072. $valarm = current($vtodo->GetComponents("VALARM"));
  1073. if ($valarm)
  1074. {
  1075. $properties = $valarm->GetProperties();
  1076. foreach ($properties as $property)
  1077. {
  1078. if ($property->Name() == "TRIGGER")
  1079. {
  1080. $parameters = $property->Parameters();
  1081. if (array_key_exists("VALUE", $parameters) && $parameters["VALUE"] == "DATE-TIME")
  1082. {
  1083. $message->remindertime = $this->_MakeUTCDate($property->Value());
  1084. $message->reminderset = "1";
  1085. }
  1086. elseif (!array_key_exists("VALUE", $parameters) || $parameters["VALUE"] == "DURATION")
  1087. {
  1088. $val = str_replace("-", "", $property->Value());
  1089. $interval = new DateInterval($val);
  1090. $start = date_create("@" . $message->utcstartdate);
  1091. $message->remindertime = date_timestamp_get(date_sub($start, $interval));
  1092. $message->reminderset = "1";
  1093. }
  1094. }
  1095. }
  1096. }
  1097. return $message;
  1098. }
  1099. /**
  1100. * Generate a VTODO from a SyncAppointment(Exception)
  1101. * @param string $data
  1102. * @param string $id
  1103. * @return iCalComponent
  1104. */
  1105. private function _ParseASTaskToVTodo($data, $id)
  1106. {
  1107. $vtodo = new iCalComponent();
  1108. $vtodo->SetType("VTODO");
  1109. if (isset($data->body))
  1110. {
  1111. $vtodo->AddProperty("DESCRIPTION", $data->body);
  1112. }
  1113. if (isset($data->complete))
  1114. {
  1115. if ($data->complete == "0")
  1116. {
  1117. $vtodo->AddProperty("STATUS", "NEEDS-ACTION");
  1118. }
  1119. else
  1120. {
  1121. $vtodo->AddProperty("STATUS", "COMPLETED");
  1122. }
  1123. }
  1124. if (isset($data->datecompleted))
  1125. {
  1126. $vtodo->AddProperty("COMPLETED", gmdate("Ymd\THis\Z", $data->datecompleted));
  1127. }
  1128. if ($data->utcduedate)
  1129. {
  1130. $vtodo->AddProperty("DUE", gmdate("Ymd\THis\Z", $data->utcduedate));
  1131. }
  1132. if (isset($data->importance))
  1133. {
  1134. if ($data->importance == "1")
  1135. {
  1136. $vtodo->AddProperty("PRIORITY", 6);
  1137. }
  1138. elseif ($data->importance == "2")
  1139. {
  1140. $vtodo->AddProperty("PRIORITY", 9);
  1141. }
  1142. else
  1143. {
  1144. $vtodo->AddProperty("PRIORITY", 1);
  1145. }
  1146. }
  1147. if (isset($data->recurrence))
  1148. {
  1149. $vtodo->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence));
  1150. }
  1151. if ($data->reminderset && $data->remindertime)
  1152. {
  1153. $valarm = new iCalComponent();
  1154. $valarm->SetType("VALARM");
  1155. $valarm->AddProperty("ACTION", "DISPLAY");
  1156. $valarm->AddProperty("TRIGGER;VALUE=DATE-TIME", gmdate("Ymd\THis\Z", $data->remindertime));
  1157. $vtodo->AddComponent($valarm);
  1158. }
  1159. if (isset($data->sensitivity))
  1160. {
  1161. switch ($data->sensitivity)
  1162. {
  1163. case "0":
  1164. $vtodo->AddProperty("CLASS", "PUBLIC");
  1165. break;
  1166. case "2":
  1167. $vtodo->AddProperty("CLASS", "PRIVATE");
  1168. break;
  1169. case "3":
  1170. $vtodo->AddProperty("CLASS", "CONFIDENTIAL");
  1171. break;
  1172. }
  1173. }
  1174. if (isset($data->utcstartdate))
  1175. {
  1176. $vtodo->AddProperty("DTSTART", gmdate("Ymd\THis\Z", $data->utcstartdate));
  1177. }
  1178. if (isset($data->subject))
  1179. {
  1180. $vtodo->AddProperty("SUMMARY", $data->subject);
  1181. }
  1182. if (isset($data->rtf))
  1183. {
  1184. $rtfparser = new rtf();
  1185. $rtfparser->loadrtf(base64_decode($data->rtf));
  1186. $rtfparser->output("ascii");
  1187. $rtfparser->parse();
  1188. $vevent->AddProperty("DESCRIPTION", $rtfparser->out);
  1189. }
  1190. if (isset($data->categories) && is_array($data->categories))
  1191. {
  1192. $vtodo->AddProperty("CATEGORIES", implode(",", $data->categories));
  1193. }
  1194. return $vtodo;
  1195. }
  1196. /**
  1197. * Generate date object from string and timezone.
  1198. * @param string $value
  1199. * @param string $timezone
  1200. */
  1201. private function _MakeUTCDate($value, $timezone = null)
  1202. {
  1203. $tz = null;
  1204. if ($timezone)
  1205. {
  1206. $tz = timezone_open($timezone);
  1207. }
  1208. if (!$tz)
  1209. {
  1210. //If there is no timezone set, we use the default timezone
  1211. $tz = timezone_open(date_default_timezone_get());
  1212. }
  1213. //20110930T090000Z
  1214. $date = date_create_from_format('Ymd\THis\Z', $value, timezone_open("UTC"));
  1215. if (!$date)
  1216. {
  1217. //20110930T090000
  1218. $date = date_create_from_format('Ymd\THis', $value, $tz);
  1219. }
  1220. if (!$date)
  1221. {
  1222. //20110930 (Append T000000Z to the date, so it starts at midnight)
  1223. $date = date_create_from_format('Ymd\THis\Z', $value . "T000000Z", $tz);
  1224. }
  1225. return date_timestamp_get($date);
  1226. }
  1227. private function _GetDateFromUTC($format, $date, $tz_str)
  1228. {
  1229. $timezone = $this->_GetTimezoneFromString($tz_str);
  1230. $dt = date_create('@' . $date);
  1231. date_timezone_set($dt, timezone_open($timezone));
  1232. return date_format($dt, $format);
  1233. }
  1234. /**
  1235. * Generate a tzid from various formats
  1236. * @param str $timezone
  1237. * @return timezone id
  1238. */
  1239. private function _ParseTimezone($timezone)
  1240. {
  1241. //(GMT+01.00) Amsterdam / Berlin / Bern / Rome / Stockholm / Vienna
  1242. if (preg_match('/GMT(\\+|\\-)0(\d)/', $timezone, $matches))
  1243. {
  1244. return "Etc/GMT" . $matches[1] . $matches[2];
  1245. }
  1246. //(GMT+10.00) XXX / XXX / XXX / XXX
  1247. if (preg_match('/GMT(\\+|\\-)1(\d)/', $timezone, $matches))
  1248. {
  1249. return "Etc/GMT" . $matches[1] . "1" . $matches[2];
  1250. }
  1251. ///inverse.ca/20101018_1/Europe/Amsterdam or /inverse.ca/20101018_1/America/Argentina/Buenos_Aires
  1252. if (preg_match('/\/[.[:word:]]+\/\w+\/(\w+)\/([\w\/]+)/', $timezone, $matches))
  1253. {
  1254. return $matches[1] . "/" . $matches[2];
  1255. }
  1256. return trim($timezone, '"');
  1257. }
  1258. //This returns a timezone that matches the timezonestring.
  1259. //We can't be sure this is the one you chose, as multiple timezones have same timezonestring
  1260. private function _GetTimezoneFromString($tz_string)
  1261. {
  1262. //Get a list of all timezones
  1263. $identifiers = DateTimeZone::listIdentifiers();
  1264. //Try the default timezone first
  1265. array_unshift($identifiers, date_default_timezone_get());
  1266. foreach ($identifiers as $tz)
  1267. {
  1268. $str = $this->_GetTimezoneString($tz, false);
  1269. if ($str == $tz_string)
  1270. {
  1271. ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendCalDAV->_GetTimezoneFromString(): Found timezone: '%s'.", $tz));
  1272. return $tz;
  1273. }
  1274. }
  1275. return date_default_timezone_get();
  1276. }
  1277. /**
  1278. * Generate ActiveSync Timezone Packed String.
  1279. * @param string $timezone
  1280. * @param string $with_names
  1281. * @throws Exception
  1282. */
  1283. private function _GetTimezoneString($timezone, $with_names = true)
  1284. {
  1285. // UTC needs special handling
  1286. if ($timezone == "UTC")
  1287. return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0));
  1288. try {
  1289. //Generate a timezone string (PHP 5.3 needed for this)
  1290. $timezone = new DateTimeZone($timezone);
  1291. $trans = $timezone->getTransitions(time());
  1292. $stdTime = null;
  1293. $dstTime = null;
  1294. if (count($trans) < 3)
  1295. {
  1296. throw new Exception();
  1297. }
  1298. if ($trans[1]['isdst'] == 1)
  1299. {
  1300. $dstTime = $trans[1];
  1301. $stdTime = $trans[2];
  1302. }
  1303. else
  1304. {
  1305. $dstTime = $trans[2];
  1306. $stdTime = $trans[1];
  1307. }
  1308. $stdTimeO = new DateTime($stdTime['time']);
  1309. $stdFirst = new DateTime(sprintf("first sun of %s %s", $stdTimeO->format('F'), $stdTimeO->format('Y')), timezone_open("UTC"));
  1310. $stdBias = $stdTime['offset'] / -60;
  1311. $stdName = $stdTime['abbr'];
  1312. $stdYear = 0;
  1313. $stdMonth = $stdTimeO->format('n');
  1314. $stdWeek = floor(($stdTimeO->format("j")-$stdFirst->format("j"))/7)+1;
  1315. $stdDay = $stdTimeO->format('w');
  1316. $stdHour = $stdTimeO->format('H');
  1317. $stdMinute = $stdTimeO->format('i');
  1318. $stdTimeO->add(new DateInterval('P7D'));
  1319. if ($stdTimeO->format('n') != $stdMonth)
  1320. {
  1321. $stdWeek = 5;
  1322. }
  1323. $dstTimeO = new DateTime($dstTime['time']);
  1324. $dstFirst = new DateTime(sprintf("first sun of %s %s", $dstTimeO->format('F'), $dstTimeO->format('Y')), timezone_open("UTC"));
  1325. $dstName = $dstTime['abbr'];
  1326. $dstYear = 0;
  1327. $dstMonth = $dstTimeO->format('n');
  1328. $dstWeek = floor(($dstTimeO->format("j")-$dstFirst->format("j"))/7)+1;
  1329. $dstDay = $dstTimeO->format('w');
  1330. $dstHour = $dstTimeO->format('H');
  1331. $dstMinute = $dstTimeO->format('i');
  1332. $dstTimeO->add(new DateInterval('P7D'));
  1333. if ($dstTimeO->format('n') != $dstMonth)
  1334. {
  1335. $dstWeek = 5;
  1336. }
  1337. $dstBias = ($dstTime['offset'] - $stdTime['offset']) / -60;
  1338. if ($with_names)
  1339. {
  1340. return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', $stdBias, $stdName, 0, $stdMonth, $stdDay, $stdWeek, $stdHour, $stdMinute, 0, 0, 0, $dstName, 0, $dstMonth, $dstDay, $dstWeek, $dstHour, $dstMinute, 0, 0, $dstBias));
  1341. }
  1342. else
  1343. {
  1344. return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', $stdBias, '', 0, $stdMonth, $stdDay, $stdWeek, $stdHour, $stdMinute, 0, 0, 0, '', 0, $dstMonth, $dstDay, $dstWeek, $dstHour, $dstMinute, 0, 0, $dstBias));
  1345. }
  1346. }
  1347. catch (Exception $e) {
  1348. // If invalid timezone is given, we return UTC
  1349. return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0));
  1350. }
  1351. return base64_encode(pack('la64vvvvvvvvla64vvvvvvvvl', 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 0, 0, 0, 0, 0, 0, 0, 0));
  1352. }
  1353. }
  1354. ?>