<?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."
 * 
 * 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
	/**
	 * Appointment Module
	*/
	
	require_once("mapi/class.recurrence.php");
	
	class AppointmentListModule extends ListModule
	{
		/**
		 * @var object Recurrence class object.
		 */
		var $recurrence;
		
		/**
		 * @var object MeetingRequest class object.
		 */
		var $meetingrequest;
		
		/**
		 * @var date start interval of view visible
		 */
		var $startdate;
		
		/**
		 * @var date end interval of view visible
		 */
		var $enddate;
	
		/**
		 * Constructor
		 * @param int $id unique id.
		 * @param array $data list of all actions.
		 */
		function AppointmentListModule($id, $data)
		{
			$this->properties = $GLOBALS["properties"]->getAppointmentProperties();

			$this->tablecolumns = array();
			$this->addColumn($this->tablecolumns, "icon_index", true, 0, _("Icon Index"), _("Sort on Icon Index"));
			$this->addColumn($this->tablecolumns, "subject", true, 1, _("Subject"), _("Sort on Subject"), PERCENTAGE);
			$this->addColumn($this->tablecolumns, "start", true, 2, _("Startdate"), _("Sort on Startdate"));
			$this->addColumn($this->tablecolumns, "end", true, 3, _("Enddate"), _("Sort on Enddate"));
			$this->addColumn($this->tablecolumns, "recurring", true, 4, _("Recurring"), _("Sort on Recurring"));
			$this->addColumn($this->tablecolumns, "busystatus", true, 5, _("Busy Status"), _("Sort on Busy Status")); 
			$this->addColumn($this->tablecolumns, "categories", false, 4, _("Categories"), _("Sort On Categories"));

			parent::ListModule($id, $data, array(OBJECT_SAVE, TABLE_SAVE, TABLE_DELETE));

			$this->sort = array();
			$this->sort[$this->properties["subject"]] = TABLE_SORT_ASCEND;

			$this->startdate = false;
			$this->enddate = false;
		}
		
		/**
		 * Function which retrieves a list of calendar items in a calendar folder
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid entryid of the folder
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure		 		 
		 */
		function messageList($store, $entryid, $action)
		{
			$result = false;
			
			if($store && $entryid) {
				$type = false;
				
				if(isset($action["restriction"])) {
					if(isset($action["restriction"]["startdate"])) {
						$this->startdate = (int) $action["restriction"]["startdate"];
					}
					
					if(isset($action["restriction"]["duedate"])) {
						$this->enddate = (int) $action["restriction"]["duedate"];
					}
				}
				
				if($this->startdate && $this->enddate) {
					$data = array();
					$data["attributes"] = array("type" => "list");
					$data["column"] = $this->tablecolumns;
					$data["item"] = $this->getCalendarItems($store, $entryid, $this->startdate, $this->enddate);
	
					array_push($this->responseData["action"], $data);
					$GLOBALS["bus"]->addData($this->responseData);
				}
			}
			
			return $result;
		}
		
		/**
		 * Function to return all Calendar items in a given timeframe. This
		 * function also takes recurring items into account.
		 * @param object $store message store
		 * @param object $calendar folder
		 * @param date $start startdate of the interval
		 * @param date $end enddate of the interval
		 */
		function getCalendarItems($store, $entryid, $start, $end)
		{
			$items = Array();
		
			$restriction = 
						// OR
						// (item[end] >= start && item[start] <= end) ||
						Array(RES_OR,
								 Array(
									   Array(RES_AND,
											 Array(
												   Array(RES_PROPERTY,
														 Array(RELOP => RELOP_GE,
															   ULPROPTAG => $this->properties["duedate"],
															   VALUE => $start
															   )
														 ),
												   Array(RES_PROPERTY,
														 Array(RELOP => RELOP_LE,
															   ULPROPTAG => $this->properties["startdate"],
															   VALUE => $end
															   )
														 )
												   )
											 ),
										//OR
										//(item[isRecurring] == true)
										Array(RES_PROPERTY,
											Array(RELOP => RELOP_EQ,
												ULPROPTAG => $this->properties["recurring"],
												VALUE => true
													)
											)		
									)
							);// global OR

			$folder = mapi_msgstore_openentry($store, $entryid);
			$table = mapi_folder_getcontentstable($folder);
			$calendaritems = mapi_table_queryallrows($table, $this->properties, $restriction);
			$storeProviderGuid = mapi_getprops($store, array(PR_MDB_PROVIDER));
			
			// open localfreebusy folder for delegate permissions
			$rootFolder = mapi_msgstore_openentry($store, null);
			$rootFolderProps = mapi_getprops($rootFolder, array(PR_FREEBUSY_ENTRYIDS));
			/**
			 *	PR_FREEBUSY_ENTRYIDS contains 4 entryids
			 *	PR_FREEBUSY_ENTRYIDS[0] gives associated freebusy folder in calendar
			 *	PR_FREEBUSY_ENTRYIDS[1] Localfreebusy (used for delegate properties)
			 *	PR_FREEBUSY_ENTRYIDS[2] global Freebusydata in public store
			 *	PR_FREEBUSY_ENTRYIDS[3] Freebusydata in IPM_SUBTREE
			 */
			// get localfreebusy folder
			$localFreeBusyFolder = mapi_msgstore_openentry($store, $rootFolderProps[PR_FREEBUSY_ENTRYIDS][1]);
			if(mapi_last_hresult() !== NOERROR) {
				$localFreeBusyFolder = false;
			}

			foreach($calendaritems as $calendaritem)
			{
				$item = null;
				if (isset($calendaritem[$this->properties["recurring"]]) && $calendaritem[$this->properties["recurring"]]) {
					$recurrence = new Recurrence($store, $calendaritem);
					$recuritems = $recurrence->getItems($start, $end);
					
					foreach($recuritems as $recuritem)
					{
						$item = Conversion::mapMAPI2XML($this->properties, $recuritem);
						if(isset($recuritem["exception"])) {
							$item["exception"] = true;
						}

						if(isset($recuritem["basedate"])) {
							$item["basedate"] = array();
							$item["basedate"]["attributes"] = array();
							$item["basedate"]["attributes"]["unixtime"] = $recuritem["basedate"];
							$item["basedate"]["_content"] = strftime("%a %d-%m-%Y %H:%M", $recuritem["basedate"]);
						}
						$item = $this->disablePrivateItem($localFreeBusyFolder, $storeProviderGuid[PR_MDB_PROVIDER], $item);
						array_push($items, $item);
					}
				} else {
					$item = Conversion::mapMAPI2XML($this->properties, $calendaritem);
					$item = $this->disablePrivateItem($localFreeBusyFolder, $storeProviderGuid[PR_MDB_PROVIDER], $item);
					array_push($items,$item);
				}

			} 
			usort($items, array("AppointmentListModule","compareCalendarItems"));

			return $items;
		}

		/**
		 * Function to remove some properties when the item is private
		 * and it will also check for delegate permissions
		 * @param object $localFreeBusyFolder localfreebusy folder
		 * @param object $storeproviderguid type of store
		 * @param object $item item properties
		 * @return object $item item with modified properties
		 */
		function disablePrivateItem($localFreeBusyFolder, $storeProviderGuid, $item)
		{
			if(isset($item["private"]) && $item["private"] == true) {
				// check for delegate permissions
				if($storeProviderGuid == ZARAFA_STORE_DELEGATE_GUID) {
					$hideItemData = false;

					if($localFreeBusyFolder === false) {
						$hideItemData = true;
					}

					// find delegate properties
					if($localFreeBusyFolder !== false) {
						$localFreeBusyFolderProps = mapi_getprops($localFreeBusyFolder, array(PR_SCHDINFO_DELEGATE_ENTRYIDS, PR_DELEGATES_SEE_PRIVATE));
						if(mapi_last_hresult() !== NOERROR) {
							// no delegate properties exists
							$hideItemData = true;
						} else {
							// if more then one delegates info is stored then find index of 
							// current user
							$userEntryId = $GLOBALS["mapisession"]->getUserEntryID();
							$userIndex = array_search($userEntryId, $localFreeBusyFolderProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]);

							if($userIndex === false || $localFreeBusyFolderProps[PR_DELEGATES_SEE_PRIVATE][$userIndex] === 0) {
								$hideItemData = true;
							}
						}
					}

					if($hideItemData) {
						$item["subject"] = _("Private Appointment");
						$item["location"] = "";
						$item["reminder"] = 0;
						$item["disabled_item"] = 1;
					}
				}
			}
			return $item;
		}

		/**
		 * Function which saves an item. This function saves a normal appointment, a
		 * recurring items and its exceptions and meetingrequests.
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid entryid of the folder
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure		 		 
		 */
		function save($store, $parententryid, $action)
		{
			$result = false;

			// Save the properties
			$messageProps = $GLOBALS["operations"]->saveAppointment($store, $parententryid, $action);
			
			/**
			 * If there is an error in exception then send the failed action to the client
			 * and send the error message with it.
			 */
			if(isset($messageProps["isexceptionallowed"])) {
				$data = array();
				$data["attributes"] = array("type" => "failed");
				
				if($messageProps["isexceptionallowed"] === "false"){
					$data["action_type"] = "moveoccurence";
					$data["error_message"] = _("Unable to reschedule occurrence: Either the new occurrence skips over an existing occurrence, or an occurrence is already present on this day");
				}
 				array_push($this->responseData["action"],$data);
 				$GLOBALS["bus"]->addData($this->responseData);
 			}
			// Send notifications that this object has changed					
			if($messageProps){
				$GLOBALS["bus"]->notify($this->entryid, TABLE_SAVE, $messageProps);
				$result = true;
			}
			return $result;
		}
		
		/**
		 * Function which deletes one or more items. Ite verifies if the delete
		 * is a action on a recurring item and if only one occurrence should be
		 * deleted in the recurrence.		  		 
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid entryid of the folder
		 * @param array $entryid list of entryids which will be deleted		 
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure
		 */
		function delete($store, $parententryid, $entryids, $action)
		{
			$result = false;
			
			if($store && $parententryid) {
				$props = array();
				$props[PR_PARENT_ENTRYID] = $parententryid;
				$props[PR_ENTRYID] = $entryids;
	
				$storeprops = mapi_getprops($store, array(PR_ENTRYID));
				$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];

				if($entryids) {
					$result = $GLOBALS["operations"]->deleteMessages($store, $parententryid, $entryids);
				} else if(isset($action["exception"])) {
					$result = $this->recurrence->saveException($store, $action["exception"]);
					
					// TODO: Add basedate to props. The client will verify if base date exists.
				}
				$GLOBALS["operations"]->publishFreeBusy($store, $parententryid);

				if($result) {
					$GLOBALS["bus"]->notify($this->entryid, TABLE_DELETE, $props);
				}
			}
			
			return $result;
		}


		/**
		 * If an event elsewhere has occurred, it enters in this methode. This method
		 * executes one ore more actions, depends on the event.
		 * @param int $event Event.
		 * @param string $entryid Entryid.
		 * @param array $data array of data.
		 */
		function update($event, $entryid, $props)
		{
			$this->reset();
			
			switch($event)
			{
				case TABLE_SAVE:
					$data = array();
					$data["attributes"] = array("type" => "item");

					if(isset($props[PR_STORE_ENTRYID])) {
						$store = $GLOBALS["mapisession"]->openMessageStore($props[PR_STORE_ENTRYID]);
						
						if(isset($props[PR_ENTRYID])) {
							$message = $GLOBALS["operations"]->openMessage($store, $props[PR_ENTRYID]);
							$messageProps = mapi_getprops($message, $this->properties);
							if (isset($messageProps[$this->properties['recurring']]) && $messageProps[$this->properties['recurring']]){
								// Because this is a multi-instanced object, we have to send a DELETE first to delete all occurring items
								// before re-adding the updated items.
								$data["attributes"] = array("type" => "delete");
								$item["parent_entryid"] = bin2hex($props[PR_PARENT_ENTRYID]);
								$item["entryid"] = array(bin2hex($props[PR_ENTRYID]));
								$data["item"] = array($item);
								array_push($this->responseData["action"], $data);
								
								$recurrence = new Recurrence($store, $messageProps);

								$recuritems = $recurrence->getItems($this->startdate, $this->enddate);
								
								$items = array();
								foreach($recuritems as $recuritem)
								{
									$item = Conversion::mapMAPI2XML($this->properties, $recuritem);
									if(isset($recuritem["exception"])) {
										$item["exception"] = true;
									}

									if(isset($recuritem["basedate"])) {
										$item["basedate"] = array();
										$item["basedate"]["attributes"] = array();
										$item["basedate"]["attributes"]["unixtime"] = $recuritem["basedate"];
										$item["basedate"]["_content"] = strftime("%a %d-%m-%Y %H:%M", $recuritem["basedate"]);
									}
						
									// This is a multi-instanced message; ie there can be more than one entry of this
									// item with the same entryid in the view. If you don't specify this, then the client
									// will be updating the same item multiple times, instead of just adding them all
									// to the view.
									$item["attributes"] = array("instance" => 1);
									
									array_push($items, $item);
								}
								$data["attributes"] = array("type" => "item");
								$data["item"] = $items;
							} else {
								$data["attributes"] = array("type" => "item");
								$data["item"] = $GLOBALS["operations"]->getMessageProps($store, $GLOBALS["operations"]->openMessage($store, $props[PR_ENTRYID]), $this->properties);
							}
						}
					}
					
					array_push($this->responseData["action"], $data);
					break;
				case TABLE_DELETE:
					if(isset($props[PR_ENTRYID]) && isset($props[PR_PARENT_ENTRYID])) {
						// Get items, which are shown under the table.
						$store = $GLOBALS["mapisession"]->openMessageStore($props[PR_STORE_ENTRYID]);
						
						$data = array();
						$data["attributes"] = array("type" => "item");
						$data["delete"] = 1;
						
						$deletedrows = 1;
						if(is_array($props[PR_ENTRYID])) {
							$deletedrows = count($props[PR_ENTRYID]);
						}

						$this->start = ($this->start + (int)$GLOBALS["settings"]->get("global/rowcount", 50)) - $deletedrows;
						$data = array_merge($data, $GLOBALS["operations"]->getTable($store, $props[PR_PARENT_ENTRYID], $this->properties, $this->sort, $this->start, $deletedrows));

						array_push($this->responseData["action"], $data);
						
						$data = array();
						$data["attributes"] = array("type" => "delete");
						$data["parent_entryid"] = bin2hex($props[PR_PARENT_ENTRYID]);
						
						if(is_array($props[PR_ENTRYID])) {
							$data["entryid"] = array();
							
							foreach($props[PR_ENTRYID] as $entryid)
							{
								array_push($data["entryid"], bin2hex($entryid));
							}
						} else {
							$data["entryid"] = bin2hex($props[PR_ENTRYID]);
						}
						
						array_push($this->responseData["action"], $data);
					}
					break;
			}
			
			$GLOBALS["bus"]->addData($this->responseData);
		}
		
		/**
		 * Function will sort items for the month view
		 * small startdate->attributes->unixtime on top	 
		 */		 		
		function compareCalendarItems($a, $b)
		{
			$start_a = $a["startdate"]["attributes"]["unixtime"];
			$start_b = $b["startdate"]["attributes"]["unixtime"];
		
		   if ($start_a == $start_b) {
		       return 0;
		   }
		   return ($start_a < $start_b) ? -1 : 1;
		}


		
	}
	
?>
