<?php
	/**
	 * ListModule
	 * Superclass of every module, which retreives a MAPI message list. It 
	 * extends the Module class.
	 */
	class ListModule extends Module
	{
		/**
		 * @var array list of columns which are selected in the previous request.
		 */
		var $properties;
		
		/**
		 * @var array sort.
		 */
		var $sort;
		
		/**
		 * @var int startrow in the table.
		 */
		var $start;
		
		/**
		 * @var array contains (when needed) a restriction used when searching
		 */
		var $searchRestriction;

		/**
		 * @var bool contains check whether a search result is listed or just the contents of a normal folder
		 */
		var $searchFolderList;

		/**
		 * @var hexString entryid of the created search folder
		 */
		var $searchFolderEntryId;

		/**
		 * @var array stores search criteria of previous request
		 */
		var $searchCriteriaCheck;

		/**
		 * @var array stores entryids and last modification time of 
		 * messages that are already sent to the server
		 */
		var $searchResults;

		/**
		 * @var MAPIFolder resource of the freebusy folder which holds
		 * information regarding delegation details, this variable will
		 * only be populated when user is a delegate
		 */
		var $localFreeBusyFolder;

		/**
		 * @var BinString binary string of PR_MDB_PROVIDER property
		 * of a store, this variable will only be populated when user is a delegate
		 */
		var $storeProviderGuid;

		/**
		 * Constructor
		 * @param int $id unique id.
		 * @param array $data list of all actions.
		 */
		function ListModule($id, $data, $events = false)
		{
			$this->start = 0;
			
			$this->searchRestriction = false;
			$this->searchFolderList = false;
			$this->searchFolderEntryId = false;
			$this->searchCriteriaCheck = false;
			$this->localFreeBusyFolder = false;
			$this->storeProviderGuid = false;

			$this->sort = array();
			
			parent::Module($id, $data, $events);
		}
		
		/**
		 * 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->getDelegateFolderInfo($store);
								$this->messageList($store, $entryid, $action, $actionType);
								break;
							default:
								$this->handleUnknownActionType($actionType);
						}
					} catch (MAPIException $e) {
						$this->processException($e, $actionType);
					}
				}
			}
		}

		/**
		 * Function does customization of MAPIException based on module data.
		 * like, here it will generate display message based on actionType
		 * for particular exception.
		 * 
		 * @param object $e Exception object
		 * @param string $actionType the action type, sent by the client
		 * @param string $entryid entryid of the message/folder.
		 * @param array $action the action data, sent by the client
		 */
		function handleException(&$e, $actionType=null, $entryid=null, $action=null)
		{
			if (is_null($e->displayMessage)) {
				switch($actionType) {
					case "list":
						if ($e->getCode() == MAPI_E_NO_ACCESS) {
							$e->setDisplayMessage(_("You have insufficient privileges to see the contents of this folder."));
						} else {
							$e->setDisplayMessage(_("Could not load the contents of this folder."));
						}
						break;
				}
			}

			parent::handleException($e, $actionType, $entryid, $action);
		}

		/**
		 * Function which retrieves a list of messages in a 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)
		{
			$this->searchFolderList = false; // Set to indicate this is not the search result, but a normal folder content

			if($store && $entryid) {
				// Restriction
				$this->parseSearchRestriction($action);

				// Sort
				$this->parseSortOrder($action, null, true);

				// Create the data array, which will be send back to the client
				$data = array();

				// filter out extra properties that are not needed in list view
				$properties = $this->properties;
				//$properties = $this->filterOutExtraProperties();

				// @TODO add implementation of https://jira.zarafa.com/browse/ZCP-9325

				// Get the table and merge the arrays
				$items = $GLOBALS["operations"]->getTable($store, $entryid, $properties, $this->sort, $this->start, false, $this->searchRestriction);

				// Disable private items
				for($index = 0, $len = count($items["item"]); $index < $len; $index++) {
					$items["item"][$index] = $this->processPrivateItem($items["item"][$index]);

					if(empty($items["item"][$index])) {
						// remove empty results from data
						unset($items["item"][$index]);
					}
				}

				// unset will remove the value but will not regenerate array keys, so we need to
				// do it here
				$items["item"] = array_values($items["item"]);

				$data = array_merge($data, $items);

				$this->addActionData("list", $data);
				$GLOBALS["bus"]->addData($this->getResponseData());
			}
		}

		/**
		 *	Function will set search restrictions on search folder and start search process
		 *	and it will also parse visible columns and sorting data when sending results to client
		 *	@param		object		$store		MAPI Message Store Object
		 *	@param		hexString	$entryid	entryid of the folder
		 *	@param		object		$action		the action data, sent by the client
		 *  @param		string		$actionType	the action type, sent by the client
		 */
		function search($store, $entryid, $action, $actionType)
		{
			$useSearchFolder = isset($action["use_searchfolder"]) ? $action["use_searchfolder"] : false;
			if(!$useSearchFolder) {
				/**
				 * store doesn't support search folders so we can't use this
				 * method instead we will pass restriction to messageList and
				 * it will give us the restricted results
				 */
				return $this->messageList($store, $entryid, $action, $actionType);
			}

			$this->searchFolderList = true; // Set to indicate this is not the normal folder, but a search folder
			$this->searchRestriction = false;

			// Parse Restriction
			$this->parseSearchRestriction($action);
			if($this->searchRestriction == false) {
				// if error in creating restriction then send error to client
				$errorInfo = array();
				$errorInfo["error_message"] = _("Error in search, please try again") . ".";
				$errorInfo["original_error_message"] = "Error in parsing restrictions.";

				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
			}

			// create or open search folder
			$searchFolder = $this->createSearchFolder($store);
			if($searchFolder === false) {
				// if error in creating search folder then send error to client
				$errorInfo = array();
				$errorInfo["error_message"] = _("Error in search, please try again") . ".";
				$errorInfo["original_error_message"] = "Error in creating search folder.";

				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
			}

			$subfolder_flag = 0;
			if(isset($action["subfolders"]) && $action["subfolders"] == "true") {
				$subfolder_flag = RECURSIVE_SEARCH;
			}

			if(!is_array($entryid)) {
				$entryids = array($entryid);
			} else {
				$entryids = $entryid;
			}

			// check if searchcriteria has changed
			$restrictionCheck = md5(serialize($this->searchRestriction) . serialize($entryids) . $subfolder_flag);

			// check if there is need to set searchcriteria again
			if($restrictionCheck != $this->searchCriteriaCheck) {
				mapi_folder_setsearchcriteria($searchFolder, $this->searchRestriction, $entryids, $subfolder_flag);
				// @FIXME currently not working as we have to retrieve/store this information in sessionData
				$this->searchCriteriaCheck = $restrictionCheck;
			}

			unset($action["restriction"]);

			// Sort
			$this->parseSortOrder($action);

			// Create the data array, which will be send back to the client
			$data = array();

			// Wait until we have some data, no point in returning before we have data. Stop waiting after 10 seconds
			$start = time();
			$table = mapi_folder_getcontentstable($searchFolder, MAPI_DEFERRED_ERRORS);
			
			while(time() - $start < 10) {
				$count = mapi_table_getrowcount($table);
				$result = mapi_folder_getsearchcriteria($searchFolder);

				// Stop looping if we have data or the search is finished
				if($count > 0)
					break;
					
				if(($result["searchstate"] & SEARCH_REBUILD) == 0)
					break; // Search is done
				
				sleep(1);
			}

			// filter out extra properties that are not needed in list view
			$properties = $this->properties;
			//$properties = $this->filterOutExtraProperties();

			// Get the table and merge the arrays
			$table = $GLOBALS["operations"]->getTable($store, hex2bin($this->searchFolderEntryId), $properties, $this->sort, $this->start);
			$data = array_merge($data, $table);

			// remember which entryid's are send to the client
			$searchResults = array();
			foreach($table["item"] as $item) {
				// store entryid => last_modification_time mapping
				$searchResults[$item["entryid"]] = $item["props"]["last_modification_time"];
			}

			// store search results into session data
			if(!isset($this->sessionData['searchResults'])) {
				$this->sessionData['searchResults'] = array();
			}
			$this->sessionData['searchResults'][$this->searchFolderEntryId] = $searchResults;

			$result = mapi_folder_getsearchcriteria($searchFolder);

			$data["search_meta"] = array();
			$data["search_meta"]["searchfolder_entryid"] = $this->searchFolderEntryId;
			$data["search_meta"]["searchstate"] = $result["searchstate"];
			$data["search_meta"]["results"] = count($searchResults);

			$this->addActionData("search", $data);
			$GLOBALS["bus"]->addData($this->getResponseData());

			return true;
		}

		/**
		 *	Function will check for the status of the search on server
		 *	and it will also send intermediate results of search, so we don't have to wait
		 *	untill search is finished on server to send results
		 *	@param		object		$store		MAPI Message Store Object
		 *	@param		hexString	$entryid	entryid of the folder
		 *	@param		object		$action		the action data, sent by the client
		 */
		function updatesearch($store, $entryid, $action)
		{
			if(!isset($entryid) || !$entryid) {
				// if no entryid is present then we can't do anything here
				return;
			}

			$listData = array();
			$folder = mapi_msgstore_openentry($store, $entryid);
			$searchResult = mapi_folder_getsearchcriteria($folder);
			$searchState = $searchResult["searchstate"];
			$table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS);

			if(is_array($this->sort) && !empty($this->sort)) {
				// this sorting will be done on currently fetched results, not all results
				// @TODO find a way to do sorting on all search results
				mapi_table_sort($table, $this->sort, TBL_BATCH);
			}

			$rowCount = $GLOBALS["settings"]->get("global/rowcount", 50);

			$searchResults = array();
			$entryid = bin2hex($entryid);
			if(isset($this->sessionData['searchResults'][$entryid])) {
				$searchResults = $this->sessionData['searchResults'][$entryid];
			}

			// searchResults contains entryids of messages
			// that are already sent to the server
			$numberOfResults = count($searchResults);

			if($numberOfResults < $rowCount) {
				$items = mapi_table_queryallrows($table, array(PR_ENTRYID, PR_LAST_MODIFICATION_TIME));

				// filter out extra properties that are not needed in list view
				$properties = $this->properties;
				//$properties = $this->filterOutExtraProperties();

				foreach($items as $props) {
					$sendItemToClient = false;

					if(!array_key_exists(bin2hex($props[PR_ENTRYID]), $searchResults)) {
						$sendItemToClient = true;
					} else {
						/**
						 * it could happen that an item in search folder has been changed
						 * after we have sent it to client, so we have to again send it
						 * so we will have to use last_modification_time of item to check
						 * that item has been modified since we have sent it to client
						 */
						// TODO if any item is deleted from search folder it will be not notified to client
						if($searchResults[bin2hex($props[PR_ENTRYID])] < $props[PR_LAST_MODIFICATION_TIME]) {
							$sendItemToClient = true;
						}
					}

					if($sendItemToClient) {
						// only get primitive properties, no need to get body, attachments or recipient information
						$message = $GLOBALS["operations"]->openMessage($store, $props[PR_ENTRYID]);
						array_push($listData, $GLOBALS["operations"]->getProps($store, $message, $properties));

						// store entryid => last_modification_time mapping
						$searchResults[bin2hex($props[PR_ENTRYID])] = $props[PR_LAST_MODIFICATION_TIME];
					}

					// when we have more results then fit in the client, we break here,
					// we only need to update the counters from this point
					$numberOfResults = count($searchResults);
					if($numberOfResults >= $rowCount) {
						break; 
					}
				}
			}

			$totalRowCount = mapi_table_getrowcount($table);

			$data = array();
			$data["search_meta"] = array();
			$data["search_meta"]["searchfolder_entryid"] = $entryid;
			$data["search_meta"]["searchstate"] = $searchState;
			$data["search_meta"]["results"] = $numberOfResults;		// actual number of items that we are sending to client

			$data["page"] = array();
			$data["page"]["start"] = 0;
			$data["page"]["rowcount"] = $rowCount;
			$data["page"]["totalrowcount"] = $totalRowCount;	// total number of items

			if(!empty($listData)) {
				$data["item"] = array_merge(array(), $listData);
			}

			// search is finished so we no more need entryids of search results so clear it up
			if($searchState & SEARCH_REBUILD === 0) {
				// remove search result entryids stored in session
				unset($this->sessionData['searchResults'][$entryid]);
			} else {
				// store data for next request
				$this->sessionData['searchResults'][$entryid] = $searchResults;
			}

			$this->addActionData("updatesearch", $data);
			$GLOBALS["bus"]->addData($this->getResponseData());

			return true;
		}

		/**
		 *	Function will stop search on the server if search folder exists
		 *	@param		object		$store		MAPI Message Store Object
		 *	@param		hexString	$entryid	entryid of the folder
		 *	@param		object		$action		the action data, sent by the client
		 */
		function stopSearch($store, $entryid, $action)
		{
			if(!isset($entryid) || !$entryid) {
				// if no entryid is present then we can't do anything here
				return;
			}

			// remove search result entryids stored in session
			unset($this->sessionData['searchResults'][bin2hex($entryid)]);

			$searchFolder = mapi_msgstore_openentry($store, $entryid);
			$searchResult = mapi_folder_getsearchcriteria($searchFolder);

			// check if search folder exists and search is in progress
			if($searchResult !== false && ($searchResult["searchstate"] & SEARCH_REBUILD !== 0)) {
				mapi_folder_setsearchcriteria($searchFolder, $searchResult['restriction'], $searchResult['folderlist'], STOP_SEARCH);
			}

			/**
			 * when stopping search process, we have to remove search folder also,
			 * so next search request with same restriction will not get uncompleted results
			 */
			if(method_exists($this, "deleteSearchFolder")) {
				$this->deleteSearchFolder($store, $entryid, $action);
			}

			// send success message to client
			$this->addActionData("stopsearch", array( 'success' => true ));
			$GLOBALS["bus"]->addData($this->getResponseData());
		}

		/**
		 * Function will delete search folder
		 * @param		object			$store		MAPI Message Store Object
		 * @param		hexString		$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 deleteSearchFolder($store, $entryid, $action)
		{
			if($entryid && $store) {
				$storeProps = mapi_getprops($store, array(PR_FINDER_ENTRYID));

				$finderFolder = mapi_msgstore_openentry($store, $storeProps[PR_FINDER_ENTRYID]);

				if(mapi_last_hresult() != NOERROR){
					return;
				}

				$hierarchyTable = mapi_folder_gethierarchytable($finderFolder, MAPI_DEFERRED_ERRORS);

				$restriction = array(RES_CONTENT,
										array(
											FUZZYLEVEL	=> FL_FULLSTRING,
											ULPROPTAG	=> PR_ENTRYID,
											VALUE		=> array(PR_ENTRYID => $entryid)
											)
									);

				mapi_table_restrict($hierarchyTable, $restriction, TBL_BATCH);

				// entryids are unique so there would be only one matching row, 
				// so only fetch first row
				$folders = mapi_table_queryrows($hierarchyTable, array(PR_ENTRYID), 0, 1);

				// delete search folder
				if(is_array($folders) && is_array($folders[0])) {
					mapi_folder_deletefolder($finderFolder, $folders[0][PR_ENTRYID]);
				}

				// reset variables
				$this->searchFolderEntryId = false;
				$this->searchCriteriaCheck = false;

				return true;
			}

			return false;
		}

		/**
		 *	Function will create a search folder in FINDER_ROOT folder
		 *	if folder exists then it will open it
		 *	@param		object				$store			MAPI Message Store Object
		 *	@param		boolean				$openIfExists	open if folder exists
		 *	@return		mapiFolderObject	$folder			created search folder
		 */
		function createSearchFolder($store, $openIfExists = true)
		{
			if($this->searchFolderEntryId && $openIfExists) {
				$searchFolder = mapi_msgstore_openentry($store, hex2bin($this->searchFolderEntryId));
				if($searchFolder !== false && mapi_last_hresult() == NOERROR) {
					// search folder exists, don't create new search folder
					return $searchFolder;
				}
			}

			// create new search folder
			$searchFolderRoot = $this->getSearchFoldersRoot($store);
			if($searchFolderRoot === false) {
				// error in finding search root folder
				// or store doesn't support search folders
				return false;
			}

			// check for folder name conflict, if conflicts then function will return new name
			$folderName = $GLOBALS["operations"]->checkFolderNameConflict($store, $searchFolderRoot, "WebApp Search Folder");
			try {
				$searchFolder = mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, FOLDER_SEARCH);

				$props = mapi_getprops($searchFolder, array(PR_ENTRYID));
				$this->searchFolderEntryId = bin2hex($props[PR_ENTRYID]);

				return $searchFolder;
			} catch (MAPIException $e) {
				// don't propogate the event to higher level exception handlers
				$e->setHandled();
			}

			return false;
		}

		/**
		 *	Function will open FINDER_ROOT folder in root container
		 *	public folder's don't have FINDER_ROOT folder
		 *	@param		object				$store		MAPI message store object
		 *	@return		mapiFolderObject	root		folder for search folders
		 */
		function getSearchFoldersRoot($store)
		{
			$searchRootFolder = true;

			// check if we can create search folders
			$storeProps = mapi_getprops($store, array(PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID));
			if(($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
				// store doesn't support search folders
				// public store don't have FINDER_ROOT folder
				$searchRootFolder = false;
			}

			if($searchRootFolder) {
				// open search folders root
				try {
					$searchRootFolder = mapi_msgstore_openentry($store, $storeProps[PR_FINDER_ENTRYID]);
				} catch (MAPIException $e) {
					$searchRootFolder = false;

					// don't propogate the event to higher level exception handlers
					$e->setHandled();
				}
			}

			return $searchRootFolder;
		}

		/**
		 *	Function will send error message to client if any error has occured in search
		 *	@param		object		$store		MAPI Message Store Object
		 *	@param		hexString	$entryid	entryid of the folder
		 *	@param		object		$action		the action data, sent by the client
		 *	@param		object		$errorInfo	the error information object
		 */
		function sendSearchErrorToClient($store, $entryid, $action, $errorInfo)
		{
			if($errorInfo) {
				$exception = new SearchException(isset($errorInfo["original_error_message"]) ? $errorInfo["original_error_message"] : $errorInfo['error_message'], mapi_last_hresult());
				$exception->setDisplayMessage($errorInfo['error_message']);

				// after sending error, remove error data
				$errorInfo = array();

				throw $exception;
			}

			return false;
		}

		/**
		 *	Function will create search restriction based on restriction array.
		 *	@param		object		$action		the action data, sent by the client
		 */
		function parseSearchRestriction($action)
		{
			if(isset($action["restriction"])) {
				if(isset($action["restriction"]["start"])) {
					// Set start variable
					$this->start = $action["restriction"]["start"];
				}

				if(!empty($action["restriction"]["search"])) {
					$this->searchRestriction = Conversion::json2restriction($action["restriction"]["search"]);
				} else {
					$this->searchRestriction = false;
				}
			}
		}

		/**
		 * Parses the incoming sort request and builds a MAPI sort order. Normally
		 * properties are mapped from the XML to MAPI by the standard $this->properties mapping. However,
		 * if you want other mappings, you can specify them in the optional $map mapping.
		 * 
		 * $allow_multi_instance is used for creating multiple instance of MV property related items.
		 * $properties is used for using a custom set of properties instead of properties stored in module
		 */
		function parseSortOrder($action, $map = false, $allow_multi_instance = false, $properties = false)
		{
			if(isset($action["sort"])) {
				$this->sort = array();

				if(!$properties) {
					$properties = $this->properties;
				}

				// Unshift MVI_FLAG of MV properties. So the table is not sort on it anymore.
				// Otherwise the server would generate multiple rows for one item (categories).
				foreach($properties as $id => $property)
				{
					switch(mapi_prop_type($property)) 
					{
						case (PT_MV_STRING8 | MVI_FLAG):
						case (PT_MV_LONG | MVI_FLAG):
							$properties[$id] = $properties[$id] &~ MV_INSTANCE;
							break;
					}
				}

				// Loop through the sort columns
				foreach($action["sort"] as $column)
				{
					if(isset($column["direction"])) {
						if(isset($properties[$column["field"]]) || ($map && isset($map[$column["field"]]))) {
							if($map && isset($map[$column["field"]])) 
								$property = $map[$column["field"]];
							else
								$property = $properties[$column["field"]];
							
							// Check if column is a MV property
							switch(mapi_prop_type($property)) 
							{
								case PT_MV_STRING8:
								case PT_MV_LONG:
									// Set MVI_FLAG.
									// The server will generate multiple rows for one item (for example: categories)
									if($allow_multi_instance){
										$properties[$column["field"]] = $properties[$column["field"]] | MVI_FLAG;
									}
									$property = $properties[$column["field"]];
									break;
							}

							// Set sort direction
							switch(strtolower($column["direction"]))
							{
								default:
								case "asc":
									$this->sort[$property] = TABLE_SORT_ASCEND;
									break;
								case "desc":
									$this->sort[$property] = TABLE_SORT_DESCEND;
									break;
							}
						}
					}
				}
			}
		}

		/**
		 * Function is used to filter out properties which aren't usefull in the listview and we should
		 * remove it when getting listview data, this function gives us performance benefit of sending less data
		 * in listview because sending properties like body, message_transport_headers in list view can increase
		 * the response data a lot.
		 * We don't want to store filtered properties in the module object because module object should always
		 * have list of all properties.
		 * this function is used as a placeholder here, child functions can override this function to filter
		 * properties based on its own logic.
		 * @return {Object} filtered properties that will be used to get data for list view.
		 */
		function filterOutExtraProperties()
		{
			return $this->properties;
		}

		/**
		 * Function which create a task from a mail by copying its subject and body.
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid entryid of the item
		 * @return boolean true on success or false on failure		 		 
		 */
		function createTaskFromMail($store, $entryid)
		{
			if($store && $entryid) {
				$inbox = mapi_msgstore_getreceivefolder($store);
				$inboxprops = mapi_getprops($inbox, Array(PR_IPM_TASK_ENTRYID, PR_STORE_ENTRYID));
				$parententryid = bin2hex($inboxprops[PR_IPM_TASK_ENTRYID]);

				// open the userstore to fetch the name of the owner of the store.
				$userstore = mapi_openmsgstore($GLOBALS["mapisession"]->getSession(),$inboxprops[PR_STORE_ENTRYID]);
				$userstoreprops = mapi_getprops($userstore, Array(PR_MAILBOX_OWNER_NAME));

				// open the mail to fetch the subject, body and importance.
				$message = mapi_msgstore_openentry($store, $entryid);
				$msgprops= mapi_getprops($message, Array(PR_SUBJECT, PR_BODY, PR_IMPORTANCE));

				$props = array ('message_class' => 'IPM.Task',
								'icon_index' => '1280',
								'subject' => $msgprops[PR_SUBJECT],
								'importance' => $msgprops[PR_IMPORTANCE],
								'body' => $msgprops[PR_BODY],
								'owner' => $userstoreprops[PR_MAILBOX_OWNER_NAME],
								'complete' => "false",
								);
				
				$messageProps = array(); // returned props
				$result = $GLOBALS["operations"]->saveMessage($store, hex2bin($parententryid), false, Conversion::mapXML2MAPI($GLOBALS["properties"]->getTaskProperties(), $props), $messageProps);
				
				$data = array();
				$data["success"] = $result;

				$this->addActionData("task_created", $data);
				$GLOBALS["bus"]->addData($this->getResponseData());

				if($result) {
					$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_SAVE, $messageProps);
				}
			}
		}

		/**
		 * 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])) {
							$data = $GLOBALS["operations"]->getMessageProps($store, $GLOBALS["operations"]->openMessage($store, $props[PR_ENTRYID]), $this->properties);
							$this->addNotificationActionData("update", array( "item" => array($data) ));
						} else if (isset($props[PR_PARENT_ENTRYID])) {
							// 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.
							$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:
					// When user has used searchfolder the entryID of that folder should be used.
					if(!$this->searchFolderList){
						$folderEntryID = (isset($props[PR_PARENT_ENTRYID]))?$props[PR_PARENT_ENTRYID]:false;
					}else{
						$folderEntryID = (isset($this->searchFolderEntryId))?hex2bin($this->searchFolderEntryId):false;
					}

					if(isset($props[PR_ENTRYID]) && $folderEntryID) {
						// Get items, which are shown under the table.
						$store = $GLOBALS["mapisession"]->openMessageStore($props[PR_STORE_ENTRYID]);
						
						$deletedrows = 1;
						if(is_array($props[PR_ENTRYID])) {
							$deletedrows = count($props[PR_ENTRYID]);
						}

						$newItemsStart = ($this->start + $GLOBALS["settings"]->get("global/rowcount", 50)) - $deletedrows;
						$newItems = $GLOBALS["operations"]->getTable($store, $folderEntryID, $this->properties, $this->sort, $newItemsStart, $deletedrows, $this->searchRestriction);
						
						if(!empty($newItems["item"])) {
							$data = array();
							$data["delete"] = 1;
							if($this->searchFolderList){
								// @FIXME do we need this attribute?
								//$data["attributes"] = array("type" => "item", "searchfolder" => $this->searchFolderEntryId);
							}
							$data["item"] = $newItems["item"];

							$this->addNotificationActionData("update", array( "item" => array($data) ));
						}
						
						$data = array();
						$data["page"] = $newItems["page"];
						$data["page"]["start"] = $this->start;
						$data["page"]["rowcount"] = $GLOBALS["settings"]->get("global/rowcount", 50);
						$data["parent_entryid"] = bin2hex($folderEntryID);
						
						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", array( "item" => array($data) ));
					}
					break;
			}

			$GLOBALS["bus"]->addData($this->getNotificationResponseData());
			$this->responseNotificationData = array();
		}

		/**
		 * Function which gets the delegation details from localfreebusy folder to use in
		 * processPrivateItems function.
		 * @param {MAPIStore} $store MAPI Message Store Object
		 */
		function getDelegateFolderInfo($store)
		{
			$this->localFreeBusyFolder = false;
			$this->storeProviderGuid = false;

			try {
				$this->storeProviderGuid = mapi_getprops($store, array(PR_MDB_PROVIDER));
				$this->storeProviderGuid = $this->storeProviderGuid[PR_MDB_PROVIDER];

				if($this->storeProviderGuid !== ZARAFA_STORE_DELEGATE_GUID) {
					// user is not a delegate, so no point of processing further
					return;
				}

				// 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
				$this->localFreeBusyFolder = mapi_msgstore_openentry($store, $rootFolderProps[PR_FREEBUSY_ENTRYIDS][1]);
			} catch(MAPIException $e) {
				// we got some error, but we don't care about that error instead just continue
				$e->setHandled();

				$this->localFreeBusyFolder = false;
				$this->storeProviderGuid = false;
			}
		}

		/**
		 * 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 entirely remove the private message but
		 * if any child class needs different behavior then this can be overriden.
		 * @param {Object} $item item properties
		 * @return {Object} item properties if its non private item otherwise empty array
		 */
		function processPrivateItem($item)
		{
			if($this->checkPrivateItem($item)) {
				// hide the item by returning empty array, that can be removed from response
				return array();
			}

			return $item;
		}

		/**
		 * Function will be used check if any item is private or not and if it private then
		 * we should process it as private, because you don't want to process private items in
		 * user's default store.
		 * This function will check we are dealing with delegate stores or not if it is then
		 * the delegator has permission to see private items of delegate.
		 * @param {Object} $item item properties
		 * @return {Boolean} true if items should be processed as private else false.
		 */
		function checkPrivateItem($item)
		{
			// flag to indicate that item should be considered as private
			$private = false;

			$isPrivate = (isset($item['props']['private']) && $item['props']['private'] === true);
			$isSensitive = (isset($item['props']['sensitivity']) && $item['props']['sensitivity'] === SENSITIVITY_PRIVATE);

			if($isPrivate && $isSensitive) {
				// check for delegate permissions for delegate store
				if($this->storeProviderGuid !== false && $this->storeProviderGuid === ZARAFA_STORE_DELEGATE_GUID) {
					// by default we should always hide the item if we are in delegate store
					$private = true;

					// find delegate properties
					if($this->localFreeBusyFolder !== false) {
						try {
							$localFreeBusyFolderProps = mapi_getprops($this->localFreeBusyFolder, array(PR_SCHDINFO_DELEGATE_ENTRYIDS, PR_DELEGATES_SEE_PRIVATE));

							if(isset($localFreeBusyFolderProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]) && isset($localFreeBusyFolderProps[PR_DELEGATES_SEE_PRIVATE])) {
								// 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] === 1) {
									// if delegate has permission then don't hide the item
									$private = false;
								}
							}
						} catch (MAPIException $e) {
							if($e->getCode() === MAPI_E_NOT_FOUND) {
								// no information available for delegates, ignore error
								$e->setHandled();
							}
						}
					}
				}
			}

			return $private;
		}
	}
?>
