1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/z-push_ynh.git synced 2024-09-03 18:05:58 +02:00
z-push_ynh/sources/backend/zarafa/mapi/class.taskrecurrence.php

463 lines
22 KiB
PHP

<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
require_once("backend/zarafa/mapi/class.baserecurrence.php");
class TaskRecurrence extends BaseRecurrence
{
/**
* Timezone info which is always false for task
*/
var $tz = false;
function TaskRecurrence($store, $message)
{
$this->store = $store;
$this->message = $message;
$properties = array();
$properties["entryid"] = PR_ENTRYID;
$properties["parent_entryid"] = PR_PARENT_ENTRYID;
$properties["icon_index"] = PR_ICON_INDEX;
$properties["message_class"] = PR_MESSAGE_CLASS;
$properties["message_flags"] = PR_MESSAGE_FLAGS;
$properties["subject"] = PR_SUBJECT;
$properties["importance"] = PR_IMPORTANCE;
$properties["sensitivity"] = PR_SENSITIVITY;
$properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
$properties["status"] = "PT_LONG:PSETID_Task:0x8101";
$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
$properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
$properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
$properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
$properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
$properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
$properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
$properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
$properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
$properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
$this->proptags = getPropIdsFromStrings($store, $properties);
parent::BaseRecurrence($store, $message, $properties);
}
/**
* Function which saves recurrence and also regenerates task if necessary.
*@param array $recur new recurrence properties
*@return array of properties of regenerated task else false
*/
function setRecurrence(&$recur)
{
$this->recur = $recur;
$this->action =& $recur;
if(!isset($this->recur["changed_occurences"]))
$this->recur["changed_occurences"] = Array();
if(!isset($this->recur["deleted_occurences"]))
$this->recur["deleted_occurences"] = Array();
if (!isset($this->recur['startocc'])) $this->recur['startocc'] = 0;
if (!isset($this->recur['endocc'])) $this->recur['endocc'] = 0;
// Save recurrence because we need proper startrecurrdate and endrecurrdate
$this->saveRecurrence();
// Update $this->recur with proper startrecurrdate and endrecurrdate updated after saveing recurrence
$msgProps = mapi_getprops($this->message, array($this->proptags['recurring_data']));
$recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
foreach($recurring_data as $key => $value) {
$this->recur[$key] = $value;
}
$this->setFirstOccurrence();
// Let's see if next occurrence has to be generated
return $this->moveToNextOccurrence();
}
/**
* Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence
*/
function setFirstOccurrence()
{
// Check if it is already the first occurrence
if($this->action['start'] == $this->recur["start"]){
return;
}else{
$items = $this->getNextOccurrence();
$props = array();
$props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
$props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
$props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
$props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
mapi_setprops($this->message, $props);
}
}
/**
* Function which creates new task as current occurrence and moves the
* existing task to next occurrence.
*
*@param array $recur $action from client
*@return boolean if moving to next occurrence succeed then it returns
* properties of either newly created task or existing task ELSE
* false because that was last occurrence
*/
function moveToNextOccurrence()
{
$result = false;
/**
* Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
* then we create first two occurrence separately and for first occurrence recurrence has ended.
*/
if ((empty($this->action['startdate']) && empty($this->action['duedate']))
|| ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])){
$nextOccurrence = $this->getNextOccurrence();
$result = mapi_getprops($this->message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
$props = array();
if ($nextOccurrence) {
if (!isset($this->action['deleteOccurrence'])) {
// Create current occurrence as separate task
$result = $this->regenerateTask($this->action['complete']);
}
// Set reminder for next occurrence
$this->setReminder($nextOccurrence);
// Update properties for next occurrence
$this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
$this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
$this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
$this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
// If current task as been mark as 'Complete' then next occurrence should be uncomplete.
if (isset($this->action['complete']) && $this->action['complete'] == 1) {
$this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
$this->action['complete'] = $props[$this->proptags["complete"]] = false;
$this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
}
$props[$this->proptags["dead_occurrence"]] = false;
} else {
if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])
return false;
// Didn't get next occurrence, probably this is the last one, so recurrence ends here
$props[$this->proptags["dead_occurrence"]] = true;
$props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
$props[$this->proptags["task_f_creator"]] = true;
//OL props
$props[$this->proptags["side_effects"]] = 1296;
$props[$this->proptags["icon_index"]] = 1280;
}
mapi_setprops($this->message, $props);
}
return $result;
}
/**
* Function which return properties of next occurrence
*@return array startdate/enddate of next occurrence
*/
function getNextOccurrence()
{
if ($this->recur) {
$items = array();
//@TODO: fix start of range
$start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
$dayend = ($this->recur['term'] == 0x23) ? 0x7fffffff : $this->dayStartOf($this->recur["end"]);
// Fix recur object
$this->recur['startocc'] = 0;
$this->recur['endocc'] = 0;
// Retrieve next occurrence
$items = $this->getItems($start, $dayend, 1);
return !empty($items) ? $items[0] : false;
}
}
/**
* Function which clones current occurrence and sets appropriate properties.
* The original recurring item is moved to next occurrence.
*@param boolean $markComplete true if existing occurrence has to be mark complete else false.
*/
function regenerateTask($markComplete)
{
// Get all properties
$taskItemProps = mapi_getprops($this->message);
if (isset($this->action["subject"])) $taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
if (isset($this->action["importance"])) $taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
if (isset($this->action["startdate"])) {
$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
}
if (isset($this->action["duedate"])) {
$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
}
$folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
$newMessage = mapi_folder_createmessage($folder);
$taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
$taskItemProps[$this->proptags["complete"]] = $markComplete;
$taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
// This occurrence has been marked as 'Complete' so disable reminder
if ($markComplete) {
$taskItemProps[$this->proptags["reset_reminder"]] = false;
$taskItemProps[$this->proptags["reminder"]] = false;
$taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
unset($this->action[$this->proptags['datecompleted']]);
}
// Recurrence ends for this item
$taskItemProps[$this->proptags["dead_occurrence"]] = true;
$taskItemProps[$this->proptags["task_f_creator"]] = true;
//OL props
$taskItemProps[$this->proptags["side_effects"]] = 1296;
$taskItemProps[$this->proptags["icon_index"]] = 1280;
// Copy recipients
$recipienttable = mapi_message_getrecipienttable($this->message);
$recipients = mapi_table_queryallrows($recipienttable, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID));
$copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
foreach($copy_to_recipientRows as $recipient) {
mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, array($recipient));
}
mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
// Copy attachments
$attachmentTable = mapi_message_getattachmenttable($this->message);
if($attachmentTable) {
$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD));
foreach($attachments as $attach_props){
$attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
$attach_newResourceMsg = mapi_message_createattach($newMessage);
mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
mapi_savechanges($attach_newResourceMsg);
}
}
mapi_setprops($newMessage, $taskItemProps);
mapi_savechanges($newMessage);
// Update body of original message
$msgbody = mapi_message_openproperty($this->message, PR_BODY);
$msgbody = trim($this->windows1252_to_utf8($msgbody), "\0");
$separator = "------------\r\n";
if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
$msgbody = $separator . $msgbody;
$stream = mapi_openpropertytostream($this->message, PR_BODY, MAPI_CREATE | MAPI_MODIFY);
mapi_stream_setsize($stream, strlen($msgbody));
mapi_stream_write($stream, $msgbody);
mapi_stream_commit($stream);
}
// We need these properties to notify client
return mapi_getprops($newMessage, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
}
/**
* processOccurrenceItem, adds an item to a list of occurrences, but only if the
* resulting occurrence starts or ends in the interval <$start, $end>
* @param array $items reference to the array to be added to
* @param date $start start of timeframe in GMT TIME
* @param date $end end of timeframe in GMT TIME
* @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
*/
function processOccurrenceItem(&$items, $start, $end, $now)
{
if ($now > $start) {
$newItem = array();
$newItem[$this->proptags['startdate']] = $now;
// If startdate and enddate are set on task, then slide enddate according to duration
if (isset($this->messageprops[$this->proptags["startdate"]]) && isset($this->messageprops[$this->proptags["duedate"]])) {
$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
} else {
$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
}
$items[] = $newItem;
}
}
/**
* Function which marks existing occurrence to 'Complete'
*@param array $recur array action from client
*@return array of properties of regenerated task else false
*/
function markOccurrenceComplete(&$recur)
{
// Fix timezone object
$this->tz = false;
$this->action =& $recur;
$dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
if (!$dead_occurrence) {
return $this->moveToNextOccurrence();
}
return false;
}
/**
* Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
*@param array $nextOccurrence properties of next occurrence
*/
function setReminder($nextOccurrence)
{
$props = array();
if ($nextOccurrence) {
// Check if reminder is reset. Default is 'false'
$reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
$reminder = $this->messageprops[$this->proptags['reminder']];
// Either reminder was already set OR reminder was set but was dismissed bty user
if ($reminder || $reset_reminder) {
// Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
$reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
$reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
$reminder_difference = $reminder_difference - $reminder_time;
// Apply duration to next calculated duedate
$next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
$props[$this->proptags['reminder_time']] = $next_reminder_time;
$props[$this->proptags['flagdueby']] = $next_reminder_time;
$this->action['reminder'] = $props[$this->proptags['reminder']] = true;
}
} else {
// Didn't get next occurrence, probably this is the last occurrence
$props[$this->proptags['reminder']] = false;
$props[$this->proptags['reset_reminder']] = false;
}
if (!empty($props))
mapi_setprops($this->message, $props);
}
/**
* Function which recurring task to next occurrence.
* It simply doesn't regenerate task
@param array $action
*/
function deleteOccurrence($action)
{
$this->tz = false;
$this->action = $action;
$result = $this->moveToNextOccurrence();
mapi_savechanges($this->message);
return $result;
}
/**
* Convert from windows-1252 encoded string to UTF-8 string
*
* The same conversion rules as utf8_to_windows1252 apply.
*
* @param string $string the Windows-1252 string to convert
* @return string UTF-8 representation of the string
*/
function windows1252_to_utf8($string)
{
if (function_exists("iconv")){
return iconv("Windows-1252", "UTF-8//TRANSLIT", $string);
}else{
return utf8_encode($string); // no euro support here
}
}
}