<?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();

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

			$this->startdate = false;
			$this->enddate = false;
		}

		/**
		 * Executes all the actions in the $data variable.
		 * @return boolean true on success of false on fialure.
		 */
		function execute()
		{
			foreach($this->data as $actionType => $action)
			{
				if(isset($actionType)) {
					try {
						$store = $this->getActionStore($action);
						$parententryid = $this->getActionParentEntryID($action);
						$entryid = $this->getActionEntryID($action);

						switch($actionType)
						{
							case "list":
								$this->messageList($store, $entryid, $action, $actionType);
								break;
							case "search":
								// @FIXME add functionality to handle private items
								$this->search($store, $entryid, $action, $actionType);
								break;
							case "updatesearch":
								$this->updatesearch($store, $entryid, $action);
								break;
							case "stopsearch":
								$this->stopSearch($store, $entryid, $action);
								break;
							default:
								$this->handleUnknownActionType($actionType);
						}
					} catch (MAPIException $e) {
						$this->processException($e, $actionType);
					}
				}
			}
		}
		
		/**
		 * 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
		 * @param string $actionType the action type, sent by the client
		 * @return boolean true on success or false on failure
		 */
		function messageList($store, $entryid, $action, $actionType)
		{
			if($store && $entryid) {
				$type = false;
				// initialize start and due date with false value so it will not take values from previous request
				$this->startdate = false;
				$this->enddate = false;

				if(isset($action["restriction"])) {
					if(isset($action["restriction"]["startdate"])) {
						$this->startdate = $action["restriction"]["startdate"];
					}

					if(isset($action["restriction"]["duedate"])) {
						$this->enddate = $action["restriction"]["duedate"];
					}
				}
				
				if($this->startdate && $this->enddate) {
					$data = array();

					if(is_array($entryid) && !empty($entryid)) {
						$data["item"] = array();
						for($index = 0, $index2 = count($entryid); $index < $index2; $index++) {
							$this->getDelegateFolderInfo($store[$index]);
							$data["item"] = array_merge($data["item"], $this->getCalendarItems($store[$index], $entryid[$index], $this->startdate, $this->enddate));
						}
					} else {
						$this->getDelegateFolderInfo($store);
						$data["item"] = $this->getCalendarItems($store, $entryid, $this->startdate, $this->enddate);
					}
					$data["restriction"] = $action["restriction"];	// Send restriction back to client

					$this->addActionData("list", $data);
					$GLOBALS["bus"]->addData($this->getResponseData());
				} else {
					// for list view in calendar as startdate and enddate is passed as false
					// this will set sorting and paging for items in listview.

					$this->getDelegateFolderInfo($store);
					parent::messageList($store, $entryid, $action, $actionType);
				}
			}
		}
		
		/**
		 * 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)
		{
			$restriction = 
				// OR
				//  - Either we want all appointments which fall within the given range
				//	- Or we want all recurring items which we manually check if an occurence
				//	  exists which will fall inside the range
				Array(RES_OR,
					Array(
						// OR
						//	- Either we want all properties which fall inside the range (or overlap the range somewhere)
						//		(start < appointmentEnd && due > appointmentStart)
						//	- Or we want all zero-minute appointments which fall at the start of the restriction.
						// Note that this will effectively exclude any appointments which have an enddate on the restriction
						// start date, as those are not useful for us. Secondly, we exclude all zero-minute appointments
						// which fall on the end of the restriction as the restriction is <start, end].
						array(RES_OR,
							array(
								// AND
								//	- The AppointmentEnd must fall after the start of the range
								//	- The AppointmentStart must fall before the end of the range
								Array(RES_AND,
									Array(
										// start < appointmentEnd
										Array(RES_PROPERTY,
											Array(RELOP => RELOP_GT,
												ULPROPTAG => $this->properties["duedate"],
												VALUE => $start
											)
										),
										// due > appointmentStart
										Array(RES_PROPERTY,
											Array(RELOP => RELOP_LT,
												ULPROPTAG => $this->properties["startdate"],
												VALUE => $end
											)
										)
									)
								),
								// AND
								//	- The AppointmentStart equals the start of the range
								//	- The AppointmentEnd equals the start of the range
								// In other words the zero-minute appointments on the start of the range
								array(RES_AND,
									array(
										// appointmentStart == start
										array(RES_PROPERTY,
											Array(RELOP => RELOP_EQ,
												ULPROPTAG => $this->properties["startdate"],
												VALUE => $start
											)
										),
										// appointmentEnd == start
										array(RES_PROPERTY,
											Array(RELOP => RELOP_EQ,
												ULPROPTAG => $this->properties["duedate"],
												VALUE => $start
											)
										)
									)
								)
							)
						),
						//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, MAPI_DEFERRED_ERRORS);
			$calendaritems = mapi_table_queryallrows($table, $this->properties, $restriction);

			$items = $this->processItems($calendaritems, $store, $entryid, $start, $end);
			return $items;
		}
		
		/**
		 * Process calendar items to prepare them for being sent back to the client
		 * @param array $calendaritems array of appointments retrieved from the mapi tablwe
 		 * @param object $store message store
		 * @param object $calendar folder
		 * @param date $start startdate of the interval
		 * @param date $end enddate of the interval
		 * @return array $items processed items
		 */
		function processItems($calendaritems, $store, $entryid, $start, $end)
		{
			$items = Array();

			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["props"]["exception"] = true;
						}

						if(isset($recuritem["basedate"])) {
							$item["props"]["basedate"] = $recuritem["basedate"];
						}

						$item = $this->processPrivateItem($item);

						// only add it in response if its not removed by above function
						if(!empty($item)) {
							array_push($items, $item);
						}
					}
				} else {
					$item = Conversion::mapMAPI2XML($this->properties, $calendaritem);

					$item = $this->processPrivateItem($item);

					// only add it in response if its not removed by above function
					if(!empty($item)) {
						array_push($items,$item);
					}
				}
			}

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

			return $items;
		}

		/**
		 * Function will be used to process private items in a list response, modules can
		 * can decide what to do with the private items, remove the entire row or just
		 * hide the data. This function will only hide the data of the private appointments.
		 * @param {Object} $item item properties
		 * @return {Object} item properties after processing private items
		 */
		function processPrivateItem($item)
		{
			if($this->startdate && $this->enddate) {
				if($this->checkPrivateItem($item)) {
					$item['props']['subject'] = _('Private Appointment');
					$item['props']['location'] = '';
					$item['props']['reminder'] = 0;
					$item['props']['access'] = 0;

					return $item;
				}

				return $item;
			} else {
				// if we are in list view then we need to follow normal procedure of other listviews
				return parent::processPrivateItem($item);
			}
		}

		/**
		 * 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();

					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);
						} else {
							$messageProps = array();
						}
						
						if(!isset($props[PR_ENTRYID]) || (isset($messageProps[$this->properties['recurring']]) && $messageProps[$this->properties['recurring']])) {

							// An object was created inside this folder for which we don't know the entryid
							// this can happen when we copy or move a message. Just tell the javascript that
							// this folder has a new object.
							// Alternatively the recurrence could have been changed, this means that we should send a notification to the client
							// to inform the possibility that a different number of occurences might be inside his view now.
							$folder = mapi_msgstore_openentry($store, $props[PR_PARENT_ENTRYID]);
							$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID, PR_CONTENT_COUNT, PR_CONTENT_UNREAD, PR_DISPLAY_NAME));
	
							$data = array(
								"item" => array(
									array(
										"content_count" => $folderProps[PR_CONTENT_COUNT],
										"content_unread" => $folderProps[PR_CONTENT_UNREAD],
										// Add store_entryid,entryid of folder and display_name of folder 
										// to JSON data in order to refresh the list.
										"store_entryid" => bin2hex($folderProps[PR_STORE_ENTRYID]),
										"parent_entryid" => bin2hex($folderProps[PR_PARENT_ENTRYID]),
										"entryid" => bin2hex($folderProps[PR_ENTRYID]),
										"display_name" => $folderProps[PR_DISPLAY_NAME]
									)
								)
							);

							$this->addNotificationActionData("newobject", $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["delete"] = 1;
						
						$deletedrows = 1;
						if(is_array($props[PR_ENTRYID])) {
							$deletedrows = count($props[PR_ENTRYID]);
						}

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

						$this->addNotificationActionData("item", $data);

						$data = array();
						$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]);
						}

						$this->addNotificationActionData("delete", $data);
					}
					break;
			}

			$GLOBALS["bus"]->addData($this->getNotificationResponseData());
			$this->responseNotificationData = array();
		}
		
		/**
		 * Function will sort items for the month view
		 * small startdate on top.
		 */
		public static function compareCalendarItems($a, $b)
		{
			$start_a = $a["props"]["startdate"];
			$start_b = $b["props"]["startdate"];
		
		   if ($start_a == $start_b) {
		       return 0;
		   }
		   return ($start_a < $start_b) ? -1 : 1;
		}
	}
?>
