<?php
	/**
	* General operations
	*
	* All mapi operations, like create, change and delete, are set in this class.
	* A module calls one of these methods. 
	*
	* Note: All entryids in this class are binary
	*
	* @todo This class is bloated. It also returns data in various arbitrary formats
	* that other functions depend on, making lots of code almost completely unreadable.
	* @package core
	*/

	include_once("server/core/class.filter.php");
	include_once("mapi/class.recurrence.php");
	include_once("mapi/class.taskrecurrence.php");
	include_once("mapi/class.meetingrequest.php");
	include_once("mapi/class.taskrequest.php");
	include_once("mapi/class.freebusypublish.php");

	class Operations 
	{
		function Operations()
		{
		}
		
		/**
		* Gets the hierarchy list of all required stores.
		*
		* getHierarchyList builds an entire hierarchy list of all folders that should be shown in various places. Most importantly,
		* it generates the list of folders to be show in the hierarchylistmodule (left-hand folder browser) on the client.
		*
		* It is also used to generate smaller hierarchy lists, for example for the 'create folder' dialog.
		*
		* The returned array is a flat array of folders, so if the caller wishes to build a tree, it is up to the caller to correlate
		* the entryids and the parent_entryids of all the folders to build the tree.
		*
		* The return value is an associated array with the following keys:
		* - store: array of stores
		*
		* Each store contains:
		* - array("store_entryid" => entryid of store, name => name of store, subtree => entryid of viewable root, type => default|public|other, folder_type => "all")
		* - folder: array of folders with each an array of properties (see Operations::setFolder() for properties)
		*
		* @param array $properties MAPI property mapping for folders
		* @param int $type Which stores to fetch (HIERARCHY_GET_ALL | HIERARCHY_GET_DEFAULT | HIERARCHY_GET_ONE)
		* @param object $store Only when $type == HIERARCHY_GET_ONE
		* @param array $storeOptions Only whe $type == HIERARCHY_GET_ONE, this overrides the  loading options which is normally
		* obtained frrom the settings for loading the store (e.g. only load calendar).
		*
		* @return array Return structure
		*/
		function getHierarchyList($properties, $type = HIERARCHY_GET_ALL, $store = null, $storeOptions = null)
		{
			
			switch($type) 
			{
				case HIERARCHY_GET_ALL:
					$storelist = $GLOBALS["mapisession"]->getAllMessageStores();
					break;
				
				case HIERARCHY_GET_DEFAULT: 
					$storelist = array($GLOBALS["mapisession"]->getDefaultMessageStore());
					break;
					
				case HIERARCHY_GET_ONE:
					$storelist = array($store);
					break;
			}
						
			$data = array();
			$data["item"] = array();

			// Get the other store options
			if (isset($storeOptions)) {
				$otherUsers = $storeOptions;
			} else {
				$otherUsers = $GLOBALS["mapisession"]->retrieveOtherUsersFromSettings();
			}

			foreach($storelist as $store)
			{
				$msgstore_props = mapi_getprops($store, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_OBJECT_TYPE, PR_STORE_SUPPORT_MASK, PR_MAILBOX_OWNER_ENTRYID, PR_MAILBOX_OWNER_NAME, PR_USER_ENTRYID, PR_USER_NAME));

				$inboxProps = array();

				// @TODO remove this
				switch ($msgstore_props[PR_MDB_PROVIDER])
				{
					case ZARAFA_SERVICE_GUID:
						$storeType = "default";
						break;
					case ZARAFA_STORE_PUBLIC_GUID:
						$storeType = "public";
						break;
					case ZARAFA_STORE_DELEGATE_GUID:
						$storeType = "other";
						break;
				}

				/**
				 * storetype is public and if public folder is disabled
				 * then continue in loop for next store.
				 */
				if($storeType == "public" && ENABLE_PUBLIC_FOLDERS == false)
					continue;

				// Obtain the real username for the store when dealing with a shared store
				if($storeType == "other"){
					$storeUserName = $GLOBALS["mapisession"]->getUserNameOfStore($msgstore_props[PR_ENTRYID]);
				}else{
					$storeUserName = $msgstore_props[PR_USER_NAME];
				}

				$storeData = array(
					"store_entryid" => bin2hex($msgstore_props[PR_ENTRYID]),
					"props" => array(
						"display_name" => $msgstore_props[PR_DISPLAY_NAME], 
						"subtree_entryid" => bin2hex($msgstore_props[PR_IPM_SUBTREE_ENTRYID]),
						"mdb_provider" => bin2hex($msgstore_props[PR_MDB_PROVIDER]),
						"object_type" => $msgstore_props[PR_OBJECT_TYPE],
						"store_support_mask" => $msgstore_props[PR_STORE_SUPPORT_MASK],
						"user_name" => $storeUserName,
						"user_entryid" => bin2hex($msgstore_props[PR_USER_ENTRYID])
					)
				);

				// these properties doesn't exist in public store
				if(isset($msgstore_props[PR_MAILBOX_OWNER_ENTRYID]) && isset($msgstore_props[PR_MAILBOX_OWNER_NAME])) {
					$storeData["props"]["mailbox_owner_entryid"] = bin2hex($msgstore_props[PR_MAILBOX_OWNER_ENTRYID]); 
					$storeData["props"]["mailbox_owner_name"] = $msgstore_props[PR_MAILBOX_OWNER_NAME];
				}

				// public store doesn't have inbox
				try {
					$inbox = mapi_msgstore_getreceivefolder($store);
					$inboxProps = mapi_getprops($inbox, array(PR_ENTRYID));
				} catch (MAPIException $e) {
					// don't propogate this error to parent handlers, if store doesn't support it
					if($e->getCode() === MAPI_E_NO_SUPPORT) {
						$e->setHandled();
					}
				}

				$root = mapi_msgstore_openentry($store, null);
				$rootProps = mapi_getprops($root, array(PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS));

				$additional_ren_entryids = array();
				if(isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) $additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS];

				$defaultfolders = array(
						"default_folder_inbox"			=>	array("inbox"=>PR_ENTRYID),
						"default_folder_outbox"			=>	array("store"=>PR_IPM_OUTBOX_ENTRYID), 
						"default_folder_sent"			=>	array("store"=>PR_IPM_SENTMAIL_ENTRYID),
						"default_folder_wastebasket"	=>	array("store"=>PR_IPM_WASTEBASKET_ENTRYID),
						"default_folder_favorites"		=>	array("store"=>PR_IPM_FAVORITES_ENTRYID),
						"default_folder_publicfolders"	=>	array("store"=>PR_IPM_PUBLIC_FOLDERS_ENTRYID),
						"default_folder_calendar"		=>	array("root" =>PR_IPM_APPOINTMENT_ENTRYID),
						"default_folder_contact"		=>	array("root" =>PR_IPM_CONTACT_ENTRYID),
						"default_folder_drafts"			=>	array("root" =>PR_IPM_DRAFTS_ENTRYID),
						"default_folder_journal"		=>	array("root" =>PR_IPM_JOURNAL_ENTRYID),
						"default_folder_note"			=>	array("root" =>PR_IPM_NOTE_ENTRYID),
						"default_folder_task"			=>	array("root" =>PR_IPM_TASK_ENTRYID),
						"default_folder_junk"			=>	array("additional" =>4),
						"default_folder_syncissues"		=>	array("additional" =>1),
						"default_folder_conflicts"		=>	array("additional" =>0),
						"default_folder_localfailures"	=>	array("additional" =>2),
						"default_folder_serverfailures"	=>	array("additional" =>3),
				);

				foreach($defaultfolders as $key=>$prop){
					$tag = reset($prop);
					$from = key($prop);
					switch($from){
						case "inbox":
							if(isset($inboxProps[$tag])) {
								$storeData["props"][$key] = bin2hex($inboxProps[$tag]);
							}
							break;
						case "store":
							if(isset($msgstore_props[$tag])) {
								$storeData["props"][$key] = bin2hex($msgstore_props[$tag]);
							}
							break;
						case "root":
							if(isset($rootProps[$tag])) {
								$storeData["props"][$key] = bin2hex($rootProps[$tag]);
							}
							break;
						case "additional":
							if(isset($additional_ren_entryids[$tag])) {
								$storeData["props"][$key] = bin2hex($additional_ren_entryids[$tag]);
							}
							break;
					}
				}
				
				$storeData["folders"] = array( "item" => array() );

				if (isset($msgstore_props[PR_IPM_SUBTREE_ENTRYID])) {
					$subtreeFolderEntryID = $msgstore_props[PR_IPM_SUBTREE_ENTRYID];

					$openWholeStore = true;
					if($storeType == "other") {
						$username = strtolower($storeData["props"]["user_name"]);
						$sharedFolders = array();

						// Check whether we should open the whole store or just single folders
						if(is_array($otherUsers)) {
							if(isset($otherUsers[ $username ])){
								$sharedFolders = $otherUsers[ $username ];
								if(!isset($otherUsers[ $username ]['all'])){
									$openWholeStore = false;
								}
							}
						}

						// Update the store properties when this function was called to
						// only open a particular shared store.
						if (is_array($storeOptions)) {
							// Update the store properties to mark previously opened 
							$prevSharedFolders = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores/" . $username ,null);
							if (!empty($prevSharedFolders)) {
								foreach ($prevSharedFolders as $type => $prevSharedFolder) {
									// Update the store properties to refer to the shared folder,
									// note that we don't care if we have access to the folder or not.
									$type = $prevSharedFolder["folder_type"];
									if ($type == "all") {
										$propname = "subtree_entryid";
									} else {
										$propname = "default_folder_" . $prevSharedFolder["folder_type"];
									}

									if (isset($storeData["props"][$propname])) {
										$folderEntryID = hex2bin($storeData["props"][$propname]);
										$storeData["props"]["shared_folder_" . $prevSharedFolder["folder_type"]] = bin2hex($folderEntryID);
									}
								}
							}
						}
					}

					// Get the IPMSUBTREE object
					$storeAccess = true;
					try {
						$subtreeFolder = mapi_msgstore_openentry($store, $subtreeFolderEntryID);
						// Add root folder
						$subtree = $this->setFolder(mapi_getprops($subtreeFolder, $properties));
						if (!$openWholeStore) {
							$subtree['props']['access'] = 0;
						}
						array_push($storeData["folders"]["item"], $subtree);
					} catch (MAPIException $e) {
						if($openWholeStore) {
							/*
							 * if we are going to open whole store and we are not able to open the subtree folder
							 * then it should be considered as an error
							 * but if we are only opening single folder then it could be possible that we don't have
							 * permission to open subtree folder so add a dummy subtree folder in the response and don't consider this as an error
							 */
							$storeAccess = false;
						}

						// Add properties to the store response to indicate to the client
						// that the store could not be loaded.
						$this->invalidateResponseStore($storeData, 'all', $subtreeFolderEntryID);

						// We've handled the event
						$e->setHandled();
					}

					if($storeAccess){
						// Open the whole store and be done with it
						if($openWholeStore){
							try {
								if($storeType != "public") {
									// Update the store properties to refer to the shared folder,
									// note that we don't care if we have access to the folder or not.
									$storeData["props"]["shared_folder_all"] = bin2hex($subtreeFolderEntryID);

									// Recursively add all subfolders
									$this->getSubFolders($subtreeFolder, $store, $properties, $storeData);
								} else {
									$this->getSubFoldersPublic($subtreeFolder, $store, $properties, $storeData);
								}
							} catch (MAPIException $e) {
								// Add properties to the store response to indicate to the client
								// that the store could not be loaded.
								$this->invalidateResponseStore($storeData, 'all', $subtreeFolderEntryID);

								// We've handled the event
								$e->setHandled();
							}

						// Open single folders under the store object
						}else{
							foreach($sharedFolders as $type => $sharedFolder){
								$openSubFolders = ($sharedFolder["show_subfolders"] == true);

								// See if the folders exists by checking if it is in the default folders entryid list
								$store_access = true;
								if (!isset($storeData["props"]["default_folder_" . $sharedFolder["folder_type"]])){
									// Create a fake folder entryid which must be used for referencing this folder
									$folderEntryID = "default_folder_" . $sharedFolder["folder_type"];

									// Add properties to the store response to indicate to the client
									// that the store could not be loaded.
									$this->invalidateResponseStore($storeData, $type, $folderEntryID);

									// Update the store properties to refer to the shared folder,
									// note that we don't care if we have access to the folder or not.
									$storeData["props"]["shared_folder_" . $sharedFolder["folder_type"]] = bin2hex($folderEntryID);

									// Indicate that we don't have access to the store,
									// so no more attempts to read properties or open entries.
									$store_access = false;

								// If you access according to the above check, go ahead and retrieve the MAPIFolder object
								}else{
									$folderEntryID = hex2bin($storeData["props"]["default_folder_" . $sharedFolder["folder_type"]]);

									// Update the store properties to refer to the shared folder,
									// note that we don't care if we have access to the folder or not.
									$storeData["props"]["shared_folder_" . $sharedFolder["folder_type"]] = bin2hex($folderEntryID);

									try {
										// load folder props
										$folder = mapi_msgstore_openentry($store, $folderEntryID);
									} catch (MAPIException $e) {
										// Add properties to the store response to indicate to the client
										// that the store could not be loaded.
										$this->invalidateResponseStore($storeData, $type, $folderEntryID);

										// Indicate that we don't have access to the store,
										// so no more attempts to read properties or open entries.
										$store_access = false;

										// We've handled the event
										$e->setHandled();
									}
								}

								// Check if a error handler already inserted a error folder,
								// or if we can insert the real folders here.
								if ($store_access === true) {
									// check if we need subfolders or not
									if($openSubFolders === true) {
										// add folder data (with all subfolders recursively)
										// get parent folder's properties
										$folderProps = mapi_getprops($folder, $properties);
										$tempFolderProps = $this->setFolder($folderProps);

										array_push($storeData["folders"]["item"], $tempFolderProps);

										// get subfolders
										if($tempFolderProps["props"]["has_subfolder"] != false) {
											$subfoldersData = array();
											$subfoldersData["folders"]["item"] = array();
											$this->getSubFolders($folder, $store, $properties, $subfoldersData);

											$storeData["folders"]["item"] = array_merge($storeData["folders"]["item"], $subfoldersData["folders"]["item"]);
										}
									} else {
										$folderProps = mapi_getprops($folder, $properties);
										$tempFolderProps = $this->setFolder($folderProps);
										$tempFolderProps["props"]["has_subfolder"] = false;
										array_push($storeData["folders"]["item"], $tempFolderProps);
									}
								}

							}

						}
					}
					array_push($data["item"], $storeData);
				}
			}

			return $data;
		}
		
		/**
		 * Helper function to get the subfolders of a folder
		 *
		 * @access private
		 * @param object $folder Mapi Folder Object.
		 * @param object $store Message Store Object
		 * @param array $properties MAPI property mappings for folders
		 * @param array $storeData Reference to an array. The folder properties are added to this array.
		 */
		function getSubFolders($folder, $store, $properties, &$storeData)
		{
			$hierarchyTable = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);

			/**
			 * remove hidden folders, folders with PR_ATTR_HIDDEN property set
			 * should not be shown to the client
			 */
			$restriction =	Array(RES_OR,
								Array(
									Array(RES_PROPERTY,
										Array(
											RELOP => RELOP_EQ,
											ULPROPTAG => PR_ATTR_HIDDEN,
											VALUE => Array( PR_ATTR_HIDDEN => false )
										)
									),
									Array(RES_NOT,
										Array(
											Array(RES_EXIST,
												Array(
													ULPROPTAG => PR_ATTR_HIDDEN
												)
											)
										)
									)
								)
							);

			// Make sure our folders are sorted by name. The ordering from the depth is not important
			// because we send the parent in the folder information anyway.
			mapi_table_sort($hierarchyTable, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH);
			$subfolders = mapi_table_queryallrows($hierarchyTable, array_merge(Array(PR_ENTRYID,PR_SUBFOLDERS), $properties), $restriction);

			if (is_array($subfolders)) {
				foreach($subfolders as $subfolder)
				{
					array_push($storeData["folders"]["item"], $this->setFolder($subfolder));
				} 
			} 
		}
		
		/**
		 * Helper to loop through the hierarchy list.in the public and replace the parent entry id
		 *
		 * @access private
		 * @param object $folder Mapi Folder Object
		 * @param object $store Message Store Object
		 * @param array $properties MAPI property mappings for folders
		 * @param array $storeData Reference to an array. The folder properties are added to this array
		*/
		function getSubFoldersPublic($folder, $store, $properties, &$storeData)
		{
			$hierarchyTable = mapi_folder_gethierarchytable($folder, MAPI_DEFERRED_ERRORS);
			mapi_table_sort($hierarchyTable, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH);

			if (mapi_table_getrowcount($hierarchyTable) == 0) {
				return false;
			}

			/**
			 * remove hidden folders, folders with PR_ATTR_HIDDEN property set
			 * should not be shown to the client
			 */
			$restriction =	Array(RES_OR,
								Array(
									Array(RES_PROPERTY,
										Array(
											RELOP => RELOP_EQ,
											ULPROPTAG => PR_ATTR_HIDDEN,
											VALUE => Array( PR_ATTR_HIDDEN => false )
										)
									),
									Array(RES_NOT,
										Array(
											Array(RES_EXIST,
												Array(
													ULPROPTAG => PR_ATTR_HIDDEN
												)
											)
										)
									)
								)
							);

			$subfolders = mapi_table_queryallrows($hierarchyTable, array_merge(Array(PR_ENTRYID,PR_SUBFOLDERS,PR_PARENT_ENTRYID), $properties), $restriction);

			$folderEntryid = mapi_getprops($folder, array(PR_ENTRYID));
			if (is_array($subfolders)) {
				foreach($subfolders as $subfolder)
				{
					$subfolder[PR_PARENT_ENTRYID] = $folderEntryid[PR_ENTRYID];

					if($subfolder[PR_SUBFOLDERS]) {
						$folderObject = mapi_msgstore_openentry($store, $subfolder[PR_ENTRYID]);

						if ($folderObject) {
							if($this->getSubFoldersPublic($folderObject, $store, $properties, $storeData) == false)
								$subfolder[PR_SUBFOLDERS] = false;
						}
					}
					array_push($storeData["folders"]["item"], $this->setFolder($subfolder));
				}
			}
			
			return true;
		}

		/**
		 * Convert MAPI properties into useful XML properties for a folder
		 *
		 * @access private
		 * @param array $folderProps Properties of a folder
		 * @return array List of properties of a folder
		 * @todo The name of this function is misleading because it doesn't 'set' anything, it just reads some properties.
		 */ 		 		
		function setFolder($folderProps)
		{
			$props = array(
				// Identification properties
				"entryid" => bin2hex($folderProps[PR_ENTRYID]),
				"parent_entryid" => bin2hex($folderProps[PR_PARENT_ENTRYID]),
				"store_entryid" => bin2hex($folderProps[PR_STORE_ENTRYID]),

				// Scalar properties
				"props" => array(
					"display_name" => $folderProps[PR_DISPLAY_NAME],
					"object_type" => isset($folderProps[PR_OBJECT_TYPE]) ? $folderProps[PR_OBJECT_TYPE] : 3, // FIXME: Why isn't this always set?
					"content_count" => isset($folderProps[PR_CONTENT_COUNT]) ? $folderProps[PR_CONTENT_COUNT] : 0,
					"content_unread" => isset($folderProps[PR_CONTENT_UNREAD]) ? $folderProps[PR_CONTENT_UNREAD] : 0,
					"has_subfolder" => isset($folderProps[PR_SUBFOLDERS])? $folderProps[PR_SUBFOLDERS] : false,
					"container_class" => isset($folderProps[PR_CONTAINER_CLASS]) ? $folderProps[PR_CONTAINER_CLASS] : "IPF.Note",
					"access" => $folderProps[PR_ACCESS],
					"rights" => isset($folderProps[PR_RIGHTS]) ? $folderProps[PR_RIGHTS] : ecRightsNone
				)
			);

			$this->setExtendedFolderFlags($folderProps, $props);

			return $props;
		}

		/**
		 * Fetches extended flags for folder. If PR_EXTENDED_FLAGS is not set then we assume that client
		 * should handle which property to display
		 * @access private
		 * @param array $folderProps Properties of a folder
		 * @param array $props properties in which flags should be set
		 */
		function setExtendedFolderFlags($folderProps, &$props)
		{
			if (isset($folderProps[PR_EXTENDED_FOLDER_FLAGS])) {
				$flags = unpack("Cid/Cconst/Cflags", $folderProps[PR_EXTENDED_FOLDER_FLAGS]);

				// ID property is '1' this means 'Data' property contains extended flags
				if ($flags["id"] == 1)
					$props["props"]["extended_flags"] = $flags["flags"];
			}
		}

		/**
		 * Used to update the storeData with a folder and properties that will
		 * inform the user that the store could not be opened.
		 * @access private
		 * @param array &$storeData The store data which will be updated
		 * @param string $folderType The foldertype which was attempted to be loaded
		 * @param array $folderEntryID The entryid of the which was attempted to be opened
		 */
		function invalidateResponseStore(&$storeData, $folderType, $folderEntryID)
		{
			$folderName = "Folder";
			$containerClass = "IPF.Note";

			switch ($folderType) {
				case  "all":
					$folderName = "IPM_SUBTREE";
					$containerClass = "IPF.Note";
					break;
				case "calendar":
					$folderName = _("Calendar");
					$containerClass = "IPF.Appointment";
					break;
				case "contact":
					$folderName = _("Contacts");
					$containerClass = "IPF.Contact";
					break;
				case "inbox":
					$folderName = _("Inbox");
					$containerClass = "IPF.Note";
					break;
				case "note":
					$folderName = _("Notes");
					$containerClass = "IPF.StickyNote";
					break;
				case "task":
					$folderName = _("Tasks");
					$containerClass = "IPF.Task";
					break;
			}

			// Insert a fake folder which will be shown to the user
			// to acknowledge that he has a shared store, but also
			// to indicate that he can't open it.
			$tempFolderProps = $this->setFolder(array(
				PR_ENTRYID => $folderEntryID,
				PR_PARENT_ENTRYID => hex2bin($storeData["props"]["subtree_entryid"]),
				PR_STORE_ENTRYID => hex2bin($storeData["store_entryid"]),
				PR_DISPLAY_NAME => $folderName,
				PR_OBJECT_TYPE => MAPI_FOLDER,
				PR_SUBFOLDERS => false,
				PR_CONTAINER_CLASS => $containerClass,
				PR_ACCESS => 0
			));
			array_push($storeData["folders"]["item"], $tempFolderProps);
			/* TRANSLATORS: This indicates that the opened folder belongs to a particular user,
			 * for example: 'Calendar of Holiday', in this case %1$s is 'Calendar' (the foldername)
			 * and %2$s is 'Holiday' (the username).
			 */
			$storeData["props"]["display_name"] = ($folderType === "all") ? $storeData["props"]["display_name"] : sprintf(_('%1$s of %2$s'), $folderName, $storeData["props"]["mailbox_owner_name"]);
			$storeData["props"]["subtree_entryid"] = $tempFolderProps["parent_entryid"];
			$storeData["props"]["folder_type"] = $folderType;
		}

		/**
		 * Create a MAPI folder
		 *
		 * This function simply creates a MAPI folder at a specific location with a specific folder
		 * type.
		 *
		 * @param object $store MAPI Message Store Object in which the folder lives
		 * @param string $parententryid The parent entryid in which the new folder should be created
		 * @param string $name The name of the new folder
		 * @param string $type The type of the folder (PR_CONTAINER_CLASS, so value should be 'IPM.Appointment', etc)
		 * @param array $folderProps reference to an array which will be filled with PR_ENTRYID and PR_STORE_ENTRYID of new folder
		 * @return boolean true if action succeeded, false if not
		 */
		function createFolder($store, $parententryid, $name, $type, &$folderProps)
		{
			$result = false;
			$folder = mapi_msgstore_openentry($store, $parententryid);

			if($folder) {
				/**
				 * @TODO: If parent folder has any sub-folder with the same name than this will return
				 * MAPI_E_COLLISION error, so show this error to client and don't close the dialog.
				 */
				$new_folder = mapi_folder_createfolder($folder, $name);

				if($new_folder) {
					mapi_setprops($new_folder, array(PR_CONTAINER_CLASS => $type));
					$result = true;
					
					$folderProps = mapi_getprops($new_folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
				}
			}
			
			return $result;
		}
		
		/**
		 * Rename a folder
		 *
		 * This function renames the specified folder. However, a conflict situation can arise
		 * if the specified folder name already exists. In this case, the folder name is postfixed with
		 * an ever-higher integer to create a unique folder name.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid The entryid of the folder to rename
		 * @param string $name The new name of the folder
		 * @param array $folderProps reference to an array which will be filled with PR_ENTRYID and PR_STORE_ENTRYID
		 * @return boolean true if action succeeded, false if not
		 * @todo Function name should be renameFolder()
		 */
		function modifyFolder($store, $entryid, $name, &$folderProps)
		{
			$result = false;
			$folder = mapi_msgstore_openentry($store, $entryid);

			if($folder && !$this->isSpecialFolder($store, $entryid)) {
				/**
				 * @TODO: If parent folder has any sub-folder with the same name than this will return
				 * MAPI_E_COLLISION error while renaming folder, so show this error to client,
				 * and revert changes in view.
				 */
				mapi_setprops($folder, array(PR_DISPLAY_NAME => $name));

				mapi_savechanges($folder);
				$result = true;
				
				$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
			}
			return $result;
		}
		
		/**
		 * Check if a folder is 'special'
		 *
		 * All default MAPI folders such as 'inbox', 'outbox', etc have special permissions; you can not rename them for example. This
		 * function returns TRUE if the specified folder is 'special'.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid The entryid of the folder
		 * @return boolean true if folder is a special folder, false if not		 		 		 		 		 
		 */
		function isSpecialFolder($store, $entryid)
		{
			$result = true;
			
			$msgstore_props = mapi_getprops($store, array(PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID));
			
			// "special" folders don't exists in public store
			if ($msgstore_props[PR_MDB_PROVIDER]==ZARAFA_STORE_PUBLIC_GUID){
				$result = false;
			}else if (array_search($entryid, $msgstore_props)===false){
				$inbox = mapi_msgstore_getreceivefolder($store);
				if($inbox) {
					$inboxProps = mapi_getprops($inbox, array(PR_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS));
					if (array_search($entryid, $inboxProps)===false){
						if (isset($inboxProps[PR_ADDITIONAL_REN_ENTRYIDS]) && is_array($inboxProps[PR_ADDITIONAL_REN_ENTRYIDS])){
							if (array_search($entryid, $inboxProps[PR_ADDITIONAL_REN_ENTRYIDS])===false) {
								$result = false;
							}
						} else {
							$result = false;
						}
					}
				}
			}
			
			return $result;
		}

		/**
		 * Delete a folder
		 *
		 * Deleting a folder normally just moves the folder to the wastebasket, which is what this function does. However,
		 * if the folder was already in the wastebasket, then the folder is really deleted.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid The parent in which the folder should be deleted
		 * @param string $entryid The entryid of the folder which will be deleted
		 * @param array $folderProps reference to an array which will be filled with PR_ENTRYID, PR_STORE_ENTRYID of the deleted object
		 * @return boolean true if action succeeded, false if not
		 * @todo subfolders of folders in the wastebasket should also be hard-deleted
		 */
		function deleteFolder($store, $parententryid, $entryid, &$folderProps)
		{
			$result = false;
			$msgprops = mapi_getprops($store, array(PR_IPM_WASTEBASKET_ENTRYID));
			$folder = mapi_msgstore_openentry($store, $parententryid);

			if($folder && !$this->isSpecialFolder($store, $entryid)) {
				if(isset($msgprops[PR_IPM_WASTEBASKET_ENTRYID])) {
					// TODO: check if not only $parententryid=wastebasket, but also the parents of that parent...
					if($msgprops[PR_IPM_WASTEBASKET_ENTRYID] == $parententryid) {
						if(mapi_folder_deletefolder($folder, $entryid, DEL_MESSAGES | DEL_FOLDERS)) {
							$result = true;
							
							// if exists, also delete settings made for this folder (client don't need an update for this)
							$GLOBALS["settings"]->delete("folders/entryid_".bin2hex($entryid));
						}
					} else {
						$wastebasket = mapi_msgstore_openentry($store, $msgprops[PR_IPM_WASTEBASKET_ENTRYID]);

						$deleted_folder = mapi_msgstore_openentry($store, $entryid);
						$props = mapi_getprops($deleted_folder, array(PR_DISPLAY_NAME));
						
						try {
							/**
				 			 * To decrease overload of checking for conflicting folder names on modification of every folder
				 			 * we should first try to copy folder and if it returns MAPI_E_COLLISION then
				 			 * only we should check for the conflicting folder names and generate a new name
				 			 * and copy folder with the generated name.
				 			 */
							mapi_folder_copyfolder($folder, $entryid, $wastebasket, $props[PR_DISPLAY_NAME], FOLDER_MOVE);
							$folderProps = mapi_getprops($deleted_folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
							$result = true;
						} catch (MAPIException $e) {
							if($e->getCode() == MAPI_E_COLLISION) {
								$foldername = $this->checkFolderNameConflict($store, $wastebasket, $props[PR_DISPLAY_NAME]);

								mapi_folder_copyfolder($folder, $entryid, $wastebasket, $foldername, FOLDER_MOVE);
								$folderProps = mapi_getprops($deleted_folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
								$result = true;
							} else {
								// all other errors should be propagated to higher level exception handlers
								throw $e;
							}
						}
					}
				} else {
					if(mapi_folder_deletefolder($folder, $entryid, DEL_MESSAGES | DEL_FOLDERS)) {
						$result = true;
						
						// if exists, also delete settings made for this folder (client don't need an update for this)
						$GLOBALS["settings"]->delete("folders/entryid_".bin2hex($entryid));
					}
				}
			}
			
			return $result;
		}
		
		/**
		 * Empty folder
		 *
		 * Removes all items from a folder. This is a real delete, not a move.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid The entryid of the folder which will be emptied
		 * @param array $folderProps reference to an array which will be filled with PR_ENTRYID and PR_STORE_ENTRYID of the emptied folder
		 * @return boolean true if action succeeded, false if not
		 */
		function emptyFolder($store, $entryid, &$folderProps)
		{
			$result = false;
			$folder = mapi_msgstore_openentry($store, $entryid);
			
			if($folder) {
				$result = mapi_folder_emptyfolder($folder, DEL_ASSOCIATED);

				// Update freebusy in case we just emptied the calendar folder
				$GLOBALS["operations"]->publishFreeBusy($store, $entryid);

				$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
				$result = true;
			}
			
			return $result;
		}
		
		/**
		 * Copy or move a folder
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $parentfolderentryid The parent entryid of the folder which will be copied or moved
		 * @param string $sourcefolderentryid The entryid of the folder which will be copied or moved
		 * @param string $destfolderentryid The entryid of the folder which the folder will be copied or moved to
		 * @param boolean $moveFolder true - move folder, false - copy folder
		 * @param array $folderProps reference to an array which will be filled with entryids
		 * @return boolean true if action succeeded, false if not
		 */
		function copyFolder($store, $parentfolderentryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder, &$folderProps)
		{
			$result = false;
			$sourceparentfolder = mapi_msgstore_openentry($store, $parentfolderentryid);
			$destfolder = mapi_msgstore_openentry($deststore, $destfolderentryid);
			if(!$this->isSpecialFolder($store, $sourcefolderentryid) && $sourceparentfolder && $destfolder && $deststore) {
				$folder = mapi_msgstore_openentry($store, $sourcefolderentryid);
				$props = mapi_getprops($folder, array(PR_DISPLAY_NAME));
				try {
					/**
		 			 * To decrease overload of checking for conflicting folder names on modification of every folder
		 			 * we should first try to copy/move folder and if it returns MAPI_E_COLLISION then
		 			 * only we should check for the conflicting folder names and generate a new name
		 			 * and copy/move folder with the generated name.
		 			 */
					if($moveFolder) {
						mapi_folder_copyfolder($sourceparentfolder, $sourcefolderentryid, $destfolder, $props[PR_DISPLAY_NAME], FOLDER_MOVE);
						$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
						$result = true;
					} else {
						mapi_folder_copyfolder($sourceparentfolder, $sourcefolderentryid, $destfolder, $props[PR_DISPLAY_NAME], COPY_SUBFOLDERS);
						$result = true;
					}
				} catch (MAPIException $e) {
					if($e->getCode() == MAPI_E_COLLISION) {
						$foldername = $this->checkFolderNameConflict($deststore, $destfolder, $props[PR_DISPLAY_NAME]);
						if($moveFolder) {
							mapi_folder_copyfolder($sourceparentfolder, $sourcefolderentryid, $destfolder, $foldername, FOLDER_MOVE);
							$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
							$result = true;
						} else {
							mapi_folder_copyfolder($sourceparentfolder, $sourcefolderentryid, $destfolder, $foldername, COPY_SUBFOLDERS);
							$result = true;
						}
					} else {
						// all other errors should be propagated to higher level exception handlers
						throw $e;
					}
				}
			}
			return $result;
		}
		
		/**
		 * Set the readflags of all messages in a folder to 'read'
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid The entryid of the folder
		 * @param array $folderProps reference to an array which will be filled with PR_ENTRYID and PR_STORE_ENTRYID of the folder
		 * @return boolean true if action succeeded, false if not
		 * @todo This function is message a 'set unread' option
		 */
		function setReadFlags($store, $entryid, &$folderProps)
		{
			$result = false;
			$folder = mapi_msgstore_openentry($store, $entryid);
			
			if($folder) {
				if(mapi_folder_setreadflags($folder, array(), SUPPRESS_RECEIPT)) {
					$result = true;
					
					$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
				}
			}
			
			return $result;
		}
		
		/**
		 * Read MAPI table
		 *
		 * This function performs various operations to open, setup, and read all rows from a MAPI table.
		 *
		 * The output from this function is an XML array structure which can be sent directly to XML serialisation.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid The entryid of the folder to read the table from
		 * @param array $properties The set of properties which will be read
		 * @param array $sort The set properties which the table will be sort on (formatted as a MAPI sort order)
		 * @param integer $start Starting row at which to start reading rows
		 * @param integer $rowcount Number of rows which should be read
		 * @param array $restriction Table restriction to apply to the table (formatted as MAPI restriction)
		 * @param array $folderProps reference to an array which will be filled with PR_ENTRYID and PR_STORE_ENTRYID of the folder
		 * @return array XML array structure with row data
		 */
		function getTable($store, $entryid, $properties, $sort, $start, $rowcount = false, $restriction = false)
		{
			$data = array();
			$folder = mapi_msgstore_openentry($store, $entryid);
			
			if($folder) {
				$table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS);

				if(!$rowcount) {
					$rowcount = $GLOBALS["settings"]->get("global/rowcount", 50);
				}

				$data["page"] = array();
				$data["page"]["start"] = $start;
				$data["page"]["rowcount"] = $rowcount;
				
				if(is_array($restriction)) {
					mapi_table_restrict($table, $restriction, TBL_BATCH);
				}

				if (is_array($sort) && !empty($sort)){
					/**
					 * If the sort array contains the PR_SUBJECT column we should change this to 
					 * PR_NORMALIZED_SUBJECT to make sure that when sorting on subjects: "sweet" and
					 * "RE: sweet", the first one is displayed before the latter one. If the subject
					 * is used for sorting the PR_MESSAGE_DELIVERY_TIME must be added as well as 
					 * Outlook behaves the same way in this case.
					 */
					if(isset($sort[PR_SUBJECT])){
						$sortReplace = Array();
						foreach($sort as $key => $value){
							if($key == PR_SUBJECT){
								$sortReplace[PR_NORMALIZED_SUBJECT] = $value;
								$sortReplace[PR_MESSAGE_DELIVERY_TIME] = TABLE_SORT_DESCEND;
							}else{
								$sortReplace[$key] = $value;
							}
						}
						$sort = $sortReplace;
					}

					mapi_table_sort($table, $sort, TBL_BATCH);
				}

				$totalrowcount = mapi_table_getrowcount($table);
				$data["page"]["totalrowcount"] = $totalrowcount;
				$data["item"] = array();

				/**
				 * Retrieving the entries should be done in batches to prevent large ammounts of 
				 * items in one list clogging up the memory limit. This is especially important when
				 * dealing with contactlists in the addressbook. Those lists can contain 10K items. 
				 */
				$batchcount = 50;
				$end = min($totalrowcount, ($start+$rowcount));
				$diff = $end - $start;
				$numBatches = ceil($diff/$batchcount);

				for($i=0;$i<$numBatches;$i++){
					$batchStart = $start + ($i * $batchcount);
					if($batchStart + $batchcount > $end){
						$batchcount = $end - $batchStart;	// Prevent the last batch to include too many items
					}

					$rows = mapi_table_queryrows($table, $properties, $batchStart, $batchcount);
					
					foreach($rows as $row){
						array_push($data["item"], Conversion::mapMAPI2XML($properties, $row));
					}
				}
			}
			
			return $data;
		}
		
		/**
		* Returns TRUE of the MAPI message only has inline attachments
		*
		* @param mapimessage $message The MAPI message object to check
		* @return boolean TRUE if the item contains only inline attachments, FALSE otherwise
		* @deprecated This function is not used, because it is much too slow to run on all messages in your inbox
		*/
		function hasOnlyInlineAttachments($message)
		{
			$attachmentTable = mapi_message_getattachmenttable($message);
			if($attachmentTable) {
				$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACHMENT_HIDDEN));
				foreach($attachments as $attachmentRow)	{
					if(!isset($attachmentRow[PR_ATTACHMENT_HIDDEN]) || !$attachmentRow[PR_ATTACHMENT_HIDDEN]) {
						return false;
					}
				}
			}
			return true;
		}

		/**
		 * Read message properties
		 *
		 * Reads a message and returns the data as an XML array structure with all data from the message that is needed
		 * to show a message (for example in the preview pane)
		 *
		 * @param object $store MAPI Message Store Object
		 * @param object $message The MAPI Message Object
		 * @param array $properties Mapping of properties that should be read
		 * @param boolean $html2text true - body will be converted from html to text, false - html body will be returned
		 * @return array item properties
		 * @todo Function name is misleading as it doesn't just get message properties
		 */
		function getMessageProps($store, $message, $properties, $html2text = false)
		{
			$props = array();
			
			if($message) {
				$messageprops = mapi_getprops($message, $properties);
				$props = Conversion::mapMAPI2XML($properties, $messageprops);
				
				// Get actual SMTP address for sent_representing_email_address and received_by_email_address
				$smtpprops = mapi_getprops($message, array(PR_SENT_REPRESENTING_ENTRYID, PR_RECEIVED_BY_ENTRYID, PR_SENDER_ENTRYID));
				
				if(isset($smtpprops[PR_SENT_REPRESENTING_ENTRYID])) {
    				$props["props"]["sent_representing_email_address"] = $this->getEmailAddressFromEntryID($smtpprops[PR_SENT_REPRESENTING_ENTRYID]);
				}
                
				if(isset($smtpprops[PR_SENDER_ENTRYID])) {
    				$props["props"]["sender_email_address"] = $this->getEmailAddressFromEntryID($smtpprops[PR_SENDER_ENTRYID]);
				}
                
                if(isset($smtpprops[PR_RECEIVED_BY_ENTRYID])) {
                    $props["props"]["received_by_email_address"] = $this->getEmailAddressFromEntryID($smtpprops[PR_RECEIVED_BY_ENTRYID]);
				}

				// Get body content
				$plaintext = $this->isPlainText($message);
				$tmpProps = mapi_getprops($message, array(PR_BODY, PR_HTML));

				$htmlcontent = '';
				$plaincontent = '';
				if (!$plaintext){
					$cpprops = mapi_message_getprops($message, array(PR_INTERNET_CPID));
					$codepage = isset($cpprops[PR_INTERNET_CPID]) ? $cpprops[PR_INTERNET_CPID] : 1252;
					if(isset($tmpProps[PR_HTML]) || propIsError(PR_HTML, $tmpProps) == MAPI_E_NOT_ENOUGH_MEMORY) {
						// only open proeprty if it exists
						$htmlcontent = Conversion::convertCodepageStringToUtf8($codepage, mapi_message_openproperty($message, PR_HTML));
					}
				
					if(!empty($htmlcontent)) {
						$filter = new filter();
		
						if(!$html2text) {
							$msgstore_props = mapi_getprops($store, array(PR_ENTRYID));
								
							$htmlcontent = $filter->safeHTML($htmlcontent, bin2hex($msgstore_props[PR_ENTRYID]), bin2hex($messageprops[PR_ENTRYID]));
							$props["props"]["isHTML"] = true;
						} else {
							$htmlcontent = '';
						}
					}

					$htmlcontent = trim($htmlcontent, "\0");
				}
				
				if(isset($tmpProps[PR_BODY]) || propIsError(PR_BODY, $tmpProps) == MAPI_E_NOT_ENOUGH_MEMORY) {
					// only open proeprty if it exists
					$plaincontent = mapi_message_openproperty($message, PR_BODY);
					$plaincontent = trim($plaincontent, "\0");
				}

				if (!empty($htmlcontent)) {
					$props["props"]["html_body"] = $htmlcontent;
					$props["props"]["isHTML"] = true;
				} else {
					$props["props"]["isHTML"] = false;
				}
				$props["props"]["body"] = $plaincontent;

				// Get reply-to information
				$messageprops = mapi_getprops($message, array(PR_REPLY_RECIPIENT_ENTRIES));
				if(isset($messageprops[PR_REPLY_RECIPIENT_ENTRIES])) {
					$props["props"]["reply-to"] = $this->readReplyRecipientEntry($messageprops[PR_REPLY_RECIPIENT_ENTRIES]);
				}
				
				// Get recipients
				$recipients = $GLOBALS["operations"]->getRecipientsInfo($store, $message);
				if(!empty($recipients)) {
					$props["recipients"] = array(
						"item" => $recipients
					);
				}

				// Get attachments
				$attachments = $GLOBALS["operations"]->getAttachmentsInfo($store, $message);
				if(!empty($attachments)) {
					$props["attachments"] = array(
						"item" => $attachments
					);
				}

				// for distlists, we need to get members data
				if(isset($props["props"]["oneoff_members"]) && isset($props["props"]["members"])) {
					// remove non-client props
					unset($props["props"]["members"]);
					unset($props["props"]["oneoff_members"]);

					// get members
					$members = $GLOBALS["operations"]->getMembersFromDistributionList($store, hex2bin($props["entryid"]), $properties);
					if(!empty($members)) {
						$props["members"] = array(
							"item" => $members
						);
					}
				}
			}

			return $props;
		}
		
		/**
		 * Get and convert properties of a message into an XML array structure
		 *
		 * @param object $store MAPI Message Store Object
		 * @param object $item The MAPI Object
		 * @param array $properties Mapping of properties that should be read
		 * @return array XML array structure
		 * @todo Function name is misleading, especially compared to getMessageProps()
		 */
		function getProps($store, $item, $properties)
		{
			$props = array();
			
			if($item) {
				$itemprops = mapi_getprops($item, $properties);
				$props = Conversion::mapMAPI2XML($properties, $itemprops);
			}
					
			return $props;
		}

		/**
		 * Get embedded message data
		 *
		 * Returns the same data as getMessageProps, but then for a specific sub/sub/sub message
		 * of a MAPI message.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid entryid of the root message
		 * @param array $properties a set of properties which will be selected
		 * @param array $attach_num a list of attachment numbers (aka 2,1 means 'attachment nr 1 of attachment nr 2')
		 * @return array item XML array structure of the embedded message
		 */
		function getEmbeddedMessage($store, $entryid, $properties, $attach_num)
		{
			$message = mapi_msgstore_openentry($store, $entryid);
			$props = array();
			
			if($message) {
				foreach($attach_num as $num)
				{
					$attachment = mapi_message_openattach($message, $num);
					
					if($attachment) {
  						$message = mapi_attach_openobj($attachment);
  					} else {
  						return $props;
  					}
  				}
  				
				$msgprops = mapi_getprops($message, Array(PR_MESSAGE_CLASS));
				switch($msgprops[PR_MESSAGE_CLASS]){
					case 'IPM.Note':
						$html2text = false;
						break;
					default:
						$html2text = true;
				}
  				$props = $this->getMessageProps($store, $message, $properties, $html2text);
			}
			
			return $props;
		}
		
		/**
		 * Create a MAPI message
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid The entryid of the folder in which the new message is to be created
		 * @return mapimessage Created MAPI message resource
		 */
		function createMessage($store, $parententryid)
		{
			$folder = mapi_msgstore_openentry($store, $parententryid); 
			return mapi_folder_createmessage($folder); 
		}
		
		/**
		 * Open a MAPI message
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid entryid of the message
		 * @return object MAPI Message
		 */
		function openMessage($store, $entryid)
		{
			return mapi_msgstore_openentry($store, $entryid); 
		}
		
		/**
		 * Save a MAPI message
		 *
		 * The to-be-saved message can be of any type, including e-mail items, appointments, contacts, etc. The message may be pre-existing
		 * or it may be a new message.
		 *
		 * The dialog_attachments parameter represents a unique ID which for the dialog in the client for which this function was called; This
		 * is used as follows; Whenever a user uploads an attachment, the attachment is stored in a temporary place on the server. At the same time,
		 * the temporary server location of the attachment is saved in the session information, accompanied by the $dialog_attachments unique ID. This
		 * way, when we save the message into MAPI, we know which attachment was previously uploaded ready for this message, because when the user saves
		 * the message, we pass the same $dialog_attachments ID as when we uploaded the file.
		 *
		 * @param object $store MAPI Message Store Object
		 * @param binary $entryid entryid of the message
		 * @param binary $parententryid Parent entryid of the message
		 * @param array $props The MAPI properties to be saved
		 * @param array $messageProps reference to an array which will be filled with PR_ENTRYID and PR_STORE_ENTRYID of the saved message
		 * @param array $recipients XML array structure of recipients for the recipient table
		 * @param array $attachments attachments array containing unique check number which checks if attachments should be added ($_SESSION)
		 * @param array $propertiesToDelete Properties specified in this array are deleted from the MAPI message
		 * @param mixed $copyFromMessageId entryid of the message from which we should
		 * copy attachments and/or recipients to the current message.
		 * @param mixed $copyFromStore storeid of the message from which we should
		 * copy attachments and/or recipients to the current message.
		 * @param boolean $copyAttachments If set we copy all attachments from the $copyFromMessageId of $copyFromStore.
		 * @param boolean $copyRecipients If set we copy all recipients from the $copyFromMessageId of $copyFromStore.
		 * @param boolean $copyInlineAttachmentsOnly if true then copy only inline attachments.
		 * @param boolean $saveChanges if true then save all change in mapi message
		 * @param boolean $send true if this function is called from submiteMessage else false.
		 * @return mapimessage Saved MAPI message resource
		 */
		function saveMessage($store, $entryid, $parententryid, $props, &$messageProps, $recipients = array(), $attachments = array(), $propertiesToDelete = array(), $copyFromMessageId = false, $copyFromStore = false, $copyAttachments = false, $copyRecipients = false, $copyInlineAttachmentsOnly = false, $saveChanges = true, $send = false)
		{
			$message = false;

			// Check if an entryid is set, otherwise create a new message
			if(isset($entryid) && !empty($entryid)) {
				$message = $this->openMessage($store, $entryid);
			} else {
				$message = $this->createMessage($store, $parententryid);
			}

			if($message) {
				$property = false;
				$body = "";

				// Check if the body is set.
				if (isset($props[PR_BODY])) {
					$body = $props[PR_BODY];
					$property = PR_BODY;
					$bodyPropertiesToDelete = array(PR_HTML, PR_RTF_COMPRESSED);
					
					if(isset($props[PR_HTML])) {
						$subject = '';
						if(isset($props[PR_SUBJECT])){
							$subject = $props[PR_SUBJECT];
						// If subject is not updated we need to get it from the message
						}else{
							$subjectProp = mapi_getprops($message, Array(PR_SUBJECT));
							if(isset($subjectProp[PR_SUBJECT])){
								$subject = $subjectProp[PR_SUBJECT];
							}
						}
						$body = $this->generateBodyHTML($props[PR_BODY], $subject);
						$property = PR_HTML;
						$bodyPropertiesToDelete = array(PR_BODY, PR_RTF_COMPRESSED);
						unset($props[PR_HTML]);
					}
					unset($props[PR_BODY]);

					$propertiesToDelete = array_unique(array_merge($propertiesToDelete, $bodyPropertiesToDelete));
				}

				if(!isset($props[PR_SENT_REPRESENTING_ENTRYID]) &&
				   isset($props[PR_SENT_REPRESENTING_EMAIL_ADDRESS]) && !empty($props[PR_SENT_REPRESENTING_EMAIL_ADDRESS]) &&
				   isset($props[PR_SENT_REPRESENTING_ADDRTYPE]) && !empty($props[PR_SENT_REPRESENTING_ADDRTYPE]) &&
				   isset($props[PR_SENT_REPRESENTING_NAME]) && !empty($props[PR_SENT_REPRESENTING_NAME])) {
					// Set FROM field properties
					$props[PR_SENT_REPRESENTING_ENTRYID] = mapi_createoneoff($props[PR_SENT_REPRESENTING_NAME], $props[PR_SENT_REPRESENTING_ADDRTYPE], $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS]);
				}

				// remove mv properties when needed
				foreach($props as $propTag=>$propVal){
					switch(mapi_prop_type($propTag)) {
						case PT_SYSTIME:
							// Empty PT_SYSTIME values mean they should be deleted (there is no way to set an empty PT_SYSTIME)
						//case PT_STRING8:	// not enabled at this moment
							// Empty Strings
						case PT_MV_LONG:
							// Empty multivalued long
						case PT_MV_STRING8:
							// Empty multivalued string
							if(empty($propVal)) {
								$propertiesToDelete[] = $propTag;
							}
							break;
					}
				}
				
				foreach($propertiesToDelete as $prop){
					unset($props[$prop]);
				}

				// Set the properties
				mapi_setprops($message, $props);
				
				// Delete the properties we don't need anymore
				mapi_deleteprops($message, $propertiesToDelete);
				
				if ($property != false){
					// Stream the body to the PR_BODY or PR_HTML property
					$stream = mapi_openproperty($message, $property, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
					mapi_stream_setsize($stream, strlen($body));
					mapi_stream_write($stream, $body);
					mapi_stream_commit($stream);
				} 

				// save the recipients
				$this->setRecipients($message, $recipients, $send, $copyRecipients, $copyFromMessageId, $copyFromStore);

				// Save the attachments with the $dialog_attachments
				if(!empty($attachments)) {
					$this->setAttachments($message, $attachments, $copyFromMessageId, $copyFromStore, $copyInlineAttachmentsOnly);
				}

				// Set 'hideattacments' if message has only inline attachments.
				$properties = $GLOBALS["properties"]->getMailProperties();
				if($this->hasOnlyInlineAttachments($message)){
					mapi_setprops($message, array($properties["hide_attachments"] => true));
				} else {
					mapi_deleteprops($message, array($properties["hide_attachments"]));
				}

				// Save changes
				if ($saveChanges) {
					mapi_savechanges($message);
				}

				// Get the PR_ENTRYID, PR_PARENT_ENTRYID and PR_STORE_ENTRYID properties of this message
				$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
			}

			return $message;
		}

		/**
		 * Save an appointment item.
		 *
		 * This is basically the same as saving any other type of message with the added complexity that
		 * we support saving exceptions to recurrence here. This means that if the client sends a basedate
		 * in the action, that we will attempt to open an existing exception and change that, and if that
		 * fails, create a new exception with the specified data. 
		 *
		 * @param mapistore $store MAPI store of the message
		 * @param string $entryid entryid of the message
		 * @param string $parententryid Parent entryid of the message (folder entryid, NOT message entryid)
		 * @param array $action Action array containing XML request
		 * @param string $actionType The action type which triggered this action
		 * @param boolean $directBookingMeetingRequest Indicates if a Meeting Request should use direct booking or not. Defaults to true.
		 * @return array of PR_ENTRYID, PR_PARENT_ENTRYID and PR_STORE_ENTRYID properties of modified item
		 */
		function saveAppointment($store, $entryid, $parententryid, $action, $actionType = "save", $directBookingMeetingRequest=true)
		{
			$exception = false;
			$messageProps = array();
			// It stores the values that is exception allowed or not false -> not allowed
			$isExceptionAllowed = true;
			$delete = $actionType == "delete";	// Flag for MeetingRequest Class whether to send update or cancel mail.
			$basedate = false;	// Flag for MeetingRequest Class whether to send an exception or not.
			$isReminderTimeAllowed = true;	// Flag to check reminder minutes is in range of the occurences
			$properties = $GLOBALS["properties"]->getAppointmentProperties();
			$send = false;
			$oldProps = array();
			if(isset($action["message_action"]) && isset($action["message_action"]["send"])) {
				$send = $action["message_action"]["send"];
			}
			if(isset($action['recipients']) && is_array($action['recipients'])) {
				$recips = $action['recipients'];
			} else {
				$recips = false;
			}

			if($store && $parententryid) {
				//@FIXME: check for $action['props'] array
				if(isset($entryid) && $entryid) {
					// Modify existing or add/change exception
					$message = mapi_msgstore_openentry($store, $entryid);

					if($message) {
						$props = mapi_getprops($message, $properties);

						// Check if appointment is an exception to a recurring item
						if(isset($action["basedate"]) && $action["basedate"] > 0) {
							$exception = true;

							// Create recurrence object
							$recurrence = new Recurrence($store, $message);

							$basedate = $action["basedate"];

							if ($delete === true) {
								$isExceptionAllowed = $recurrence->createException(array(), $basedate, true);
							} else {
								$exception_recips = array();
								if (isset($recips["add"])) {
									$exception_recips["add"] = $this->createRecipientList($recips["add"], "add", true, true);
								}
								if (isset($recips["remove"])) {
									$exception_recips["remove"] = $this->createRecipientList($recips["remove"], "remove");
								}
								if (isset($recips["modify"])) {
									$exception_recips["modify"] = $this->createRecipientList($recips["modify"], "modify", true, true);
								}

								if(isset($action["props"]["reminder_minutes"]) && isset($action["props"]["startdate"])){
									$isReminderTimeAllowed = $recurrence->isValidReminderTime($basedate, $action["props"]["reminder_minutes"], $action["props"]["startdate"]);
								}

								// As the reminder minutes occurs before other occurences don't modify the item.
								if($isReminderTimeAllowed){
									if($recurrence->isException($basedate)){
										$oldProps = $recurrence->getExceptionProperties($recurrence->getChangeException($basedate));
										//to get the old exceptions properties
										if(isset($action["drag"]) && $action["drag"]){
											$previousExceptionProps = $recurrence->getChangeException($basedate);
										}

										$isExceptionAllowed = $recurrence->modifyException(Conversion::mapXML2MAPI($properties, $action["props"]), $basedate, $exception_recips);
									} else {
										$oldProps[$properties['startdate']] = $recurrence->getOccurrenceStart($basedate);
										$oldProps[$properties['duedate']] = $recurrence->getOccurrenceEnd($basedate);

										$isExceptionAllowed = $recurrence->createException(Conversion::mapXML2MAPI($properties, $action["props"]), $basedate, false, $exception_recips);
									}
									mapi_savechanges($message);
								}
							}
						} else {
							$oldProps = mapi_getprops($message, array($properties['startdate'], $properties['duedate']));

							// Modifying non-exception (the series) or normal appointment item
							$message = $GLOBALS["operations"]->saveMessage($store, $entryid, $parententryid, Conversion::mapXML2MAPI($properties, $action["props"]), $messageProps, $recips ? $recips : array(), isset($action['attachments']) ? $action['attachments'] : array(), array(), false, false, false, false, false, false, $send);

							// Only save recurrence if it has been changed by the user (because otherwise we'll reset
							// the exceptions)
							if(isset($action["props"]['recurring_reset']) && $action["props"]['recurring_reset'] == true) {
								$recur = new Recurrence($store, $message);
								
								if(isset($action['props']['timezone'])) {
									$tzprops = Array('timezone','timezonedst','dststartmonth','dststartweek','dststartday','dststarthour','dstendmonth','dstendweek','dstendday','dstendhour');
									
									// Get timezone info
									$tz = Array();
									foreach($tzprops as $tzprop) {
										$tz[$tzprop] = $action['props'][$tzprop];
									}
								}

								// Act like the 'props' are the recurrence pattern; it has more information but that
								// is ignored
								$recur->setRecurrence(isset($tz) ? $tz : false, $action["props"]);
							}
						}

						// Get the properties of the main object of which the exception was changed, and post
						// that message as being modified. This will cause the update function to update all
						// occurrences of the item to the client
						$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID, PR_OBJECT_TYPE, PR_MESSAGE_CLASS));
					}
				} else {
					$tz = null;
					//Set sender of new Appointment.
					$this->setSenderAddress($store, $action);

					$message = $GLOBALS["operations"]->saveMessage($store, $entryid, $parententryid, Conversion::mapXML2MAPI($properties, $action["props"]), $messageProps, $recips ? $recips : array(), isset($action['attachments']) ? $action['attachments'] : array(), array(), false, false, false, false, false, false, $send);
				
					if(isset($action['props']['timezone'])) {
						$tzprops = Array('timezone','timezonedst','dststartmonth','dststartweek','dststartday','dststarthour','dstendmonth','dstendweek','dstendday','dstendhour');
						
						// Get timezone info
						$tz = Array();
						foreach($tzprops as $tzprop) {
							$tz[$tzprop] = $action['props'][$tzprop];
						}
					}

					// Set recurrence
					if(isset($action["props"]['recurring']) && $action["props"]['recurring'] == true) {
						$recur = new Recurrence($store, $message);
						$recur->setRecurrence($tz, $action["props"]);
					}
				}
			}

			$result = false;
			// Check to see if it should be sent as a meeting request
			if($send === true && $isExceptionAllowed){
				$request = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $directBookingMeetingRequest);
				$request->updateMeetingRequest($basedate);

				$isRecurrenceChanged = isset($action['props']['recurring_reset']) && $action['props']['recurring_reset'] == true;
				$request->checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged);

				// Update extra body information
				if(isset($action["message_action"]['meetingTimeInfo']) && !empty($action["message_action"]['meetingTimeInfo'])) {
					$request->setMeetingTimeInfo($action["message_action"]['meetingTimeInfo']);
					unset($action["message_action"]['meetingTimeInfo']);
				}

				$deletedRecipients = false;
				if ($recips && isset($recips['remove']) && !empty($recips['remove'])) {
					$deletedRecipients = $this->createRecipientList($recips['remove'], 'remove');
				}

				$sendMeetingRequestResult = $request->sendMeetingRequest($delete, false, $basedate, $deletedRecipients);

				if ($recips) {
					$this->addEmailsToRecipientHistory($recips);
				}

				if($sendMeetingRequestResult === true){
					mapi_savechanges($message);
					// Indicate that the message was correctly sent
					$messageProps[$properties['request_sent']] = true;
					// Return message properties that can be sent to the bus to notify changes
					$result = $messageProps;
				}else{
					$sendMeetingRequestResult[PR_ENTRYID] = $messageProps[PR_ENTRYID];
					$sendMeetingRequestResult[PR_PARENT_ENTRYID] = $messageProps[PR_PARENT_ENTRYID];
					$sendMeetingRequestResult[PR_STORE_ENTRYID] = $messageProps[PR_STORE_ENTRYID];
					$sendMeetingRequestResult[PR_OBJECT_TYPE] = $messageProps[PR_OBJECT_TYPE];
					$sendMeetingRequestResult[PR_MESSAGE_CLASS] = $messageProps[PR_MESSAGE_CLASS];
					$result = $sendMeetingRequestResult;
				}
			} else {

				mapi_savechanges($message);

				if(isset($isExceptionAllowed)){
					if($isExceptionAllowed === false) {
						$messageProps["isexceptionallowed"] = false;
					}
				}

				if(isset($isReminderTimeAllowed)){
					if($isReminderTimeAllowed === false) {
						$messageProps["remindertimeerror"] = false;
					}
				}
				// Return message properties that can be sent to the bus to notify changes
				$result = $messageProps;
			}

			if ($store && $parententryid) {
				// Publish updated free/busy information
				$GLOBALS["operations"]->publishFreeBusy($store, $parententryid);
			}

			return $result;
		}
		
		/**
		 * Set sent_representing_email_address property of Appointment.
		 *
		 * Before saving any new appointment, sent_representing_email_address property of appointment
		 * should contain email_address of user, who is the owner of store(in which the appointment 
		 * is created).
		 *
		 * @param mapistore $store  MAPI store of the message
		 * @param array     $action reference to action array containing XML request
		 */
		function setSenderAddress($store, &$action)
		{
			$storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
			// check for public store
			if(!isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) {
				$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
				$storeProps = mapi_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
			}
			$mailuser = mapi_ab_openentry($GLOBALS["mapisession"]->getAddressbook(), $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
			if($mailuser){
				$userprops = mapi_getprops($mailuser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS));
				$action["props"]["sent_representing_entryid"]       = bin2hex($storeProps[PR_MAILBOX_OWNER_ENTRYID]);
				// we do conversion here, because before passing props to saveMessage() props are converted from utf8-to-w
				$action["props"]["sent_representing_name"]          = $userprops[PR_DISPLAY_NAME];
				$action["props"]["sent_representing_address_type"]      = $userprops[PR_ADDRTYPE];
				if($userprops[PR_ADDRTYPE] == 'SMTP'){
					$emailAddress = $userprops[PR_EMAIL_ADDRESS];
				}else{
					$emailAddress = $userprops[PR_SMTP_ADDRESS];
				}
				$action["props"]["sent_representing_email_address"] = $emailAddress;
				$action["props"]["sent_representing_search_key"]    = bin2hex(strtoupper($userprops[PR_ADDRTYPE]) . ":" .strtoupper($emailAddress));
			}
		}
                
        /**
		 * Get sender structure of the MAPI Message.
		 *
         * @param mapimessage $mapiMessage  MAPI Message resource from which we need to get the sender.
         * @return array with properties
		 */
		function getSenderAddress($mapiMessage)
		{
            $messageProps  = mapi_getprops($mapiMessage, array(PR_SENT_REPRESENTING_ENTRYID, PR_SENDER_ENTRYID));
            $senderEntryID = isset($messageProps[PR_SENT_REPRESENTING_ENTRYID])? $messageProps[PR_SENT_REPRESENTING_ENTRYID] : $messageProps[PR_SENDER_ENTRYID];
            $senderUser = mapi_ab_openentry($GLOBALS["mapisession"]->getAddressbook(), $senderEntryID);
            if($senderUser) {
                $userprops = mapi_getprops($senderUser, array(PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_OBJECT_TYPE,PR_RECIPIENT_TYPE, PR_DISPLAY_TYPE, PR_DISPLAY_TYPE_EX, PR_ENTRYID));

                $senderStructure = array();
                $senderStructure["props"]['entryid']         = bin2hex($userprops[PR_ENTRYID]);
                $senderStructure["props"]['display_name']    = isset($userprops[PR_DISPLAY_NAME]) ? $userprops[PR_DISPLAY_NAME] : '';
                $senderStructure["props"]['email_address']   = isset($userprops[PR_EMAIL_ADDRESS]) ? $userprops[PR_EMAIL_ADDRESS] : '';
                $senderStructure["props"]['smtp_address']    = isset($userprops[PR_SMTP_ADDRESS]) ? $userprops[PR_SMTP_ADDRESS] : '';
                $senderStructure["props"]['address_type']    = isset($userprops[PR_ADDRTYPE]) ? $userprops[PR_ADDRTYPE] : '';
                $senderStructure["props"]['object_type']     = $userprops[PR_OBJECT_TYPE];
                $senderStructure["props"]['recipient_type']  = MAPI_TO;
                $senderStructure["props"]['display_type']    = isset($userprops[PR_DISPLAY_TYPE])    ? $userprops[PR_DISPLAY_TYPE]    : MAPI_MAILUSER;
                $senderStructure["props"]['display_type_ex'] = isset($userprops[PR_DISPLAY_TYPE_EX]) ? $userprops[PR_DISPLAY_TYPE_EX] : MAPI_MAILUSER;
            }
            return $senderStructure;
		}
		
		/**
		 * Submit a message for sending
		 *
		 * This function is an extension of the saveMessage() function, with the extra functionality
		 * that the item is actually sent and queued for moving to 'Sent Items'. Also, the e-mail addresses
		 * used in the message are processed for later auto-suggestion.
		 *
		 * @see Operations::saveMessage() for more information on the parameters, which are identical.
		 *
		 * @param mapistore $store MAPI Message Store Object
		 * @param binary $entryid Entryid of the message
		 * @param array $props The properties to be saved
		 * @param array $messageProps reference to an array which will be filled with PR_ENTRYID, PR_PARENT_ENTRYID and PR_STORE_ENTRYID
		 * @param array $recipients XML array structure of recipients for the recipient table
		 * @param array $attachments array of attachments consisting unique ID of attachments for this message
		 * @param array $copyFromMessageId if set this holds the entryid of the message which holds the attachments and recipients
		 * @param array $copyFromStore resource of the store that will be used for copying original attachments and recipients from message
		 * @param boolean $copyAttachments If set we copy all attachments from the $copyFromMessageId of $copyFromStore.
		 * @param boolean $copyRecipients If set we copy all recipients from the $copyFromMessageId of $copyFromStore.
		 * @param boolean $copyInlineAttachmentsOnly if true then copy only inline attachments.
		 * @return boolean true if action succeeded, false if not
		 *
		 */
		function submitMessage($store, $entryid, $props, &$messageProps, $recipients = array(), $attachments = array(), $copyFromMessageId = false, $copyFromStore = false, $copyAttachments = false, $copyRecipients = false, $copyInlineAttachmentsOnly = false, $replyOrigMessageDetails = false)
		{
			$result = false;
			$message = false;
			$origStore = $store;

			// Get the outbox and sent mail entryid, ignore the given $store, use the default store for submitting messages
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
			$storeprops = mapi_getprops($store, array(PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_ENTRYID));
			$origStoreprops = mapi_getprops($origStore, array(PR_ENTRYID));

			if(isset($storeprops[PR_IPM_OUTBOX_ENTRYID])) {
				if(isset($storeprops[PR_IPM_SENTMAIL_ENTRYID])) {
					$props[PR_SENTMAIL_ENTRYID] = $storeprops[PR_IPM_SENTMAIL_ENTRYID];
				}
				
				// Set reply to settings
				$replyTo = $GLOBALS['settings']->get("createmail/reply_to",null);
				if (!empty($replyTo)){
					$props[PR_REPLY_RECIPIENT_NAMES] = $replyTo;
					$replyRecip = array(array(PR_EMAIL_ADDRESS=>$replyTo, PR_ADDRTYPE=>"SMTP"));
					$props[PR_REPLY_RECIPIENT_ENTRIES] = $this->writeFlatEntryList($replyRecip);
				}
				/**
				 * Check if replying then set PR_INTERNET_REFERENCES and PR_IN_REPLY_TO_ID properties in props.
				 */
				if(isset($replyOrigMessageDetails) && $replyOrigMessageDetails){
					$originalMessageStore = $GLOBALS["mapisession"]->openMessageStore(hex2bin($replyOrigMessageDetails["source_store_entryid"]));
					$origmessage = mapi_msgstore_openentry($originalMessageStore, hex2bin($replyOrigMessageDetails["source_entryid"]));
					$origmsgprops = mapi_getprops($origmessage);
					if(isset($origmsgprops[PR_INTERNET_MESSAGE_ID])){
						$props[PR_INTERNET_REFERENCES] = $origmsgprops[PR_INTERNET_MESSAGE_ID];
						$props[PR_IN_REPLY_TO_ID] = $origmsgprops[PR_INTERNET_MESSAGE_ID];
					}
				}

				if (!$GLOBALS["entryid"]->compareStoreEntryIds(bin2hex($origStoreprops[PR_ENTRYID]), bin2hex($storeprops[PR_ENTRYID]))) {
					// set properties for "on behalf of" mails
					$origStoreProps = mapi_getprops($origStore, array(PR_MAILBOX_OWNER_ENTRYID, PR_MDB_PROVIDER));

					// set PR_SENDER_* properties, which contains currently logged users data
					$ab = $GLOBALS['mapisession']->getAddressbook();
					$abitem = mapi_ab_openentry($ab, $GLOBALS["mapisession"]->getUserEntryID());
					$abitemprops = mapi_getprops($abitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));

					$props[PR_SENDER_ENTRYID] = $GLOBALS["mapisession"]->getUserEntryID();
					$props[PR_SENDER_NAME] = $abitemprops[PR_DISPLAY_NAME];
					$props[PR_SENDER_EMAIL_ADDRESS] = $abitemprops[PR_EMAIL_ADDRESS];
					$props[PR_SENDER_ADDRTYPE] = "ZARAFA";

					/**
					 * if delegate store then set PR_SENT_REPRESENTING_* properties
					 * based on delegate store's owner data
					 * if public store then set PR_SENT_REPRESENTING_* properties based on 
					 * default store's owner data
					 */
					if($origStoreProps[PR_MDB_PROVIDER] === ZARAFA_STORE_DELEGATE_GUID) {
						$abitem = mapi_ab_openentry($ab, $origStoreProps[PR_MAILBOX_OWNER_ENTRYID]);
						$abitemprops = mapi_getprops($abitem, array(PR_DISPLAY_NAME, PR_EMAIL_ADDRESS));

						$props[PR_SENT_REPRESENTING_ENTRYID] = $origStoreProps[PR_MAILBOX_OWNER_ENTRYID];
						$props[PR_SENT_REPRESENTING_NAME] = $abitemprops[PR_DISPLAY_NAME];
						$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $abitemprops[PR_EMAIL_ADDRESS];
						$props[PR_SENT_REPRESENTING_ADDRTYPE] = "ZARAFA";
					} else if($origStoreProps[PR_MDB_PROVIDER] === ZARAFA_STORE_PUBLIC_GUID) {
						$props[PR_SENT_REPRESENTING_ENTRYID] = $props[PR_SENDER_ENTRYID];
						$props[PR_SENT_REPRESENTING_NAME] = $props[PR_SENDER_NAME];
						$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $props[PR_SENDER_EMAIL_ADDRESS];
						$props[PR_SENT_REPRESENTING_ADDRTYPE] = $props[PR_SENDER_ADDRTYPE];
					}

					/**
					 * we are sending mail from delegate's account, so we can't use delegate's outbox and sent items folder
					 * so we have to copy the mail from delegate's store to logged user's store and in outbox folder and then
					 * we can send mail from logged user's outbox folder
					 *
					 * if we set $entryid to false before passing it to saveMessage function then it will assume
					 * that item doesn't exist and it will create a new item (in outbox of logged in user)
					 */
					if(isset($entryid) && $entryid) {
						$oldEntryId = $entryid;
						$entryid = false;

						// if we are sending mail from drafts folder then we have to copy
						// its recipients and attachments also.
						if($copyFromMessageId === false && $copyFromStore === false) {
							$copyFromMessageId = $oldEntryId;
							$copyFromStore = $origStore;
							$copyRecipients = true;
						}
					}

					if($copyFromMessageId && $copyFromStore) {
						// Get properties of original message, to copy recipients and attachments in new message
						$copyMessage = mapi_msgstore_openentry($copyFromStore, $copyFromMessageId);
						$copyMessageProps = mapi_getprops($copyMessage);

						$oldParentEntryId = $copyMessageProps[PR_PARENT_ENTRYID];

						// unset id properties before merging the props, so we will be creating new item instead of sending same item
						unset($copyMessageProps[PR_ENTRYID]);
						unset($copyMessageProps[PR_PARENT_ENTRYID]);
						unset($copyMessageProps[PR_STORE_ENTRYID]);

						// Merge original message props with props sent by client
						$props = $props + $copyMessageProps;
					}

					// Save the new message properties
					$message = $this->saveMessage($store, $entryid, $storeprops[PR_IPM_OUTBOX_ENTRYID], $props, $messageProps, $recipients, $attachments, array(), $copyFromMessageId, $copyFromStore, $copyAttachments, $copyRecipients, $copyInlineAttachmentsOnly, true, true);

					// FIXME: currently message is deleted from original store and new message is created
					// in current user's store, but message should be moved

					// delete message from it's original location
					if(!empty($oldEntryId) && !empty($oldParentEntryId)) {
						$folder = mapi_msgstore_openentry($origStore, $oldParentEntryId);
						mapi_folder_deletemessages($folder, array($oldEntryId), DELETE_HARD_DELETE);
					}
				}else{
					// When the message is in your own store, just move it to your outbox. We move it manually so we know the new entryid after it has been moved.
					$outbox = mapi_msgstore_openentry($store, $storeprops[PR_IPM_OUTBOX_ENTRYID]);
					
					// Open the old and the new message
					$newmessage = mapi_folder_createmessage($outbox);
					$oldEntryId = $entryid;

					// Remember the new entryid
					$newprops = mapi_getprops($newmessage, array(PR_ENTRYID));
					$entryid = $newprops[PR_ENTRYID];

					if(!empty($oldEntryId)) {
						$message = mapi_msgstore_openentry($store, $oldEntryId);

						// Copy the entire message
						mapi_copyto($message, array(), array(), $newmessage);

						// Delete the old message
						mapi_folder_deletemessages($outbox, array($oldEntryId));
					}

					// save changes to new message created in outbox
					mapi_message_savechanges($newmessage);

					// Save the new message properties
					$message = $this->saveMessage($store, $entryid, $storeprops[PR_IPM_OUTBOX_ENTRYID], $props, $messageProps, $recipients, $attachments, array(), $copyFromMessageId, $copyFromStore, $copyAttachments, $copyRecipients, $copyInlineAttachmentsOnly, true, true);
				}

				if($message) {
					// Submit the message (send)
					mapi_message_submitmessage($message);

					$tmp_props = mapi_getprops($message, array(PR_PARENT_ENTRYID));
					$messageProps[PR_PARENT_ENTRYID] = $tmp_props[PR_PARENT_ENTRYID];
					$result = true;

					$this->addEmailsToRecipientHistory($recipients);
				}
			}
			
			return $result;
		}
		
		/**
		 * Delete messages
		 *
		 * This function does what is needed when a user presses 'delete' on a MAPI message. This means that:
		 *
		 * - Items in the own store are moved to the wastebasket
		 * - Items in the wastebasket are deleted
		 * - Items in other users stores are moved to our own wastebasket
		 * - Items in the public store are deleted
		 *
		 * @param mapistore $store MAPI Message Store Object
		 * @param string $parententryid parent entryid of the messages to be deleted
		 * @param array $entryids a list of entryids which will be deleted
		 * @param boolean $softDelete flag for soft-deleteing (when user presses Shift+Del)
		 * @return boolean true if action succeeded, false if not
		 */
		function deleteMessages($store, $parententryid, $entryids, $softDelete = false)
		{
			$result = false;
			if(!is_array($entryids)) {
				$entryids = array($entryids);
			}

			$folder = mapi_msgstore_openentry($store, $parententryid);

			$msgprops = mapi_getprops($store, array(PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER));
			
			switch($msgprops[PR_MDB_PROVIDER]){
				case ZARAFA_STORE_DELEGATE_GUID:
					// with a store from an other user we need our own waste basket...
					if(isset($msgprops[PR_IPM_WASTEBASKET_ENTRYID]) && $msgprops[PR_IPM_WASTEBASKET_ENTRYID] == $parententryid || $softDelete == true) {
						// except when it is the waste basket itself
						$result = mapi_folder_deletemessages($folder, $entryids);
					}else{
						$defaultstore = $GLOBALS["mapisession"]->getDefaultMessageStore();
						$msgprops = mapi_getprops($defaultstore, array(PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER));

						if(isset($msgprops[PR_IPM_WASTEBASKET_ENTRYID]) && $msgprops[PR_IPM_WASTEBASKET_ENTRYID] != $parententryid) {
							try {
								$result = $this->copyMessages($store, $parententryid, $defaultstore, $msgprops[PR_IPM_WASTEBASKET_ENTRYID], $entryids, true);
							} catch (MAPIException $e) {
								$e->setHandled();
								// if moving fails, try normal delete
								$result = mapi_folder_deletemessages($folder, $entryids);
							}
						}else{
							$result = mapi_folder_deletemessages($folder, $entryids);
						}
					}
					break;

				case ZARAFA_STORE_PUBLIC_GUID:
					// always delete in public store
					$result = mapi_folder_deletemessages($folder, $entryids);
					break;

				case ZARAFA_SERVICE_GUID:
					// delete message when in your own waste basket, else move it to the waste basket
					if(isset($msgprops[PR_IPM_WASTEBASKET_ENTRYID]) && $msgprops[PR_IPM_WASTEBASKET_ENTRYID] == $parententryid  || $softDelete == true) {
						$result = mapi_folder_deletemessages($folder, $entryids);
					}else{
						$result = $this->copyMessages($store, $parententryid, $store, $msgprops[PR_IPM_WASTEBASKET_ENTRYID], $entryids, true);
						if (!$result){ // if moving fails, try normal delete
							$result = mapi_folder_deletemessages($folder, $entryids);
						}
					}
					break;
			}

			return $result;
		}
		
		/**
		 * Copy or move messages
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid parent entryid of the messages		 
		 * @param string $destentryid destination folder		 
		 * @param array $entryids a list of entryids which will be copied or moved
		 * @param boolean $moveMessages true - move messages, false - copy messages
		 * @return boolean true if action succeeded, false if not		  
		 * @todo Duplicate code here which is easily fixed
		 */
		function copyMessages($store, $parententryid, $destStore, $destentryid, $entryids, $moveMessages)
		{
			$sourcefolder = mapi_msgstore_openentry($store, $parententryid); 
			$destfolder = mapi_msgstore_openentry($destStore, $destentryid); 
			
			if(!is_array($entryids)) {
				$entryids = array($entryids);
			}

			if($moveMessages) {
				mapi_folder_copymessages($sourcefolder, $entryids, $destfolder, MESSAGE_MOVE);
			} else {
				mapi_folder_copymessages($sourcefolder, $entryids, $destfolder);
			}

			return true;
		}

		/**
		 * Set message read flag
		 *
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid entryid of the message		 
		 * @param array $flags Array of options, may contain "unread" and "noreceipt". The absence of these flags indicate the opposite ("read" and "sendreceipt").		 
		 * @param array $messageProps reference to an array which will be filled with PR_ENTRYID, PR_STORE_ENTRYID and PR_PARENT_ENTRYID of the message
		 * @param array $props properties of the message
		 * @return boolean true if action succeeded, false if not
		 */
		function setMessageFlag($store, $entryid, $flags, $msg_action = false, &$props = false)
		{
			$message = $this->openMessage($store, $entryid);

			if($message) {
				/**
				 * convert flags of PR_MESSAGE_FLAGS property to flags that is
				 * used in mapi_message_setreadflag
				 */
				$flag = 0;		// set unread flag, read receipt will be sent

				if(($flags & MSGFLAG_RN_PENDING) && $msg_action !== false && isset($msg_action['read_receipt']) && $msg_action['read_receipt'] == 'noreceipt') {
					$flag |= SUPPRESS_RECEIPT;
				} else {
					if(!($flags & MSGFLAG_READ)) {
						$flag |= CLEAR_READ_FLAG;
					}

					if(!($flags & MSGFLAG_NRN_PENDING)) {
						$flag |= CLEAR_NRN_PENDING;
					}

					if(!($flags & MSGFLAG_RN_PENDING)) {
						$flag |= CLEAR_RN_PENDING;
					}
				}

				mapi_message_setreadflag($message, $flag);

				if(is_array($props)) {
					$props = mapi_getprops($message, array(PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID));
				}
			}

			return true;
		}

		/**
		 * Create a unique folder name based on a provided new folder name
		 *
		 * checkFolderNameConflict() checks if a folder name conflict is caused by the given $foldername.
		 * This function is used for copying of moving a folder to another folder. It returns
		 * a unique foldername.		 		  
		 *
		 * @access private
		 * @param object $store MAPI Message Store Object
		 * @param object $folder MAPI Folder Object
		 * @param string $foldername the folder name		 
		 * @return string correct foldername		  
		 */
		function checkFolderNameConflict($store, $folder, $foldername)
		{
			$folderNames = array();
			
			$hierarchyTable = mapi_folder_gethierarchytable($folder, MAPI_DEFERRED_ERRORS);
			mapi_table_sort($hierarchyTable, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH); 
			
			$subfolders = mapi_table_queryallrows($hierarchyTable, array(PR_ENTRYID));

			if (is_array($subfolders)) {
				foreach($subfolders as $subfolder)
				{
					$folderObject = mapi_msgstore_openentry($store, $subfolder[PR_ENTRYID]); 
					$folderProps = mapi_folder_getprops($folderObject, array(PR_DISPLAY_NAME));
					
					array_push($folderNames, strtolower($folderProps[PR_DISPLAY_NAME]));
				} 
			}

			if(array_search(strtolower($foldername), $folderNames) !== false) {
				$i = 1;
				
				while(array_search((strtolower($foldername) . $i), $folderNames) !== false)
				{
					$i++;
				}
				
				$foldername .= $i;
			}

			return $foldername;
		}
	
		/**
		 * Set the recipients of a MAPI message
		 *
		 * @access private
		 * @param object $message MAPI Message Object
		 * @param array $recipients XML array structure of recipients
		 * @param boolean $send true if we are going to send this message else false
		 * @param boolean $copyRecipients true if we want to copy recipients from $copyFromMessageId
		 * @param binary $copyFromMessageId If set we copy all recipients from this entryid to this item
		 * @param object $copyFromStore MAPI Message Store Object
		 */
		function setRecipients($message, $recipients, $send = false, $copyRecipients = false, $copyFromMessageId = false, $copyFromStore = false)
		{
			/*
			 * If we are sending mail from delegator's folder, then we need to copy
			 * all recipients from original message first, so if function is provided
			 * with copyFromMessage thencopy all the recipients from the original message,
			 * and if delegate has added or removed any recipients then they will be
			 * added/removed using recipients array.
			 */
			if ($copyRecipients === true && $copyFromMessageId !== false && $copyFromStore !== false) {
				$copyFrom = mapi_msgstore_openentry($copyFromStore, $copyFromMessageId);
				$recipienttable = mapi_message_getrecipienttable($copyFrom);
				$messageRecipients = mapi_table_queryallrows($recipienttable, $GLOBALS["properties"]->getRecipientProperties());

				if(!empty($messageRecipients)) {
					mapi_message_modifyrecipients($message, MODRECIP_ADD, $messageRecipients);
				}
			}

			if(empty($recipients)) {
				// no recipients are sent from client
				return;
			}

			$newRecipients = array();
			$removeRecipients = array();
			$modifyRecipients = array();

			if(isset($recipients['add']) && !empty($recipients['add'])) {
				$newRecipients = $this->createRecipientList($recipients['add'], 'add', false, $send);
			}

			if(isset($recipients['remove']) && !empty($recipients['remove'])) {
				$removeRecipients = $this->createRecipientList($recipients['remove'], 'remove');
			}

			if(isset($recipients['modify']) && !empty($recipients['modify'])) {
				$modifyRecipients = $this->createRecipientList($recipients['modify'], 'modify', false, $send);
			}

			if(!empty($removeRecipients)){
				mapi_message_modifyrecipients($message, MODRECIP_REMOVE, $removeRecipients);
			}

			if(!empty($modifyRecipients)){
				mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $modifyRecipients);
			}

			if(!empty($newRecipients)){
				mapi_message_modifyrecipients($message, MODRECIP_ADD, $newRecipients);
			}
		}
		
		/**
		 * Delete properties in a message
		 *
		 * @todo Why does this function call savechange while most other functions
		 *       here do not? (for example setRecipients)
		 * @param object $store MAPI Message Store Object		 
		 * @param string $entryid Entryid of the message in which to delete the properties
		 * @param array $props array of property tags which to be deleted
		 */
		function deleteProps($store, $entryid, $props)
		{
			$message = $this->openMessage($store, $entryid);
			mapi_deleteprops($message, $props);
			mapi_savechanges($message);
		}
		
		/**
		 * Set attachments in a MAPI message
		 *
		 * This function reads any attachments that have been previously uploaded and copies them into
		 * the passed MAPI message resource. For a description of the dialog_attachments variable and
		 * generally how attachments work when uploading, see Operations::saveMessage()
		 *
		 * @see Operations::saveMessage()
		 * @param object $message MAPI Message Object
		 * @param array $attachments XML array structure of attachments
		 * @param boolean $copyFromMessageId if set, copy the attachments from this entryid in addition to the uploaded attachments
		 * @param object $copyFromStore MAPI Message Store Object
		 * @param boolean $copyInlineAttachmentsOnly if true then copy only inline attachments.
		 */
		function setAttachments($message, $attachments = array(), $copyFromMessageId = false, $copyFromStore = false, $copyInlineAttachmentsOnly = false)
		{
			// check if we need to copy attachments from a forwared message first
			if ($copyFromMessageId !== false && $copyFromStore !== false){
				$copyfrom = mapi_msgstore_openentry($copyFromStore, $copyFromMessageId);

				$attachmentTable = mapi_message_getattachmenttable($copyfrom);
				if($attachmentTable) {
					$existingAttachments = 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($existingAttachments as $props) {
						// check if this attachment is "deleted"
						if(isset($_SESSION["deleteattachment"]) && $attachments['dialog_attachments'] && isset($_SESSION["deleteattachment"][$attachments['dialog_attachments']])) {
							if (in_array($props[PR_ATTACH_NUM],$_SESSION["deleteattachment"][$attachments['dialog_attachments']])) {
								// skip attachment
								continue;
							}
						}

						$old = mapi_message_openattach($copyfrom, $props[PR_ATTACH_NUM]);
						
						// If reply message, then copy only inline attachments.
						if ($copyInlineAttachmentsOnly){
							$oldProps = mapi_getprops($old, array(PR_ATTACH_CONTENT_ID));
							if (!isset($oldProps[PR_ATTACH_CONTENT_ID])){
								// for inline images hidden should be true
								// skip normal attachments
								continue;
							}
						}

						$new = mapi_message_createattach($message);
						mapi_copyto($old, array(), array(), $new, 0);
						mapi_savechanges($new);
					}
				}
			} else {
				// Check if attachments should be deleted. This is set in the "upload_attachment.php" file
				if(isset($_SESSION["deleteattachment"]) && isset($_SESSION["deleteattachment"][$attachments['dialog_attachments']])) {
					foreach($_SESSION["deleteattachment"][$attachments['dialog_attachments']] as $attach_num) {
						mapi_message_deleteattach($message, (int) $attach_num);
					}
					// The delete action has been handled, remove it from the session.
					unset($_SESSION["deleteattachment"][$attachments['dialog_attachments']]);
				}
			}

			$addedInlineAttachmentCidMapping = Array();
			if(is_array($attachments) && !empty($attachments)) {
				// Set contentId to saved attachments.
				if(isset($attachments['add']) && is_array($attachments['add']) && !empty($attachments['add'])) {
					foreach($attachments['add'] as $key => $attach) {
						if($attach && isset($attach['inline']) && $attach['inline']) {
							$addedInlineAttachmentCidMapping[ $attach['attach_num'] ] = $attach['cid'];
							$msgattachment = mapi_message_openattach($message, $attach['attach_num']);
							if($msgattachment) {
								$props = array(PR_ATTACH_CONTENT_ID => $attach['cid'], PR_ATTACHMENT_HIDDEN => true);
								mapi_setprops($msgattachment, $props);
								mapi_savechanges($msgattachment);
							}
						}
					}
				}

				// Delete saved inline images if removed from body.	
				if(isset($attachments['remove']) && is_array($attachments['remove']) && !empty($attachments['remove'])) {
					foreach($attachments['remove'] as $key => $attach) {
						if($attach && isset($attach['inline']) && $attach['inline']) {
							$msgattachment = mapi_message_openattach($message, $attach['attach_num']);
							if($msgattachment) {
								mapi_message_deleteattach($message, $attach['attach_num']);
								mapi_savechanges($message);
							}
						}
					}
				}
			}

			if($attachments['dialog_attachments']) {
				$dialog_attachments = $attachments['dialog_attachments'];
			} else {
				return;
			}

			// Check if ["files"] and ["files"][$dialog_attachments] is set
			if(isset($_SESSION["files"])) {
				if(isset($_SESSION["files"][$dialog_attachments])) {
					// Loop through teh uploaded attachments
					foreach ($_SESSION["files"][$dialog_attachments] as $tmpname => $fileinfo)
					{
						if (is_file(TMP_PATH."/".$tmpname)) {
							// Set contentId if attachment is inline
							if(isset($addedInlineAttachmentCidMapping[ $tmpname ])){
								$cid = $addedInlineAttachmentCidMapping[ $tmpname ];
							}else{
								$cid = '';
							}

							// Set attachment properties
							$props = Array(PR_ATTACH_LONG_FILENAME => $fileinfo["name"],
										   PR_DISPLAY_NAME => $fileinfo["name"],
										   PR_ATTACH_METHOD => ATTACH_BY_VALUE,
										   PR_ATTACH_DATA_BIN => "",
										   PR_ATTACH_MIME_TAG => $fileinfo["type"],
										   PR_ATTACHMENT_HIDDEN => $cid?true:false);

							if ($cid != "") {
								$props[PR_ATTACH_CONTENT_ID] = $cid;
							}

							// Create attachment and set props
							$attachment = mapi_message_createattach($message);
							mapi_setprops($attachment, $props);

							// Stream the file to the PR_ATTACH_DATA_BIN property 
							$stream = mapi_openproperty($attachment, PR_ATTACH_DATA_BIN, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
							$handle = fopen(TMP_PATH."/".$tmpname, "r");
							while (!feof($handle))
							{
								$contents = fread($handle, BLOCK_SIZE);
								mapi_stream_write($stream, $contents);
							}

							// Commit the stream and save changes
							mapi_stream_commit($stream);
							mapi_savechanges($attachment);
							fclose($handle);
							unlink(TMP_PATH."/".$tmpname);
						} else {//thats means the attachment is a message item and is uploaded in sesssion file as mapi message Obj
							$props = array();
							$props[PR_ATTACH_METHOD] = ATTACH_EMBEDDED_MSG;
							$props[PR_DISPLAY_NAME] = $fileinfo["name"];

							//Create new attachment.
							$attachment = mapi_message_createattach($message);
							mapi_message_setprops($attachment, $props);
							
							//open embedded msg
							$imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
							$copyfromStore = $GLOBALS["mapisession"]->openMessageStore($fileinfo["storeid"]);
							$copyfrom = mapi_msgstore_openentry($copyfromStore , $fileinfo["entryid"]);
							
							$msg_props = mapi_message_getprops($copyfrom );
							//set the props of embedded message same as the selected message
							$props = $props + $msg_props;
							mapi_message_setprops($imessage, $props);
							//hardcode the message class props as we need all message as normal.
							mapi_message_setprops($imessage,array(PR_MESSAGE_CLASS => "IPM.NOTE" ));

							// copy attachment property to embedded message
							$attachmentTable = mapi_message_getattachmenttable($copyfrom);
							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){
									if ($attach_props[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG)
										continue;

									$attach_old = mapi_message_openattach($copyfrom, $attach_props[PR_ATTACH_NUM]);
									$attach_newResourceMsg = mapi_message_createattach($imessage);
									mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
									mapi_savechanges($attach_newResourceMsg);
								}
							}
							
							//copy the recipient of embedded message
							$recipienttable = mapi_message_getrecipienttable($copyfrom);
							$recipients = mapi_table_queryallrows($recipienttable, $GLOBALS["properties"]->getRecipientProperties());
							$copy_to_recipientTable = mapi_message_getrecipienttable($imessage);
							$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
							foreach($copy_to_recipientRows as $recipient) {
								mapi_message_modifyrecipients($imessage, MODRECIP_REMOVE, array($recipient));
							}
							mapi_message_modifyrecipients($imessage, MODRECIP_ADD, $recipients);

							//save changes in the embedded message and the final attachment 
							mapi_savechanges($imessage);
							mapi_savechanges($attachment);
						}
					}
					// Delete all the files in the $_SESSION["files"][$dialog_attachments].
					unset($_SESSION["files"][$dialog_attachments]);
				}
			}
		}
		
		/**
		 * get attachments information of a particular message
		 *
		 * @param MapiStore $store MAPI Store Object
		 * @param MapiMessage $message MAPI Message Object
		 * @param Boolean $excludeHidden exclude hidden attachments
		 */
		function getAttachmentsInfo($store, $message, $excludeHidden = false)
		{
			$attachmentsInfo = array();

			$hasattachProp = mapi_getprops($message, array(PR_HASATTACH));
			if (isset($hasattachProp[PR_HASATTACH]) && $hasattachProp[PR_HASATTACH]) {
				$attachmentTable = mapi_message_getattachmenttable($message);

				$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME,
																			PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD,
																			PR_ATTACH_CONTENT_ID, PR_ATTACH_MIME_TAG,
																			PR_ATTACHMENT_CONTACTPHOTO, PR_OBJECT_TYPE));

				foreach($attachments as $attachmentRow) {
						$props = array();
						$props["object_type"] = $attachmentRow[PR_OBJECT_TYPE];
						$props["attach_num"] = $attachmentRow[PR_ATTACH_NUM];
						$props["attach_method"] = $attachmentRow[PR_ATTACH_METHOD];
						$props["size"] = $attachmentRow[PR_ATTACH_SIZE];

						if(isset($attachmentRow[PR_ATTACH_CONTENT_ID]) && $attachmentRow[PR_ATTACH_CONTENT_ID]) {
							$props["cid"] = $attachmentRow[PR_ATTACH_CONTENT_ID];
						}

						if(isset($attachmentRow[PR_ATTACH_MIME_TAG]) && $attachmentRow[PR_ATTACH_MIME_TAG]) {
							$props["filetype"] = $attachmentRow[PR_ATTACH_MIME_TAG];
						}

						$props["hidden"] = isset($attachmentRow[PR_ATTACHMENT_HIDDEN]) ? $attachmentRow[PR_ATTACHMENT_HIDDEN] : false;
						if($excludeHidden && $props["hidden"]) {
							continue;
						}

						if(isset($attachmentRow[PR_ATTACH_LONG_FILENAME])) {
							$props["name"] = $attachmentRow[PR_ATTACH_LONG_FILENAME];
						} else if(isset($attachmentRow[PR_ATTACH_FILENAME])) {
							$props["name"] = $attachmentRow[PR_ATTACH_FILENAME];
						} else if(isset($attachmentRow[PR_DISPLAY_NAME])) {
							$props["name"] = $attachmentRow[PR_DISPLAY_NAME];
						} else {
							$props["name"] = "untitled";
						}

						if(isset($attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) && $attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) {
							$props["attachment_contactphoto"] = $attachmentRow[PR_ATTACHMENT_CONTACTPHOTO];
							$props["hidden"] = true;

							//Open contact photo attachement in binary format.
							$attach = mapi_message_openattach($message, $props["attach_num"]);
							$photo = mapi_attach_openbin($attach,PR_ATTACH_DATA_BIN);

							// Process photo and restrict its size to 96.
							if ($photo) {
								$compressionRatio=1;
								for ($length=2, $len = strlen($photo); $length <= $len;) {
									$partinfo = unpack("Cmarker/Ccode/nlength",substr($photo,$length,4));
									if ($partinfo['marker'] != 0xff) break; // error in structure???
									if ($partinfo['code'] >= 0xc0 &&
										$partinfo['code'] <= 0xc3) { // this is the size block
										$photo_size = unpack("Cunknown/ny/nx",substr($photo,$length+4,5));
										// find the resize factor, picture should be not higher than 96 pixel.
										$compressionRatio = ceil($photo_size['y']/96);
										break;
									} else { // jump to next block
										$length = $length+$partinfo['length']+2;
									}
								}
								if ($partinfo['marker'] == 0xff) {
									$props["attachment_contactphoto_sizex"] = (int)($photo_size['x'] / $compressionRatio);
									$props["attachment_contactphoto_sizey"] = (int)($photo_size['y'] / $compressionRatio);
								}
							}
						}

						if ($props["attach_method"] == ATTACH_EMBEDDED_MSG){
							// open attachment to get the message class
							$attach = mapi_message_openattach($message, $props["attach_num"]);
							$embMessage = mapi_attach_openobj($attach);
							$embProps = mapi_getprops($embMessage, array(PR_MESSAGE_CLASS));
							if (isset($embProps[PR_MESSAGE_CLASS]))
								$props["attach_message_class"] = $embProps[PR_MESSAGE_CLASS];
						}

						array_push($attachmentsInfo, array( "props" => $props ));
				}
			}

			return $attachmentsInfo;
		}

		/**
		 * get recipients information of a particular message
		 *
		 * @param MapiStore $store MAPI Store Object
		 * @param MapiMessage $message MAPI Message Object
		 * @param Boolean $excludeDeleted exclude deleted recipients
		 */
		function getRecipientsInfo($store, $message, $excludeDeleted = true)
		{
			$recipientsInfo = array();

			$recipientTable = mapi_message_getrecipienttable($message);
			if($recipientTable && mapi_table_getrowcount($recipientTable) > 0) {
				$recipients = mapi_table_queryallrows($recipientTable, $GLOBALS['properties']->getRecipientProperties());

				foreach($recipients as $recipientRow) {
					if ($excludeDeleted && isset($recipientRow[PR_RECIPIENT_FLAGS]) && (($recipientRow[PR_RECIPIENT_FLAGS] & recipExceptionalDeleted) == recipExceptionalDeleted)) {
						continue;
					}

					$props = array();
					$props['rowid'] = $recipientRow[PR_ROWID];
					$props['display_name'] = isset($recipientRow[PR_DISPLAY_NAME]) ? $recipientRow[PR_DISPLAY_NAME] : '';
					$props['email_address'] = isset($recipientRow[PR_EMAIL_ADDRESS]) ? $recipientRow[PR_EMAIL_ADDRESS] : '';
					$props['smtp_address'] = isset($recipientRow[PR_SMTP_ADDRESS]) ? $recipientRow[PR_SMTP_ADDRESS] : '';
					$props['address_type'] = isset($recipientRow[PR_ADDRTYPE]) ? $recipientRow[PR_ADDRTYPE] : '';
					$props['object_type'] = $recipientRow[PR_OBJECT_TYPE];
					$props['recipient_type'] = $recipientRow[PR_RECIPIENT_TYPE];
					$props['display_type'] = isset($recipientRow[PR_DISPLAY_TYPE]) ? $recipientRow[PR_DISPLAY_TYPE] : 0;;
					$props['display_type_ex'] = isset($recipientRow[PR_DISPLAY_TYPE_EX]) ? $recipientRow[PR_DISPLAY_TYPE_EX] : 0;

					if(isset($recipientRow[PR_RECIPIENT_FLAGS])) {
						$props['recipient_flags'] = $recipientRow[PR_RECIPIENT_FLAGS];
					}

					if(isset($recipientRow[PR_ENTRYID])) {
						$props['entryid'] = bin2hex($recipientRow[PR_ENTRYID]);

						// Get the SMTP address from the addressbook if no address is found
						if(empty($props['smtp_address']) && $recipientRow[PR_ADDRTYPE] == 'ZARAFA') {
							$props['smtp_address'] = $GLOBALS['operations']->getEmailAddressFromEntryID($recipientRow[PR_ENTRYID]);
						}
					}

					// Set propose new time properties
					if(isset($recipientRow[PR_PROPOSEDNEWTIME]) && isset($recipientRow[PR_PROPOSEDNEWTIME_START]) && isset($recipientRow[PR_PROPOSEDNEWTIME_END])) {
						$props['proposednewtime_start'] = $recipientRow[PR_PROPOSEDNEWTIME_START];
						$props['proposednewtime_end'] = $recipientRow[PR_PROPOSEDNEWTIME_END];
						$props['proposednewtime'] = $recipientRow[PR_PROPOSEDNEWTIME];
					} else {
						$props['proposednewtime'] = false;
					}

					$props['recipient_trackstatus'] = !empty($recipientRow[PR_RECIPIENT_TRACKSTATUS]) ? $recipientRow[PR_RECIPIENT_TRACKSTATUS] : olRecipientTrackStatusNone;
					$props['recipient_trackstatus_time'] = (!empty($recipientRow[PR_RECIPIENT_TRACKSTATUS_TIME])) ? $recipientRow[PR_RECIPIENT_TRACKSTATUS_TIME] : null;

					array_push($recipientsInfo, array( "props" => $props ));
				}
			}

			return $recipientsInfo;
		}
		
		/**
		 * Create a MAPI recipient list from an XML array structure
		 *
		 * This functions is used for setting the recipient table of a message.		 
		 * @param array $recipientList a list of recipients as XML array structure
		 * @param string $opType the type of operation that will be performed on this recipient list (add, remove, modify).
		 * @param boolean $send true if we are going to send this message else false.
		 * @return array list of recipients with the correct MAPI properties ready for mapi_message_modifyrecipients()
		 */
		function createRecipientList($recipientList, $opType = 'add', $isException = false, $send = false)
		{
			$recipients = array();
			$addrbook = $GLOBALS["mapisession"]->getAddressbook();

			foreach($recipientList as $recipientItem) {
				if ($isException) {
					// We do not add organizer to exception msg in organizer's calendar.
					if (isset($recipientItem[PR_RECIPIENT_FLAGS]) && $recipientItem[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer))
						continue;

					$recipient[PR_RECIPIENT_FLAGS] = (recipSendable | recipExceptionalResponse | recipReserved);
				}

				if(!empty($recipientItem["smtp_address"]) && empty($recipientItem["email_address"])){
					$recipientItem["email_address"] = $recipientItem["smtp_address"];
				}

				// if we are saving a mail then we can allow empty email address but not when sending it
				if(empty($recipientItem["display_name"]) || ($send && empty($recipientItem["email_address"]))) {
					return;
				}

				// to modify or remove recipients we need PR_ROWID property
				if($opType !== 'add' && ( !isset($recipientItem['rowid']) || !is_numeric($recipientItem['rowid']) )) {
					continue;
				}

				if(isset($recipientItem["entryid"]) && !empty($recipientItem["entryid"])) {
					// entryids sent from client are in hex format so convert it to binary format
					$recipientItem["entryid"] = hex2bin($recipientItem["entryid"]);
				}

				/**
				 * For external contacts (DT_REMOTE_MAILUSER) email_address contains display name of contact
				 * which is obviously not unique so for that we need to resolve address based on smtp_address
				 * if provided
				 */
				$addressToResolve = $recipientItem["email_address"];
				if(isset($recipientItem["smtp_address"]) && !empty($recipientItem["smtp_address"])) {
					$addressToResolve = $recipientItem["smtp_address"];
				}

				// Re-lookup information in GAB if possible so we have up-to-date name for given address
				$user = array( array( PR_DISPLAY_NAME => $addressToResolve ) );
				try {
					// resolve users based on email address with strict matching
					$user = mapi_ab_resolvename($addrbook, $user, EMS_AB_ADDRESS_LOOKUP);
					$recipientItem["display_name"] = $user[0][PR_DISPLAY_NAME];
					$recipientItem["entryid"] = $user[0][PR_ENTRYID];
					$recipientItem["search_key"] = $user[0][PR_SEARCH_KEY];
					$recipientItem["email_address"] = $user[0][PR_EMAIL_ADDRESS];
					$recipientItem["address_type"] = $user[0][PR_ADDRTYPE];
				} catch (MAPIException $e) {
					// recipient is not resolved or it got multiple matches,
					// so ignore this error and continue with normal processing
					$e->setHandled();
				}

				$recipient = array();
				$recipient[PR_DISPLAY_NAME] = $recipientItem["display_name"];
				$recipient[PR_DISPLAY_TYPE] = $recipientItem["display_type"];
				$recipient[PR_DISPLAY_TYPE_EX] = $recipientItem["display_type_ex"];
				$recipient[PR_EMAIL_ADDRESS] = $recipientItem["email_address"];
				$recipient[PR_SMTP_ADDRESS] = $recipientItem["smtp_address"];
				if(isset($recipientItem["search_key"])) {
					$recipient[PR_SEARCH_KEY] = $recipientItem["search_key"];
				}
				$recipient[PR_ADDRTYPE] = $recipientItem["address_type"];
				$recipient[PR_OBJECT_TYPE] = $recipientItem["object_type"];
				$recipient[PR_RECIPIENT_TYPE] = $recipientItem["recipient_type"];
				if($opType != 'add') {
					$recipient[PR_ROWID] = $recipientItem["rowid"];
				}
				
				if (isset($recipientItem["recipient_status"]) && !empty($recipientItem["recipient_status"])){
					$recipient[PR_RECIPIENT_TRACKSTATUS] = $recipientItem["recipient_status"];
				}
				
				if(isset($recipientItem["recipient_flags"]) && !empty($recipient["recipient_flags"])){
					$recipient[PR_RECIPIENT_FLAGS] = $recipientItem["recipient_flags"];
				}else{
					$recipient[PR_RECIPIENT_FLAGS] = recipSendable;
				}

				if(isset($recipientItem["proposednewtime"]) && !empty($recipientItem["proposednewtime"]) && isset($recipientItem["proposednewtime_start"]) && isset($recipientItem["proposednewtime_end"])){
					$recipient[PR_PROPOSEDNEWTIME] = $recipientItem["proposednewtime"];
					$recipient[PR_PROPOSEDNEWTIME_START] = $recipientItem["proposednewtime_start"];
					$recipient[PR_PROPOSEDNEWTIME_END] = $recipientItem["proposednewtime_end"];
				} else {
					$recipient[PR_PROPOSEDNEWTIME] = false;
				}

				// Use given entryid if possible, otherwise create a one-off entryid
				if(isset($recipientItem["entryid"]) && !empty($recipientItem["entryid"])) {
					$recipient[PR_ENTRYID] = $recipientItem["entryid"];
				} else if ($send) {
					// only create one-off entryid when we are actually sending the message not saving it
					$recipient[PR_ENTRYID] = mapi_createoneoff($recipient[PR_DISPLAY_NAME], $recipient[PR_ADDRTYPE], $recipient[PR_EMAIL_ADDRESS]);
				}

				array_push($recipients, $recipient);
			}

			return $recipients;
		}
		
		/**
		 * Create a reply-to property value
		 *
		 * @param array $recipients list of recipients
		 * @return string the value for the PR_REPLY_RECIPIENT_ENTRIES property
		 * @todo remove it, it is unused
		 */
		function createReplyRecipientEntry($replyTo)
		{
			// TODO: implement. Is used for the setting: Reply To
			// for now, use the old function:
			return $this->createReplyRecipientEntry_old($replyTo);
		}
		
		/**
		 * Parse reply-to value from PR_REPLY_RECIPIENT_ENTRIES property
		 * @param string $flatEntryList the PR_REPLY_RECIPIENT_ENTRIES value
		 * @return array list of recipients in XML array structure
		 */
		function readReplyRecipientEntry($flatEntryList)
		{
			// Unpack number of entries, the byte count and the entries
			$unpacked = unpack("V1cEntries/V1cbEntries/a*abEntries", $flatEntryList);
			
			$abEntries = Array();
			$stream = $unpacked['abEntries'];
			$pos = 8;
			
			for ($i=0; $i<$unpacked['cEntries']; $i++)
			{
				$findEntry = unpack("a".$pos."before/V1cb/a*after", $flatEntryList);
				// Go to after the unsigned int
				$pos += 4;
				$entry = unpack("a".$pos."before/a".$findEntry['cb']."abEntry/a*after", $flatEntryList);
				// Move to after the entry
				$pos += $findEntry['cb'];
				// Move to next 4-byte boundary
				$pos += $pos%4;
				// One one-off entry id
				$abEntries[] = $entry['abEntry'];
			}
			
			$recipients = Array();
			foreach ($abEntries as $abEntry)
			{
				// Unpack the one-off entry identifier
				$findID = unpack("V1version/a16mapiuid/v1flags/v1abFlags/a*abEntry", $abEntry);
				$tempArray = Array();
				// Split the entry in its three fields
				
				// Workaround (if Unicode then strip \0's)
				if (($findID['abFlags'] & 0x8000)) {
					$idParts = explode("\0\0", $findID['abEntry']);
					foreach ($idParts as $idPart)
					{
						// Remove null characters from the field contents
						$tempArray[] = str_replace("\x00", "", $idPart);
					}
				} else {
					// Not Unicode. Just split by \0.
					$tempArray = explode("\0", $findID['abEntry']);
				}
				
				// Put data in recipient array
				$recipients[] = Array(
					"display_name" => $tempArray[0],
					"email_address" => $tempArray[2]
				);
			}
			
			return $recipients;
		}
		
		/**
		 * Build full-page HTML from the FCK Editor fragment
		 *
		 * This function basically takes the generated HTML from FCK editor and embeds it in
		 * a standonline HTML page (including header and CSS) to form.
		 *
		 * @param string $fck_html This is the HTML created by the FCK Editor
		 * @param string $title  Optional, this string is placed in the <title>
		 * @return string full HTML message		 
		 *
		 */
		function generateBodyHTML($fck_html, $title = "Zarafa WebApp"){
			$html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"
					."<html>\n"
					."<head>\n"
					."  <meta name=\"Generator\" content=\"Zarafa WebApp v".phpversion("mapi")."\">\n"
					."  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"
					."  <title>".htmlspecialchars($title)."</title>\n";

			$html .= "</head>\n"
					."<body>\n"
					. $fck_html . "\n"
					."</body>\n"
					."</html>";
			
			return $html;
		}

		/**
		 * Calculate the total size for all items in the given folder
		 *
		 * @param mapifolder $folder The folder for which the size must be calculated
		 * @return number The folder size
		 */
		function calcFolderMessageSize($folder)
		{
			$size = 0;

			$table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS);
				
			$item_count = mapi_table_getrowcount($table);
			for($i = 0; $i<$item_count; $i+=50){
				$messages = mapi_table_queryrows($table, array(PR_MESSAGE_SIZE), $i, 50);
				foreach($messages as $message){
					if (isset($message[PR_MESSAGE_SIZE])){
						$size += $message[PR_MESSAGE_SIZE];
					}
				}
			}
			return $size;
		}



		/**
		 * Save task item.
		 *
		 * just one step more before saving task message that to support recurrence and task request here. We need
		 * to regenerate task if it is recurring and client has changed either set as complete or delete or 
		 * given new start or end date.
		 *
		 * @param mapistore $store MAPI store of the message
		 * @param string $parententryid Parent entryid of the message (folder entryid, NOT message entryid)
		 * @param array $action Action array containing XML request
		 * @return array of PR_ENTRYID, PR_PARENT_ENTRYID and PR_STORE_ENTRYID properties of modified item
		 */
		function saveTask($store, $parententryid, $entryid, $action)
		{
			$properties = $GLOBALS["properties"]->getTaskProperties();
			$send = isset($action["send"]) ? $action["send"] : false;

			if($store && $parententryid) {
				if(isset($action["props"])) {

					$props = $action["props"];

					if (!isset($action["props"]["entryid"])) {

						// Fetch message store properties of a current store.
						$msgstoreProps = mapi_getprops($store, array(PR_USER_ENTRYID, PR_MDB_PROVIDER, PR_MAILBOX_OWNER_ENTRYID));

						// Get current session and open addressbook
						$addrbook = $GLOBALS["mapisession"]->getAddressbook();

						// Store PR_SENT_REPRESENTING_* properties for task according to store type.
						switch ($msgstoreProps[PR_MDB_PROVIDER])
						{
							case ZARAFA_STORE_PUBLIC_GUID:
								try {
									// Open addressbook entry for current user.
									$userObject = mapi_ab_openentry($addrbook, $msgstoreProps[PR_USER_ENTRYID]);
									$userProps = mapi_getprops($userObject);
									// Store PR_SENDER_* properties for task in props variable.
									$props["sender_email_address"] = $userProps[PR_EMAIL_ADDRESS];
									$props["sender_name"] = $userProps[PR_DISPLAY_NAME];
									$props["sender_address_type"] = $userProps[PR_ADDRTYPE];
									$props["sender_entryid"] = bin2hex($userProps[PR_ENTRYID]);
									$props["sender_search_key"] = bin2hex($userProps[PR_SEARCH_KEY]);

									/**
									 * store type is "public"
									 * Store PR_SENT_REPRESENTING_* properties for task in props variable.
									 */
									$props["sent_representing_entryid"] = bin2hex($userProps[PR_ENTRYID]);
									$props["sent_representing_name"] = $userProps[PR_DISPLAY_NAME];
									$props["sent_representing_address_type"] = $userProps[PR_ADDRTYPE];
									$props["sent_representing_email_address"] = $userProps[PR_EMAIL_ADDRESS];
									$props["sent_representing_search_key"] = bin2hex($userProps[PR_SEARCH_KEY]);
								} catch (MAPIException $e) {
									$e->setHandled();
								}
								break;
							case ZARAFA_SERVICE_GUID:
								// store type is "default"
							case ZARAFA_STORE_DELEGATE_GUID:
								/**
								 * store type is "delegate"
								 * Open addressbook entry for mailbox owner.
								 */
								try {
									$ownerObject = mapi_ab_openentry($addrbook, $msgstoreProps[PR_MAILBOX_OWNER_ENTRYID]);
									$ownerProps = mapi_getprops($ownerObject);
										// Store PR_SENT_REPRESENTING_* properties for task in props variable.
										$props["sent_representing_entryid"] = bin2hex($ownerProps[PR_ENTRYID]);
										$props["sent_representing_name"] = $ownerProps[PR_DISPLAY_NAME];
										$props["sent_representing_address_type"] = $ownerProps[PR_ADDRTYPE];
										$props["sent_representing_email_address"] = $ownerProps[PR_EMAIL_ADDRESS];
										$props["sent_representing_search_key"] = bin2hex($ownerProps[PR_SEARCH_KEY]);
								} catch (MAPIException $e) {
									$e->setHandled();
								}
								break;
						}
					}

					$messageProps = array();
					$recips = array();
					if(isset($action["recipients"]) && is_array($action["recipients"])) {
						$recips = $action["recipients"];
					} else {
						$recips = false;
					}

					if (isset($action["props"]["entryid"]) && !empty($action["props"]["entryid"])) {
						$message = mapi_msgstore_openentry($store, hex2bin($action["props"]["entryid"]));

						if ($message) {
							$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID, $properties['recurring']));

							if ((isset($messageProps[$properties['recurring']]) && $messageProps[$properties['recurring']]) ||
								(isset($props['recurring']) && $props['recurring'])) {
								$recur = new TaskRecurrence($store, $message);

								if (isset($props['recurring_reset']) && $props['recurring_reset'] == 1) {
									$msgProps = $recur->setRecurrence($props);
								} else if ((isset($props['complete']) && $props['complete'] == 1)) {
									$msgProps = $recur->markOccurrenceComplete($props);
								}
							}
							mapi_savechanges($message);

							$messageProps = Conversion::mapXML2MAPI($properties, $props);

							$message = $GLOBALS["operations"]->saveMessage($store, $entryid, $parententryid, $messageProps, $messageProps, $recips ? $recips : array(), isset($action['attachments']) ? $action['attachments'] : array(), array());

							if (isset($msgProps) && $msgProps) {
								$messageProps = $msgProps;
							}
						}
					} else {
						$messageProps = Conversion::mapXML2MAPI($properties, $props);
						//New message
						$message = $GLOBALS["operations"]->saveMessage($store, $entryid, $parententryid, $messageProps, $messageProps, $recips ? $recips : array(), isset($action['attachments']) ? $action['attachments'] : array(), array());

						// Set recurrence
						if (isset($action['props']['recurring']) && $action['props']['recurring'] == 1) {
							$recur = new TaskRecurrence($store, $message);
							$recur->setRecurrence($props);
						}
					}

					if ($message) {
						// The task may be a delegated task, do an update if needed (will fail for non-delegated tasks)
						$tr = new TaskRequest($store, $message, $GLOBALS["mapisession"]->getSession());

						// @TODO: check whether task is request and not a normal task
						switch($send)
						{
							case "accept":
								$result = $tr->doAccept(_("Task Accepted:") . " ");
								break;
							case "decline":
								$result = $tr->doDecline(_("Task Declined:") . " ");
								break;
							case "request":
								$tr->sendTaskRequest(_("Task Request:") . " ");
								break;
							case "unassign":
								$tr->createUnassignedCopy();
								break;
							case "reclaim":
								$tr->reclaimownership();
								break;
							default:
								if (isset($props["messagechanged"]) && $props["messagechanged"])
									$tr->doUpdate(_("Task Updated:") . " ", _("Task Completed:") . " ");
						}

						// Notify Inbox that task request has been deleted
						if (isset($result) && is_array($result))
							$GLOBALS["bus"]->notify(bin2hex($result[PR_PARENT_ENTRYID]), TABLE_DELETE, $result);
					}
				}
			}

			mapi_savechanges($message);

			// Return message properties that can be sent to the bus to notify changes
			return $messageProps;
		}

		/**
		 * Deletes a task.
		 *
		 * deletes occurrence if task is a recurring item.
		 * @param mapistore $store MAPI Message Store Object
		 * @param string $parententryid parent entryid of the messages to be deleted
		 * @param array $entryids a list of entryids which will be deleted
		 * @param boolean $softDelete flag for soft-deleteing (when user presses Shift+Del)
		 * @return boolean true if action succeeded, false if not
		 */
		function deleteTask($store, $parententryid, $entryids, $action)
		{
			$result = false;

			// If user wants to delete only occurrence then delete this occurrence
			if (!is_array($entryids) && isset($action['deleteFlag'])) {
				$message = mapi_msgstore_openentry($store, $entryids);

				if ($message) {
					if ($action['deleteFlag'] == 'occurrence') {
						$recur = new TaskRecurrence($store, $message);
						$occurrenceDeleted = $recur->deleteOccurrence($action);
					} else if ($action['deleteFlag'] == 'decline' || $action['deleteFlag'] == 'complete') {
						$taskReq = new TaskRequest($store, $message, $GLOBALS["mapisession"]->getSession());

						if ($action['deleteFlag'] == 'decline') $taskReq->doDecline(_("Task Declined:") . " ");
						else if ($action['deleteFlag'] == 'complete') $taskReq->sendCompleteUpdate(_("Task Updated:") . " ", $action, _("Task Completed:") . " ");
					}
				}
			}

			// Deleting occurrence failed, maybe that was its last occurrence, so now we delete whole series.
			if (!isset($occurrenceDeleted) || !$occurrenceDeleted) {
				// If softdelete is set then set it in softDelete variable and pass it for deleteing message.
				$softDelete = isset($action["softdelete"]) ? $action["softdelete"] : false;
				$result = $GLOBALS["operations"]->deleteMessages($store, $parententryid, $entryids, $softDelete);
			} else {
				$result = array('occurrenceDeleted' => true);
			}

			return $result;
		}

		/**
		* Create a flat entrylist (used for PR_REPLY_RECIPIENT_ENTRIES) from a list of recipients
		*
		* These flatentrylists are used in PR_REPLY_RECIPIENT_ENTRIES, remember to
		* keep this property synchronized with PR_REPLY_RECIPIENT_NAMES.
		*
		* @param String $recipientArray The array with recipients to convert
		* @return boolean Returns the resulting flatentrylist
		*/
		function writeFlatEntryList($recipientArray)
		{
			$oneOffs = Array();
			foreach ($recipientArray as $recipient)
			{
				// Add display name if it doesn't exist
				if (!array_key_exists(PR_DISPLAY_NAME, $recipient)||empty($recipient[PR_DISPLAY_NAME]))
					$recipient[PR_DISPLAY_NAME] = $recipient[PR_EMAIL_ADDRESS];
				$oneOffs[] = mapi_createoneoff($recipient[PR_DISPLAY_NAME], $recipient[PR_ADDRTYPE], $recipient[PR_EMAIL_ADDRESS]);
			}
		
			// Construct string from array with (padded) One-Off entry identifiers
			//
			// Remember, if you want to take the createOneOff part above out: that code
			// produces a padded OneOff and we add the right amount of null characters
			// below.
			//
			// So below is a wrong method for composing a flatentrylist from oneoffs and
			// above is a wrong method form composing a oneoff.
			$flatEntryString = "";
			for ($i = 0, $len = count($oneOffs); $i < $len; $i++)
			{
				$flatEntryString .= pack("Va*", strlen($oneOffs[$i]), $oneOffs[$i]);
				// Fill to 4-byte boundary
				$rest = strlen($oneOffs[$i])%4;
				for ($j=0;$j<$rest;$j++)
					$flatEntryString .= "\0";
			}
			// Pack the string with the number of flatentries and the stringlength
			return pack("V2a*", count($oneOffs), strlen($flatEntryString), $flatEntryString);
		}
		
		/**
		* Get a text body for a Non-Delivery report
		*
		* This function reads the necessary properties from the passed message and constructs
		* a user-readable NDR message from those properties
		*
		* @param mapimessage $message The NDR message to read the information from
		* @return string NDR body message as plaintext message.
		*/
		function getNDRbody($message)
		{
			$message_props  = mapi_getprops($message, array(PR_ORIGINAL_SUBJECT,PR_ORIGINAL_SUBMIT_TIME, PR_BODY));
			$body = '';

			// use PR_BODY if it's there, otherwise create a recipient failed message
			if(isset($message_props[PR_BODY]) || propIsError(PR_BODY, $message_props) == MAPI_E_NOT_ENOUGH_MEMORY) {
				$body = mapi_openproperty($message, PR_BODY);
			}

			if (empty($body)) {
				$body = _("Your message did not reach some or all of the intended recipients")."\n\n";
				$body .= "\t"._("Subject").": ".$message_props[PR_ORIGINAL_SUBJECT]."\n";
				$body .= "\t"._("Sent").":    ".strftime("%a %x %X",$message_props[PR_ORIGINAL_SUBMIT_TIME])."\n\n";
				$body .= _("The following recipient(s) could not be reached").":\n";

				$recipienttable = mapi_message_getrecipienttable($message);
				$recipientrows = mapi_table_queryallrows($recipienttable,array(PR_DISPLAY_NAME,PR_REPORT_TIME,PR_REPORT_TEXT));
				foreach ($recipientrows as $recipient){
					$body .= "\n\t".$recipient[PR_DISPLAY_NAME]." on ".strftime("%a %x %X",$recipient[PR_REPORT_TIME])."\n";
					$body .= "\t\t".$recipient[PR_REPORT_TEXT]."\n";
				}
			}

			// Bon voyage!
			return $body;
		}

		/**
		* Detect plaintext body type of message
		*
		* @param mapimessage $message MAPI message resource to check
		* @return boolean TRUE if the message is a plaintext message, FALSE if otherwise
		*/
		function isPlainText($message)
		{
			// first check if property exists or not, otherwise
			// mapi_message_openproperty will throw an exception
			$props = mapi_getprops($message, array(PR_RTF_COMPRESSED));

			if(isset($props[PR_RTF_COMPRESSED]) || propIsError(PR_RTF_COMPRESSED, $props) == MAPI_E_NOT_ENOUGH_MEMORY) {
				$rtf = mapi_message_openproperty($message, PR_RTF_COMPRESSED);

				if (!$rtf)
					return true; // no RTF is found, so we use plain text

				// get first line of the RTF (removing all other lines after opening/decompressing)
				$rtf = preg_replace("/(\n.*)/m","",mapi_decompressrtf($rtf));

				// check if "\fromtext" exists, if so, it was plain text
				return strpos($rtf,"\\fromtext") !== false;
			}

			// no RTF is found, so we use plain text
			return true;
		}

		/**
		* Parse email recipient list and add all e-mail addresses to the recipient history
		*
		* The recipient history is used for auto-suggestion when writing e-mails. This function
		* opens the recipient history property (PR_EC_RECIPIENT_HISTORY_JSON) and updates or appends
		* it with the passed email addresses.
		*
		* @param emailAddresses XML array structure with recipients
		*/
		function addEmailsToRecipientHistory($emailAddresses){
			if(is_array($emailAddresses) && !empty($emailAddresses)){
				// Retrieve the recipient history
				$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
				$storeProps  = mapi_getprops($store, array(PR_EC_RECIPIENT_HISTORY_JSON));
				$recipient_history = false;

				if(isset($storeProps[PR_EC_RECIPIENT_HISTORY_JSON]) || propIsError(PR_EC_RECIPIENT_HISTORY_JSON, $storeProps) == MAPI_E_NOT_ENOUGH_MEMORY) {
					$stream = mapi_openproperty($store, PR_EC_RECIPIENT_HISTORY_JSON, IID_IStream, 0, 0);

					$stat = mapi_stream_stat($stream);
					mapi_stream_seek($stream, 0, STREAM_SEEK_SET);
					$datastring = '';
					for($i=0;$i<$stat['cb'];$i+=1024){
						$datastring .= mapi_stream_read($stream, 1024);
					}

					if($datastring !== "") {
						$recipient_history = JSON::Decode($datastring, true);
					}
				}
				
				/**
				 * recipient structure passed to this function will be wrapped in add, modify and remove tags
				 * so we need to remove that wrapping of identifiers.
				 */
				$tmpEmailAddresses = $emailAddresses;
				$emailAddresses = array();
				foreach($tmpEmailAddresses as $key => $recipients) {
					if(!empty($recipients)) {
						$emailAddresses = array_merge($emailAddresses, $recipients);
					}
				}

				$l_aNewHistoryItems = Array();
				// Loop through all new recipients
				for($i = 0, $len = count($emailAddresses); $i < $len; $i++){
					if($emailAddresses[$i]['address_type'] == 'ZARAFA'){
						$emailAddress = $emailAddresses[$i]['email_address'];
					}else{
						$emailAddress = $emailAddresses[$i]['smtp_address'];
					}
					
					$l_bFoundInHistory = false;
					// Loop through all the recipients in history
					if(is_array($recipient_history) && !empty($recipient_history['recipients'])) {
						for($j = 0, $lenJ = count($recipient_history['recipients']); $j < $lenJ;$j++){
							// Email address already found in history
							$l_bFoundInHistory = false;
							if($emailAddresses[$i]['address_type'] == 'ZARAFA') {
								if($emailAddress == $recipient_history['recipients'][$j]['email_address'])
									$l_bFoundInHistory = true;
							} else {
								if($emailAddress == $recipient_history['recipients'][$j]['smtp_address'])
									$l_bFoundInHistory = true;
							}

							if($l_bFoundInHistory == true){
								// Check if a name has been supplied.
								$newDisplayName = trim($emailAddresses[$i]['display_name']);
								if(!empty($newDisplayName)){
									$oldDisplayName = trim($recipient_history['recipients'][$j]['display_name']);

									// Check if the name is not the same as the email address
									if ($newDisplayName != $emailAddresses[$i]['smtp_address']) {
										$recipient_history['recipients'][$j]['display_name'] = $newDisplayName;
									// Check if the recipient history has no name for this email
									} else if(empty($oldDisplayName)) {
										$recipient_history['recipients'][$j]['display_name'] = $newDisplayName;
									}
								}
								$recipient_history['recipients'][$j]['count']++;
								$recipient_history['recipients'][$j]['last_used'] = time();
								break;
							}
						}
					}
					if(!$l_bFoundInHistory && !isset($l_aNewHistoryItems[$emailAddress])){
						$l_aNewHistoryItems[$emailAddress] = Array(
							'display_name' => $emailAddresses[$i]['display_name'],
							'smtp_address' => $emailAddresses[$i]['smtp_address'],
							'email_address' => $emailAddresses[$i]['email_address'],
							'address_type' => $emailAddresses[$i]['address_type'],
							'count' => 1,
							'last_used' => time(),
							'object_type' => $emailAddresses[$i]['object_type']
						);
					}
				}
				if(!empty($l_aNewHistoryItems)){
					foreach($l_aNewHistoryItems as $l_aValue){
						$recipient_history['recipients'][] = $l_aValue;
					}
				}

				$l_sNewRecipientHistoryJSON = JSON::Encode($recipient_history);

				$stream = mapi_openproperty($store, PR_EC_RECIPIENT_HISTORY_JSON, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
				mapi_stream_setsize($stream, strlen($l_sNewRecipientHistoryJSON));
				mapi_stream_write($stream, $l_sNewRecipientHistoryJSON);
				mapi_stream_commit($stream);
				mapi_savechanges($store);
			}
		}

		/**
		* Extract all email addresses from a list of recipients
		*
		* @param string $p_sRecipients String containing e-mail addresses, as typed by user (eg. '<john doe> john@doe.org; jane@doe.org')
		* @return array Array of e-mail address parts (eg. 'john@doe.org', 'jane@doe.org')
		*
		* this function is currently unused
		*/
		function extractEmailAddresses($p_sRecipients){
			$l_aRecipients = explode(';', $p_sRecipients);
			$l_aReturn = Array();
			for($i = 0, $len = count($l_aRecipients); $i < $len; $i++){
				$l_aRecipients[$i] = trim($l_aRecipients[$i]);
				$l_sRegex = '/^([^<]*<){0,1}(([a-z0-9=_\+\.\-\'\/])+\@(([a-z0-9\-])+\.)+([a-z0-9]{2,5})+)>{0,1}$/';
				preg_match($l_sRegex, $l_aRecipients[$i], $l_aMatches);
				$l_aReturn[] = $l_aMatches[0];
			}
			return $l_aReturn;
		}
		
		/**
		* Get the SMTP e-mail of an addressbook entry
		*
		* @param string $entryid Addressbook entryid of object
		* @return string SMTP e-mail address of that entry or FALSE on error
		*/
		function getEmailAddressFromEntryID($entryid) {
			try {
				$mailuser = mapi_ab_openentry($GLOBALS["mapisession"]->getAddressbook(), $entryid);
			} catch (MAPIException $e) {
				// if any invalid entryid is passed in this function then it should silently ignore it
				// and continue with execution
				if($e->getCode() == MAPI_E_UNKNOWN_ENTRYID) {
					$e->setHandled();
					return "";
				}
			}

			if(!isset($mailuser)) {
				return "";
			}

			$abprops = mapi_getprops($mailuser, array(PR_SMTP_ADDRESS, PR_EMAIL_ADDRESS));
			if(isset($abprops[PR_SMTP_ADDRESS])) {
				return $abprops[PR_SMTP_ADDRESS];
			} else if(isset($abprops[PR_EMAIL_ADDRESS])) {
				return $abprops[PR_EMAIL_ADDRESS];
			} else {
				return "";
			}
		}

		/**
		* Get all rules of a store
		*
		* This function opens the rules table for the specified store, and reads
		* all rules with PR_RULE_PROVIDER equal to 'RuleOrganizer'. These are the rules
		* that the user sees when managing rules from Outlook.
		*
		* @param mapistore $store Store in which rules reside
		* @param array $properties Property mappings for rules properties
		* @return array XML array structure of rules from the store
		*/
		function getRules($store, $properties) {
			$inbox = mapi_msgstore_getreceivefolder($store);
			
			if(!$inbox)
				return false;
				
			$modifyrulestable = mapi_folder_openmodifytable($inbox);
			$rulestable = mapi_rules_gettable($modifyrulestable);

			mapi_table_restrict($rulestable, array(RES_CONTENT,
												array(
													FUZZYLEVEL	=>	FL_FULLSTRING | FL_IGNORECASE,
													ULPROPTAG	=>	PR_RULE_PROVIDER,
													VALUE		=>	array(
														PR_RULE_PROVIDER	=>	"RuleOrganizer"
													)
												)
											),
											TBL_BATCH
			);


			mapi_table_sort($rulestable, array(PR_RULE_SEQUENCE => TABLE_SORT_ASCEND), TBL_BATCH);
			$rows = mapi_table_queryrows($rulestable, $properties, 0, 0x7fffffff);
	
			$xmlrows = array();
		
			foreach($rows as $row) {
				$xmlrow = Conversion::mapMAPI2XML($properties, $row);
				array_push($xmlrows, $xmlrow);
			}
			
			return $xmlrows;
		}

		/**
		* Update rules in a store
		* 
		* This function replaces all rules in a store with the passed new ruleset.
		*
		* @param mapistore $store The store whose rules should be modified
		* @param array $rules The XML array structure with the new rules
		* @param array $properties The property mapping for rules properties
		* @return boolean True on success, false on error
		*/
		function updateRules($store, $rules, $properties)
		{
			if (!is_array($rules))
				return false;

			$inbox = mapi_msgstore_getreceivefolder($store);
			
			if(!$inbox)
				return false;
				
			$modifyrulestable = mapi_folder_openmodifytable($inbox);
			
			// get provider data id's from all rules
			$rulestable = mapi_rules_gettable($modifyrulestable);
			mapi_table_restrict($rulestable, array(RES_CONTENT,
												array(
													FUZZYLEVEL	=>	FL_FULLSTRING | FL_IGNORECASE,
													ULPROPTAG	=>	PR_RULE_PROVIDER,
													VALUE		=>	array(
														PR_RULE_PROVIDER	=>	"RuleOrganizer"
													)
												)
											),
											TBL_BATCH
			);
			mapi_table_sort($rulestable, array(PR_RULE_SEQUENCE => TABLE_SORT_ASCEND), TBL_BATCH);			
			$current_rules = mapi_table_queryrows($rulestable, $properties, 0, 0x7fffffff);
			$provider_data = array();
			foreach($current_rules as $row){
				$data = unpack("Vnum/Vid/a*datetime", $row[PR_RULE_PROVIDER_DATA]);
				$provider_data[$row[PR_RULE_ID]] = $data["id"];
			}

			// delete all rules from the rules table
			$deleteRules = array();
			foreach($current_rules as $delRow){
				$deleteRules[] = array(
						"rowflags"	=>	ROW_REMOVE,
						"properties"=>	$delRow
				);
			}
			mapi_rules_modifytable($modifyrulestable, $deleteRules);

			if (!isset($rules[0]))
				$rules = array($rules);

			// find rules to delete
			$deleteRules = array();
			$deletedRowIDs = array();
			foreach($rules as $key=>$rule){
				if (isset($rule["deleted"])){
					$deleteRules[] = $rule;
					$convertedRule = Conversion::mapXML2MAPI($properties, $rule);
					$deletedRowIDs[] = $convertedRule[PR_RULE_ID];
					unset($rules[$key]);
				}
			}

			// add/modify new rules
			foreach($rules as $k=>$rule){
				$rules[$k] = Conversion::mapXML2MAPI($properties, $rule);
			}

			$rows = array();

			$modified = array();

			// add/update rules from the client
			foreach($rules as $rule){

				if (!isset($rule[PR_RULE_ID])){
					$row_flags = ROW_ADD;
					if (is_array($provider_data)){
						$dataId = max($provider_data)+1;
					}else{
						$dataId = 1;
					}
					$provider_data[] = $dataId;
				}else{
					$row_flags = ROW_ADD;
					$dataId = $provider_data[$rule[PR_RULE_ID]];
					$modified[] = $rule[PR_RULE_ID]; 
				}
			
				if (!isset($rule[PR_RULE_ACTIONS])){
					$rule[PR_RULE_ACTIONS] = array(array("action"=>OP_DEFER_ACTION, "dam"=>hex2bin("E0C810000120000100000000000000010000000000000001000000360000000200FFFF00000C004352756C65456C656D656E7490010000010000000000000001000000018064000000010000000000000001000000")));
				}
				if (!isset($rule[PR_RULE_CONDITION])){
					$rule[PR_RULE_CONDITION] = array(RES_EXIST, array(ULPROPTAG=>PR_MESSAGE_CLASS));
				}
				if (!isset($rule[PR_RULE_SEQUENCE])){
					$rule[PR_RULE_SEQUENCE] = 10;
				}

				if (!isset($rule[PR_RULE_STATE])){
					$rule[PR_RULE_STATE] = ST_DISABLED;
				}

				if (!isset($rule[PR_RULE_NAME]) || $rule[PR_RULE_NAME]==""){
					$rule[PR_RULE_NAME] = _("Untitled rule");
				}

				$rule[PR_RULE_LEVEL] = 0;
				$rule[PR_RULE_PROVIDER] = "RuleOrganizer";
				$rule[PR_RULE_PROVIDER_DATA] = pack("VVa*", 1, $dataId, Conversion::UnixTimeToCOleDateTime(time()));

				$rows[] = array(
						"rowflags"	=>	$row_flags,
						"properties"=>	$rule
				);
			}
			// update PR_RULE_PROVIDER_DATA for every other rule
			foreach($current_rules as $rule){
				if (!in_array($rule[PR_RULE_ID], $modified) && !in_array($rule[PR_RULE_ID], $deletedRowIDs)){

					$dataId = $provider_data[$rule[PR_RULE_ID]];
					$rule[PR_RULE_PROVIDER_DATA] = pack("VVa*", 1, $dataId, Conversion::UnixTimeToCOleDateTime(time()));

					$rows[] = array("properties"=>$rule, "rowflags"=> ROW_MODIFY);
				}
			}

			// sort rules on PR_RULE_SEQUENCE to fix the order
			usort($rows, array($this, "cmpRuleSequence"));
			$seq = 10;
			foreach($rows as $k=>$row){
				$rows[$k]["properties"][PR_RULE_SEQUENCE] = $seq;
				$seq++;
				unset($rows[$k][PR_RULE_ID]); // remove RULE_ID because this is the property Outlook does its sort when displaying rules instead of RULE_SEQUENCE!
			}
			
			// sort the rules, so they will get a RULE_ID in the right order when adding the rules
			uasort($rows, create_function('$a,$b','return $a["properties"][PR_RULE_SEQUENCE]>$b["properties"][PR_RULE_SEQUENCE];'));

			mapi_rules_modifytable($modifyrulestable, $rows);

			// delete (outlook) client rules
			$assocTable = mapi_folder_getcontentstable($inbox, MAPI_DEFERRED_ERRORS | MAPI_ASSOCIATED);
			mapi_table_restrict($assocTable, array(RES_CONTENT,
												array(
													FUZZYLEVEL	=>	FL_FULLSTRING | FL_IGNORECASE,
													ULPROPTAG	=>	PR_MESSAGE_CLASS,
													VALUE		=>	array(
														PR_MESSAGE_CLASS	=>	"IPM.RuleOrganizer"
													)
												)
											),
											TBL_BATCH
			);
			$messages = mapi_table_queryallrows($assocTable, array(PR_ENTRYID, PR_MESSAGE_CLASS));
			$delete = array();
			for($i = 0, $len = count($messages); $i < $len; $i++){
				if ($messages[$i][PR_MESSAGE_CLASS] == "IPM.RuleOrganizer"){ // just to be sure that the restriction worked ;)
					array_push($delete, $messages[$i][PR_ENTRYID]);
				}
			}
			if (!empty($delete)){
				mapi_folder_deletemessages($inbox, $delete);
			}

			return $result;
		}
		
		/**
		* @access private
		*/
		public static function cmpRuleSequence($a, $b)
		{
			if ($a["properties"][PR_RULE_SEQUENCE]==$b["properties"][PR_RULE_SEQUENCE])
				return 0;
			return ($a["properties"][PR_RULE_SEQUENCE]<$b["properties"][PR_RULE_SEQUENCE])?-1:1;
		}

		/**
		* Get folder name
		*
		* @param $storeid Store entryid of store in which folder resides
		* @param $folderid Folder entryid of folder
		* @return string Folder name of specified folder
		*/
		function getFolderName($storeid, $folderid) {
			$store = $GLOBALS["mapisession"]->openMessageStore($storeid);
			if(!$store)
				return false;
			
			$folder = mapi_msgstore_openentry($store, $folderid);
			if(!$folder)
				return false;
				
			$folderprops = mapi_getprops($folder, array(PR_DISPLAY_NAME));
			
			return $folderprops[PR_DISPLAY_NAME];
		}

		/**
		* Send a meeting cancellation
		*
		* This function sends a meeting cancellation for the meeting references by the passed entryid. It
		* will send the meeting cancellation and move the item itself to the waste basket.
		*
		* @param mapistore $store The store in which the meeting request resides
		* @param string $entryid Entryid of the appointment for which the cancellation should be sent.
		* @param Object $action data sent by client.
		* @param boolean $directBookingMeetingRequest Indicates if a Meeting Request should use direct booking or not
		*/
		function cancelInvitation($store, $entryid, $action, $directBookingMeetingRequest) {
			$message = $GLOBALS["operations"]->openMessage($store, $entryid);

			$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $directBookingMeetingRequest);
			$messageProps = mapi_getprops($req->message, array($req->proptags["recurring"]));

			// Update extra body information
			if(isset($action["message_action"]['meetingTimeInfo']) && !empty($action["message_action"]['meetingTimeInfo'])) {
				$req->setMeetingTimeInfo($action["message_action"]['meetingTimeInfo']);
				unset($action["message_action"]['meetingTimeInfo']);
			}

			if(isset($messageProps[$req->proptags["recurring"]]) && $messageProps[$req->proptags["recurring"]] === true) {
				$recurrence = new Recurrence($store, $message);
				// basedate is only passed when dealing with occurences
				$basedate = (isset($action['basedate']) && !empty($action['basedate'])) ? $action['basedate'] : false;

				if($basedate) {
					$recurrence->createException(array(), $basedate, true);
				}

				// if recurrence pattern of meeting request is changed then need to send full update
				// Send the meeting request
				$req->updateMeetingRequest();
				// @TODO if user is deleting meetings in past then no cancellation should be shown
				$req->sendMeetingRequest(true, _("Canceled: "), $basedate);

				// save changes in the message
				mapi_message_savechanges($message);

				// Notify the bus that the exception has been created
				if($basedate) {
					$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID));
					$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
				} else {
					$root = mapi_msgstore_openentry($store);
					$storeprops = mapi_getprops($store, array(PR_IPM_WASTEBASKET_ENTRYID));
					$wastebasket = mapi_msgstore_openentry($store, $storeprops[PR_IPM_WASTEBASKET_ENTRYID]);

					// Move the message to the deleted items
					mapi_folder_copymessages($root, array($entryid), $wastebasket, MESSAGE_MOVE);

					// Notify the bus that the message has been deleted
					$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID));
					$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_DELETE, $messageProps);
				}
			} else {
				// Send the cancellation
				$req->updateMeetingRequest();
				$req->sendMeetingRequest(true, _("Canceled: "));

				// save changes in the message
				mapi_message_savechanges($message);

				$root = mapi_msgstore_openentry($store);
				$storeprops = mapi_getprops($store, array(PR_IPM_WASTEBASKET_ENTRYID));
				$wastebasket = mapi_msgstore_openentry($store, $storeprops[PR_IPM_WASTEBASKET_ENTRYID]);

				// Move the message to the deleted items
				mapi_folder_copymessages($root, array($entryid), $wastebasket, MESSAGE_MOVE);

				// Notify the bus that the message has been deleted
				$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID));
				$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_DELETE, $messageProps);
			}
		}
		
		/**
		* Remove all appointments for a certain meeting request
		*
		* This function searches the default calendar for all meeting requests for the specified
		* meeting. All those appointments are then removed.
		*
		* @param mapistore $store Mapi store in which the meeting request and the calendar reside
		* @param string $entryid Entryid of the meeting request or appointment for which all items should be deleted
		* @param string $basedate if specified contains starttime of day of an occurrence 
		* @param boolean $directBookingMeetingRequest Indicates if a Meeting Request should use direct booking or not
		*/
		function removeFromCalendar($store, $entryid, $basedate, $directBookingMeetingRequest) {
			$message = $GLOBALS["operations"]->openMessage($store, $entryid);
			
			$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $directBookingMeetingRequest);
			
			$req->doRemoveFromCalendar($basedate);
			
			// Notify the bus that the message has been deleted			
			$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID));
			$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), $basedate? TABLE_SAVE : TABLE_DELETE, $messageProps);
		}
		
		/**
		* Get addressbook hierarchy
		*
		* This function returns the entire hierarchy of the addressbook, with global addressbooks, and contacts
		* folders.
		*
		* The input parameter $storelist is an associative array with the format:
		* $storelist["store"] = array of store objects to traverse for all contacts folders
		* $storelist["folder"] = array of store objects to traverse for only the default contacts folder
		* 
		* The output array contains an associative array for each found contact folder. Each entry contains
		* "display_name" => Name of the folder, "entryid" => entryid of the folder, "parent_entryid" => parent entryid
		* "storeid" => store entryid, "type" => gab | contacts
		* 
		* @param array Associative array with store information
		* @return array Array of associative arrays with addressbook container information
		* @todo Fix bizarre input parameter format
		*/
		function getAddressbookHierarchy($storelist=false)
		{
			$ab = $GLOBALS["mapisession"]->getAddressbook();
			$dir = mapi_ab_openentry($ab);
			$table = mapi_folder_gethierarchytable($dir, MAPI_DEFERRED_ERRORS | CONVENIENT_DEPTH);
			$items = mapi_table_queryallrows($table, array(PR_DISPLAY_NAME, PR_ENTRYID, PR_PARENT_ENTRYID, PR_DEPTH));
			
			$folders = array();

			$parent = false;
			foreach($items as $item){
				// TODO: fix for missing PR_PARENT_ENTRYID, see #2190
				if ($item[PR_DEPTH]==0)
					$parent = $item[PR_ENTRYID];

				$item[PR_PARENT_ENTRYID] = $parent;
				
				$folders[] = array(
					"props" => array(
						"display_name"	=> $item[PR_DISPLAY_NAME],
						"entryid"		=> bin2hex($item[PR_ENTRYID]),
						"parent_entryid"=> bin2hex($item[PR_PARENT_ENTRYID]),
						"depth"			=> $item[PR_DEPTH],
						"type"			=> "gab",
						"object_type"	=> MAPI_ABCONT
					)
				);
			}

			if ($storelist){
				foreach($storelist["store"] as $store){
					if (empty($store))
						continue;

					$cur_store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($store));
					$store_props = mapi_getprops($cur_store, array(PR_IPM_SUBTREE_ENTRYID, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_MDB_PROVIDER, PR_DISPLAY_NAME, PR_MAILBOX_OWNER_NAME, PR_IPM_WASTEBASKET_ENTRYID));
					/**
					 * Check to see if we are dealing with a public store or not. The first level below
					 * the public folder contains folders that are linked. The CONVENIENT_DEPTH property
					 * will not work there. The PR_IPM_PUBLIC_FOLDERS_ENTRYID holds the entryID of the
					 * folder that we can use as the $subtree.
					*/
					// Public store
					if($store_props[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID){
						try{
							$subtree = mapi_msgstore_openentry($cur_store, $store_props[PR_IPM_PUBLIC_FOLDERS_ENTRYID]);
						} catch (MAPIException $e) {
							$e->setHandled();
							continue;
						}
					// Private store
					} else {
						try {
							$subtree = mapi_msgstore_openentry($cur_store, $store_props[PR_IPM_SUBTREE_ENTRYID]);
						} catch (MAPIException $e) {
							$e->setHandled();
							continue;
						}
					}

					// Get "Deleted Items" folder
					if (isset($store_props[PR_IPM_WASTEBASKET_ENTRYID])) {
						$deleted_items_folder = mapi_msgstore_openentry($cur_store, $store_props[PR_IPM_WASTEBASKET_ENTRYID]);
					}
					
					// Remove folder from hierarchylist that are also in "Deleted Items" folder
					$deletedFoldesrRestriction = array();

					if (isset($deleted_items_folder)) {
						// Get all contact folders from "Deleted Items" folder
						$hie = mapi_folder_gethierarchytable($deleted_items_folder, MAPI_DEFERRED_ERRORS | CONVENIENT_DEPTH);
						mapi_table_restrict($hie, array(RES_CONTENT, 
													array(
															FUZZYLEVEL	=>	FL_PREFIX|FL_IGNORECASE,
															ULPROPTAG	=>	PR_CONTAINER_CLASS,
															VALUE		=>	array(PR_CONTAINER_CLASS	=>	"IPF.Contact")
													)
												),
												TBL_BATCH
						);
						
						$deleted_folders = mapi_table_queryallrows($hie, array(PR_ENTRYID));

						/**
						 * Prepare restriction that removes all folders 
						 * which are also in "Deleted Items" folder.
						 */
						$deletedFoldersRestriction = array();
						foreach($deleted_folders as $folder){
							$deletedFoldersRestriction[] = 	array(RES_PROPERTY,
														array(
																RELOP		=>	RELOP_NE,
																ULPROPTAG	=>	PR_ENTRYID,
																VALUE		=>	array(PR_ENTRYID	=>	$folder[PR_ENTRYID])
														)
													);
						}
					}

					// Get all contact folders from hierarchylist
					$hierarchy = mapi_folder_gethierarchytable($subtree, MAPI_DEFERRED_ERRORS | CONVENIENT_DEPTH);

					// Perform restriction on folders are found in 'Deleted Items'
					if (!empty($deletedFoldersRestriction)){
						mapi_table_restrict($hierarchy, array(RES_AND,
															array(
																array(RES_AND,
																	$deletedFoldersRestriction
																),
																array(RES_CONTENT, 
																	array(
																			FUZZYLEVEL	=>	FL_PREFIX|FL_IGNORECASE,
																			ULPROPTAG	=>	PR_CONTAINER_CLASS,
																			VALUE		=>	array(PR_CONTAINER_CLASS	=>	"IPF.Contact")
																	)
																)
															)
														),
														TBL_BATCH
						);
					} else {
						mapi_table_restrict($hierarchy, array(RES_CONTENT, 
															array(
																	FUZZYLEVEL	=>	FL_PREFIX|FL_IGNORECASE,
																	ULPROPTAG	=>	PR_CONTAINER_CLASS,
																	VALUE		=>	array(PR_CONTAINER_CLASS	=>	"IPF.Contact")
															)
														),
														TBL_BATCH
						);
					}

					switch($store_props[PR_MDB_PROVIDER]){
						case ZARAFA_STORE_PUBLIC_GUID:
							$store_owner = " - " . $store_props[PR_DISPLAY_NAME];
							break;
						case ZARAFA_STORE_DELEGATE_GUID:
						default:
							$store_owner = " - " . $store_props[PR_MAILBOX_OWNER_NAME];
							break;
					}

					$store_folders = mapi_table_queryallrows($hierarchy, array(PR_DISPLAY_NAME, PR_ENTRYID));
					foreach($store_folders as $item){
						$folders[] = array(
							"props" => array(
								"display_name"	=> $item[PR_DISPLAY_NAME] . $store_owner,
								"entryid"		=> bin2hex($item[PR_ENTRYID]),
								"parent_entryid"=> bin2hex($item[PR_ENTRYID]),
								"store_entryid"	=> $store,
								"depth"			=> 0,
								"type"			=> "contacts",
								"object_type"	=> MAPI_ABCONT
							)
						);
					}
				}

				foreach($storelist["folder"] as $store){
					if (empty($store))
						continue;
					
					$cur_store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($store));
					$store_props = mapi_getprops($cur_store, array(PR_MDB_PROVIDER, PR_DISPLAY_NAME, PR_MAILBOX_OWNER_NAME));
					$root_folder = mapi_msgstore_openentry($cur_store, null);
					$root_props = mapi_getprops($root_folder, array(PR_IPM_CONTACT_ENTRYID));
					try {
						$contact_folder = mapi_msgstore_openentry($cur_store, $root_props[PR_IPM_CONTACT_ENTRYID]);
					} catch (MAPIException $e) {
						$e->setHandled();
						continue;
					}

					switch($store_props[PR_MDB_PROVIDER]){
						case ZARAFA_STORE_PUBLIC_GUID:
							$store_owner = " - " . $store_props[PR_DISPLAY_NAME];
							break;
						case ZARAFA_STORE_DELEGATE_GUID:
						default:
							$store_owner = " - " . $store_props[PR_MAILBOX_OWNER_NAME];
							break;
					}

					$contact_props = mapi_getprops($contact_folder, array(PR_DISPLAY_NAME, PR_ENTRYID));
					$folders[] = array(
						"props" => array(
							"display_name"	=> $item[PR_DISPLAY_NAME] . $store_owner,
							"entryid"		=> bin2hex($contact_props[PR_ENTRYID]),
							"parent_entryid"=> bin2hex($contact_props[PR_ENTRYID]),
							"store_entryid"	=> $store,
							"depth"			=> 0,
							"type"			=> "contacts",
							"object_type"	=> MAPI_ABCONT
						)
					);
				}
			}
			
			return $folders;
		}

		/**
		 * Publishing the FreeBusy information of the default calendar. The 
		 * folderentryid argument is used to check if the default calendar 
		 * should be updated or not.
		 * 
		 * @param $store MAPIobject Store object of the store that needs publishing
		 * @param $folderentryid binary entryid of the folder that needs to be updated.
		 */
		function publishFreeBusy($store, $folderentryid=false){
			// Publish updated free/busy information
			// First get default calendar from the root folder
			$rootFolder = mapi_msgstore_openentry($store, null);
			$rootFolderProps = mapi_getprops($rootFolder, array(PR_IPM_APPOINTMENT_ENTRYID));

			// If no folderentryid supplied or if the supplied entryid matches the default calendar.
			if(!$folderentryid || ( isset($rootFolderProps[PR_IPM_APPOINTMENT_ENTRYID]) && $rootFolderProps[PR_IPM_APPOINTMENT_ENTRYID] == $folderentryid )){
				// Get the calendar and owner entryID
				$calendar = mapi_msgstore_openentry($store, $rootFolderProps[PR_IPM_APPOINTMENT_ENTRYID]);
				$storeProps = mapi_msgstore_getprops($store, array(PR_MAILBOX_OWNER_ENTRYID));
				if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])){
					// Lets share!
					$pub = new FreeBusyPublish($GLOBALS["mapisession"]->getSession(), $store, $calendar, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
					$pub->publishFB(time() - (7 * 24 * 60 * 60), 6 * 30 * 24 * 60 * 60); // publish from one week ago, 6 months ahead
				}
			}
		}

		/**
		 * get quota information of store
		 * 
		 * @param $store MAPIobject Store object of the store
		 * @return array information about quota and current store size (in KB)
		 */
		function getQuotaDetails($store = false){
			$checkQouta = true;

			if($store === false) {
				$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
			}

			// @TODO use mapi_zarafa_getquota here
			$storeProps = mapi_getprops($store, array(PR_MDB_PROVIDER, PR_QUOTA_WARNING_THRESHOLD, PR_QUOTA_SEND_THRESHOLD, PR_QUOTA_RECEIVE_THRESHOLD, PR_MESSAGE_SIZE_EXTENDED));

			switch($storeProps[PR_MDB_PROVIDER]){
				case ZARAFA_STORE_PUBLIC_GUID:
					$checkQouta = false;
					break;
				case ZARAFA_STORE_DELEGATE_GUID:
				default:
					$checkQouta = true;
					break;
			}

			if(!$checkQouta) {
				return false;
			} else {
				return array(
					"store_size" => round($storeProps[PR_MESSAGE_SIZE_EXTENDED]/1024),
					"quota_warning" => $storeProps[PR_QUOTA_WARNING_THRESHOLD],
					"quota_soft" => $storeProps[PR_QUOTA_SEND_THRESHOLD],
					"quota_hard" => $storeProps[PR_QUOTA_RECEIVE_THRESHOLD]
				);
			}
		}
		/**
		 * Function checks whether user has an access over the specified folder or not.
		 * @param object MAPI Message Store Object
		 * @param string $parententryid entryid of the folder
		 * @return boolean true if user has an access over the folder, false if not.
		 */
		function checkFolderAccess($store, $parententryid)
		{
			$accessToFolder = false;
			try {
				$folder = mapi_msgstore_openentry($store, $parententryid);
				if($folder){
					$folderProps = mapi_getProps($folder, Array(PR_ACCESS));
					if(($folderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) != 0){
						$accessToFolder = true;
					}
				}
			} catch (MAPIException $e) {
				if($e->getCode() !== MAPI_E_NO_ACCESS) {
					/*
					 * don't propogate this no access errors because of permissions,
					 * if there isn't permission to open folder, just return false.
					 * 
					 * all other errors should be propagated to higher level exception handlers
					 */
					throw $e;
				}
			}
			return $accessToFolder;
		}
		
		/**
		 * Function stores the uploaded items in gloabal session.
		 * @param object MAPI Message Store Object
		 * @param array $entryids entryids of the selected message items
		 * @param string $dialog_attachments
		 * @return array of attachment items data
		 */
		function setAttachmentInSession($store, $entryids, $dialog_attachments)
		{
			$attachments = array();

			// Check if there are no files uploaded
			if(!isset($_SESSION["files"])) {
				$_SESSION["files"] = array();
			}
			
			// Check if no files are uploaded with this dialog_attachments
			if(!isset($_SESSION["files"][$dialog_attachments])) {
				$_SESSION["files"][$dialog_attachments] = array();
			}
					
			foreach($entryids as $key =>$items){
				
				$message = mapi_msgstore_openentry($store, hex2bin($items));
				$msg_props = mapi_getprops($message, array(PR_STORE_ENTRYID, PR_ENTRYID, PR_MESSAGE_SIZE, PR_SUBJECT,  PR_MESSAGE_CLASS));
				// stripping path details
				$tmpname = $msg_props[PR_SUBJECT].rand();
				
				// Add file information to the session
				$_SESSION["files"][$dialog_attachments][$tmpname] = Array(
					"name"			=> empty($msg_props[PR_SUBJECT])?_("Untitled"):$msg_props[PR_SUBJECT],
					"size"			=> $msg_props[PR_MESSAGE_SIZE],
					"type"			=> 'message/rfc822',
					"sourcetype"	=> 'items',
					"storeid"		=> $msg_props[PR_STORE_ENTRYID],
					"entryid"		=> $msg_props[PR_ENTRYID],
					"message_class" => $msg_props[PR_MESSAGE_CLASS]
					);

				//creating attachment array
				$attachment = array();
				$attachment["attach_num"] = $tmpname;
				$attachment["attach_method"] = 5 ;
				$attachment["size"] = $msg_props[PR_MESSAGE_SIZE];
				$attachment["entryid"] = bin2hex($msg_props[PR_ENTRYID]);
				$attachment["filetype"] = 'message/rfc822';
				$attachment["name"] = empty($msg_props[PR_SUBJECT])?_("Untitled"):$msg_props[PR_SUBJECT];
				$attachment["attach_message_class"] = 'IPM.NOTE';

				array_push($attachments, $attachment);
			}
			return $attachments;
		}

		/**
		 * Function which fetches all members of a distribution list recursively.
		 * 
		 * @param {MAPIStore} $store MAPI Message Store Object
		 * @param {BinString} $entryid entryid of the distribution list
		 * @param {Array} $properties array of properties to get properties of distlist
		 * @param {Boolean} $isRecursive function will be called recursively if there is/are
		 * distribution list inside the distlist to expand all the members. 
		 * pass true to expand distlist recursively, false to not expand.
		 * @param {Array} $listEntryIDs list of already expanded Distribution list from contacts folder,
		 * This parameter is used for recursive call of the function
		 * @return object $items all members of a distlist.
		 */
		function getMembersFromDistributionList($store, $entryid, $properties, $isRecursive = false, $listEntryIDs = array())
		{
			if (in_array($entryid, $listEntryIDs)){
				// don't expand a distlist that is already expanded
				return array();
			}

			$listEntryIDs[] = $entryid;

			$message = mapi_msgstore_openentry($store, $entryid);
			$items = array();
			if($message) {
				$props = mapi_getprops($message, array($properties["oneoff_members"], $properties["members"]));

				if(!isset($props[$properties["oneoff_members"]]) || !isset($props[$properties["members"]])) {
					return array();
				}

				if(isset($props[$properties["oneoff_members"]])) {
					$members = $props[$properties["members"]];
				}

				// parse oneoff members
				$oneoffmembers = array();
				foreach($props[$properties["oneoff_members"]] as $key=>$item){
					$oneoffmembers[$key] = mapi_parseoneoff($item);
				}

				foreach($members as $key=>$item){
					$parts = unpack("Vnull/A16guid/Ctype/A*entryid", $item);
					$memberItem = array();
					$memberItem["props"] = array();
					$memberItem["props"]["distlist_type"] = $parts["type"];

					if ($parts["guid"] === hex2bin("812b1fa4bea310199d6e00dd010f5402")){
						// custom e-mail address (no user or contact)
						$oneoff = mapi_parseoneoff($item);
						
						$memberItem["props"]["display_name"] = $oneoff["name"];
						$memberItem["props"]["address_type"] = $oneoff["type"];
						$memberItem["props"]["email_address"] = $oneoff["address"];
						$memberItem["props"]["entryid"] = bin2hex($members[$key]);

						$items[] = $memberItem;
					}else{
						if($parts["type"] === DL_DIST && $isRecursive) {
							// Expand distribution list to get distlist members inside the distributionlist.
							$items = array_merge($items, $this->getMembersFromDistributionList($store, $parts["entryid"], $properties, true, $listEntryIDs));
						} else {
							$memberItem["props"]["entryid"] = bin2hex($parts["entryid"]);
							$memberItem["props"]["display_name"] = $oneoffmembers[$key]["name"];
							$memberItem["props"]["address_type"] = $oneoffmembers[$key]["type"];
							// distribution lists don't have valid email address so ignore that property

							if($parts["type"] !== DL_DIST) {
								$memberItem["props"]["email_address"] = $oneoffmembers[$key]["address"];
							}

							$items[] = $memberItem;
						}
					}
				}
			}

			return $items;
		}
	}
?>
