2016-06-20 16:19:05 +02:00
|
|
|
<?php
|
2012-12-05 22:31:10 +01:00
|
|
|
/**
|
2016-11-29 19:58:23 +01:00
|
|
|
* module.calendar.php, Kopano calender to ics im/exporter
|
2012-12-05 22:31:10 +01:00
|
|
|
*
|
|
|
|
* Author: Christoph Haas <christoph.h@sprinternet.at>
|
2016-11-29 19:58:23 +01:00
|
|
|
* Copyright (C) 2012-2016 Christoph Haas
|
2012-12-05 22:31:10 +01:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
*/
|
2016-06-20 15:04:53 +02:00
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
include_once(__DIR__ . "/vendor/autoload.php");
|
|
|
|
include_once(__DIR__ . "/helper.php");
|
2012-12-05 22:31:10 +01:00
|
|
|
|
2016-06-20 19:42:07 +02:00
|
|
|
use Sabre\VObject;
|
2016-11-30 11:37:16 +01:00
|
|
|
use calendarimporter\Helper;
|
2016-06-20 19:42:07 +02:00
|
|
|
|
2016-06-20 16:19:05 +02:00
|
|
|
class CalendarModule extends Module
|
|
|
|
{
|
|
|
|
|
2016-11-29 19:58:23 +01:00
|
|
|
private $DEBUG = false; // enable error_log debugging
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
private $busyStates = null;
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
private $labels = null;
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
private $attendeeType = null;
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @constructor
|
|
|
|
* @param $id
|
|
|
|
* @param $data
|
|
|
|
*/
|
|
|
|
public function __construct($id, $data)
|
|
|
|
{
|
|
|
|
parent::__construct($id, $data);
|
|
|
|
|
|
|
|
// init default timezone
|
|
|
|
date_default_timezone_set(PLUGIN_CALENDARIMPORTER_DEFAULT_TIMEZONE);
|
|
|
|
|
|
|
|
// init mappings
|
2016-11-30 11:37:16 +01:00
|
|
|
$this->busyStates = array(
|
2016-11-29 19:58:23 +01:00
|
|
|
"FREE",
|
|
|
|
"TENTATIVE",
|
|
|
|
"BUSY",
|
|
|
|
"OOF"
|
|
|
|
);
|
|
|
|
|
2016-12-05 18:47:21 +01:00
|
|
|
$this->freeBusyStates = array( // http://www.kanzaki.com/docs/ical/fbtype.html
|
|
|
|
"FREE",
|
|
|
|
"BUSY-TENTATIVE",
|
|
|
|
"BUSY",
|
|
|
|
"BUSY-UNAVAILABLE"
|
|
|
|
);
|
|
|
|
|
2016-11-29 19:58:23 +01:00
|
|
|
$this->labels = array(
|
|
|
|
"NONE",
|
|
|
|
"IMPORTANT",
|
|
|
|
"WORK",
|
|
|
|
"PERSONAL",
|
|
|
|
"HOLIDAY",
|
|
|
|
"REQUIRED",
|
|
|
|
"TRAVEL REQUIRED",
|
|
|
|
"PREPARATION REQUIERED",
|
|
|
|
"BIRTHDAY",
|
|
|
|
"SPECIAL DATE",
|
|
|
|
"PHONE INTERVIEW"
|
|
|
|
);
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$this->attendeeType = array(
|
2016-11-29 19:58:23 +01:00
|
|
|
"NON-PARTICIPANT", // needed as zarafa starts counting at 1
|
|
|
|
"REQ-PARTICIPANT",
|
|
|
|
"OPT-PARTICIPANT",
|
|
|
|
"NON-PARTICIPANT"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes all the actions in the $data variable.
|
|
|
|
* Exception part is used for authentication errors also
|
2016-11-30 11:37:16 +01:00
|
|
|
*
|
2016-11-29 19:58:23 +01:00
|
|
|
* @return boolean true on success or false on failure.
|
|
|
|
*/
|
|
|
|
public function execute()
|
|
|
|
{
|
|
|
|
$result = false;
|
|
|
|
|
|
|
|
if (!$this->DEBUG) {
|
|
|
|
/* disable error printing - otherwise json communication might break... */
|
|
|
|
ini_set('display_errors', '0');
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($this->data as $actionType => $actionData) {
|
|
|
|
if (isset($actionType)) {
|
|
|
|
try {
|
|
|
|
if ($this->DEBUG) {
|
|
|
|
error_log("exec: " . $actionType);
|
|
|
|
}
|
|
|
|
switch ($actionType) {
|
|
|
|
case "load":
|
|
|
|
$result = $this->loadCalendar($actionType, $actionData);
|
|
|
|
break;
|
|
|
|
case "export":
|
|
|
|
$result = $this->exportCalendar($actionType, $actionData);
|
|
|
|
break;
|
|
|
|
case "import":
|
|
|
|
$result = $this->importCalendar($actionType, $actionData);
|
|
|
|
break;
|
|
|
|
case "importattachment":
|
|
|
|
$result = $this->getAttachmentPath($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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a property from the array.
|
2016-11-30 11:37:16 +01:00
|
|
|
*
|
2016-11-29 19:58:23 +01:00
|
|
|
* @param $props
|
2016-11-30 11:37:16 +01:00
|
|
|
* @param $propName
|
2016-11-29 19:58:23 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
2016-11-30 11:37:16 +01:00
|
|
|
private function getProp($props, $propName)
|
2016-11-29 19:58:23 +01:00
|
|
|
{
|
2016-11-30 11:37:16 +01:00
|
|
|
if (isset($props["props"][$propName])) {
|
|
|
|
return $props["props"][$propName];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
/**
|
|
|
|
* Get a duration string form given minutes
|
|
|
|
*
|
|
|
|
* @param $minutes
|
|
|
|
* @param bool $pos
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
private function getDurationStringFromMinutes($minutes, $pos = false)
|
2016-11-29 19:58:23 +01:00
|
|
|
{
|
|
|
|
$pos = $pos === true ? "+" : "-";
|
|
|
|
$str = $pos . "P";
|
|
|
|
|
|
|
|
|
|
|
|
// variables for holding values
|
2016-11-30 11:37:16 +01:00
|
|
|
$min = intval($minutes);
|
2016-11-29 19:58:23 +01:00
|
|
|
$hours = 0;
|
|
|
|
$days = 0;
|
|
|
|
$weeks = 0;
|
|
|
|
|
|
|
|
// calculations
|
2016-11-30 11:37:16 +01:00
|
|
|
if ($min >= 60) {
|
|
|
|
$hours = (int)($min / 60);
|
|
|
|
$min = $min % 60;
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
if ($hours >= 24) {
|
|
|
|
$days = (int)($hours / 24);
|
|
|
|
$hours = $hours % 60;
|
|
|
|
}
|
|
|
|
if ($days >= 7) {
|
|
|
|
$weeks = (int)($days / 7);
|
|
|
|
$days = $days % 7;
|
|
|
|
}
|
|
|
|
|
|
|
|
// format result
|
|
|
|
if ($weeks) {
|
|
|
|
$str .= "{$weeks}W";
|
|
|
|
}
|
|
|
|
if ($days) {
|
|
|
|
$str .= "{$days}D";
|
|
|
|
}
|
|
|
|
if ($hours) {
|
|
|
|
$str .= "{$hours}H";
|
|
|
|
}
|
2016-11-30 11:37:16 +01:00
|
|
|
if ($min) {
|
|
|
|
$str .= "{$min}M";
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $str;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The main export function, creates the ics file for download
|
2016-11-30 11:37:16 +01:00
|
|
|
*
|
2016-11-29 19:58:23 +01:00
|
|
|
* @param $actionType
|
|
|
|
* @param $actionData
|
|
|
|
*/
|
|
|
|
private function exportCalendar($actionType, $actionData)
|
|
|
|
{
|
|
|
|
// Get store id
|
2016-11-30 11:37:16 +01:00
|
|
|
$storeId = false;
|
2016-11-29 19:58:23 +01:00
|
|
|
if (isset($actionData["storeid"])) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$storeId = $actionData["storeid"];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get records
|
|
|
|
$records = array();
|
|
|
|
if (isset($actionData["records"])) {
|
|
|
|
$records = $actionData["records"];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get folders
|
|
|
|
$folder = false;
|
|
|
|
if (isset($actionData["folder"])) {
|
|
|
|
$folder = $actionData["folder"];
|
|
|
|
}
|
|
|
|
|
|
|
|
$response = array();
|
|
|
|
$error = false;
|
|
|
|
$error_msg = "";
|
|
|
|
|
|
|
|
// write csv
|
2016-11-30 11:37:16 +01:00
|
|
|
$token = Helper::randomstring(16);
|
2016-11-29 19:58:23 +01:00
|
|
|
$file = PLUGIN_CALENDARIMPORTER_TMP_UPLOAD . "ics_" . $token . ".ics";
|
|
|
|
file_put_contents($file, "");
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeId));
|
2016-11-29 19:58:23 +01:00
|
|
|
if ($store) {
|
|
|
|
// load folder first
|
|
|
|
if ($folder !== false) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$mapiFolder = mapi_msgstore_openentry($store, hex2bin($folder));
|
2016-11-29 19:58:23 +01:00
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$table = mapi_folder_getcontentstable($mapiFolder);
|
2016-11-29 19:58:23 +01:00
|
|
|
$list = mapi_table_queryallrows($table, array(PR_ENTRYID));
|
|
|
|
|
|
|
|
foreach ($list as $item) {
|
|
|
|
$records[] = bin2hex($item[PR_ENTRYID]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$vCalendar = new VObject\Component\VCalendar();
|
2016-11-29 19:58:23 +01:00
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
// Add static stuff to vCalendar
|
|
|
|
$vCalendar->add('METHOD', 'PUBLISH');
|
|
|
|
$vCalendar->add('X-WR-CALDESC', 'Exported Kopano Calendar');
|
|
|
|
$vCalendar->add('X-WR-TIMEZONE', date_default_timezone_get());
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
// TODO: add VTIMEZONE object to ical.
|
|
|
|
|
|
|
|
for ($index = 0, $count = count($records); $index < $count; $index++) {
|
|
|
|
$message = mapi_msgstore_openentry($store, hex2bin($records[$index]));
|
|
|
|
|
|
|
|
// get message properties.
|
|
|
|
$properties = $GLOBALS['properties']->getAppointmentProperties();
|
|
|
|
$plaintext = true;
|
|
|
|
$messageProps = $GLOBALS['operations']->getMessageProps($store, $message, $properties, $plaintext);
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$vEvent = $vCalendar->add('VEVENT', [
|
2016-11-29 19:58:23 +01:00
|
|
|
'SUMMARY' => $this->getProp($messageProps, "subject"),
|
|
|
|
'DTSTART' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "startdate")),
|
|
|
|
'DTEND' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "duedate")),
|
|
|
|
'CREATED' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "creation_time")),
|
|
|
|
'LAST-MODIFIED' => date_timestamp_set(new DateTime(), $this->getProp($messageProps, "last_modification_time")),
|
2016-12-05 18:47:21 +01:00
|
|
|
'PRIORITY' => intval($this->getProp($messageProps, "importance")),
|
2016-11-30 11:37:16 +01:00
|
|
|
'X-MICROSOFT-CDO-INTENDEDSTATUS' => $this->busyStates[intval($this->getProp($messageProps, "busystatus"))], // both seem to be valid...
|
|
|
|
'X-MICROSOFT-CDO-BUSYSTATUS' => $this->busyStates[intval($this->getProp($messageProps, "busystatus"))], // both seem to be valid...
|
2016-12-05 18:47:21 +01:00
|
|
|
'FBTYPE' => $this->freeBusyStates[intval($this->getProp($messageProps, "busystatus"))],
|
2016-11-29 19:58:23 +01:00
|
|
|
'X-ZARAFA-LABEL' => $this->labels[intval($this->getProp($messageProps, "label"))],
|
|
|
|
'CLASS' => $this->getProp($messageProps, "private") ? "PRIVATE" : "PUBLIC",
|
|
|
|
'COMMENT' => "eid:" . $records[$index]
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Add organizer
|
2016-12-05 20:37:35 +01:00
|
|
|
$organizer = $this->getProp($messageProps, "sender_email_address");
|
|
|
|
if(!empty($organizer)) {
|
|
|
|
$vEvent->add('ORGANIZER', 'mailto:' . $organizer);
|
2016-12-05 18:47:21 +01:00
|
|
|
$vEvent->ORGANIZER['CN'] = $this->getProp($messageProps, "sender_name");
|
|
|
|
}
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
// Add Attendees
|
|
|
|
if (isset($messageProps["recipients"]) && count($messageProps["recipients"]["item"]) > 0) {
|
|
|
|
foreach ($messageProps["recipients"]["item"] as $attendee) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$att = $vEvent->add('ATTENDEE', "mailto:" . $this->getProp($attendee, "email_address"));
|
2016-11-29 19:58:23 +01:00
|
|
|
$att["CN"] = $this->getProp($attendee, "display_name");
|
2016-11-30 11:37:16 +01:00
|
|
|
$att["ROLE"] = $this->attendeeType[intval($this->getProp($attendee, "recipient_type"))];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add alarms
|
2016-12-01 00:25:23 +01:00
|
|
|
$reminder = $this->getProp($messageProps, "reminder");
|
|
|
|
if (!empty($reminder) && $this->getProp($messageProps, "reminder") == 1) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$vAlarm = $vEvent->add('VALARM', [
|
2016-11-29 19:58:23 +01:00
|
|
|
'ACTION' => 'DISPLAY',
|
|
|
|
'DESCRIPTION' => $this->getProp($messageProps, "subject") // reuse the event summary
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Add trigger
|
2016-11-30 11:37:16 +01:00
|
|
|
$durationValue = $this->getDurationStringFromMinutes($this->getProp($messageProps, "reminder_minutes"), false);
|
|
|
|
$vAlarm->add('TRIGGER', $durationValue); // default trigger type is duration (see 4.8.6.3)
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
$valarm->add('TRIGGER', date_timestamp_set(new DateTime(), $this->getProp($messageProps, "reminder_time"))); // trigger type "DATE-TIME"
|
|
|
|
$valarm->TRIGGER['VALUE'] = 'DATE-TIME';
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add location
|
2016-12-01 00:25:23 +01:00
|
|
|
$location = $this->getProp($messageProps, "location");
|
|
|
|
if (!empty($location)) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$vEvent->add('LOCATION', $this->getProp($messageProps, "location"));
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add description
|
|
|
|
$body = $this->getProp($messageProps, "isHTML") ? $this->getProp($messageProps, "html_body") : $this->getProp($messageProps, "body");
|
|
|
|
if (!empty($body)) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$vEvent->add('DESCRIPTION', $body);
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
2016-12-05 18:47:21 +01:00
|
|
|
|
|
|
|
// Add categories
|
2016-12-05 20:37:35 +01:00
|
|
|
$categories = $this->getProp($messageProps, "categories");
|
|
|
|
if (!empty($categories)) {
|
|
|
|
$categoryArray = array_map('trim', explode(';', trim($categories, " ;")));
|
2016-12-05 18:47:21 +01:00
|
|
|
|
2016-12-05 20:37:35 +01:00
|
|
|
$vEvent->add('CATEGORIES', $categoryArray);
|
2016-12-05 18:47:21 +01:00
|
|
|
}
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// write combined ics file
|
2016-11-30 11:37:16 +01:00
|
|
|
file_put_contents($file, file_get_contents($file) . $vCalendar->serialize());
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (count($records) > 0) {
|
|
|
|
$response['status'] = true;
|
|
|
|
$response['download_token'] = $token;
|
|
|
|
// TRANSLATORS: Filename suffix for exported files
|
|
|
|
$response['filename'] = count($records) . dgettext("plugin_calendarimporter", "_events.ics");
|
|
|
|
} else {
|
|
|
|
$response['status'] = false;
|
|
|
|
$response['message'] = dgettext("plugin_calendarimporter", "No events found. Export skipped!");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->addActionData($actionType, $response);
|
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The main import function, parses the uploaded ics file
|
|
|
|
* @param $actionType
|
|
|
|
* @param $actionData
|
|
|
|
*/
|
|
|
|
private function importCalendar($actionType, $actionData)
|
|
|
|
{
|
|
|
|
// Get uploaded vcf path
|
2016-11-30 11:37:16 +01:00
|
|
|
$icsFile = false;
|
2016-11-29 19:58:23 +01:00
|
|
|
if (isset($actionData["ics_filepath"])) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$icsFile = $actionData["ics_filepath"];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get store id
|
|
|
|
$storeid = false;
|
|
|
|
if (isset($actionData["storeid"])) {
|
|
|
|
$storeid = $actionData["storeid"];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get folder entryid
|
2016-11-30 11:37:16 +01:00
|
|
|
$folderId = false;
|
2016-11-29 19:58:23 +01:00
|
|
|
if (isset($actionData["folderid"])) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$folderId = $actionData["folderid"];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get uids
|
|
|
|
$uids = array();
|
|
|
|
if (isset($actionData["uids"])) {
|
|
|
|
$uids = $actionData["uids"];
|
|
|
|
}
|
|
|
|
|
|
|
|
$response = array();
|
|
|
|
$error = false;
|
|
|
|
$error_msg = "";
|
|
|
|
|
|
|
|
// parse the ics file a last time...
|
|
|
|
$parser = null;
|
|
|
|
try {
|
|
|
|
$parser = VObject\Reader::read(
|
2016-11-30 11:37:16 +01:00
|
|
|
fopen($icsFile, 'r')
|
2016-11-29 19:58:23 +01:00
|
|
|
);
|
|
|
|
} catch (Exception $e) {
|
|
|
|
$error = true;
|
|
|
|
$error_msg = $e->getMessage();
|
|
|
|
}
|
|
|
|
|
|
|
|
$events = array();
|
|
|
|
if (count($parser->VEVENT) > 0) {
|
|
|
|
$events = $this->parseCalendarToArray($parser);
|
|
|
|
$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeid));
|
2016-11-30 11:37:16 +01:00
|
|
|
$folder = mapi_msgstore_openentry($store, hex2bin($folderId));
|
2016-11-29 19:58:23 +01:00
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$importAll = false;
|
2016-11-29 19:58:23 +01:00
|
|
|
if (count($uids) == count($events)) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$importAll = true;
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$propValuesMAPI = array();
|
|
|
|
$properties = $GLOBALS['properties']->getAppointmentProperties();
|
|
|
|
// extend properties...
|
|
|
|
$properties["body"] = PR_BODY;
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
// iterate through all events and import them :)
|
|
|
|
foreach ($events as $event) {
|
2016-11-30 11:37:16 +01:00
|
|
|
if (isset($event["startdate"]) && ($importAll || in_array($event["internal_fields"]["event_uid"], $uids))) {
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
$message = mapi_folder_createmessage($folder);
|
|
|
|
|
|
|
|
// parse the arraykeys
|
|
|
|
foreach ($event as $key => $value) {
|
|
|
|
if ($key !== "internal_fields") {
|
|
|
|
if (isset($properties[$key])) {
|
|
|
|
$propValuesMAPI[$properties[$key]] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$propValuesMAPI[$properties["commonstart"]] = $propValuesMAPI[$properties["startdate"]];
|
|
|
|
$propValuesMAPI[$properties["commonend"]] = $propValuesMAPI[$properties["duedate"]];
|
|
|
|
$propValuesMAPI[$properties["duration"]] = ($propValuesMAPI[$properties["duedate"]] - $propValuesMAPI[$properties["startdate"]]) / 60; // Minutes needed
|
|
|
|
$propValuesMAPI[$properties["reminder"]] = false; // needed, overwritten if there is a timer
|
|
|
|
|
|
|
|
$propValuesMAPI[$properties["message_class"]] = "IPM.Appointment";
|
|
|
|
$propValuesMAPI[$properties["icon_index"]] = "1024";
|
|
|
|
|
|
|
|
// TODO: set attendees and alarms
|
|
|
|
|
|
|
|
mapi_setprops($message, $propValuesMAPI);
|
|
|
|
mapi_savechanges($message);
|
|
|
|
if ($this->DEBUG) {
|
|
|
|
error_log("New event added: \"" . $event["subject"] . "\".\n");
|
|
|
|
}
|
|
|
|
$count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$response['status'] = true;
|
|
|
|
$response['count'] = $count;
|
|
|
|
$response['message'] = "";
|
|
|
|
|
|
|
|
} else {
|
|
|
|
$response['status'] = false;
|
|
|
|
$response['count'] = 0;
|
|
|
|
$response['message'] = $error ? $error_msg : dgettext("plugin_calendarimporter", "ICS file empty!");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->addActionData($actionType, $response);
|
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Store the file to a temporary directory, prepare it for oc upload
|
|
|
|
* @param $actionType
|
|
|
|
* @param $actionData
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private function getAttachmentPath($actionType, $actionData)
|
|
|
|
{
|
|
|
|
// Get store id
|
2016-11-30 11:37:16 +01:00
|
|
|
$storeId = false;
|
2016-11-29 19:58:23 +01:00
|
|
|
if (isset($actionData["store"])) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$storeId = $actionData["store"];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get message entryid
|
2016-11-30 11:37:16 +01:00
|
|
|
$entryId = false;
|
2016-11-29 19:58:23 +01:00
|
|
|
if (isset($actionData["entryid"])) {
|
2016-11-30 11:37:16 +01:00
|
|
|
$entryId = $actionData["entryid"];
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check which type isset
|
|
|
|
$openType = "attachment";
|
|
|
|
|
|
|
|
// Get number of attachment which should be opened.
|
|
|
|
$attachNum = false;
|
|
|
|
if (isset($actionData["attachNum"])) {
|
|
|
|
$attachNum = $actionData["attachNum"];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if storeid and entryid isset
|
2016-11-30 11:37:16 +01:00
|
|
|
if ($storeId && $entryId) {
|
2016-11-29 19:58:23 +01:00
|
|
|
// Open the store
|
2016-11-30 11:37:16 +01:00
|
|
|
$store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeId));
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
if ($store) {
|
|
|
|
// Open the message
|
2016-11-30 11:37:16 +01:00
|
|
|
$message = mapi_msgstore_openentry($store, hex2bin($entryId));
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
if ($message) {
|
|
|
|
$attachment = false;
|
|
|
|
|
|
|
|
// Check if attachNum isset
|
|
|
|
if ($attachNum) {
|
|
|
|
// Loop through the attachNums, message in message in message ...
|
|
|
|
for ($i = 0; $i < (count($attachNum) - 1); $i++) {
|
|
|
|
// Open the attachment
|
2016-11-30 11:37:16 +01:00
|
|
|
$tempAttach = mapi_message_openattach($message, (int)$attachNum[$i]);
|
|
|
|
if ($tempAttach) {
|
2016-11-29 19:58:23 +01:00
|
|
|
// Open the object in the attachment
|
2016-11-30 11:37:16 +01:00
|
|
|
$message = mapi_attach_openobj($tempAttach);
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the attachment
|
|
|
|
$attachment = mapi_message_openattach($message, (int)$attachNum[(count($attachNum) - 1)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the attachment is opened
|
|
|
|
if ($attachment) {
|
|
|
|
|
|
|
|
// Get the props of the attachment
|
|
|
|
$props = mapi_attach_getprops($attachment, array(PR_ATTACH_LONG_FILENAME, PR_ATTACH_MIME_TAG, PR_DISPLAY_NAME, PR_ATTACH_METHOD));
|
|
|
|
// Content Type
|
|
|
|
$contentType = "application/octet-stream";
|
|
|
|
// Filename
|
|
|
|
$filename = "ERROR";
|
|
|
|
|
|
|
|
// Set filename
|
|
|
|
if (isset($props[PR_ATTACH_LONG_FILENAME])) {
|
|
|
|
$filename = $props[PR_ATTACH_LONG_FILENAME];
|
|
|
|
} else {
|
|
|
|
if (isset($props[PR_ATTACH_FILENAME])) {
|
|
|
|
$filename = $props[PR_ATTACH_FILENAME];
|
|
|
|
} else {
|
|
|
|
if (isset($props[PR_DISPLAY_NAME])) {
|
|
|
|
$filename = $props[PR_DISPLAY_NAME];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set content type
|
|
|
|
if (isset($props[PR_ATTACH_MIME_TAG])) {
|
|
|
|
$contentType = $props[PR_ATTACH_MIME_TAG];
|
|
|
|
} else {
|
|
|
|
// Parse the extension of the filename to get the content type
|
|
|
|
if (strrpos($filename, ".") !== false) {
|
|
|
|
$extension = strtolower(substr($filename, strrpos($filename, ".")));
|
|
|
|
$contentType = "application/octet-stream";
|
|
|
|
if (is_readable("mimetypes.dat")) {
|
|
|
|
$fh = fopen("mimetypes.dat", "r");
|
|
|
|
$ext_found = false;
|
|
|
|
while (!feof($fh) && !$ext_found) {
|
|
|
|
$line = fgets($fh);
|
2016-11-30 11:37:16 +01:00
|
|
|
preg_match('/(\.[a-z0-9]+)[ \t]+([^ \t\n\r]*)/i', $line, $result);
|
2016-11-29 19:58:23 +01:00
|
|
|
if ($extension == $result[1]) {
|
|
|
|
$ext_found = true;
|
|
|
|
$contentType = $result[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose($fh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$tmpName = tempnam(TMP_PATH, stripslashes($filename));
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
// Open a stream to get the attachment data
|
|
|
|
$stream = mapi_openpropertytostream($attachment, PR_ATTACH_DATA_BIN);
|
|
|
|
$stat = mapi_stream_stat($stream);
|
|
|
|
// File length = $stat["cb"]
|
|
|
|
|
2016-11-30 11:37:16 +01:00
|
|
|
$fHandle = fopen($tmpName, 'w');
|
2016-11-29 19:58:23 +01:00
|
|
|
$buffer = null;
|
|
|
|
for ($i = 0; $i < $stat["cb"]; $i += BLOCK_SIZE) {
|
|
|
|
// Write stream
|
|
|
|
$buffer = mapi_stream_read($stream, BLOCK_SIZE);
|
2016-11-30 11:37:16 +01:00
|
|
|
fwrite($fHandle, $buffer, strlen($buffer));
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
2016-11-30 11:37:16 +01:00
|
|
|
fclose($fHandle);
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
$response = array();
|
2016-11-30 11:37:16 +01:00
|
|
|
$response['tmpname'] = $tmpName;
|
2016-11-29 19:58:23 +01:00
|
|
|
$response['filename'] = $filename;
|
2016-11-30 11:37:16 +01:00
|
|
|
$response['contenttype'] = $contentType;
|
2016-11-29 19:58:23 +01:00
|
|
|
$response['status'] = true;
|
|
|
|
$this->addActionData($actionType, $response);
|
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$response['status'] = false;
|
|
|
|
$response['message'] = dgettext("plugin_calendarimporter", "Store could not be opened!");
|
|
|
|
$this->addActionData($actionType, $response);
|
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$response['status'] = false;
|
|
|
|
$response['message'] = dgettext("plugin_calendarimporter", "Wrong call, store and entryid have to be set!");
|
|
|
|
$this->addActionData($actionType, $response);
|
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function that parses the uploaded ics file and posts it via json
|
|
|
|
* @param $actionType
|
|
|
|
* @param $actionData
|
|
|
|
*/
|
|
|
|
private function loadCalendar($actionType, $actionData)
|
|
|
|
{
|
|
|
|
$error = false;
|
2016-11-30 11:37:16 +01:00
|
|
|
$errorMsg = "";
|
2016-11-29 19:58:23 +01:00
|
|
|
|
|
|
|
if (is_readable($actionData["ics_filepath"])) {
|
|
|
|
$parser = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
$parser = VObject\Reader::read(
|
|
|
|
fopen($actionData["ics_filepath"], 'r')
|
|
|
|
);
|
|
|
|
//error_log(print_r($parser->VTIMEZONE, true));
|
|
|
|
} catch (Exception $e) {
|
|
|
|
$error = true;
|
2016-11-30 11:37:16 +01:00
|
|
|
$errorMsg = $e->getMessage();
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
|
|
|
if ($error) {
|
|
|
|
$response['status'] = false;
|
2016-11-30 11:37:16 +01:00
|
|
|
$response['message'] = $errorMsg;
|
2016-11-29 19:58:23 +01:00
|
|
|
} else {
|
|
|
|
if (count($parser->VEVENT) == 0) {
|
|
|
|
$response['status'] = false;
|
|
|
|
$response['message'] = dgettext("plugin_calendarimporter", "No event in ics file");
|
|
|
|
} else {
|
|
|
|
$response['status'] = true;
|
|
|
|
$response['parsed_file'] = $actionData["ics_filepath"];
|
|
|
|
$response['parsed'] = array(
|
|
|
|
'events' => $this->parseCalendarToArray($parser),
|
|
|
|
'timezone' => isset($parser->VTIMEZONE->TZID) ? (string)$parser->VTIMEZONE->TZID : (string)$parser->{'X-WR-TIMEZONE'},
|
|
|
|
'calendar' => (string)$parser->PRODID
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$response['status'] = false;
|
|
|
|
$response['message'] = dgettext("plugin_calendarimporter", "File could not be read by server");
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->addActionData($actionType, $response);
|
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData());
|
|
|
|
|
|
|
|
if ($this->DEBUG) {
|
|
|
|
error_log("parsing done, bus data written!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a array with contacts
|
|
|
|
*
|
|
|
|
* @param {VObject} $calendar ics parser object
|
|
|
|
* @return array parsed events
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
private function parseCalendarToArray($calendar)
|
|
|
|
{
|
|
|
|
$events = array();
|
|
|
|
foreach ($calendar->VEVENT as $Index => $vEvent) {
|
|
|
|
// Sabre\VObject\Parser\XML\Element\VEvent
|
|
|
|
$properties = array();
|
|
|
|
|
|
|
|
//uid - used for front/backend communication
|
|
|
|
$properties["internal_fields"] = array();
|
|
|
|
$properties["internal_fields"]["event_uid"] = base64_encode($Index . $vEvent->UID);
|
|
|
|
|
|
|
|
$properties["startdate"] = (string)$vEvent->DTSTART->getDateTime()->getTimestamp();
|
|
|
|
$properties["duedate"] = (string)$vEvent->DTEND->getDateTime()->getTimestamp();
|
|
|
|
$properties["location"] = (string)$vEvent->LOCATION;
|
|
|
|
$properties["subject"] = (string)$vEvent->SUMMARY;
|
|
|
|
$properties["body"] = (string)$vEvent->DESCRIPTION;
|
|
|
|
$properties["comment"] = (string)$vEvent->COMMENT;
|
|
|
|
$properties["timezone"] = (string)$vEvent->DTSTART["TZID"];
|
|
|
|
$properties["organizer"] = (string)$vEvent->ORGANIZER;
|
2016-12-05 20:42:11 +01:00
|
|
|
|
|
|
|
$fbType = (string)$vEvent->FBTYPE;
|
|
|
|
$msIntendedStatus = (string)$vEvent->{'X-MICROSOFT-CDO-INTENDEDSTATUS'};
|
|
|
|
$msBusyStatus = (string)$vEvent->{'X-MICROSOFT-CDO-BUSYSTATUS'};
|
|
|
|
if(!empty($fbType)) {
|
|
|
|
$properties["busystatus"] = array_search($fbType, $this->freeBusyStates);
|
|
|
|
} else if(!empty($msIntendedStatus)) {
|
|
|
|
$properties["busystatus"] = array_search($msIntendedStatus, $this->busyStates);
|
|
|
|
} else if(!empty($msBusyStatus)) {
|
|
|
|
$properties["busystatus"] = array_search($msBusyStatus, $this->busyStates);
|
2016-12-05 18:47:21 +01:00
|
|
|
} else {
|
|
|
|
$properties["busystatus"] = array_search("BUSY", $this->busyStates);
|
|
|
|
}
|
2016-11-29 19:58:23 +01:00
|
|
|
$properties["transp"] = (string)$vEvent->TRANSP;
|
|
|
|
//$properties["trigger"] = (string)$vEvent->COMMENT;
|
|
|
|
$properties["priority"] = (string)$vEvent->PRIORITY;
|
|
|
|
$properties["private"] = ((string)$vEvent->CLASS) == "PRIVATE" ? true : false;
|
2016-12-01 00:25:23 +01:00
|
|
|
$zLabel = (string)$vEvent->{'X-ZARAFA-LABEL'};
|
|
|
|
if (!empty($zLabel)) {
|
|
|
|
$properties["label"] = array_search($zLabel, $this->labels);
|
2016-11-29 19:58:23 +01:00
|
|
|
}
|
2016-12-05 20:42:11 +01:00
|
|
|
$lastModified = (string)$vEvent->{'LAST-MODIFIED'};
|
|
|
|
if (!empty($lastModified)) {
|
2016-12-05 17:48:28 +01:00
|
|
|
$properties["last_modification_time"] = (string)$vEvent->{'LAST-MODIFIED'}->getDateTime()->getTimestamp();
|
|
|
|
} else {
|
|
|
|
$properties["last_modification_time"] = time();
|
|
|
|
}
|
2016-12-05 20:42:11 +01:00
|
|
|
$created = (string)$vEvent->CREATED;
|
|
|
|
if (!empty($created)) {
|
2016-12-05 17:48:28 +01:00
|
|
|
$properties["creation_time"] = (string)$vEvent->CREATED->getDateTime()->getTimestamp();
|
|
|
|
} else {
|
|
|
|
$properties["creation_time"] = time();
|
|
|
|
}
|
2016-11-29 19:58:23 +01:00
|
|
|
$properties["rrule"] = (string)$vEvent->RRULE;
|
|
|
|
|
2016-12-05 18:47:21 +01:00
|
|
|
if (isset($vEvent->CATEGORIES) && count($vEvent->CATEGORIES) > 0) {
|
|
|
|
$categories = array();
|
|
|
|
foreach ($vEvent->CATEGORIES as $category) {
|
|
|
|
$categories[] = (string) $category;
|
|
|
|
}
|
|
|
|
|
|
|
|
$properties["categories"] = $categories;
|
|
|
|
}
|
|
|
|
|
2016-11-29 19:58:23 +01:00
|
|
|
// Attendees
|
|
|
|
$properties["attendees"] = array();
|
|
|
|
if (isset($vEvent->ATTENDEE) && count($vEvent->ATTENDEE) > 0) {
|
|
|
|
foreach ($vEvent->ATTENDEE as $attendee) {
|
|
|
|
$properties["attendees"][] = array(
|
|
|
|
"name" => (string)$attendee["CN"],
|
|
|
|
"mail" => (string)$attendee,
|
|
|
|
"status" => (string)$attendee["PARTSTAT"],
|
|
|
|
"role" => (string)$attendee["ROLE"]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alarms
|
|
|
|
$properties["alarms"] = array();
|
|
|
|
if (isset($vEvent->VALARM) && count($vEvent->VALARM) > 0) {
|
|
|
|
foreach ($vEvent->VALARM as $alarm) {
|
|
|
|
$properties["alarms"][] = array(
|
|
|
|
"description" => (string)$alarm->DESCRIPTION,
|
|
|
|
"trigger" => (string)$alarm->TRIGGER,
|
|
|
|
"type" => (string)$alarm->TRIGGER["VALUE"]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
array_push($events, $properties);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $events;
|
|
|
|
}
|
2016-06-20 16:19:05 +02:00
|
|
|
}
|