<?php
	/**
	 * ItemModule
	 * Module which openes, creates, saves and deletes an item. It 
	 * extends the Module class.
	 */
	class ItemModule extends Module
	{
		/**
		 * The setting whether Meeting Requests should be booked directly or not.
		 */
		var $directBookingMeetingRequest;

		/**
		 * Constructor
		 * @param int $id unique id.
		 * @param array $data list of all actions.
		 */
		function ItemModule($id, $data)
		{
			$this->directBookingMeetingRequest = ENABLE_DIRECT_BOOKING;
			parent::Module($id, $data);
		}
		
		/**
		 * Executes all the actions in the $data variable.
		 * @return boolean true on success of false on fialure.
		 */
		function execute()
		{
			foreach($this->data as $actionType => $action)
			{
				if(isset($actionType)) {
					try {
						$store = $this->getActionStore($action);
						$parententryid = $this->getActionParentEntryID($action);
						$entryid = $this->getActionEntryID($action);

						$overQouta = $this->checkOverQoutaRestriction($store, $actionType, $action);
						if(!empty($overQouta)) {
							if($overQouta == "quota_hard") {
								throw new ZarafaException(_("The message store has exceeded its hard quota limit.") . "\n\n" .
														  _("To reduce the amount of data in this message store, select some items that you no longer need, and permanently (SHIFT + DEL) delete them.")
								);
							} else if($overQouta == "quota_soft") {
								throw new ZarafaException(_("The message store has exceeded its soft quota limit.") . "\n\n" .
														  _("To reduce the amount of data in this message store, select some items that you no longer need, and permanently (SHIFT + DEL) delete them.")
								);
							}
						}

						switch($actionType)
						{
							case "open":
								$this->open($store, $entryid, $action);
								break;
							case "save":
								if ($store && $parententryid) {
									/*
									 * The "message_action" object has been set, check the action_type field for
									 * the exact action which must be taken.
									 * Supported actions:
									 *   - acceptmeetingrequest: attendee has accepted mr
									 *   - declineMeetingRequest: attendee has declined mr
									 *   - cancelInvitation: organizer cancels already scheduled meeting
									 *   - removeFromCalendar: attendee receives meeting cancellation and wants to remove item from calendar
									 * switch-statement.
									 */
									if (isset($action["message_action"]) && isset($action["message_action"]["action_type"])) {
										switch($action["message_action"]["action_type"])
										{
											case "declineMeetingRequest":
											case "acceptMeetingRequest":
												$message = $GLOBALS["operations"]->openMessage($store, $entryid);
												$basedate = (isset($action['basedate']) ? $action['basedate'] : false);
												$delete = false;
												/**
												 * Get message class from original message. This can be changed to 
												 * IPM.Appointment if the item is a Meeting Request in the maillist. 
												 * After Accepting/Declining the message is moved and changed.
												 */
												$originalMessageProps = mapi_getprops($message, array(PR_MESSAGE_CLASS));
												$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $this->directBookingMeetingRequest);

												// 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']);
												}

												// sendResponse flag if it is set then send the mail response to the organzer.
												$sendResponse = true;
												if(isset($action["message_action"]["sendResponse"]) && $action["message_action"]["sendResponse"] == false) {
													$sendResponse = false;
												}

												// @FIXME: fix body
												$body = false;
												if (isset($action["props"]["isHTML"]) && $action["props"]["isHTML"] === true) {
													$body = isset($action["props"]["html_body"]) ? $action["props"]["html_body"] : false;
												} else {
													$body = isset($action["props"]["body"]) ? $action["props"]["body"] : false;
												}

												if($action["message_action"]["action_type"] == "acceptMeetingRequest") {
													$tentative = $action["message_action"]["responseType"] === olResponseTentative;
													$newProposedStartTime = isset($action["message_action"]["proposed_starttime"]) ? $action["message_action"]["proposed_starttime"] : false;
													$newProposedEndTime = isset($action["message_action"]["proposed_endtime"]) ? $action["message_action"]["proposed_endtime"] : false;

													// We are accepting MR from preview-read-mail so set delete the actual mail flag.
													$delete = (stristr($originalMessageProps[PR_MESSAGE_CLASS], 'IPM.Schedule.Meeting') !== false) ? true : false;
													$req->doAccept($tentative, $sendResponse, $delete, $newProposedStartTime, $newProposedEndTime, $body, true, $store, $basedate);
												} else {
													$delete = $req->doDecline($sendResponse, $store, $basedate, $body);
												}

												// Publish updated free/busy information
												$GLOBALS["operations"]->publishFreeBusy($store);

												/**
												 * Now if the item is the Meeting Request that was sent to the attendee 
												 * it is removed when the user has clicked on Accept/Decline. If the 
												 * item is the appointment in the calendar it will not be moved. To only
												 * notify the bus when the item is a Meeting Request we are going to 
												 * check the PR_MESSAGE_CLASS and see if it is "IPM.Meeting*".
												 */
												$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_STORE_ENTRYID, PR_PARENT_ENTRYID));

												if($delete){
													// send TABLE_DELETE event because the message has moved
													$this->sendFeedback(true);
													$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_DELETE, $messageProps);
												} else {
													$this->addActionData("update", array("item" => Conversion::mapMAPI2XML($this->properties, $messageProps)));
													$GLOBALS["bus"]->addData($this->getResponseData());

													// send TABLE_SAVE event because an occurrence is deleted
													$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
												}

											break;

											case "read_flag":
												$this->setReadFlag($store, $parententryid, $entryid, $action);
												break;

											case "copy":
											case "move":
												$this->copy($store, $parententryid, $entryid, $action);
												break;

											case "reply":
											case "replyall":
											case "forward":
											default:
												$this->save($store, $parententryid, $entryid, $action);
										}
									} else {
										$this->save($store, $parententryid, $entryid, $action);
									}
								} else {
									/**
									 * if parententryid or storeentryid is not passed then we can take a guess that
									 * it would be  a save operation but instead of depending on server to get default
									 * parent and store client should always send parententryid and storeentryid
									 *
									 * we can also assume that user has permission to right in his own store
									 */
									$this->save($store, $parententryid, $entryid, $action);
								}
								break;
							case "delete":
								$subActionType = false;
								if (isset($action["message_action"]) && isset($action["message_action"]["action_type"])) {
									$subActionType = $action["message_action"]["action_type"];
								}

								switch($subActionType)
								{
									case "removeFromCalendar":
										$basedate = (isset($action['basedate']) && !empty($action['basedate'])) ? $action['basedate'] : false;

										$GLOBALS["operations"]->removeFromCalendar($store, $entryid, $basedate, $this->directBookingMeetingRequest);
										$this->sendFeedback(true);
										break;

									case "cancelInvitation":
										$GLOBALS["operations"]->cancelInvitation($store, $entryid, $action, $this->directBookingMeetingRequest);
										$this->sendFeedback(true);
										break;

									case "declineMeeting":
										$message = $GLOBALS["operations"]->openMessage($store, $entryid);
										// @FIXME: 'basedate' should be moved, maybe either in 'props' or 'message_action'?
										$basedate = (isset($action['basedate']) && !empty($action['basedate'])) ? $action['basedate'] : false;

										$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $this->directBookingMeetingRequest);

										// @FIXME: may be we can remove this body check any get it while declining meeting 'body'
										$body = false;
										if (isset($action["props"]["isHTML"]) && $action["props"]["isHTML"] === true) {
											$body = isset($action["props"]["html_body"]) ? $action["props"]["html_body"] : false;
										} else {
											$body = isset($action["props"]["body"]) ? $action["props"]["body"] : false;
										}
										$req->doDecline(true, $store, $basedate, $body);

										// Publish updated free/busy information
										$GLOBALS["operations"]->publishFreeBusy($store);

										$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);

										break;

									default:
										// Deleting an occurence means that we have to save the message to
										// generate an exception. So when the basedate is provided, we actually
										// perform a save rather then delete.
										if (isset($action['basedate']) && !empty($action['basedate'])) {
											$this->save($store, $parententryid, $entryid, $action, "delete");
										} else {
											$this->delete($store, $parententryid, $entryid, $action);
											$GLOBALS["operations"]->publishFreeBusy($store, $parententryid);
										}
										break;
								}
								break;
							case "read_flag":
								$this->setReadFlag($store, $parententryid, $entryid, $action);
								break;
							case "checknames":
								$this->checkNames($store, $action);
								break;

							case "resolveConflict":
								$this->resolveConflict($store, $parententryid, $entryid, $action);
								break;

							case "attach_items":
								$this->forwardMultipleItems($store, $parententryid, $action);
								break;

							case "reclaimownership":
								$message = $GLOBALS["operations"]->openMessage($store, $entryid);
								$tr = new TaskRequest($store, $message, $GLOBALS["mapisession"]->getSession());
								$tr->reclaimownership();
								break;

							case "acceptTaskRequest":
							case "declineTaskRequest":
								$message = $GLOBALS["operations"]->openMessage($store, $entryid);
								// 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());
								if ($action["attributes"]["type"] == "acceptTaskRequest") {
									$result = $tr->doAccept(_("Task Accepted:") . " ");
								} else {
									$result = $tr->doDecline(_("Task Declined:") . " ");
								}

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

								mapi_savechanges($message);
								break;
							default:
								$this->handleUnknownActionType($actionType);
						}
					} catch (MAPIException $e) {
						$this->processException($e, $actionType, null, $action);
					}
				}
			}
		}

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

					case "save":
						if($e->getCode() == MAPI_E_NO_ACCESS) {
							if (isset($action["message_action"]) && isset($action["message_action"]["action_type"])) {
								switch($action["message_action"]["action_type"])
								{
									case "declineMeetingRequest":
									case "acceptMeetingRequest":
										$e->setDisplayMessage(_("Access Denied") . ".");
										break;
									case "copy":
										$e->setDisplayMessage(_("Could not copy message") . ".");
										break;
									case "move":
										$e->setDisplayMessage(_("Could not move message") . ".");
										break;
								}
							}

							if(empty($e->displayMessage)) {
								$e->setDisplayMessage(_("You have insufficient privileges to save items in this folder") . ".");
							}
						} else {
							$e->setDisplayMessage(_("Could not save message") . ".");
						}
						break;

					case "checknames":
					case "read_flag":
						if($e->getCode() == MAPI_E_NO_ACCESS)
							$e->setDisplayMessage(_("You have insufficient privileges to perform this action."));
						else
							$e->setDisplayMessage(_("Could not perform action."));
						break;

					case "removeFromCalendar":
						if($e->getCode() == MAPI_E_NO_ACCESS)
							$e->setDisplayMessage(_("You have insufficient privileges to remove item from the calendar."));
						else
							$e->setDisplayMessage(_("Could not remove item from the calendar."));
						break;

					case "resolveConflict":
						$e->setDisplayMessage(_("Could not resolve conflict."));
						break;

					case "attach_items":
						if($e->getCode() == MAPI_E_NO_ACCESS)
							$e->setDisplayMessage(_("You have insufficient privileges to attach item as an attachment."));
						else
							$e->setDisplayMessage(_("Could not attach item as an attachment."));
						break;

					case "reclaimownership":
						if($e->getCode() == MAPI_E_NO_ACCESS)
							$e->setDisplayMessage(_("You have insufficient privileges to reclaim the ownership for the Task Request."));
						else
							$e->setDisplayMessage(_("Could not reclaim the ownership for the Task Request."));
						break;

					case "acceptTaskRequest":
						if($e->getCode() == MAPI_E_NO_ACCESS)
							$e->setDisplayMessage(_("You have insufficient privileges to accept this Task Request."));
						else
							$e->setDisplayMessage(_("Could not accept Task Request."));
						break;

					case "declineTaskRequest":
						if($e->getCode() == MAPI_E_NO_ACCESS)
							$e->setDisplayMessage(_("You have insufficient privileges to decline this Task Request."));
						else
							$e->setDisplayMessage(_("Could not decline Task Request."));
						break;
				}
			}

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

		/**
		 * Function which sets selected messages as attachment to the item
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid entryid of the message
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure 
		 */
		function forwardMultipleItems($store, $parententryid, $action)
		{
			if($store){
				if(isset($action["entryids"]) && $action["entryids"]){
					
					if(!is_array($action["entryids"])){
						$action["entryids"] = array($action["entryids"]);
					}

					//add attach items to session variables
					$attachments = $GLOBALS["operations"]->setAttachmentInSession($store, $action["entryids"], $action["dialog_attachments"]);
				}

				$data = array();
				$data["item"] = array();
				$data["item"]["parententryid"] = bin2hex($parententryid);
				$data["item"]["attachments"] = array_merge($data["item"]["attachments"], $attachments);

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

		/**
		 * Function which opens an item.
		 * @param object $store MAPI Message Store Object
		 * @param string $entryid entryid of the message
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure 
		 */
		function open($store, $entryid, $action)
		{
			if($store && $entryid) {
				$message = $GLOBALS["operations"]->openMessage($store, $entryid);
			} else if($entryid) {
				$message = $GLOBALS["mapisession"]->openMessage($entryid);
				$messageStoreInfo = mapi_getprops($message, array(PR_STORE_ENTRYID));
				$store = $GLOBALS["mapisession"]->openMessageStore($messageStoreInfo[PR_STORE_ENTRYID]);
			}

			if(empty($message)) {
				return;
			}

			$data = array();
			if (isset($this->plaintext) && $this->plaintext){
				$data["item"] = $GLOBALS["operations"]->getMessageProps($store, $message, $this->properties, true);
			}else{
				$data["item"] = $GLOBALS["operations"]->getMessageProps($store, $message, $this->properties, false);
			}

			// Check for meeting request, do processing if necessary
			if(isset($data["item"]['props']["message_class"]) && strpos($data["item"]['props']["message_class"], "IPM.Schedule.Meeting") !== false) {
				$req = new Meetingrequest($store, $message, $GLOBALS["mapisession"]->getSession(), $this->directBookingMeetingRequest);

				$defaultCalendarId = $req->getDefaultFolderEntryID(PR_IPM_APPOINTMENT_ENTRYID);

				if($GLOBALS["operations"]->checkFolderAccess($store, $defaultCalendarId)) {
					if($req->isMeetingRequestResponse()) {
						// @FIXME do we need this check here? shouldn't meetingrequest class decide if its delegate mail or not?
						if($req->isLocalOrganiser()) {
							// We received a meeting request response, and we're the organiser
							$apptProps = $req->processMeetingRequestResponse();
						}else if(isset($data["item"]['props']['received_representing_name'])){
							// We can also received a meeting request response, when we are delegate
							$apptProps = $req->processMeetingRequestResponseAsDelegate();
						}
						mapi_savechanges($message);

						if (isset($apptProps) && $apptProps) {
							$data['item']['props'] = array_merge($data['item']['props'], array(
								'appointment_entryid' => $apptProps['entryid'],
								'appointment_store_entryid' => $apptProps['storeid'],
								'appointment_parent_entryid' => $apptProps['parententryid'],
								'appointment_basedate' => isset($apptProps['basedate']) && $apptProps['basedate'] ? $apptProps['basedate'] : null,
								'appointment_updatecounter' => $apptProps['updatecounter'],
								'meeting_updated' => $apptProps['meeting_updated']
							));
						}
					} else if($req->isMeetingRequest()) {
						if(!$req->isLocalOrganiser()) {
							if ($req->isMeetingOutOfDate()) {
								// we know that meeting is out of date so directly set this properties
								$data['item']['props']['meetingtype'] = mtgOutOfDate;
								$data['item']['props']['icon_index'] = 1033;

								// send update to maillistmodule that meeting request is updated with out of date flag
								$messageProps = mapi_getprops($message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
								$GLOBALS["bus"]->notify(bin2hex($messageProps[PR_PARENT_ENTRYID]), TABLE_SAVE, $messageProps);
							} else {
								/**
								 * if meeting request is not out of date then process it for the first time
								 * which will create corresponding appointment in the user's calendar
								 */
								$req->doAccept(true, false, false);

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

							// Show user whether meeting request conflict with other appointment or not.
							$meetingConflicts = $req->isMeetingConflicting();
							/**
							 * if $meetingConflicts is boolean and true then its a normal meeting.
							 * if $meetingConflicts is integer then it indicates no of instances of a recurring meeting which conflicts with Calendar.
							 */
							if($meetingConflicts !== false) {
								if ($meetingConflicts === true) {
									$data['item']['props']['conflictinfo'] = _('Conflicts with another appointment on your Calendar.');
								} else {
									$data['item']['props']['conflictinfo'] = sprintf(ngettext('%s occurrence of this recurring appointment conflicts with other appointment on your Calendar.', '%s occurrences of this recurring appointment conflicts with other appointments on your Calendar.', $meetingConflicts), $meetingConflicts);
								}
							}
						}
					} else if($req->isMeetingCancellation()) {
						if(!$req->isLocalOrganiser()) {
							$req->processMeetingCancellation();
						}
					}
					
					if(!$req->isInCalendar()) {
						$data['item']['props']['appointment_not_found'] = true;
					}
				}
			}

			if (isset($data["item"]['props']["message_class"]) && strpos($data["item"]['props']["message_class"], "IPM.TaskRequest") !== false) {
				$tr = new TaskRequest($store, $message, $GLOBALS["mapisession"]->getSession());
				$properties = $GLOBALS["properties"]->getTaskProperties();
				
				// @FIXME is this code used anywhere?
				if($tr->isTaskRequest()) {
					$tr->processTaskRequest();
					$task = $tr->getAssociatedTask(false);
					$taskProps = $GLOBALS["operations"]->getMessageProps($store, $task, $properties, true);
					$data["item"] = $taskProps;

					// notify task folder that new task has been created
					$GLOBALS["bus"]->notify($taskProps["parent_entryid"]["_content"], TABLE_SAVE, array(
						PR_ENTRYID => hex2bin($taskProps["entryid"]["_content"]),
						PR_PARENT_ENTRYID => hex2bin($taskProps["parent_entryid"]["_content"]),
						PR_STORE_ENTRYID => hex2bin($taskProps["store_entryid"]["_content"])
					));
				}
				
				if($tr->isTaskRequestResponse()) {
					$tr->processTaskResponse();
					$task = $tr->getAssociatedTask(false);

					$data["item"] = $GLOBALS["operations"]->getMessageProps($store, $task, $properties, true);
				}
			}
			
			// Open embedded message in embedded message in ...
			if(isset($action["rootentryid"]) && isset($action["attachments"])) {
				if(isset($action["attachments"]["attach_num"]) && is_array($action["attachments"]["attach_num"])) {
					$data["item"] = $GLOBALS["operations"]->getEmbeddedMessage($store, $entryid, $this->properties, $action["attachments"]["attach_num"]);
				}
			}

			// check if this message is a NDR (mail)message, if so, generate a new body message
			if(isset($data["item"]["props"]["message_class"]) && strtoupper($data["item"]["props"]["message_class"]) == "REPORT.IPM.NOTE.NDR"){
				$data["item"]["props"]["isHTML"] = false;
				$data["item"]["props"]["body"] = $GLOBALS["operations"]->getNDRbody($GLOBALS["operations"]->openMessage($store, $entryid));
			}
							
			// Allowing to hook in just before the data sent away to be sent to the client
			$GLOBALS['PluginManager']->triggerHook("server.module.itemmodule.open.after", array(
				'moduleObject' =>& $this,
				'store' => $store,
				'entryid' => $entryid,
				'action' => $action,
				'message' =>& $message,
				'data' =>& $data
				));

			$this->addActionData("item", $data);
			$GLOBALS["bus"]->addData($this->getResponseData());
		}
		
		/**
		 * Function which saves an item.
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid parent entryid of the message
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure		 		 
		 */
		function save($store, $parententryid, $entryid, $action)
		{
			$result = false;
			
			if(isset($action["props"])) {
				if(!$store && !$parententryid) {
					if(isset($action["props"]["message_class"])) {
						$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
						$parententryid = $this->getDefaultFolderEntryID($store, $action["props"]["message_class"]);
					}
				}else if(!$parententryid){
					if(isset($action["props"]["message_class"]))
						$parententryid = $this->getDefaultFolderEntryID($store, $action["props"]["message_class"]);
				}

				if($store && $parententryid) {
					$props = Conversion::mapXML2MAPI($this->properties, $action["props"]);

					$messageProps = array(); // returned props
					$result = $GLOBALS["operations"]->saveMessage($store, $entryid, $parententryid, $props, $messageProps, array(), (isset($action['attachments']) ? $action['attachments'] : array()));
					
					if($result) {
						$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_SAVE, $messageProps);

						$this->addActionData("update", array("item" => Conversion::mapMAPI2XML($this->properties, $messageProps)));
						$GLOBALS["bus"]->addData($this->getResponseData());
					}
				}
			}
		}
		
		/**
		 * Function which deletes an item.
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid parent entryid of the message
		 * @param string $entryid entryid of the message		 
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure		 		 
		 */
		function delete($store, $parententryid, $entryid, $action)
		{
			$result = false;
			
			if($store && $parententryid && $entryid) {
				$props = array();
				$props[PR_PARENT_ENTRYID] = $parententryid;
				$props[PR_ENTRYID] = $entryid;
	
				$storeprops = mapi_getprops($store, array(PR_ENTRYID));
				$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];
				
				$result = $GLOBALS["operations"]->deleteMessages($store, $parententryid, $entryid);

				if($result) {
					$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_DELETE, $props);
					$this->sendFeedback(true);
				}
			}
		}
		
		/**
		 * Function which sets the PR_MESSAGE_FLAGS property of an item.
		 * @param object $store MAPI Message Store Object
		 * @param string $parententryid parent entryid of the message
		 * @param string $entryid entryid of the message
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure
		 */
		function setReadFlag($store, $parententryid, $entryid, $action)
		{
			$result = false;
			
			if($store && $parententryid && $entryid) {
				$flags = MSGFLAG_READ;
				if(isset($action["props"]) && isset($action["props"]["message_flags"])) {
					$flags = $action['props']['message_flags'];
				}

				$props = array();

				$msg_action = isset($action['message_action']) ? $action['message_action'] : false;
				$result = $GLOBALS["operations"]->setMessageFlag($store, $entryid, $flags, $msg_action, $props);

				if($result) {
					$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_SAVE, $props);
				}
			}
		}
				
		/**
		 * Function which returns the entryid of a default folder.		 		 		 
		 * @param object $store MAPI Message Store Object
		 * @param string $messageClass the class of the folder
		 * @return string entryid of a default folder, false if not found		 		 
		 */
		function getDefaultFolderEntryID($store, $messageClass)
		{
			$entryid = false;
			
			if($store) {
				$rootcontainer = mapi_msgstore_openentry($store);
				$rootcontainerprops = mapi_getprops($rootcontainer, array(PR_IPM_DRAFTS_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID));

				switch($messageClass)
				{
					case "IPM.Appointment":
						if(isset($rootcontainerprops[PR_IPM_APPOINTMENT_ENTRYID])) {
							$entryid = $rootcontainerprops[PR_IPM_APPOINTMENT_ENTRYID];
						}
						break;
					case "IPM.Contact":
					case "IPM.DistList":
						if(isset($rootcontainerprops[PR_IPM_CONTACT_ENTRYID])) {
							$entryid = $rootcontainerprops[PR_IPM_CONTACT_ENTRYID];
						}
						break;
					case "IPM.StickyNote":
						if(isset($rootcontainerprops[PR_IPM_NOTE_ENTRYID])) {
							$entryid = $rootcontainerprops[PR_IPM_NOTE_ENTRYID];
						}
						break;
					case "IPM.Task":
						if(isset($rootcontainerprops[PR_IPM_TASK_ENTRYID])) {
							$entryid = $rootcontainerprops[PR_IPM_TASK_ENTRYID];
						}
						break;
					default:
						if(isset($rootcontainerprops[PR_IPM_DRAFTS_ENTRYID])) {
							$entryid = $rootcontainerprops[PR_IPM_DRAFTS_ENTRYID];
						}
						break;
				}
			}
			
			return $entryid;
		}
		
		function resolveConflict($store, $parententryid, $entryid, $action)
		{
			
			if(!is_array($entryid)) {
				$entryid = array($entryid);
			}
			$srcmessage = mapi_openentry($GLOBALS["mapisession"]->getSession(), $entryid[0], 0);
			if(!$srcmessage)
				return false;
			
			$dstmessage = mapi_openentry($GLOBALS["mapisession"]->getSession(), hex2bin($action["conflictentryid"]), MAPI_MODIFY);
			if(!$dstmessage)
				return false;
			
			$srcfolder = mapi_openentry($GLOBALS["mapisession"]->getSession(), $parententryid, MAPI_MODIFY);
			
			$result = mapi_copyto($srcmessage, array(), array(PR_CONFLICT_ITEMS, PR_SOURCE_KEY, PR_CHANGE_KEY, PR_PREDECESSOR_CHANGE_LIST), $dstmessage);
			if(!$result)
				return $result;
				
			//remove srcmessage entryid from PR_CONFLICT_ITEMS
			$props = mapi_getprops($dstmessage, array(PR_CONFLICT_ITEMS));
			if(isset($props[PR_CONFLICT_ITEMS])){
				$binentryid = hex2bin($entryid[0]);
				foreach($props[PR_CONFLICT_ITEMS] as $i => $conflict){
					if($conflict == $binentryid){
						array_splice($props[PR_CONFLICT_ITEMS],$i,1);
					}else{
						$tmp = mapi_openentry($GLOBALS["mapisession"]->getSession(), $conflict, 0);
						if(!$tmp){
							array_splice($props[PR_CONFLICT_ITEMS],$i,1);
						}
						unset($tmp);
					}
				}
				if(empty($props[PR_CONFLICT_ITEMS])){
					mapi_setprops($dstmessage, $props);
				}else{
					mapi_deleteprops($dstmessage, array(PR_CONFLICT_ITEMS));
				}
			}
			
				
			mapi_savechanges($dstmessage);
			
			$result = mapi_folder_deletemessages($srcfolder, $entryid);
			
			$props = array();
			$props[PR_PARENT_ENTRYID] = $parententryid;
			$props[PR_ENTRYID] = $entryid[0];
			
			$storeprops = mapi_getprops($store, array(PR_ENTRYID));
			$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];
			$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_DELETE, $props);
			
			if(!$result)
				return $result;
		}

		/**
		 * Function which copies or moves one or more items.
		 * @param MAPIStore $store MAPI Message Store Object
		 * @param binString $parententryid entryid of the folder
		 * @param array $entryid list of entryids which will be copied or moved (in binary format)
		 * @param array $action the action data, sent by the client
		 * @return boolean true on success or false on failure
		 */
		function copy($store, $parententryid, $entryids, $action)
		{
			$result = false;
			
			if($store && $parententryid && $entryids) {
				$dest_store = $store;
				if(isset($action["message_action"]["destination_store_entryid"])) {
					$dest_storeentryid = hex2bin($action["message_action"]["destination_store_entryid"]);
					$dest_store = $GLOBALS["mapisession"]->openMessageStore($dest_storeentryid);
				}
				
				$dest_folderentryid = false;
				if(isset($action["message_action"]["destination_parent_entryid"])) {
					$dest_folderentryid = hex2bin($action["message_action"]["destination_parent_entryid"]);
				}

				$moveMessages = false;
				if(isset($action["message_action"]["action_type"]) && $action["message_action"]["action_type"] == "move") {
					$moveMessages = true;
				}

				// drag & drop from a public store to other store should always be copy instead of move
				$destStoreProps = mapi_getprops($dest_store, array(PR_MDB_PROVIDER));
				$storeProps = mapi_getprops($store, array(PR_MDB_PROVIDER));

				if($storeProps[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID && $destStoreProps[PR_MDB_PROVIDER] != ZARAFA_STORE_PUBLIC_GUID) {
					$moveMessages = false;
				}

				$props = array();
				$props[PR_PARENT_ENTRYID] = $parententryid;
				$props[PR_ENTRYID] = $entryids;

				$storeprops = mapi_getprops($store, array(PR_ENTRYID));
				$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];

				$result = $GLOBALS["operations"]->copyMessages($store, $parententryid, $dest_store, $dest_folderentryid, $entryids, $moveMessages);

				if($result) {
					if($moveMessages) {
						$GLOBALS["bus"]->notify(bin2hex($parententryid), TABLE_DELETE, $props);
					}

					// Delete the PR_ENTRYID, the copied or moved message has a new entryid,
					// and at this time we have no idea what that might be. So make sure
					// we unset it, otherwise the notification handlers get weird ideas
					// and could reset the PR_PARENT_ENTRYID to the old folder again.
					unset($props[PR_ENTRYID]);
					$props[PR_PARENT_ENTRYID] = $dest_folderentryid;
					$props[PR_STORE_ENTRYID] = $dest_storeentryid;
					$GLOBALS["bus"]->notify(bin2hex($dest_folderentryid), TABLE_SAVE, $props);
				}

				$this->sendFeedback($result, array());
			}
		}
	}
?>
