Christoph Haas
fb426098d6
Dynamic timezone-change preview =) TODO: - complete timezone mapping list! - check exporter timezone management
346 lines
13 KiB
PHP
346 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* class.calendarexporter.php, zarafa calender to ics exporter
|
|
*
|
|
* Author: Christoph Haas <christoph.h@sprinternet.at>
|
|
* Copyright (C) 2012 Christoph Haas
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
include_once('mapi/class.recurrence.php');
|
|
include_once('plugins/calendarimporter/php/ical/class.icalcreator.php');
|
|
|
|
class CalendarexporterModule extends Module {
|
|
|
|
private $DEBUG = false; // enable error_log debugging
|
|
|
|
/**
|
|
* @constructor
|
|
* @param $id
|
|
* @param $data
|
|
*/
|
|
public function __construct($id, $data) {
|
|
parent::Module($id, $data);
|
|
}
|
|
|
|
/**
|
|
* Executes all the actions in the $data variable.
|
|
* Exception part is used for authentication errors also
|
|
* @return boolean true on success or false on failure.
|
|
*/
|
|
public function execute() {
|
|
$result = false;
|
|
|
|
foreach($this->data as $actionType => $actionData) {
|
|
if(isset($actionType)) {
|
|
try {
|
|
if($this->DEBUG) {
|
|
error_log("exec: " . $actionType);
|
|
}
|
|
switch($actionType) {
|
|
case "export":
|
|
$result = $this->exportCalendar($actionType, $actionData);
|
|
break;
|
|
default:
|
|
$this->handleUnknownActionType($actionType);
|
|
}
|
|
|
|
} catch (MAPIException $e) {
|
|
if($this->DEBUG) {
|
|
error_log("mapi exception: " . $e->getMessage());
|
|
}
|
|
} catch (Exception $e) {
|
|
if($this->DEBUG) {
|
|
error_log("exception: " . $e->getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function randomstring($length = 6) {
|
|
// $chars - all allowed charakters
|
|
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
|
|
srand((double)microtime()*1000000);
|
|
$i = 0;
|
|
while ($i < $length) {
|
|
$num = rand() % strlen($chars);
|
|
$tmp = substr($chars, $num, 1);
|
|
$pass = $pass . $tmp;
|
|
$i++;
|
|
}
|
|
return $pass;
|
|
}
|
|
|
|
private function createSecIDFile($secid) {
|
|
$lockFile = TMP_PATH . "/secid." . $secid;
|
|
$fh = fopen($lockFile, 'w') or die("can't open secid file");
|
|
$stringData = date(DATE_RFC822);
|
|
fwrite($fh, $stringData);
|
|
fclose($fh);
|
|
}
|
|
|
|
private function getIcalDate($time, $incl_time = true) {
|
|
return $incl_time ? date('Ymd\THis', $time) : date('Ymd', $time);
|
|
}
|
|
|
|
private function addEvent(&$vevent, $event) {
|
|
|
|
$busystate = array("FREE", "TENTATIVE", "BUSY", "OOF");
|
|
$zlabel = array("NONE", "IMPORTANT", "WORK", "PERSONAL", "HOLIDAY", "REQUIRED", "TRAVEL REQUIRED", "PREPARATION REQUIERED", "BIRTHDAY", "SPECIAL DATE", "PHONE INTERVIEW");
|
|
|
|
$vevent->setProperty("LOCATION", $event["location"]); // property name - case independent
|
|
$vevent->setProperty("SUMMARY", $event["subject"]);
|
|
$vevent->setProperty("DESCRIPTION", str_replace("\n", "\\n",$event["description"]));
|
|
$vevent->setProperty("COMMENT", "Exported from Zarafa" );
|
|
$vevent->setProperty("ORGANIZER", $event["sent_representing_email_address"]);
|
|
$vevent->setProperty("DTSTART", $this->getIcalDate($event["commonstart"]) . "Z");
|
|
$vevent->setProperty("DTEND", $this->getIcalDate($event["commonend"]) . "Z");
|
|
$vevent->setProperty("DTSTAMP", $this->getIcalDate($event["creation_time"]) . "Z");
|
|
$vevent->setProperty("CREATED", $this->getIcalDate($event["creation_time"]) . "Z");
|
|
$vevent->setProperty("LAST-MODIFIED", $this->getIcalDate($event["last_modification_time"]) . "Z");
|
|
$vevent->setProperty("X-MICROSOFT-CDO-BUSYSTATUS", $busystate[$event["busystatus"]]);
|
|
$vevent->setProperty("X-ZARAFA-LABEL", $zlabel[$event["label"]]);
|
|
$vevent->setProperty("PRIORITY", $event["importance"]);
|
|
$vevent->setProperty("CLASS", $event["private"] ? "PRIVATE" : "PUBLIC");
|
|
|
|
// ATTENDEES
|
|
if(count($event["attendees"]) > 0) {
|
|
foreach($event["attendees"] as $attendee) {
|
|
$vevent->setProperty("ATTENDEE", $attendee["props"]["smtp_address"]);
|
|
}
|
|
}
|
|
|
|
// REMINDERS
|
|
if($event["reminder"]) {
|
|
$valarm = & $vevent->newComponent("valarm"); // create an event alarm
|
|
$valarm->setProperty("action", "DISPLAY" );
|
|
$valarm->setProperty("description", $vevent->getProperty("SUMMARY")); // reuse the event summary
|
|
$valarm->setProperty("trigger", $this->getIcalDate($event["reminder_time"]) . "Z"); // create alarm trigger (in UTC datetime)
|
|
}
|
|
|
|
|
|
}
|
|
|
|
private function loadEventDescription($event) {
|
|
$entryid = $this->getActionEntryID($event);
|
|
$store = $this->getActionStore($event);
|
|
|
|
$basedate = null;
|
|
|
|
$properties = $GLOBALS['properties']->getAppointmentProperties();
|
|
$plaintext = true;
|
|
|
|
$data = array();
|
|
|
|
if($store && $entryid) {
|
|
$message = $GLOBALS['operations']->openMessage($store, $entryid);
|
|
|
|
|
|
// add all standard properties from the series/normal message
|
|
$data['item'] = $GLOBALS['operations']->getMessageProps($store, $message, $properties, (isset($plaintext) && $plaintext));
|
|
|
|
// if appointment is recurring then only we should get properties of occurence if basedate is supplied
|
|
if($data['item']['props']['recurring'] === true) {
|
|
if(isset($basedate) && $basedate) {
|
|
$recur = new Recurrence($store, $message);
|
|
|
|
$exceptionatt = $recur->getExceptionAttachment($basedate);
|
|
|
|
// Single occurences are never recurring
|
|
$data['item']['props']['recurring'] = false;
|
|
|
|
if($exceptionatt) {
|
|
// Existing exception (open existing item, which includes basedate)
|
|
$exceptionattProps = mapi_getprops($exceptionatt, array(PR_ATTACH_NUM));
|
|
$exception = mapi_attach_openobj($exceptionatt, 0);
|
|
|
|
// overwrite properties with the ones from the exception
|
|
$exceptionProps = $GLOBALS['operations']->getMessageProps($store, $exception, $properties, (isset($plaintext) && $plaintext));
|
|
|
|
/**
|
|
* If recurring item has set reminder to true then
|
|
* all occurrences before the 'flagdueby' value(of recurring item)
|
|
* should not show that reminder is set.
|
|
*/
|
|
if (isset($exceptionProps['props']['reminder']) && $data['item']['props']['reminder'] == true) {
|
|
$flagDueByDay = $recur->dayStartOf($data['item']['props']['flagdueby']);
|
|
|
|
if ($flagDueByDay > $basedate) {
|
|
$exceptionProps['props']['reminder'] = false;
|
|
}
|
|
}
|
|
|
|
// The properties must be merged, if the recipients or attachments are present in the exception
|
|
// then that list should be used. Otherwise the list from the series must be applied (this
|
|
// corresponds with OL2007).
|
|
// @FIXME getMessageProps should not return empty string if exception doesn't contain body
|
|
// by this change we can handle a situation where user has set empty string in the body explicitly
|
|
if (!empty($exceptionProps['props']['body']) || !empty($exceptionProps['props']['html_body'])) {
|
|
if(!empty($exceptionProps['props']['body'])) {
|
|
$data['item']['props']['body'] = $exceptionProps['props']['body'];
|
|
}
|
|
|
|
if(!empty($exceptionProps['props']['html_body'])) {
|
|
$data['item']['props']['html_body'] = $exceptionProps['props']['html_body'];
|
|
}
|
|
|
|
$data['item']['props']['isHTML'] = $exceptionProps['props']['isHTML'];
|
|
}
|
|
// remove properties from $exceptionProps so array_merge will not overwrite it
|
|
unset($exceptionProps['props']['html_body']);
|
|
unset($exceptionProps['props']['body']);
|
|
unset($exceptionProps['props']['isHTML']);
|
|
|
|
$data['item']['props'] = array_merge($data['item']['props'], $exceptionProps['props']);
|
|
if (isset($exceptionProps['recipients'])) {
|
|
$data['item']['recipients'] = $exceptionProps['recipients'];
|
|
}
|
|
|
|
if (isset($exceptionProps['attachments'])) {
|
|
$data['item']['attachments'] = $exceptionProps['attachments'];
|
|
}
|
|
|
|
// Make sure we are using the passed basedate and not something wrong in the opened item
|
|
$data['item']['props']['basedate'] = $basedate;
|
|
} else {
|
|
// opening an occurence of a recurring series (same as normal open, but add basedate, startdate and enddate)
|
|
$data['item']['props']['basedate'] = $basedate;
|
|
$data['item']['props']['startdate'] = $recur->getOccurrenceStart($basedate);
|
|
$data['item']['props']['duedate'] = $recur->getOccurrenceEnd($basedate);
|
|
$data['item']['props']['commonstart'] = $data['item']['props']['startdate'];
|
|
$data['item']['props']['commonend'] = $data['item']['props']['duedate'];
|
|
unset($data['item']['props']['reminder_time']);
|
|
|
|
/**
|
|
* If recurring item has set reminder to true then
|
|
* all occurrences before the 'flagdueby' value(of recurring item)
|
|
* should not show that reminder is set.
|
|
*/
|
|
if (isset($exceptionProps['props']['reminder']) && $data['item']['props']['reminder'] == true) {
|
|
$flagDueByDay = $recur->dayStartOf($data['item']['props']['flagdueby']);
|
|
|
|
if ($flagDueByDay > $basedate) {
|
|
$exceptionProps['props']['reminder'] = false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Opening a recurring series, get the recurrence information
|
|
$recur = new Recurrence($store, $message);
|
|
$recurpattern = $recur->getRecurrence();
|
|
$tz = $recur->tz; // no function to do this at the moment
|
|
|
|
// Add the recurrence pattern to the data
|
|
if(isset($recurpattern) && is_array($recurpattern)) {
|
|
$data['item']['props'] += $recurpattern;
|
|
}
|
|
|
|
// Add the timezone information to the data
|
|
if(isset($tz) && is_array($tz)) {
|
|
$data['item']['props'] += $tz;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $data['item']['props']['body'];
|
|
}
|
|
|
|
private function loadAttendees($event) {
|
|
$entryid = $this->getActionEntryID($event);
|
|
$store = $this->getActionStore($event);
|
|
|
|
$basedate = null;
|
|
|
|
$properties = $GLOBALS['properties']->getAppointmentProperties();
|
|
$plaintext = true;
|
|
|
|
$data = array();
|
|
|
|
if($store && $entryid) {
|
|
$message = $GLOBALS['operations']->openMessage($store, $entryid);
|
|
|
|
|
|
// add all standard properties from the series/normal message
|
|
$data['item'] = $GLOBALS['operations']->getMessageProps($store, $message, $properties, (isset($plaintext) && $plaintext));
|
|
|
|
}
|
|
|
|
return $data['item']['recipients']['item'];
|
|
}
|
|
|
|
private function exportCalendar($actionType, $actionData) {
|
|
$secid = $this->randomstring();
|
|
$this->createSecIDFile($secid);
|
|
$tmpname = stripslashes($actionData["calendar"] . ".ics." . $this->randomstring(8));
|
|
$filename = TMP_PATH . "/" . $tmpname . "." . $secid;
|
|
|
|
$tz = date("e"); // use php timezone (maybe set up in php.ini, date.timezone)
|
|
|
|
if($this->DEBUG) {
|
|
error_log("PHP Timezone: " . $tz);
|
|
}
|
|
|
|
$config = array(
|
|
"language" => substr($GLOBALS["settings"]->get("zarafa/v1/main/language"),0,2),
|
|
"directory" => TMP_PATH . "/",
|
|
"filename" => $tmpname . "." . $secid,
|
|
"unique_id" => "zarafa-export-plugin",
|
|
"TZID" => $tz
|
|
);
|
|
|
|
$v = new vcalendar($config);
|
|
$v->setProperty("method", "PUBLISH"); // required of some calendar software
|
|
$v->setProperty("x-wr-calname", $actionData["calendar"]); // required of some calendar software
|
|
$v->setProperty("X-WR-CALDESC", "Exported Zarafa Calendar"); // required of some calendar software
|
|
$v->setProperty("X-WR-TIMEZONE", $tz);
|
|
|
|
$xprops = array("X-LIC-LOCATION" => $tz); // required of some calendar software
|
|
iCalUtilityFunctions::createTimezone($v, $tz, $xprops); // create timezone object in calendar
|
|
|
|
|
|
foreach($actionData["data"]["item"] as $event) {
|
|
$event["props"]["description"] = $this->loadEventDescription($event);
|
|
$event["props"]["attendees"] = $this->loadAttendees($event);
|
|
|
|
$vevent = & $v->newComponent("vevent"); // create a new event object
|
|
$this->addEvent($vevent, $event["props"]);
|
|
}
|
|
|
|
$v->saveCalendar();
|
|
|
|
$response['status'] = true;
|
|
$response['fileid'] = $tmpname; // number of entries that will be exported
|
|
$response['basedir'] = TMP_PATH;
|
|
$response['secid'] = $secid;
|
|
$response['realname'] = $actionData["calendar"];
|
|
$this->addActionData($actionType, $response);
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
if($this->DEBUG) {
|
|
error_log("export done, bus data written!");
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
?>
|