<?php
/*
 * Copyright 2005 - 2009  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/>.
 * 
 */

?>
<?php
	require_once("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 (count($items) > 0) ? $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 (count($props) > 0) 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
			}
		}
	}
?>