Test Setup Failed
Branch master (463395)
by Mike
02:40
created
server.php 1 patch
Indentation   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -21,7 +21,7 @@  discard block
 block discarded – undo
21 21
 
22 22
 // don't log any Sabre asset requests (images etc)
23 23
 if (isset($_REQUEST['sabreAction']) && $_REQUEST['sabreAction'] == 'asset') {
24
-	$logger->resetConfiguration();
24
+    $logger->resetConfiguration();
25 25
 }
26 26
 
27 27
 // log the start data
@@ -39,9 +39,9 @@  discard block
 block discarded – undo
39 39
 
40 40
 // Setting up the directory tree
41 41
 $nodes = [
42
-	new \Sabre\DAVACL\PrincipalCollection($principalBackend),
43
-	new \Sabre\CardDAV\AddressBookRoot($principalBackend, $gCarddavBackend),
44
-	new \Sabre\CalDAV\CalendarRoot($principalBackend, $gCaldavBackend),
42
+    new \Sabre\DAVACL\PrincipalCollection($principalBackend),
43
+    new \Sabre\CardDAV\AddressBookRoot($principalBackend, $gCarddavBackend),
44
+    new \Sabre\CalDAV\CalendarRoot($principalBackend, $gCaldavBackend),
45 45
 ];
46 46
 
47 47
 // initialize the server
@@ -76,11 +76,11 @@  discard block
 block discarded – undo
76 76
 $server->addPlugin($caldavPlugin);
77 77
 
78 78
 if (strlen(SYNC_DB) > 0) {
79
-	$server->addPlugin(new \Sabre\DAV\Sync\Plugin());
79
+    $server->addPlugin(new \Sabre\DAV\Sync\Plugin());
80 80
 }
81 81
 
82 82
 if (DEVELOPER_MODE) {
83
-	$server->addPlugin(new \Sabre\DAV\Browser\Plugin(false));
83
+    $server->addPlugin(new \Sabre\DAV\Browser\Plugin(false));
84 84
 }
85 85
 
86 86
 $server->exec();
@@ -89,10 +89,10 @@  discard block
 block discarded – undo
89 89
 $logger->LogOutgoing($server->httpResponse);
90 90
 
91 91
 $logger->debug(
92
-	"httpcode='%s' memory='%s/%s' time='%ss'",
93
-	http_response_code(),
94
-	$logger->FormatBytes(memory_get_peak_usage(false)),
95
-	$logger->FormatBytes(memory_get_peak_usage(true)),
96
-	number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2)
92
+    "httpcode='%s' memory='%s/%s' time='%ss'",
93
+    http_response_code(),
94
+    $logger->FormatBytes(memory_get_peak_usage(false)),
95
+    $logger->FormatBytes(memory_get_peak_usage(true)),
96
+    number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2)
97 97
 );
98 98
 $logger->debug('------------------ End');
Please login to merge, or discard this patch.
mapi/class.baseexception.php 1 patch
Indentation   +117 added lines, -117 removed lines patch added patch discarded remove patch
@@ -18,121 +18,121 @@
 block discarded – undo
18 18
  * getTraceAsString() - formatted string of trace
19 19
  */
20 20
 class BaseException extends Exception {
21
-	/**
22
-	 * Base name of the file, so we don't have to use static path of the file.
23
-	 */
24
-	private $baseFile;
25
-
26
-	/**
27
-	 * Flag to check if exception is already handled or not.
28
-	 */
29
-	public $isHandled = false;
30
-
31
-	/**
32
-	 * The exception message to show at client side.
33
-	 */
34
-	public $displayMessage;
35
-
36
-	/**
37
-	 * @param string    $errorMessage
38
-	 * @param int       $code
39
-	 * @param Exception $previous
40
-	 * @param string    $displayMessage
41
-	 */
42
-	public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) {
43
-		// assign display message
44
-		$this->displayMessage = $displayMessage;
45
-
46
-		parent::__construct($errorMessage, (int) $code, $previous);
47
-	}
48
-
49
-	/**
50
-	 * @return string returns file name and line number combined where exception occurred
51
-	 */
52
-	public function getFileLine() {
53
-		return $this->getBaseFile() . ':' . $this->getLine();
54
-	}
55
-
56
-	/**
57
-	 * @return string returns message that should be sent to client to display
58
-	 */
59
-	public function getDisplayMessage() {
60
-		if (!is_null($this->displayMessage)) {
61
-			return $this->displayMessage;
62
-		}
63
-
64
-		return $this->getMessage();
65
-	}
66
-
67
-	/**
68
-	 * Function sets display message of an exception that will be sent to the client side
69
-	 * to show it to user.
70
-	 *
71
-	 * @param string $message display message
72
-	 */
73
-	public function setDisplayMessage($message) {
74
-		$this->displayMessage = $message;
75
-	}
76
-
77
-	/**
78
-	 * Function sets a flag in exception class to indicate that exception is already handled
79
-	 * so if it is caught again in the top level of function stack then we have to silently
80
-	 * ignore it.
81
-	 */
82
-	public function setHandled() {
83
-		$this->isHandled = true;
84
-	}
85
-
86
-	/**
87
-	 * @return string returns base path of the file where exception occurred
88
-	 */
89
-	public function getBaseFile() {
90
-		if (is_null($this->baseFile)) {
91
-			$this->baseFile = basename(parent::getFile());
92
-		}
93
-
94
-		return $this->baseFile;
95
-	}
96
-
97
-	/**
98
-	 * Function will check for PHP version if it is greater than 5.3 then we can use its default implementation
99
-	 * otherwise we have to use our own implementation of chanining functionality.
100
-	 *
101
-	 * @return Exception returns previous exception
102
-	 */
103
-	public function _getPrevious() {
104
-		if (version_compare(PHP_VERSION, '5.3.0', '<')) {
105
-			return $this->_previous;
106
-		}
107
-
108
-		return parent::getPrevious();
109
-	}
110
-
111
-	/**
112
-	 * String representation of the exception, also handles previous exception.
113
-	 *
114
-	 * @return string
115
-	 */
116
-	public function __toString() {
117
-		if (version_compare(PHP_VERSION, '5.3.0', '<')) {
118
-			if (($e = $this->getPrevious()) !== null) {
119
-				return $e->__toString() .
120
-						"\n\nNext " .
121
-						parent::__toString();
122
-			}
123
-		}
124
-
125
-		return parent::__toString();
126
-	}
127
-
128
-	/**
129
-	 * Name of the class of exception.
130
-	 *
131
-	 * @return string
132
-	 */
133
-	public function getName() {
134
-		return get_class($this);
135
-	}
136
-
137
-	// @TODO getTrace and getTraceAsString
21
+    /**
22
+     * Base name of the file, so we don't have to use static path of the file.
23
+     */
24
+    private $baseFile;
25
+
26
+    /**
27
+     * Flag to check if exception is already handled or not.
28
+     */
29
+    public $isHandled = false;
30
+
31
+    /**
32
+     * The exception message to show at client side.
33
+     */
34
+    public $displayMessage;
35
+
36
+    /**
37
+     * @param string    $errorMessage
38
+     * @param int       $code
39
+     * @param Exception $previous
40
+     * @param string    $displayMessage
41
+     */
42
+    public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) {
43
+        // assign display message
44
+        $this->displayMessage = $displayMessage;
45
+
46
+        parent::__construct($errorMessage, (int) $code, $previous);
47
+    }
48
+
49
+    /**
50
+     * @return string returns file name and line number combined where exception occurred
51
+     */
52
+    public function getFileLine() {
53
+        return $this->getBaseFile() . ':' . $this->getLine();
54
+    }
55
+
56
+    /**
57
+     * @return string returns message that should be sent to client to display
58
+     */
59
+    public function getDisplayMessage() {
60
+        if (!is_null($this->displayMessage)) {
61
+            return $this->displayMessage;
62
+        }
63
+
64
+        return $this->getMessage();
65
+    }
66
+
67
+    /**
68
+     * Function sets display message of an exception that will be sent to the client side
69
+     * to show it to user.
70
+     *
71
+     * @param string $message display message
72
+     */
73
+    public function setDisplayMessage($message) {
74
+        $this->displayMessage = $message;
75
+    }
76
+
77
+    /**
78
+     * Function sets a flag in exception class to indicate that exception is already handled
79
+     * so if it is caught again in the top level of function stack then we have to silently
80
+     * ignore it.
81
+     */
82
+    public function setHandled() {
83
+        $this->isHandled = true;
84
+    }
85
+
86
+    /**
87
+     * @return string returns base path of the file where exception occurred
88
+     */
89
+    public function getBaseFile() {
90
+        if (is_null($this->baseFile)) {
91
+            $this->baseFile = basename(parent::getFile());
92
+        }
93
+
94
+        return $this->baseFile;
95
+    }
96
+
97
+    /**
98
+     * Function will check for PHP version if it is greater than 5.3 then we can use its default implementation
99
+     * otherwise we have to use our own implementation of chanining functionality.
100
+     *
101
+     * @return Exception returns previous exception
102
+     */
103
+    public function _getPrevious() {
104
+        if (version_compare(PHP_VERSION, '5.3.0', '<')) {
105
+            return $this->_previous;
106
+        }
107
+
108
+        return parent::getPrevious();
109
+    }
110
+
111
+    /**
112
+     * String representation of the exception, also handles previous exception.
113
+     *
114
+     * @return string
115
+     */
116
+    public function __toString() {
117
+        if (version_compare(PHP_VERSION, '5.3.0', '<')) {
118
+            if (($e = $this->getPrevious()) !== null) {
119
+                return $e->__toString() .
120
+                        "\n\nNext " .
121
+                        parent::__toString();
122
+            }
123
+        }
124
+
125
+        return parent::__toString();
126
+    }
127
+
128
+    /**
129
+     * Name of the class of exception.
130
+     *
131
+     * @return string
132
+     */
133
+    public function getName() {
134
+        return get_class($this);
135
+    }
136
+
137
+    // @TODO getTrace and getTraceAsString
138 138
 }
Please login to merge, or discard this patch.
mapi/class.freebusypublish.php 1 patch
Indentation   +238 added lines, -238 removed lines patch added patch discarded remove patch
@@ -6,271 +6,271 @@
 block discarded – undo
6 6
  */
7 7
 
8 8
 class FreeBusyPublish {
9
-	public $session;
10
-	public $calendar;
11
-	public $entryid;
12
-	public $starttime;
13
-	public $length;
14
-	public $store;
15
-	public $proptags;
9
+    public $session;
10
+    public $calendar;
11
+    public $entryid;
12
+    public $starttime;
13
+    public $length;
14
+    public $store;
15
+    public $proptags;
16 16
 
17
-	/**
18
-	 * @param mapi_session $session  MAPI Session
19
-	 * @param mapi_folder  $calendar Calendar to publish
20
-	 * @param string       $entryid  AddressBook Entry ID for the user we're publishing for
21
-	 * @param mixed        $store
22
-	 */
23
-	public function __construct($session, $store, $calendar, $entryid) {
24
-		$properties["entryid"] = PR_ENTRYID;
25
-		$properties["parent_entryid"] = PR_PARENT_ENTRYID;
26
-		$properties["message_class"] = PR_MESSAGE_CLASS;
27
-		$properties["icon_index"] = PR_ICON_INDEX;
28
-		$properties["subject"] = PR_SUBJECT;
29
-		$properties["display_to"] = PR_DISPLAY_TO;
30
-		$properties["importance"] = PR_IMPORTANCE;
31
-		$properties["sensitivity"] = PR_SENSITIVITY;
32
-		$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
33
-		$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
34
-		$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
35
-		$properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
36
-		$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
37
-		$properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
38
-		$properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
39
-		$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
40
-		$properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
41
-		$properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
42
-		$properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
43
-		$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
44
-		$properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
45
-		$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
46
-		$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
47
-		$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
48
-		$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
49
-		$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
50
-		$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
51
-		$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
52
-		$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
53
-		$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
54
-		$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
55
-		$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
56
-		$this->proptags = getPropIdsFromStrings($store, $properties);
17
+    /**
18
+     * @param mapi_session $session  MAPI Session
19
+     * @param mapi_folder  $calendar Calendar to publish
20
+     * @param string       $entryid  AddressBook Entry ID for the user we're publishing for
21
+     * @param mixed        $store
22
+     */
23
+    public function __construct($session, $store, $calendar, $entryid) {
24
+        $properties["entryid"] = PR_ENTRYID;
25
+        $properties["parent_entryid"] = PR_PARENT_ENTRYID;
26
+        $properties["message_class"] = PR_MESSAGE_CLASS;
27
+        $properties["icon_index"] = PR_ICON_INDEX;
28
+        $properties["subject"] = PR_SUBJECT;
29
+        $properties["display_to"] = PR_DISPLAY_TO;
30
+        $properties["importance"] = PR_IMPORTANCE;
31
+        $properties["sensitivity"] = PR_SENSITIVITY;
32
+        $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
33
+        $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
34
+        $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
35
+        $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
36
+        $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
37
+        $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
38
+        $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
39
+        $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
40
+        $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
41
+        $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
42
+        $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
43
+        $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
44
+        $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
45
+        $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
46
+        $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
47
+        $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
48
+        $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
49
+        $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
50
+        $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
51
+        $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
52
+        $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
53
+        $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
54
+        $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
55
+        $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
56
+        $this->proptags = getPropIdsFromStrings($store, $properties);
57 57
 
58
-		$this->session = $session;
59
-		$this->calendar = $calendar;
60
-		$this->entryid = $entryid;
61
-		$this->store = $store;
62
-	}
58
+        $this->session = $session;
59
+        $this->calendar = $calendar;
60
+        $this->entryid = $entryid;
61
+        $this->store = $store;
62
+    }
63 63
 
64
-	/**
65
-	 * Publishes the information.
66
-	 *
67
-	 * @paam timestamp $starttime Time from which to publish data  (usually now)
68
-	 * @paam integer $length Amount of seconds from $starttime we should publish
69
-	 *
70
-	 * @param mixed $starttime
71
-	 * @param mixed $length
72
-	 */
73
-	public function publishFB($starttime, $length) {
74
-		$start = $starttime;
75
-		$end = $starttime + $length;
64
+    /**
65
+     * Publishes the information.
66
+     *
67
+     * @paam timestamp $starttime Time from which to publish data  (usually now)
68
+     * @paam integer $length Amount of seconds from $starttime we should publish
69
+     *
70
+     * @param mixed $starttime
71
+     * @param mixed $length
72
+     */
73
+    public function publishFB($starttime, $length) {
74
+        $start = $starttime;
75
+        $end = $starttime + $length;
76 76
 
77
-		// Get all the items in the calendar that we need
77
+        // Get all the items in the calendar that we need
78 78
 
79
-		$calendaritems = [];
79
+        $calendaritems = [];
80 80
 
81
-		$restrict = [RES_OR, [
82
-			// (item[start] >= start && item[start] <= end)
83
-			[RES_AND, [
84
-				[RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => $this->proptags["startdate"], VALUE => $start]],
85
-				[RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => $this->proptags["startdate"], VALUE => $end]],
86
-			]],
87
-			// OR
88
-			// (item[end] >= start && item[end] <= end)
89
-			[RES_AND, [
90
-				[RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => $this->proptags["duedate"], VALUE => $start]],
91
-				[RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => $this->proptags["duedate"], VALUE => $end]],
92
-			]],
93
-			// OR
94
-			// (item[start] < start && item[end] > end)
95
-			[RES_AND, [
96
-				[RES_PROPERTY, [RELOP => RELOP_LT, ULPROPTAG => $this->proptags["startdate"], VALUE => $start]],
97
-				[RES_PROPERTY, [RELOP => RELOP_GT, ULPROPTAG => $this->proptags["duedate"], VALUE => $end]],
98
-			]],
99
-			// OR
100
-			[RES_OR, [
101
-				// OR
102
-				// (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start)
103
-				[RES_AND, [
104
-					[RES_EXIST, [ULPROPTAG => $this->proptags["enddate_recurring"]]],
105
-					[RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->proptags["recurring"], VALUE => true]],
106
-					[RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => $this->proptags["enddate_recurring"], VALUE => $start]],
107
-				]],
108
-				// OR
109
-				// (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
110
-				[RES_AND, [
111
-					[RES_NOT, [
112
-						[RES_EXIST, [ULPROPTAG => $this->proptags["enddate_recurring"]]],
113
-					]],
114
-					[RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => $this->proptags["startdate"], VALUE => $end]],
115
-					[RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->proptags["recurring"], VALUE => true]],
116
-				]],
117
-			]], // EXISTS OR
118
-		]]; // global OR
81
+        $restrict = [RES_OR, [
82
+            // (item[start] >= start && item[start] <= end)
83
+            [RES_AND, [
84
+                [RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => $this->proptags["startdate"], VALUE => $start]],
85
+                [RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => $this->proptags["startdate"], VALUE => $end]],
86
+            ]],
87
+            // OR
88
+            // (item[end] >= start && item[end] <= end)
89
+            [RES_AND, [
90
+                [RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => $this->proptags["duedate"], VALUE => $start]],
91
+                [RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => $this->proptags["duedate"], VALUE => $end]],
92
+            ]],
93
+            // OR
94
+            // (item[start] < start && item[end] > end)
95
+            [RES_AND, [
96
+                [RES_PROPERTY, [RELOP => RELOP_LT, ULPROPTAG => $this->proptags["startdate"], VALUE => $start]],
97
+                [RES_PROPERTY, [RELOP => RELOP_GT, ULPROPTAG => $this->proptags["duedate"], VALUE => $end]],
98
+            ]],
99
+            // OR
100
+            [RES_OR, [
101
+                // OR
102
+                // (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start)
103
+                [RES_AND, [
104
+                    [RES_EXIST, [ULPROPTAG => $this->proptags["enddate_recurring"]]],
105
+                    [RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->proptags["recurring"], VALUE => true]],
106
+                    [RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => $this->proptags["enddate_recurring"], VALUE => $start]],
107
+                ]],
108
+                // OR
109
+                // (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
110
+                [RES_AND, [
111
+                    [RES_NOT, [
112
+                        [RES_EXIST, [ULPROPTAG => $this->proptags["enddate_recurring"]]],
113
+                    ]],
114
+                    [RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => $this->proptags["startdate"], VALUE => $end]],
115
+                    [RES_PROPERTY, [RELOP => RELOP_EQ, ULPROPTAG => $this->proptags["recurring"], VALUE => true]],
116
+                ]],
117
+            ]], // EXISTS OR
118
+        ]]; // global OR
119 119
 
120
-		$contents = mapi_folder_getcontentstable($this->calendar);
121
-		mapi_table_restrict($contents, $restrict);
120
+        $contents = mapi_folder_getcontentstable($this->calendar);
121
+        mapi_table_restrict($contents, $restrict);
122 122
 
123
-		while (1) {
124
-			$rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50);
123
+        while (1) {
124
+            $rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50);
125 125
 
126
-			if (!is_array($rows)) {
127
-				break;
128
-			}
126
+            if (!is_array($rows)) {
127
+                break;
128
+            }
129 129
 
130
-			if (empty($rows)) {
131
-				break;
132
-			}
130
+            if (empty($rows)) {
131
+                break;
132
+            }
133 133
 
134
-			foreach ($rows as $row) {
135
-				$occurrences = [];
136
-				if (isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) {
137
-					$recur = new Recurrence($this->store, $row);
134
+            foreach ($rows as $row) {
135
+                $occurrences = [];
136
+                if (isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) {
137
+                    $recur = new Recurrence($this->store, $row);
138 138
 
139
-					$occurrences = $recur->getItems($starttime, $starttime + $length);
140
-				}
141
-				else {
142
-					$occurrences[] = $row;
143
-				}
139
+                    $occurrences = $recur->getItems($starttime, $starttime + $length);
140
+                }
141
+                else {
142
+                    $occurrences[] = $row;
143
+                }
144 144
 
145
-				$calendaritems = array_merge($calendaritems, $occurrences);
146
-			}
147
-		}
145
+                $calendaritems = array_merge($calendaritems, $occurrences);
146
+            }
147
+        }
148 148
 
149
-		// $calendaritems now contains all the calendar items in the specified time
150
-		// frame. We now need to merge these into a flat array of begin/end/status
151
-		// objects. This also filters out all the 'free' items (status 0)
149
+        // $calendaritems now contains all the calendar items in the specified time
150
+        // frame. We now need to merge these into a flat array of begin/end/status
151
+        // objects. This also filters out all the 'free' items (status 0)
152 152
 
153
-		$freebusy = $this->mergeItemsFB($calendaritems);
153
+        $freebusy = $this->mergeItemsFB($calendaritems);
154 154
 
155
-		// $freebusy now contains the start, end and status of all items, merged.
155
+        // $freebusy now contains the start, end and status of all items, merged.
156 156
 
157
-		// Get the FB interface
158
-		try {
159
-			$fbsupport = mapi_freebusysupport_open($this->session, $this->store);
160
-		}
161
-		catch (MAPIException $e) {
162
-			if ($e->getCode() == MAPI_E_NOT_FOUND) {
163
-				$e->setHandled();
164
-				if (function_exists("dump")) {
165
-					dump("Error in opening freebusysupport object.");
166
-				}
167
-			}
168
-		}
157
+        // Get the FB interface
158
+        try {
159
+            $fbsupport = mapi_freebusysupport_open($this->session, $this->store);
160
+        }
161
+        catch (MAPIException $e) {
162
+            if ($e->getCode() == MAPI_E_NOT_FOUND) {
163
+                $e->setHandled();
164
+                if (function_exists("dump")) {
165
+                    dump("Error in opening freebusysupport object.");
166
+                }
167
+            }
168
+        }
169 169
 
170
-		// Open updater for this user
171
-		if (!isset($fbsupport) || !$fbsupport) {
172
-			return;
173
-		}
174
-		$updaters = mapi_freebusysupport_loadupdate($fbsupport, [$this->entryid]);
175
-		$updater = $updaters[0];
170
+        // Open updater for this user
171
+        if (!isset($fbsupport) || !$fbsupport) {
172
+            return;
173
+        }
174
+        $updaters = mapi_freebusysupport_loadupdate($fbsupport, [$this->entryid]);
175
+        $updater = $updaters[0];
176 176
 
177
-		// Send the data
178
-		mapi_freebusyupdate_reset($updater);
179
-		mapi_freebusyupdate_publish($updater, $freebusy);
180
-		mapi_freebusyupdate_savechanges($updater, $start - 24 * 60 * 60, $end);
177
+        // Send the data
178
+        mapi_freebusyupdate_reset($updater);
179
+        mapi_freebusyupdate_publish($updater, $freebusy);
180
+        mapi_freebusyupdate_savechanges($updater, $start - 24 * 60 * 60, $end);
181 181
 
182
-		// We're finished
183
-		mapi_freebusysupport_close($fbsupport);
184
-	}
182
+        // We're finished
183
+        mapi_freebusysupport_close($fbsupport);
184
+    }
185 185
 
186
-	/**
187
-	 * Sorts by timestamp, if equal, then end before start.
188
-	 *
189
-	 * @param mixed $a
190
-	 * @param mixed $b
191
-	 */
192
-	public function cmp($a, $b) {
193
-		if ($a["time"] != $b["time"]) {
194
-			return $a["time"] > $b["time"] ? 1 : -1;
195
-		}
196
-		if ($a["type"] < $b["type"]) {
197
-			return 1;
198
-		}
199
-		if ($a["type"] > $b["type"]) {
200
-			return -1;
201
-		}
186
+    /**
187
+     * Sorts by timestamp, if equal, then end before start.
188
+     *
189
+     * @param mixed $a
190
+     * @param mixed $b
191
+     */
192
+    public function cmp($a, $b) {
193
+        if ($a["time"] != $b["time"]) {
194
+            return $a["time"] > $b["time"] ? 1 : -1;
195
+        }
196
+        if ($a["type"] < $b["type"]) {
197
+            return 1;
198
+        }
199
+        if ($a["type"] > $b["type"]) {
200
+            return -1;
201
+        }
202 202
 
203
-		return 0;
204
-	}
203
+        return 0;
204
+    }
205 205
 
206
-	/**
207
-	 * Function mergeItems.
208
-	 *
209
-	 * @author Steve Hardy
210
-	 *
211
-	 * @param mixed $items
212
-	 */
213
-	public function mergeItemsFB($items) {
214
-		$merged = [];
215
-		$timestamps = [];
216
-		$csubj = [];
217
-		$cbusy = [];
218
-		$level = 0;
219
-		$laststart = null;
206
+    /**
207
+     * Function mergeItems.
208
+     *
209
+     * @author Steve Hardy
210
+     *
211
+     * @param mixed $items
212
+     */
213
+    public function mergeItemsFB($items) {
214
+        $merged = [];
215
+        $timestamps = [];
216
+        $csubj = [];
217
+        $cbusy = [];
218
+        $level = 0;
219
+        $laststart = null;
220 220
 
221
-		foreach ($items as $item) {
222
-			$ts["type"] = 0;
223
-			$ts["time"] = $item[$this->proptags["startdate"]];
224
-			$ts["subject"] = $item[PR_SUBJECT];
225
-			$ts["status"] = isset($item[$this->proptags["busystatus"]]) ? $item[$this->proptags["busystatus"]] : 0; // ZP-197
226
-			$timestamps[] = $ts;
221
+        foreach ($items as $item) {
222
+            $ts["type"] = 0;
223
+            $ts["time"] = $item[$this->proptags["startdate"]];
224
+            $ts["subject"] = $item[PR_SUBJECT];
225
+            $ts["status"] = isset($item[$this->proptags["busystatus"]]) ? $item[$this->proptags["busystatus"]] : 0; // ZP-197
226
+            $timestamps[] = $ts;
227 227
 
228
-			$ts["type"] = 1;
229
-			$ts["time"] = $item[$this->proptags["duedate"]];
230
-			$ts["subject"] = $item[PR_SUBJECT];
231
-			$ts["status"] = isset($item[$this->proptags["busystatus"]]) ? $item[$this->proptags["busystatus"]] : 0; // ZP-197
232
-			$timestamps[] = $ts;
233
-		}
228
+            $ts["type"] = 1;
229
+            $ts["time"] = $item[$this->proptags["duedate"]];
230
+            $ts["subject"] = $item[PR_SUBJECT];
231
+            $ts["status"] = isset($item[$this->proptags["busystatus"]]) ? $item[$this->proptags["busystatus"]] : 0; // ZP-197
232
+            $timestamps[] = $ts;
233
+        }
234 234
 
235
-		usort($timestamps, [$this, "cmp"]);
236
-		$laststart = 0;
235
+        usort($timestamps, [$this, "cmp"]);
236
+        $laststart = 0;
237 237
 
238
-		foreach ($timestamps as $ts) {
239
-			switch ($ts["type"]) {
240
-		case 0: // Start
241
-				if ($level != 0 && $laststart != $ts["time"]) {
242
-					$newitem["start"] = $laststart;
243
-					$newitem["end"] = $ts["time"];
244
-					$newitem["subject"] = join(",", $csubj);
245
-					$newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
246
-					if ($newitem["status"] > 0) {
247
-						$merged[] = $newitem;
248
-					}
249
-				}
250
-			++$level;
251
-			$csubj[] = $ts["subject"];
252
-			$cbusy[] = $ts["status"];
253
-			$laststart = $ts["time"];
254
-			break;
238
+        foreach ($timestamps as $ts) {
239
+            switch ($ts["type"]) {
240
+        case 0: // Start
241
+                if ($level != 0 && $laststart != $ts["time"]) {
242
+                    $newitem["start"] = $laststart;
243
+                    $newitem["end"] = $ts["time"];
244
+                    $newitem["subject"] = join(",", $csubj);
245
+                    $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
246
+                    if ($newitem["status"] > 0) {
247
+                        $merged[] = $newitem;
248
+                    }
249
+                }
250
+            ++$level;
251
+            $csubj[] = $ts["subject"];
252
+            $cbusy[] = $ts["status"];
253
+            $laststart = $ts["time"];
254
+            break;
255 255
 
256
-		case 1: // End
257
-			if ($laststart != $ts["time"]) {
258
-				$newitem["start"] = $laststart;
259
-				$newitem["end"] = $ts["time"];
260
-				$newitem["subject"] = join(",", $csubj);
261
-				$newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
262
-				if ($newitem["status"] > 0) {
263
-					$merged[] = $newitem;
264
-				}
265
-			}
266
-			--$level;
267
-			array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1);
268
-			array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1);
269
-			$laststart = $ts["time"];
270
-			break;
271
-			}
272
-		}
256
+        case 1: // End
257
+            if ($laststart != $ts["time"]) {
258
+                $newitem["start"] = $laststart;
259
+                $newitem["end"] = $ts["time"];
260
+                $newitem["subject"] = join(",", $csubj);
261
+                $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
262
+                if ($newitem["status"] > 0) {
263
+                    $merged[] = $newitem;
264
+                }
265
+            }
266
+            --$level;
267
+            array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1);
268
+            array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1);
269
+            $laststart = $ts["time"];
270
+            break;
271
+            }
272
+        }
273 273
 
274
-		return $merged;
275
-	}
274
+        return $merged;
275
+    }
276 276
 }
Please login to merge, or discard this patch.
mapi/class.taskrecurrence.php 1 patch
Indentation   +403 added lines, -403 removed lines patch added patch discarded remove patch
@@ -5,409 +5,409 @@
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020 grommunio GmbH
6 6
  */
7 7
 
8
-	class TaskRecurrence extends BaseRecurrence {
9
-		/**
10
-		 * Timezone info which is always false for task.
11
-		 */
12
-		public $tz = false;
13
-
14
-		public function __construct($store, $message) {
15
-			$this->store = $store;
16
-			$this->message = $message;
17
-
18
-			$properties = [];
19
-			$properties["entryid"] = PR_ENTRYID;
20
-			$properties["parent_entryid"] = PR_PARENT_ENTRYID;
21
-			$properties["icon_index"] = PR_ICON_INDEX;
22
-			$properties["message_class"] = PR_MESSAGE_CLASS;
23
-			$properties["message_flags"] = PR_MESSAGE_FLAGS;
24
-			$properties["subject"] = PR_SUBJECT;
25
-			$properties["importance"] = PR_IMPORTANCE;
26
-			$properties["sensitivity"] = PR_SENSITIVITY;
27
-			$properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
28
-			$properties["status"] = "PT_LONG:PSETID_Task:0x8101";
29
-			$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
30
-			$properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
31
-			$properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
32
-			$properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
33
-			$properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
34
-			$properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
35
-			$properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
36
-			$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
37
-			$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
38
-			$properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
39
-			$properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
40
-			$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
41
-			$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
42
-
43
-			$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
44
-			$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
45
-			$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
46
-
47
-			$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
48
-			$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
49
-			$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
50
-			$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
51
-
52
-			$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
53
-			$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
54
-			$properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
55
-			$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
56
-			$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
57
-			$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
58
-			$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
59
-
60
-			$this->proptags = getPropIdsFromStrings($store, $properties);
61
-
62
-			parent::__construct($store, $message, $properties);
63
-		}
64
-
65
-		/**
66
-		 * Function which saves recurrence and also regenerates task if necessary.
67
-		 *
68
-		 *@param array $recur new recurrence properties
69
-		 *
70
-		 *@return array of properties of regenerated task else false
71
-		 */
72
-		public function setRecurrence(&$recur) {
73
-			$this->recur = $recur;
74
-			$this->action = &$recur;
75
-
76
-			if (!isset($this->recur["changed_occurences"])) {
77
-				$this->recur["changed_occurences"] = [];
78
-			}
79
-
80
-			if (!isset($this->recur["deleted_occurences"])) {
81
-				$this->recur["deleted_occurences"] = [];
82
-			}
83
-
84
-			if (!isset($this->recur['startocc'])) {
85
-				$this->recur['startocc'] = 0;
86
-			}
87
-			if (!isset($this->recur['endocc'])) {
88
-				$this->recur['endocc'] = 0;
89
-			}
90
-
91
-			// Save recurrence because we need proper startrecurrdate and endrecurrdate
92
-			$this->saveRecurrence();
93
-
94
-			// Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence
95
-			$msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]);
96
-			$recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
97
-			foreach ($recurring_data as $key => $value) {
98
-				$this->recur[$key] = $value;
99
-			}
100
-
101
-			$this->setFirstOccurrence();
102
-
103
-			// Let's see if next occurrence has to be generated
104
-			return $this->moveToNextOccurrence();
105
-		}
106
-
107
-		/**
108
-		 * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence.
109
-		 */
110
-		public function setFirstOccurrence() {
111
-			// Check if it is already the first occurrence
112
-			if ($this->action['start'] == $this->recur["start"]) {
113
-				return;
114
-			}
115
-			$items = $this->getNextOccurrence();
116
-
117
-			$props = [];
118
-			$props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
119
-			$props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
120
-
121
-			$props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
122
-			$props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
123
-
124
-			mapi_setprops($this->message, $props);
125
-		}
126
-
127
-		/**
128
-		 * Function which creates new task as current occurrence and moves the
129
-		 * existing task to next occurrence.
130
-		 *
131
-		 *@param array $recur $action from client
132
-		 *
133
-		 *@return bool if moving to next occurrence succeed then it returns
134
-		 *		properties of either newly created task or existing task ELSE
135
-		 *		false because that was last occurrence
136
-		 */
137
-		public function moveToNextOccurrence() {
138
-			$result = false;
139
-			/*
8
+    class TaskRecurrence extends BaseRecurrence {
9
+        /**
10
+         * Timezone info which is always false for task.
11
+         */
12
+        public $tz = false;
13
+
14
+        public function __construct($store, $message) {
15
+            $this->store = $store;
16
+            $this->message = $message;
17
+
18
+            $properties = [];
19
+            $properties["entryid"] = PR_ENTRYID;
20
+            $properties["parent_entryid"] = PR_PARENT_ENTRYID;
21
+            $properties["icon_index"] = PR_ICON_INDEX;
22
+            $properties["message_class"] = PR_MESSAGE_CLASS;
23
+            $properties["message_flags"] = PR_MESSAGE_FLAGS;
24
+            $properties["subject"] = PR_SUBJECT;
25
+            $properties["importance"] = PR_IMPORTANCE;
26
+            $properties["sensitivity"] = PR_SENSITIVITY;
27
+            $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
28
+            $properties["status"] = "PT_LONG:PSETID_Task:0x8101";
29
+            $properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
30
+            $properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
31
+            $properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
32
+            $properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
33
+            $properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
34
+            $properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
35
+            $properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
36
+            $properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
37
+            $properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
38
+            $properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
39
+            $properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
40
+            $properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
41
+            $properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
42
+
43
+            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
44
+            $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
45
+            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
46
+
47
+            $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
48
+            $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
49
+            $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
50
+            $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
51
+
52
+            $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
53
+            $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
54
+            $properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
55
+            $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
56
+            $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
57
+            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
58
+            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
59
+
60
+            $this->proptags = getPropIdsFromStrings($store, $properties);
61
+
62
+            parent::__construct($store, $message, $properties);
63
+        }
64
+
65
+        /**
66
+         * Function which saves recurrence and also regenerates task if necessary.
67
+         *
68
+         *@param array $recur new recurrence properties
69
+         *
70
+         *@return array of properties of regenerated task else false
71
+         */
72
+        public function setRecurrence(&$recur) {
73
+            $this->recur = $recur;
74
+            $this->action = &$recur;
75
+
76
+            if (!isset($this->recur["changed_occurences"])) {
77
+                $this->recur["changed_occurences"] = [];
78
+            }
79
+
80
+            if (!isset($this->recur["deleted_occurences"])) {
81
+                $this->recur["deleted_occurences"] = [];
82
+            }
83
+
84
+            if (!isset($this->recur['startocc'])) {
85
+                $this->recur['startocc'] = 0;
86
+            }
87
+            if (!isset($this->recur['endocc'])) {
88
+                $this->recur['endocc'] = 0;
89
+            }
90
+
91
+            // Save recurrence because we need proper startrecurrdate and endrecurrdate
92
+            $this->saveRecurrence();
93
+
94
+            // Update $this->recur with proper startrecurrdate and endrecurrdate updated after saving recurrence
95
+            $msgProps = mapi_getprops($this->message, [$this->proptags['recurring_data']]);
96
+            $recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
97
+            foreach ($recurring_data as $key => $value) {
98
+                $this->recur[$key] = $value;
99
+            }
100
+
101
+            $this->setFirstOccurrence();
102
+
103
+            // Let's see if next occurrence has to be generated
104
+            return $this->moveToNextOccurrence();
105
+        }
106
+
107
+        /**
108
+         * Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence.
109
+         */
110
+        public function setFirstOccurrence() {
111
+            // Check if it is already the first occurrence
112
+            if ($this->action['start'] == $this->recur["start"]) {
113
+                return;
114
+            }
115
+            $items = $this->getNextOccurrence();
116
+
117
+            $props = [];
118
+            $props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
119
+            $props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
120
+
121
+            $props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
122
+            $props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
123
+
124
+            mapi_setprops($this->message, $props);
125
+        }
126
+
127
+        /**
128
+         * Function which creates new task as current occurrence and moves the
129
+         * existing task to next occurrence.
130
+         *
131
+         *@param array $recur $action from client
132
+         *
133
+         *@return bool if moving to next occurrence succeed then it returns
134
+         *		properties of either newly created task or existing task ELSE
135
+         *		false because that was last occurrence
136
+         */
137
+        public function moveToNextOccurrence() {
138
+            $result = false;
139
+            /*
140 140
 			 * Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
141 141
 			 * then we create first two occurrence separately and for first occurrence recurrence has ended.
142 142
 			 */
143
-			if ((empty($this->action['startdate']) && empty($this->action['duedate'])) ||
144
-				($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) {
145
-				$nextOccurrence = $this->getNextOccurrence();
146
-				$result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
147
-
148
-				$props = [];
149
-				if ($nextOccurrence) {
150
-					if (!isset($this->action['deleteOccurrence'])) {
151
-						// Create current occurrence as separate task
152
-						$result = $this->regenerateTask($this->action['complete']);
153
-					}
154
-
155
-					// Set reminder for next occurrence
156
-					$this->setReminder($nextOccurrence);
157
-
158
-					// Update properties for next occurrence
159
-					$this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
160
-					$this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
161
-
162
-					$this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
163
-					$this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
164
-
165
-					// If current task as been mark as 'Complete' then next occurrence should be incomplete.
166
-					if (isset($this->action['complete']) && $this->action['complete'] == 1) {
167
-						$this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
168
-						$this->action['complete'] = $props[$this->proptags["complete"]] = false;
169
-						$this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
170
-					}
171
-
172
-					$props[$this->proptags["dead_occurrence"]] = false;
173
-				}
174
-				else {
175
-					if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
176
-						return false;
177
-					}
178
-
179
-					// Didn't get next occurrence, probably this is the last one, so recurrence ends here
180
-					$props[$this->proptags["dead_occurrence"]] = true;
181
-					$props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
182
-					$props[$this->proptags["task_f_creator"]] = true;
183
-
184
-					// OL props
185
-					$props[$this->proptags["side_effects"]] = 1296;
186
-					$props[$this->proptags["icon_index"]] = 1280;
187
-				}
188
-
189
-				mapi_setprops($this->message, $props);
190
-			}
191
-
192
-			return $result;
193
-		}
194
-
195
-		/**
196
-		 * Function which return properties of next occurrence.
197
-		 *
198
-		 *@return array startdate/enddate of next occurrence
199
-		 */
200
-		public function getNextOccurrence() {
201
-			if ($this->recur) {
202
-				$items = [];
203
-
204
-				// @TODO: fix start of range
205
-				$start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
206
-				$dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]);
207
-
208
-				// Fix recur object
209
-				$this->recur['startocc'] = 0;
210
-				$this->recur['endocc'] = 0;
211
-
212
-				// Retrieve next occurrence
213
-				$items = $this->getItems($start, $dayend, 1);
214
-
215
-				return !empty($items) ? $items[0] : false;
216
-			}
217
-		}
218
-
219
-		/**
220
-		 * Function which clones current occurrence and sets appropriate properties.
221
-		 * The original recurring item is moved to next occurrence.
222
-		 *
223
-		 *@param bool $markComplete true if existing occurrence has to be mark complete else false
224
-		 */
225
-		public function regenerateTask($markComplete) {
226
-			// Get all properties
227
-			$taskItemProps = mapi_getprops($this->message);
228
-
229
-			if (isset($this->action["subject"])) {
230
-				$taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
231
-			}
232
-			if (isset($this->action["importance"])) {
233
-				$taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
234
-			}
235
-			if (isset($this->action["startdate"])) {
236
-				$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
237
-				$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
238
-			}
239
-			if (isset($this->action["duedate"])) {
240
-				$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
241
-				$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
242
-			}
243
-
244
-			$folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
245
-			$newMessage = mapi_folder_createmessage($folder);
246
-
247
-			$taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
248
-			$taskItemProps[$this->proptags["complete"]] = $markComplete;
249
-			$taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
250
-
251
-			// This occurrence has been marked as 'Complete' so disable reminder
252
-			if ($markComplete) {
253
-				$taskItemProps[$this->proptags["reset_reminder"]] = false;
254
-				$taskItemProps[$this->proptags["reminder"]] = false;
255
-				$taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
256
-
257
-				unset($this->action[$this->proptags['datecompleted']]);
258
-			}
259
-
260
-			// Recurrence ends for this item
261
-			$taskItemProps[$this->proptags["dead_occurrence"]] = true;
262
-			$taskItemProps[$this->proptags["task_f_creator"]] = true;
263
-
264
-			// OL props
265
-			$taskItemProps[$this->proptags["side_effects"]] = 1296;
266
-			$taskItemProps[$this->proptags["icon_index"]] = 1280;
267
-
268
-			// Copy recipients
269
-			$recipienttable = mapi_message_getrecipienttable($this->message);
270
-			$recipients = mapi_table_queryallrows($recipienttable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID]);
271
-
272
-			$copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
273
-			$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
274
-			foreach ($copy_to_recipientRows as $recipient) {
275
-				mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]);
276
-			}
277
-			mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
278
-
279
-			// Copy attachments
280
-			$attachmentTable = mapi_message_getattachmenttable($this->message);
281
-			if ($attachmentTable) {
282
-				$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
283
-
284
-				foreach ($attachments as $attach_props) {
285
-					$attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
286
-					$attach_newResourceMsg = mapi_message_createattach($newMessage);
287
-
288
-					mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
289
-					mapi_savechanges($attach_newResourceMsg);
290
-				}
291
-			}
292
-
293
-			mapi_setprops($newMessage, $taskItemProps);
294
-			mapi_savechanges($newMessage);
295
-
296
-			// Update body of original message
297
-			$msgbody = mapi_openproperty($this->message, PR_BODY);
298
-			$msgbody = trim($msgbody, "\0");
299
-			$separator = "------------\r\n";
300
-
301
-			if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
302
-				$msgbody = $separator . $msgbody;
303
-				$stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
304
-				mapi_stream_setsize($stream, strlen($msgbody));
305
-				mapi_stream_write($stream, $msgbody);
306
-				mapi_stream_commit($stream);
307
-			}
308
-
309
-			// We need these properties to notify client
310
-			return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
311
-		}
312
-
313
-		/**
314
-		 * processOccurrenceItem, adds an item to a list of occurrences, but only if the
315
-		 * resulting occurrence starts or ends in the interval <$start, $end>.
316
-		 *
317
-		 * @param array $items    reference to the array to be added to
318
-		 * @param date  $start    start of timeframe in GMT TIME
319
-		 * @param date  $end      end of timeframe in GMT TIME
320
-		 * @param date  $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
321
-		 * @param mixed $now
322
-		 */
323
-		public function processOccurrenceItem(&$items, $start, $end, $now) {
324
-			if ($now <= $start) {
325
-				return;
326
-			}
327
-			$newItem = [];
328
-			$newItem[$this->proptags['startdate']] = $now;
329
-
330
-			// If startdate and enddate are set on task, then slide enddate according to duration
331
-			if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) {
332
-				$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
333
-			}
334
-			else {
335
-				$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
336
-			}
337
-			$items[] = $newItem;
338
-		}
339
-
340
-		/**
341
-		 * Function which marks existing occurrence to 'Complete'.
342
-		 *
343
-		 *@param array $recur array action from client
344
-		 *
345
-		 *@return array of properties of regenerated task else false
346
-		 */
347
-		public function markOccurrenceComplete(&$recur) {
348
-			// Fix timezone object
349
-			$this->tz = false;
350
-			$this->action = &$recur;
351
-			$dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
352
-
353
-			if (!$dead_occurrence) {
354
-				return $this->moveToNextOccurrence();
355
-			}
356
-
357
-			return false;
358
-		}
359
-
360
-		/**
361
-		 * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
362
-		 *
363
-		 *@param array $nextOccurrence properties of next occurrence
364
-		 */
365
-		public function setReminder($nextOccurrence) {
366
-			$props = [];
367
-			if ($nextOccurrence) {
368
-				// Check if reminder is reset. Default is 'false'
369
-				$reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
370
-				$reminder = $this->messageprops[$this->proptags['reminder']];
371
-
372
-				// Either reminder was already set OR reminder was set but was dismissed bty user
373
-				if ($reminder || $reset_reminder) {
374
-					// Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
375
-					$reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
376
-					$reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
377
-					$reminder_difference = $reminder_difference - $reminder_time;
378
-
379
-					// Apply duration to next calculated duedate
380
-					$next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
381
-
382
-					$props[$this->proptags['reminder_time']] = $next_reminder_time;
383
-					$props[$this->proptags['flagdueby']] = $next_reminder_time;
384
-					$this->action['reminder'] = $props[$this->proptags['reminder']] = true;
385
-				}
386
-			}
387
-			else {
388
-				// Didn't get next occurrence, probably this is the last occurrence
389
-				$props[$this->proptags['reminder']] = false;
390
-				$props[$this->proptags['reset_reminder']] = false;
391
-			}
392
-
393
-			if (!empty($props)) {
394
-				mapi_setprops($this->message, $props);
395
-			}
396
-		}
397
-
398
-		/**
399
-		 * Function which recurring task to next occurrence.
400
-		 * It simply doesn't regenerate task.
401
-		 *
402
-		 * @param array $action
403
-		 */
404
-		public function deleteOccurrence($action) {
405
-			$this->tz = false;
406
-			$this->action = $action;
407
-			$result = $this->moveToNextOccurrence();
408
-
409
-			mapi_savechanges($this->message);
410
-
411
-			return $result;
412
-		}
413
-	}
143
+            if ((empty($this->action['startdate']) && empty($this->action['duedate'])) ||
144
+                ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])) {
145
+                $nextOccurrence = $this->getNextOccurrence();
146
+                $result = mapi_getprops($this->message, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
147
+
148
+                $props = [];
149
+                if ($nextOccurrence) {
150
+                    if (!isset($this->action['deleteOccurrence'])) {
151
+                        // Create current occurrence as separate task
152
+                        $result = $this->regenerateTask($this->action['complete']);
153
+                    }
154
+
155
+                    // Set reminder for next occurrence
156
+                    $this->setReminder($nextOccurrence);
157
+
158
+                    // Update properties for next occurrence
159
+                    $this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
160
+                    $this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
161
+
162
+                    $this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
163
+                    $this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
164
+
165
+                    // If current task as been mark as 'Complete' then next occurrence should be incomplete.
166
+                    if (isset($this->action['complete']) && $this->action['complete'] == 1) {
167
+                        $this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
168
+                        $this->action['complete'] = $props[$this->proptags["complete"]] = false;
169
+                        $this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
170
+                    }
171
+
172
+                    $props[$this->proptags["dead_occurrence"]] = false;
173
+                }
174
+                else {
175
+                    if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence']) {
176
+                        return false;
177
+                    }
178
+
179
+                    // Didn't get next occurrence, probably this is the last one, so recurrence ends here
180
+                    $props[$this->proptags["dead_occurrence"]] = true;
181
+                    $props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
182
+                    $props[$this->proptags["task_f_creator"]] = true;
183
+
184
+                    // OL props
185
+                    $props[$this->proptags["side_effects"]] = 1296;
186
+                    $props[$this->proptags["icon_index"]] = 1280;
187
+                }
188
+
189
+                mapi_setprops($this->message, $props);
190
+            }
191
+
192
+            return $result;
193
+        }
194
+
195
+        /**
196
+         * Function which return properties of next occurrence.
197
+         *
198
+         *@return array startdate/enddate of next occurrence
199
+         */
200
+        public function getNextOccurrence() {
201
+            if ($this->recur) {
202
+                $items = [];
203
+
204
+                // @TODO: fix start of range
205
+                $start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
206
+                $dayend = ($this->recur['term'] == 0x23) ? 0x7FFFFFFF : $this->dayStartOf($this->recur["end"]);
207
+
208
+                // Fix recur object
209
+                $this->recur['startocc'] = 0;
210
+                $this->recur['endocc'] = 0;
211
+
212
+                // Retrieve next occurrence
213
+                $items = $this->getItems($start, $dayend, 1);
214
+
215
+                return !empty($items) ? $items[0] : false;
216
+            }
217
+        }
218
+
219
+        /**
220
+         * Function which clones current occurrence and sets appropriate properties.
221
+         * The original recurring item is moved to next occurrence.
222
+         *
223
+         *@param bool $markComplete true if existing occurrence has to be mark complete else false
224
+         */
225
+        public function regenerateTask($markComplete) {
226
+            // Get all properties
227
+            $taskItemProps = mapi_getprops($this->message);
228
+
229
+            if (isset($this->action["subject"])) {
230
+                $taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
231
+            }
232
+            if (isset($this->action["importance"])) {
233
+                $taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
234
+            }
235
+            if (isset($this->action["startdate"])) {
236
+                $taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
237
+                $taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
238
+            }
239
+            if (isset($this->action["duedate"])) {
240
+                $taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
241
+                $taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
242
+            }
243
+
244
+            $folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
245
+            $newMessage = mapi_folder_createmessage($folder);
246
+
247
+            $taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
248
+            $taskItemProps[$this->proptags["complete"]] = $markComplete;
249
+            $taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
250
+
251
+            // This occurrence has been marked as 'Complete' so disable reminder
252
+            if ($markComplete) {
253
+                $taskItemProps[$this->proptags["reset_reminder"]] = false;
254
+                $taskItemProps[$this->proptags["reminder"]] = false;
255
+                $taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
256
+
257
+                unset($this->action[$this->proptags['datecompleted']]);
258
+            }
259
+
260
+            // Recurrence ends for this item
261
+            $taskItemProps[$this->proptags["dead_occurrence"]] = true;
262
+            $taskItemProps[$this->proptags["task_f_creator"]] = true;
263
+
264
+            // OL props
265
+            $taskItemProps[$this->proptags["side_effects"]] = 1296;
266
+            $taskItemProps[$this->proptags["icon_index"]] = 1280;
267
+
268
+            // Copy recipients
269
+            $recipienttable = mapi_message_getrecipienttable($this->message);
270
+            $recipients = mapi_table_queryallrows($recipienttable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID]);
271
+
272
+            $copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
273
+            $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
274
+            foreach ($copy_to_recipientRows as $recipient) {
275
+                mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, [$recipient]);
276
+            }
277
+            mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
278
+
279
+            // Copy attachments
280
+            $attachmentTable = mapi_message_getattachmenttable($this->message);
281
+            if ($attachmentTable) {
282
+                $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
283
+
284
+                foreach ($attachments as $attach_props) {
285
+                    $attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
286
+                    $attach_newResourceMsg = mapi_message_createattach($newMessage);
287
+
288
+                    mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
289
+                    mapi_savechanges($attach_newResourceMsg);
290
+                }
291
+            }
292
+
293
+            mapi_setprops($newMessage, $taskItemProps);
294
+            mapi_savechanges($newMessage);
295
+
296
+            // Update body of original message
297
+            $msgbody = mapi_openproperty($this->message, PR_BODY);
298
+            $msgbody = trim($msgbody, "\0");
299
+            $separator = "------------\r\n";
300
+
301
+            if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
302
+                $msgbody = $separator . $msgbody;
303
+                $stream = mapi_openproperty($this->message, PR_BODY, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
304
+                mapi_stream_setsize($stream, strlen($msgbody));
305
+                mapi_stream_write($stream, $msgbody);
306
+                mapi_stream_commit($stream);
307
+            }
308
+
309
+            // We need these properties to notify client
310
+            return mapi_getprops($newMessage, [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID]);
311
+        }
312
+
313
+        /**
314
+         * processOccurrenceItem, adds an item to a list of occurrences, but only if the
315
+         * resulting occurrence starts or ends in the interval <$start, $end>.
316
+         *
317
+         * @param array $items    reference to the array to be added to
318
+         * @param date  $start    start of timeframe in GMT TIME
319
+         * @param date  $end      end of timeframe in GMT TIME
320
+         * @param date  $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
321
+         * @param mixed $now
322
+         */
323
+        public function processOccurrenceItem(&$items, $start, $end, $now) {
324
+            if ($now <= $start) {
325
+                return;
326
+            }
327
+            $newItem = [];
328
+            $newItem[$this->proptags['startdate']] = $now;
329
+
330
+            // If startdate and enddate are set on task, then slide enddate according to duration
331
+            if (isset($this->messageprops[$this->proptags["startdate"]], $this->messageprops[$this->proptags["duedate"]])) {
332
+                $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
333
+            }
334
+            else {
335
+                $newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
336
+            }
337
+            $items[] = $newItem;
338
+        }
339
+
340
+        /**
341
+         * Function which marks existing occurrence to 'Complete'.
342
+         *
343
+         *@param array $recur array action from client
344
+         *
345
+         *@return array of properties of regenerated task else false
346
+         */
347
+        public function markOccurrenceComplete(&$recur) {
348
+            // Fix timezone object
349
+            $this->tz = false;
350
+            $this->action = &$recur;
351
+            $dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
352
+
353
+            if (!$dead_occurrence) {
354
+                return $this->moveToNextOccurrence();
355
+            }
356
+
357
+            return false;
358
+        }
359
+
360
+        /**
361
+         * Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
362
+         *
363
+         *@param array $nextOccurrence properties of next occurrence
364
+         */
365
+        public function setReminder($nextOccurrence) {
366
+            $props = [];
367
+            if ($nextOccurrence) {
368
+                // Check if reminder is reset. Default is 'false'
369
+                $reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
370
+                $reminder = $this->messageprops[$this->proptags['reminder']];
371
+
372
+                // Either reminder was already set OR reminder was set but was dismissed bty user
373
+                if ($reminder || $reset_reminder) {
374
+                    // Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
375
+                    $reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
376
+                    $reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
377
+                    $reminder_difference = $reminder_difference - $reminder_time;
378
+
379
+                    // Apply duration to next calculated duedate
380
+                    $next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
381
+
382
+                    $props[$this->proptags['reminder_time']] = $next_reminder_time;
383
+                    $props[$this->proptags['flagdueby']] = $next_reminder_time;
384
+                    $this->action['reminder'] = $props[$this->proptags['reminder']] = true;
385
+                }
386
+            }
387
+            else {
388
+                // Didn't get next occurrence, probably this is the last occurrence
389
+                $props[$this->proptags['reminder']] = false;
390
+                $props[$this->proptags['reset_reminder']] = false;
391
+            }
392
+
393
+            if (!empty($props)) {
394
+                mapi_setprops($this->message, $props);
395
+            }
396
+        }
397
+
398
+        /**
399
+         * Function which recurring task to next occurrence.
400
+         * It simply doesn't regenerate task.
401
+         *
402
+         * @param array $action
403
+         */
404
+        public function deleteOccurrence($action) {
405
+            $this->tz = false;
406
+            $this->action = $action;
407
+            $result = $this->moveToNextOccurrence();
408
+
409
+            mapi_savechanges($this->message);
410
+
411
+            return $result;
412
+        }
413
+    }
Please login to merge, or discard this patch.
mapi/class.recurrence.php 1 patch
Indentation   +1232 added lines, -1232 removed lines patch added patch discarded remove patch
@@ -5,14 +5,14 @@  discard block
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020 grommunio GmbH
6 6
  */
7 7
 
8
-	/**
9
-	 * Recurrence.
10
-	 *
11
-	 * @author Steve Hardy <[email protected]>
12
-	 * @author Michel de Ron <[email protected]>
13
-	 */
14
-	class Recurrence extends BaseRecurrence {
15
-		/*
8
+    /**
9
+     * Recurrence.
10
+     *
11
+     * @author Steve Hardy <[email protected]>
12
+     * @author Michel de Ron <[email protected]>
13
+     */
14
+    class Recurrence extends BaseRecurrence {
15
+        /*
16 16
 		 * ABOUT TIMEZONES
17 17
 		 *
18 18
 		 * Timezones are rather complicated here so here are some rules to think about:
@@ -24,1241 +24,1241 @@  discard block
 block discarded – undo
24 24
 		 *   always in LOCAL time.
25 25
 		 */
26 26
 
27
-		// All properties for a recipient that are interesting
28
-		public $recipprops = [
29
-			PR_ENTRYID,
30
-			PR_SEARCH_KEY,
31
-			PR_DISPLAY_NAME,
32
-			PR_EMAIL_ADDRESS,
33
-			PR_RECIPIENT_ENTRYID,
34
-			PR_RECIPIENT_TYPE,
35
-			PR_SEND_INTERNET_ENCODING,
36
-			PR_SEND_RICH_INFO,
37
-			PR_RECIPIENT_DISPLAY_NAME,
38
-			PR_ADDRTYPE,
39
-			PR_DISPLAY_TYPE,
40
-			PR_DISPLAY_TYPE_EX,
41
-			PR_RECIPIENT_TRACKSTATUS,
42
-			PR_RECIPIENT_TRACKSTATUS_TIME,
43
-			PR_RECIPIENT_FLAGS,
44
-			PR_ROWID,
45
-		];
46
-
47
-		/**
48
-		 * @param resource $store   MAPI Message Store Object
49
-		 * @param resource $message the MAPI (appointment) message
50
-		 */
51
-		public function __construct($store, $message) {
52
-			$properties = [];
53
-			$properties["entryid"] = PR_ENTRYID;
54
-			$properties["parent_entryid"] = PR_PARENT_ENTRYID;
55
-			$properties["message_class"] = PR_MESSAGE_CLASS;
56
-			$properties["icon_index"] = PR_ICON_INDEX;
57
-			$properties["subject"] = PR_SUBJECT;
58
-			$properties["display_to"] = PR_DISPLAY_TO;
59
-			$properties["importance"] = PR_IMPORTANCE;
60
-			$properties["sensitivity"] = PR_SENSITIVITY;
61
-			$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
62
-			$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
63
-			$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
64
-			$properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
65
-			$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
66
-			$properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
67
-			$properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
68
-			$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
69
-			$properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
70
-			$properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
71
-			$properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
72
-			$properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
73
-			$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
74
-			$properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
75
-			$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
76
-			$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
77
-			$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
78
-			$properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231";
79
-			$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
80
-			$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
81
-			$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
82
-			$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
83
-			$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
84
-			$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
85
-			$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
86
-			$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
87
-			$properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
88
-			$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
89
-			$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
90
-			$properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514";
91
-
92
-			$this->proptags = getPropIdsFromStrings($store, $properties);
93
-
94
-			parent::__construct($store, $message);
95
-		}
96
-
97
-		/**
98
-		 * Create an exception.
99
-		 *
100
-		 * @param array        $exception_props  the exception properties (same properties as normal recurring items)
101
-		 * @param date         $base_date        the base date of the exception (LOCAL time of non-exception occurrence)
102
-		 * @param bool         $delete           true - delete occurrence, false - create new exception or modify existing
103
-		 * @param array        $exception_recips true - delete occurrence, false - create new exception or modify existing
104
-		 * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
105
-		 */
106
-		public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false) {
107
-			$baseday = $this->dayStartOf($base_date);
108
-			$basetime = $baseday + $this->recur["startocc"] * 60;
109
-
110
-			// Remove any pre-existing exception on this base date
111
-			if ($this->isException($baseday)) {
112
-				$this->deleteException($baseday);
113
-			} // note that deleting an exception is different from creating a deleted exception (deleting an occurrence).
114
-
115
-			if (!$delete) {
116
-				if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
117
-					return false;
118
-				}
119
-				// Properties in the attachment are the properties of the base object, plus $exception_props plus the base date
120
-				foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) {
121
-					if (isset($this->messageprops[$this->proptags[$propname]])) {
122
-						$props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]];
123
-					}
124
-				}
125
-
126
-				$props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
127
-				unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]);
128
-
129
-				$props = $exception_props + $props;
130
-
131
-				// Basedate in the exception attachment is the GMT time at which the original occurrence would have been
132
-				$props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime);
133
-
134
-				if (!isset($exception_props[$this->proptags["startdate"]])) {
135
-					$props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date);
136
-				}
137
-				if (!isset($exception_props[$this->proptags["duedate"]])) {
138
-					$props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date);
139
-				}
140
-
141
-				// synchronize commonstart/commonend with startdate/duedate
142
-				if (isset($props[$this->proptags["startdate"]])) {
143
-					$props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]];
144
-				}
145
-				if (isset($props[$this->proptags["duedate"]])) {
146
-					$props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]];
147
-				}
148
-
149
-				// Save the data into an attachment
150
-				$this->createExceptionAttachment($props, $exception_recips, $copy_attach_from);
151
-
152
-				$changed_item = [];
153
-
154
-				$changed_item["basedate"] = $baseday;
155
-				$changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]);
156
-				$changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]);
157
-
158
-				if (array_key_exists($this->proptags["subject"], $exception_props)) {
159
-					$changed_item["subject"] = $exception_props[$this->proptags["subject"]];
160
-				}
161
-				if (array_key_exists($this->proptags["location"], $exception_props)) {
162
-					$changed_item["location"] = $exception_props[$this->proptags["location"]];
163
-				}
164
-				if (array_key_exists($this->proptags["label"], $exception_props)) {
165
-					$changed_item["label"] = $exception_props[$this->proptags["label"]];
166
-				}
167
-				if (array_key_exists($this->proptags["reminder"], $exception_props)) {
168
-					$changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]];
169
-				}
170
-				if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
171
-					$changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
172
-				}
173
-				if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
174
-					$changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
175
-				}
176
-				if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
177
-					$changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]];
178
-				}
179
-
180
-				// Add the changed occurrence to the list
181
-				array_push($this->recur["changed_occurrences"], $changed_item);
182
-			}
183
-			else {
184
-				// Delete the occurrence by placing it in the deleted occurrences list
185
-				array_push($this->recur["deleted_occurrences"], $baseday);
186
-			}
187
-
188
-			// Turn on hideattachments, because the attachments in this item are the exceptions
189
-			mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]);
190
-
191
-			// Save recurrence data to message
192
-			$this->saveRecurrence();
193
-
194
-			return true;
195
-		}
196
-
197
-		/**
198
-		 * Modifies an existing exception, but only updates the given properties
199
-		 * NOTE: You can't remove properties from an exception, only add new ones.
200
-		 *
201
-		 * @param mixed $exception_props
202
-		 * @param mixed $base_date
203
-		 * @param mixed $exception_recips
204
-		 * @param mixed $copy_attach_from
205
-		 */
206
-		public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false) {
207
-			if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
208
-				return false;
209
-			}
210
-
211
-			$baseday = $this->dayStartOf($base_date);
212
-			$basetime = $baseday + $this->recur["startocc"] * 60;
213
-			$extomodify = false;
214
-
215
-			for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) {
216
-				if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) {
217
-					$extomodify = &$this->recur["changed_occurrences"][$i];
218
-				}
219
-			}
220
-
221
-			if (!$extomodify) {
222
-				return false;
223
-			}
224
-
225
-			// remove basedate property as we want to preserve the old value
226
-			// client will send basedate with time part as zero, so discard that value
227
-			unset($exception_props[$this->proptags["basedate"]]);
228
-
229
-			if (array_key_exists($this->proptags["startdate"], $exception_props)) {
230
-				$extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
231
-			}
232
-			if (array_key_exists($this->proptags["duedate"], $exception_props)) {
233
-				$extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
234
-			}
235
-			if (array_key_exists($this->proptags["subject"], $exception_props)) {
236
-				$extomodify["subject"] = $exception_props[$this->proptags["subject"]];
237
-			}
238
-			if (array_key_exists($this->proptags["location"], $exception_props)) {
239
-				$extomodify["location"] = $exception_props[$this->proptags["location"]];
240
-			}
241
-			if (array_key_exists($this->proptags["label"], $exception_props)) {
242
-				$extomodify["label"] = $exception_props[$this->proptags["label"]];
243
-			}
244
-			if (array_key_exists($this->proptags["reminder"], $exception_props)) {
245
-				$extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]];
246
-			}
247
-			if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
248
-				$extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
249
-			}
250
-			if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
251
-				$extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
252
-			}
253
-			if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
254
-				$extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]];
255
-			}
256
-
257
-			$exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
258
-
259
-			// synchronize commonstart/commonend with startdate/duedate
260
-			if (isset($exception_props[$this->proptags["startdate"]])) {
261
-				$exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]];
262
-			}
263
-			if (isset($exception_props[$this->proptags["duedate"]])) {
264
-				$exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]];
265
-			}
266
-
267
-			$attach = $this->getExceptionAttachment($baseday);
268
-			if (!$attach) {
269
-				if ($copy_attach_from) {
270
-					$this->deleteExceptionAttachment($base_date);
271
-					$this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
272
-				}
273
-				else {
274
-					$this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
275
-				}
276
-			}
277
-			else {
278
-				$message = mapi_attach_openobj($attach, MAPI_MODIFY);
279
-
280
-				// Set exception properties on embedded message and save
281
-				mapi_setprops($message, $exception_props);
282
-				$this->setExceptionRecipients($message, $exception_recips, false);
283
-				mapi_savechanges($message);
284
-
285
-				// If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME'
286
-				// on the attachment which holds the embedded msg and save everything.
287
-				$props = [];
288
-				if (isset($exception_props[$this->proptags["startdate"]])) {
289
-					$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
290
-				}
291
-				if (isset($exception_props[$this->proptags["duedate"]])) {
292
-					$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
293
-				}
294
-				if (!empty($props)) {
295
-					mapi_setprops($attach, $props);
296
-				}
297
-				mapi_savechanges($attach);
298
-			}
299
-
300
-			// Save recurrence data to message
301
-			$this->saveRecurrence();
302
-
303
-			return true;
304
-		}
305
-
306
-		// Checks to see if the following is true:
307
-		// 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration)
308
-		// 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!)
309
-		//
310
-		// Both $basedate and $start are in LOCAL time
311
-		public function isValidExceptionDate($basedate, $start) {
312
-			// The way we do this is to look at the days that we're 'moving' the item in the exception. Each
313
-			// of these days may only contain the item that we're modifying. Any other item violates the rules.
314
-
315
-			if ($this->isException($basedate)) {
316
-				// If we're modifying an exception, we want to look at the days that we're 'moving' compared to where
317
-				// the exception used to be.
318
-				$oldexception = $this->getChangeException($basedate);
319
-				$prevday = $this->dayStartOf($oldexception["start"]);
320
-			}
321
-			else {
322
-				// If its a new exception, we want to look at the original placement of this item.
323
-				$prevday = $basedate;
324
-			}
325
-
326
-			$startday = $this->dayStartOf($start);
327
-
328
-			// Get all the occurrences on the days between the basedate (may be reversed)
329
-			if ($prevday < $startday) {
330
-				$items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
331
-			}
332
-			else {
333
-				$items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
334
-			}
335
-
336
-			// There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range,
337
-			// then we abort the change, since one of the rules has been violated.
338
-			return count($items) == 1;
339
-		}
340
-
341
-		/**
342
-		 * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:.
343
-		 *
344
-		 * Both must be true:
345
-		 * - reminder time of this item is not before the starttime of the previous recurring item
346
-		 * - reminder time of the next item is not before the starttime of this item
347
-		 *
348
-		 * @param date   $basedate        the base date of the exception (LOCAL time of non-exception occurrence)
349
-		 * @param string $reminderminutes reminder minutes which is set of the item
350
-		 * @param date   $startdate       the startdate of the selected item
351
-		 * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE)
352
-		 */
353
-		public function isValidReminderTime($basedate, $reminderminutes, $startdate) {
354
-			$isreminderrangeset = false;
355
-
356
-			// get all occurrence items before the seleceted items occurrence starttime
357
-			$occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
358
-
359
-			if (!empty($occitems)) {
360
-				// as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems .
361
-				$previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
362
-
363
-				// if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
364
-				if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) {
365
-					return false;
366
-				}
367
-			}
368
-
369
-			// Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
370
-			$currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
371
-
372
-			// If there are another two occurrences, then the first is the current occurrence, and the one after that
373
-			// is the next occurrence.
374
-			if (count($currentOcc) > 1) {
375
-				$next = $currentOcc[1];
376
-				// Get reminder time of the next occurrence.
377
-				$nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
378
-				// If the reminder time of the next item is before the start of this item, then that's not allowed
379
-				if ($nextOccReminderTime <= $startdate) {
380
-					return false;
381
-				}
382
-			}
383
-
384
-			// All was ok
385
-			return true;
386
-		}
387
-
388
-		public function setRecurrence($tz, $recur) {
389
-			// only reset timezone if specified
390
-			if ($tz) {
391
-				$this->tz = $tz;
392
-			}
393
-
394
-			$this->recur = $recur;
395
-
396
-			if (!isset($this->recur["changed_occurrences"])) {
397
-				$this->recur["changed_occurrences"] = [];
398
-			}
399
-
400
-			if (!isset($this->recur["deleted_occurrences"])) {
401
-				$this->recur["deleted_occurrences"] = [];
402
-			}
403
-
404
-			$this->deleteAttachments();
405
-			$this->saveRecurrence();
406
-
407
-			// if client has not set the recurring_pattern then we should generate it and save it
408
-			$messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
409
-			if (empty($messageProps[$this->proptags["recurring_pattern"]])) {
410
-				$this->saveRecurrencePattern();
411
-			}
412
-		}
413
-
414
-		// Returns the start or end time of the occurrence on the given base date.
415
-		// This assumes that the basedate you supply is in LOCAL time
416
-		public function getOccurrenceStart($basedate) {
417
-			$daystart = $this->dayStartOf($basedate);
418
-
419
-			return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
420
-		}
421
-
422
-		public function getOccurrenceEnd($basedate) {
423
-			$daystart = $this->dayStartOf($basedate);
424
-
425
-			return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
426
-		}
427
-
428
-		// Backwards compatible code
429
-		public function getOccurenceStart($basedate) {
430
-			return $this->getOccurrenceStart($basedate);
431
-		}
432
-
433
-		public function getOccurenceEnd($basedate) {
434
-			return $this->getOccurrenceEnd($basedate);
435
-		}
436
-
437
-		/**
438
-		 * This function returns the next remindertime starting from $timestamp
439
-		 * When no next reminder exists, false is returned.
440
-		 *
441
-		 * Note: Before saving this new reminder time (when snoozing), you must check for
442
-		 *       yourself if this reminder time is earlier than your snooze time, else
443
-		 *       use your snooze time and not this reminder time.
444
-		 *
445
-		 * @param mixed $timestamp
446
-		 */
447
-		public function getNextReminderTime($timestamp) {
448
-			/**
449
-			 * Get next item from now until forever, but max 1 item with reminder set
450
-			 * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT....
451
-			 * Here for getting next 10 occurrences assuming that next here we will be able to find
452
-			 * nextreminder occurrence in 10 occureneces.
453
-			 */
454
-			$items = $this->getItems($timestamp, 0x7FF00000, 10, true);
455
-
456
-			// Initially setting nextreminder to false so when no next reminder exists, false is returned.
457
-			$nextreminder = false;
458
-			/*
27
+        // All properties for a recipient that are interesting
28
+        public $recipprops = [
29
+            PR_ENTRYID,
30
+            PR_SEARCH_KEY,
31
+            PR_DISPLAY_NAME,
32
+            PR_EMAIL_ADDRESS,
33
+            PR_RECIPIENT_ENTRYID,
34
+            PR_RECIPIENT_TYPE,
35
+            PR_SEND_INTERNET_ENCODING,
36
+            PR_SEND_RICH_INFO,
37
+            PR_RECIPIENT_DISPLAY_NAME,
38
+            PR_ADDRTYPE,
39
+            PR_DISPLAY_TYPE,
40
+            PR_DISPLAY_TYPE_EX,
41
+            PR_RECIPIENT_TRACKSTATUS,
42
+            PR_RECIPIENT_TRACKSTATUS_TIME,
43
+            PR_RECIPIENT_FLAGS,
44
+            PR_ROWID,
45
+        ];
46
+
47
+        /**
48
+         * @param resource $store   MAPI Message Store Object
49
+         * @param resource $message the MAPI (appointment) message
50
+         */
51
+        public function __construct($store, $message) {
52
+            $properties = [];
53
+            $properties["entryid"] = PR_ENTRYID;
54
+            $properties["parent_entryid"] = PR_PARENT_ENTRYID;
55
+            $properties["message_class"] = PR_MESSAGE_CLASS;
56
+            $properties["icon_index"] = PR_ICON_INDEX;
57
+            $properties["subject"] = PR_SUBJECT;
58
+            $properties["display_to"] = PR_DISPLAY_TO;
59
+            $properties["importance"] = PR_IMPORTANCE;
60
+            $properties["sensitivity"] = PR_SENSITIVITY;
61
+            $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
62
+            $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
63
+            $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
64
+            $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
65
+            $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
66
+            $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
67
+            $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
68
+            $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
69
+            $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
70
+            $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
71
+            $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
72
+            $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
73
+            $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
74
+            $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
75
+            $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
76
+            $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
77
+            $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
78
+            $properties["recurrencetype"] = "PT_LONG:PSETID_Appointment:0x8231";
79
+            $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
80
+            $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
81
+            $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
82
+            $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
83
+            $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
84
+            $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
85
+            $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
86
+            $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
87
+            $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
88
+            $properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
89
+            $properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
90
+            $properties["hideattachments"] = "PT_BOOLEAN:PSETID_Common:0x8514";
91
+
92
+            $this->proptags = getPropIdsFromStrings($store, $properties);
93
+
94
+            parent::__construct($store, $message);
95
+        }
96
+
97
+        /**
98
+         * Create an exception.
99
+         *
100
+         * @param array        $exception_props  the exception properties (same properties as normal recurring items)
101
+         * @param date         $base_date        the base date of the exception (LOCAL time of non-exception occurrence)
102
+         * @param bool         $delete           true - delete occurrence, false - create new exception or modify existing
103
+         * @param array        $exception_recips true - delete occurrence, false - create new exception or modify existing
104
+         * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
105
+         */
106
+        public function createException($exception_props, $base_date, $delete = false, $exception_recips = [], $copy_attach_from = false) {
107
+            $baseday = $this->dayStartOf($base_date);
108
+            $basetime = $baseday + $this->recur["startocc"] * 60;
109
+
110
+            // Remove any pre-existing exception on this base date
111
+            if ($this->isException($baseday)) {
112
+                $this->deleteException($baseday);
113
+            } // note that deleting an exception is different from creating a deleted exception (deleting an occurrence).
114
+
115
+            if (!$delete) {
116
+                if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
117
+                    return false;
118
+                }
119
+                // Properties in the attachment are the properties of the base object, plus $exception_props plus the base date
120
+                foreach (["subject", "location", "label", "reminder", "reminder_minutes", "alldayevent", "busystatus"] as $propname) {
121
+                    if (isset($this->messageprops[$this->proptags[$propname]])) {
122
+                        $props[$this->proptags[$propname]] = $this->messageprops[$this->proptags[$propname]];
123
+                    }
124
+                }
125
+
126
+                $props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
127
+                unset($exception_props[PR_MESSAGE_CLASS], $exception_props[PR_ICON_INDEX]);
128
+
129
+                $props = $exception_props + $props;
130
+
131
+                // Basedate in the exception attachment is the GMT time at which the original occurrence would have been
132
+                $props[$this->proptags["basedate"]] = $this->toGMT($this->tz, $basetime);
133
+
134
+                if (!isset($exception_props[$this->proptags["startdate"]])) {
135
+                    $props[$this->proptags["startdate"]] = $this->getOccurrenceStart($base_date);
136
+                }
137
+                if (!isset($exception_props[$this->proptags["duedate"]])) {
138
+                    $props[$this->proptags["duedate"]] = $this->getOccurrenceEnd($base_date);
139
+                }
140
+
141
+                // synchronize commonstart/commonend with startdate/duedate
142
+                if (isset($props[$this->proptags["startdate"]])) {
143
+                    $props[$this->proptags["commonstart"]] = $props[$this->proptags["startdate"]];
144
+                }
145
+                if (isset($props[$this->proptags["duedate"]])) {
146
+                    $props[$this->proptags["commonend"]] = $props[$this->proptags["duedate"]];
147
+                }
148
+
149
+                // Save the data into an attachment
150
+                $this->createExceptionAttachment($props, $exception_recips, $copy_attach_from);
151
+
152
+                $changed_item = [];
153
+
154
+                $changed_item["basedate"] = $baseday;
155
+                $changed_item["start"] = $this->fromGMT($this->tz, $props[$this->proptags["startdate"]]);
156
+                $changed_item["end"] = $this->fromGMT($this->tz, $props[$this->proptags["duedate"]]);
157
+
158
+                if (array_key_exists($this->proptags["subject"], $exception_props)) {
159
+                    $changed_item["subject"] = $exception_props[$this->proptags["subject"]];
160
+                }
161
+                if (array_key_exists($this->proptags["location"], $exception_props)) {
162
+                    $changed_item["location"] = $exception_props[$this->proptags["location"]];
163
+                }
164
+                if (array_key_exists($this->proptags["label"], $exception_props)) {
165
+                    $changed_item["label"] = $exception_props[$this->proptags["label"]];
166
+                }
167
+                if (array_key_exists($this->proptags["reminder"], $exception_props)) {
168
+                    $changed_item["reminder_set"] = $exception_props[$this->proptags["reminder"]];
169
+                }
170
+                if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
171
+                    $changed_item["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
172
+                }
173
+                if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
174
+                    $changed_item["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
175
+                }
176
+                if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
177
+                    $changed_item["busystatus"] = $exception_props[$this->proptags["busystatus"]];
178
+                }
179
+
180
+                // Add the changed occurrence to the list
181
+                array_push($this->recur["changed_occurrences"], $changed_item);
182
+            }
183
+            else {
184
+                // Delete the occurrence by placing it in the deleted occurrences list
185
+                array_push($this->recur["deleted_occurrences"], $baseday);
186
+            }
187
+
188
+            // Turn on hideattachments, because the attachments in this item are the exceptions
189
+            mapi_setprops($this->message, [$this->proptags["hideattachments"] => true]);
190
+
191
+            // Save recurrence data to message
192
+            $this->saveRecurrence();
193
+
194
+            return true;
195
+        }
196
+
197
+        /**
198
+         * Modifies an existing exception, but only updates the given properties
199
+         * NOTE: You can't remove properties from an exception, only add new ones.
200
+         *
201
+         * @param mixed $exception_props
202
+         * @param mixed $base_date
203
+         * @param mixed $exception_recips
204
+         * @param mixed $copy_attach_from
205
+         */
206
+        public function modifyException($exception_props, $base_date, $exception_recips = [], $copy_attach_from = false) {
207
+            if (isset($exception_props[$this->proptags["startdate"]]) && !$this->isValidExceptionDate($base_date, $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]))) {
208
+                return false;
209
+            }
210
+
211
+            $baseday = $this->dayStartOf($base_date);
212
+            $basetime = $baseday + $this->recur["startocc"] * 60;
213
+            $extomodify = false;
214
+
215
+            for ($i = 0, $len = count($this->recur["changed_occurrences"]); $i < $len; ++$i) {
216
+                if ($this->isSameDay($this->recur["changed_occurrences"][$i]["basedate"], $baseday)) {
217
+                    $extomodify = &$this->recur["changed_occurrences"][$i];
218
+                }
219
+            }
220
+
221
+            if (!$extomodify) {
222
+                return false;
223
+            }
224
+
225
+            // remove basedate property as we want to preserve the old value
226
+            // client will send basedate with time part as zero, so discard that value
227
+            unset($exception_props[$this->proptags["basedate"]]);
228
+
229
+            if (array_key_exists($this->proptags["startdate"], $exception_props)) {
230
+                $extomodify["start"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
231
+            }
232
+            if (array_key_exists($this->proptags["duedate"], $exception_props)) {
233
+                $extomodify["end"] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
234
+            }
235
+            if (array_key_exists($this->proptags["subject"], $exception_props)) {
236
+                $extomodify["subject"] = $exception_props[$this->proptags["subject"]];
237
+            }
238
+            if (array_key_exists($this->proptags["location"], $exception_props)) {
239
+                $extomodify["location"] = $exception_props[$this->proptags["location"]];
240
+            }
241
+            if (array_key_exists($this->proptags["label"], $exception_props)) {
242
+                $extomodify["label"] = $exception_props[$this->proptags["label"]];
243
+            }
244
+            if (array_key_exists($this->proptags["reminder"], $exception_props)) {
245
+                $extomodify["reminder_set"] = $exception_props[$this->proptags["reminder"]];
246
+            }
247
+            if (array_key_exists($this->proptags["reminder_minutes"], $exception_props)) {
248
+                $extomodify["remind_before"] = $exception_props[$this->proptags["reminder_minutes"]];
249
+            }
250
+            if (array_key_exists($this->proptags["alldayevent"], $exception_props)) {
251
+                $extomodify["alldayevent"] = $exception_props[$this->proptags["alldayevent"]];
252
+            }
253
+            if (array_key_exists($this->proptags["busystatus"], $exception_props)) {
254
+                $extomodify["busystatus"] = $exception_props[$this->proptags["busystatus"]];
255
+            }
256
+
257
+            $exception_props[PR_MESSAGE_CLASS] = "IPM.OLE.CLASS.{00061055-0000-0000-C000-000000000046}";
258
+
259
+            // synchronize commonstart/commonend with startdate/duedate
260
+            if (isset($exception_props[$this->proptags["startdate"]])) {
261
+                $exception_props[$this->proptags["commonstart"]] = $exception_props[$this->proptags["startdate"]];
262
+            }
263
+            if (isset($exception_props[$this->proptags["duedate"]])) {
264
+                $exception_props[$this->proptags["commonend"]] = $exception_props[$this->proptags["duedate"]];
265
+            }
266
+
267
+            $attach = $this->getExceptionAttachment($baseday);
268
+            if (!$attach) {
269
+                if ($copy_attach_from) {
270
+                    $this->deleteExceptionAttachment($base_date);
271
+                    $this->createException($exception_props, $base_date, false, $exception_recips, $copy_attach_from);
272
+                }
273
+                else {
274
+                    $this->createExceptionAttachment($exception_props, $exception_recips, $copy_attach_from);
275
+                }
276
+            }
277
+            else {
278
+                $message = mapi_attach_openobj($attach, MAPI_MODIFY);
279
+
280
+                // Set exception properties on embedded message and save
281
+                mapi_setprops($message, $exception_props);
282
+                $this->setExceptionRecipients($message, $exception_recips, false);
283
+                mapi_savechanges($message);
284
+
285
+                // If a new start or duedate is provided, we update the properties 'PR_EXCEPTION_STARTTIME' and 'PR_EXCEPTION_ENDTIME'
286
+                // on the attachment which holds the embedded msg and save everything.
287
+                $props = [];
288
+                if (isset($exception_props[$this->proptags["startdate"]])) {
289
+                    $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
290
+                }
291
+                if (isset($exception_props[$this->proptags["duedate"]])) {
292
+                    $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
293
+                }
294
+                if (!empty($props)) {
295
+                    mapi_setprops($attach, $props);
296
+                }
297
+                mapi_savechanges($attach);
298
+            }
299
+
300
+            // Save recurrence data to message
301
+            $this->saveRecurrence();
302
+
303
+            return true;
304
+        }
305
+
306
+        // Checks to see if the following is true:
307
+        // 1) The exception to be created doesn't create two exceptions starting on one day (however, they can END on the same day by modifying duration)
308
+        // 2) The exception to be created doesn't 'jump' over another occurrence (which may be an exception itself!)
309
+        //
310
+        // Both $basedate and $start are in LOCAL time
311
+        public function isValidExceptionDate($basedate, $start) {
312
+            // The way we do this is to look at the days that we're 'moving' the item in the exception. Each
313
+            // of these days may only contain the item that we're modifying. Any other item violates the rules.
314
+
315
+            if ($this->isException($basedate)) {
316
+                // If we're modifying an exception, we want to look at the days that we're 'moving' compared to where
317
+                // the exception used to be.
318
+                $oldexception = $this->getChangeException($basedate);
319
+                $prevday = $this->dayStartOf($oldexception["start"]);
320
+            }
321
+            else {
322
+                // If its a new exception, we want to look at the original placement of this item.
323
+                $prevday = $basedate;
324
+            }
325
+
326
+            $startday = $this->dayStartOf($start);
327
+
328
+            // Get all the occurrences on the days between the basedate (may be reversed)
329
+            if ($prevday < $startday) {
330
+                $items = $this->getItems($this->toGMT($this->tz, $prevday), $this->toGMT($this->tz, $startday + 24 * 60 * 60));
331
+            }
332
+            else {
333
+                $items = $this->getItems($this->toGMT($this->tz, $startday), $this->toGMT($this->tz, $prevday + 24 * 60 * 60));
334
+            }
335
+
336
+            // There should now be exactly one item, namely the item that we are modifying. If there are any other items in the range,
337
+            // then we abort the change, since one of the rules has been violated.
338
+            return count($items) == 1;
339
+        }
340
+
341
+        /**
342
+         * Check to see if the exception proposed at a certain basedate is allowed concerning reminder times:.
343
+         *
344
+         * Both must be true:
345
+         * - reminder time of this item is not before the starttime of the previous recurring item
346
+         * - reminder time of the next item is not before the starttime of this item
347
+         *
348
+         * @param date   $basedate        the base date of the exception (LOCAL time of non-exception occurrence)
349
+         * @param string $reminderminutes reminder minutes which is set of the item
350
+         * @param date   $startdate       the startdate of the selected item
351
+         * @returns boolean if the reminder minutes value valid (FALSE if either of the rules above are FALSE)
352
+         */
353
+        public function isValidReminderTime($basedate, $reminderminutes, $startdate) {
354
+            $isreminderrangeset = false;
355
+
356
+            // get all occurrence items before the seleceted items occurrence starttime
357
+            $occitems = $this->getItems($this->messageprops[$this->proptags["startdate"]], $this->toGMT($this->tz, $basedate));
358
+
359
+            if (!empty($occitems)) {
360
+                // as occitems array is sorted in ascending order of startdate, to get the previous occurrence we take the last items in occitems .
361
+                $previousitem_startdate = $occitems[count($occitems) - 1][$this->proptags["startdate"]];
362
+
363
+                // if our reminder is set before or equal to the beginning of the previous occurrence, then that's not allowed
364
+                if ($startdate - ($reminderminutes * 60) <= $previousitem_startdate) {
365
+                    return false;
366
+                }
367
+            }
368
+
369
+            // Get the endtime of the current occurrence and find the next two occurrences (including the current occurrence)
370
+            $currentOcc = $this->getItems($this->toGMT($this->tz, $basedate), 0x7FF00000, 2, true);
371
+
372
+            // If there are another two occurrences, then the first is the current occurrence, and the one after that
373
+            // is the next occurrence.
374
+            if (count($currentOcc) > 1) {
375
+                $next = $currentOcc[1];
376
+                // Get reminder time of the next occurrence.
377
+                $nextOccReminderTime = $next[$this->proptags["startdate"]] - ($next[$this->proptags["reminder_minutes"]] * 60);
378
+                // If the reminder time of the next item is before the start of this item, then that's not allowed
379
+                if ($nextOccReminderTime <= $startdate) {
380
+                    return false;
381
+                }
382
+            }
383
+
384
+            // All was ok
385
+            return true;
386
+        }
387
+
388
+        public function setRecurrence($tz, $recur) {
389
+            // only reset timezone if specified
390
+            if ($tz) {
391
+                $this->tz = $tz;
392
+            }
393
+
394
+            $this->recur = $recur;
395
+
396
+            if (!isset($this->recur["changed_occurrences"])) {
397
+                $this->recur["changed_occurrences"] = [];
398
+            }
399
+
400
+            if (!isset($this->recur["deleted_occurrences"])) {
401
+                $this->recur["deleted_occurrences"] = [];
402
+            }
403
+
404
+            $this->deleteAttachments();
405
+            $this->saveRecurrence();
406
+
407
+            // if client has not set the recurring_pattern then we should generate it and save it
408
+            $messageProps = mapi_getprops($this->message, [$this->proptags["recurring_pattern"]]);
409
+            if (empty($messageProps[$this->proptags["recurring_pattern"]])) {
410
+                $this->saveRecurrencePattern();
411
+            }
412
+        }
413
+
414
+        // Returns the start or end time of the occurrence on the given base date.
415
+        // This assumes that the basedate you supply is in LOCAL time
416
+        public function getOccurrenceStart($basedate) {
417
+            $daystart = $this->dayStartOf($basedate);
418
+
419
+            return $this->toGMT($this->tz, $daystart + $this->recur["startocc"] * 60);
420
+        }
421
+
422
+        public function getOccurrenceEnd($basedate) {
423
+            $daystart = $this->dayStartOf($basedate);
424
+
425
+            return $this->toGMT($this->tz, $daystart + $this->recur["endocc"] * 60);
426
+        }
427
+
428
+        // Backwards compatible code
429
+        public function getOccurenceStart($basedate) {
430
+            return $this->getOccurrenceStart($basedate);
431
+        }
432
+
433
+        public function getOccurenceEnd($basedate) {
434
+            return $this->getOccurrenceEnd($basedate);
435
+        }
436
+
437
+        /**
438
+         * This function returns the next remindertime starting from $timestamp
439
+         * When no next reminder exists, false is returned.
440
+         *
441
+         * Note: Before saving this new reminder time (when snoozing), you must check for
442
+         *       yourself if this reminder time is earlier than your snooze time, else
443
+         *       use your snooze time and not this reminder time.
444
+         *
445
+         * @param mixed $timestamp
446
+         */
447
+        public function getNextReminderTime($timestamp) {
448
+            /**
449
+             * Get next item from now until forever, but max 1 item with reminder set
450
+             * Note 0x7ff00000 instead of 0x7fffffff because of possible overflow failures when converting to GMT....
451
+             * Here for getting next 10 occurrences assuming that next here we will be able to find
452
+             * nextreminder occurrence in 10 occureneces.
453
+             */
454
+            $items = $this->getItems($timestamp, 0x7FF00000, 10, true);
455
+
456
+            // Initially setting nextreminder to false so when no next reminder exists, false is returned.
457
+            $nextreminder = false;
458
+            /*
459 459
 			 * Loop through all reminder which we get in items variable
460 460
 			 * and check whether the remindertime is greater than timestamp.
461 461
 			 * On the first occurrence of greater nextreminder break the loop
462 462
 			 * and return the value to calling function.
463 463
 			 */
464
-			for ($i = 0, $len = count($items); $i < $len; ++$i) {
465
-				$item = $items[$i];
466
-				$tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60);
467
-
468
-				// If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
469
-				if ($tempnextreminder > $timestamp) {
470
-					$nextreminder = $tempnextreminder;
471
-
472
-					break;
473
-				}
474
-			}
475
-
476
-			return $nextreminder;
477
-		}
478
-
479
-		/**
480
-		 * Note: Static function, more like a utility function.
481
-		 *
482
-		 * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
483
-		 * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
484
-		 * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
485
-		 * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
486
-		 *
487
-		 * @param $store resource The store in which the calendar resides
488
-		 * @param $calendar resource The calendar to get the items from
489
-		 * @param $viewstart int Timestamp of beginning of view window
490
-		 * @param $viewend int Timestamp of end of view window
491
-		 * @param $propsrequested array Array of properties to return
492
-		 * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
493
-		 *                    expanded so that it seems that there are only many single appointments in the table.
494
-		 */
495
-		public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
496
-			return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
497
-		}
498
-
499
-		/*
464
+            for ($i = 0, $len = count($items); $i < $len; ++$i) {
465
+                $item = $items[$i];
466
+                $tempnextreminder = $item[$this->proptags["startdate"]] - ($item[$this->proptags["reminder_minutes"]] * 60);
467
+
468
+                // If tempnextreminder is greater than timestamp then save it in nextreminder and break from the loop.
469
+                if ($tempnextreminder > $timestamp) {
470
+                    $nextreminder = $tempnextreminder;
471
+
472
+                    break;
473
+                }
474
+            }
475
+
476
+            return $nextreminder;
477
+        }
478
+
479
+        /**
480
+         * Note: Static function, more like a utility function.
481
+         *
482
+         * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
483
+         * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
484
+         * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
485
+         * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
486
+         *
487
+         * @param $store resource The store in which the calendar resides
488
+         * @param $calendar resource The calendar to get the items from
489
+         * @param $viewstart int Timestamp of beginning of view window
490
+         * @param $viewend int Timestamp of end of view window
491
+         * @param $propsrequested array Array of properties to return
492
+         * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
493
+         *                    expanded so that it seems that there are only many single appointments in the table.
494
+         */
495
+        public static function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
496
+            return getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested);
497
+        }
498
+
499
+        /*
500 500
 		 * CODE BELOW THIS LINE IS FOR INTERNAL USE ONLY
501 501
 		 *****************************************************************************************************************
502 502
 		 */
503 503
 
504
-		/**
505
-		 * Generates and stores recurrence pattern string to recurring_pattern property.
506
-		 */
507
-		public function saveRecurrencePattern() {
508
-			// Start formatting the properties in such a way we can apply
509
-			// them directly into the recurrence pattern.
510
-			$type = $this->recur['type'];
511
-			$everyn = $this->recur['everyn'];
512
-			$start = $this->recur['start'];
513
-			$end = $this->recur['end'];
514
-			$term = $this->recur['term'];
515
-			$numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false;
516
-			$startocc = $this->recur['startocc'];
517
-			$endocc = $this->recur['endocc'];
518
-			$pattern = '';
519
-			$occSingleDayRank = false;
520
-			$occTimeRange = ($startocc != 0 && $endocc != 0);
521
-
522
-			switch ($type) {
523
-				// Daily
524
-				case 0x0A:
525
-					if ($everyn == 1) {
526
-						$type = dgettext("kopano", "workday");
527
-						$occSingleDayRank = true;
528
-					}
529
-					elseif ($everyn == (24 * 60)) {
530
-						$type = dgettext("kopano", "day");
531
-						$occSingleDayRank = true;
532
-					}
533
-					else {
534
-						$everyn /= (24 * 60);
535
-						$type = dgettext("kopano", "days");
536
-						$occSingleDayRank = false;
537
-					}
538
-					break;
539
-				// Weekly
540
-				case 0x0B:
541
-					if ($everyn == 1) {
542
-						$type = dgettext("kopano", "week");
543
-						$occSingleDayRank = true;
544
-					}
545
-					else {
546
-						$type = dgettext("kopano", "weeks");
547
-						$occSingleDayRank = false;
548
-					}
549
-					break;
550
-				// Monthly
551
-				case 0x0C:
552
-					if ($everyn == 1) {
553
-						$type = dgettext("kopano", "month");
554
-						$occSingleDayRank = true;
555
-					}
556
-					else {
557
-						$type = dgettext("kopano", "months");
558
-						$occSingleDayRank = false;
559
-					}
560
-					break;
561
-				// Yearly
562
-				case 0x0D:
563
-					if ($everyn <= 12) {
564
-						$everyn = 1;
565
-						$type = dgettext("kopano", "year");
566
-						$occSingleDayRank = true;
567
-					}
568
-					else {
569
-						$everyn = $everyn / 12;
570
-						$type = dgettext("kopano", "years");
571
-						$occSingleDayRank = false;
572
-					}
573
-					break;
574
-			}
575
-
576
-			// get timings of the first occurrence
577
-			$firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
578
-			$firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
579
-
580
-			$start = gmdate(dgettext("kopano", "d-m-Y"), $firstoccstartdate);
581
-			$end = gmdate(dgettext("kopano", "d-m-Y"), $firstoccenddate);
582
-			$startocc = gmdate(dgettext("kopano", "G:i"), $firstoccstartdate);
583
-			$endocc = gmdate(dgettext("kopano", "G:i"), $firstoccenddate);
584
-
585
-			// Based on the properties, we need to generate the recurrence pattern string.
586
-			// This is obviously very easy since we can simply concatenate a bunch of strings,
587
-			// however this messes up translations for languages which order their words
588
-			// differently.
589
-			// To improve translation quality we create a series of default strings, in which
590
-			// we only have to fill in the correct variables. The base string is thus selected
591
-			// based on the available properties.
592
-			if ($term == 0x23) {
593
-				// Never ends
594
-				if ($occTimeRange) {
595
-					if ($occSingleDayRank) {
596
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s from %s to %s."), $type, $start, $startocc, $endocc);
597
-					}
598
-					else {
599
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s from %s to %s."), $everyn, $type, $start, $startocc, $endocc);
600
-					}
601
-				}
602
-				else {
603
-					if ($occSingleDayRank) {
604
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s."), $type, $start);
605
-					}
606
-					else {
607
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s."), $everyn, $type, $start);
608
-					}
609
-				}
610
-			}
611
-			elseif ($term == 0x22) {
612
-				// After a number of times
613
-				if ($occTimeRange) {
614
-					if ($occSingleDayRank) {
615
-						$pattern = sprintf(dngettext(
616
-							"kopano",
617
-							"Occurs every %s effective %s for %s occurrence from %s to %s.",
618
-							"Occurs every %s effective %s for %s occurrences from %s to %s.",
619
-							$numocc
620
-						), $type, $start, $numocc, $startocc, $endocc);
621
-					}
622
-					else {
623
-						$pattern = sprintf(dngettext(
624
-							"kopano",
625
-							"Occurs every %s %s effective %s for %s occurrence from %s to %s.",
626
-							"Occurs every %s %s effective %s for %s occurrences %s to %s.",
627
-							$numocc
628
-						), $everyn, $type, $start, $numocc, $startocc, $endocc);
629
-					}
630
-				}
631
-				else {
632
-					if ($occSingleDayRank) {
633
-						$pattern = sprintf(dngettext(
634
-							"kopano",
635
-							"Occurs every %s effective %s for %s occurrence.",
636
-							"Occurs every %s effective %s for %s occurrences.",
637
-							$numocc
638
-						), $type, $start, $numocc);
639
-					}
640
-					else {
641
-						$pattern = sprintf(dngettext(
642
-							"kopano",
643
-							"Occurs every %s %s effective %s for %s occurrence.",
644
-							"Occurs every %s %s effective %s for %s occurrences.",
645
-							$numocc
646
-						), $everyn, $type, $start, $numocc);
647
-					}
648
-				}
649
-			}
650
-			elseif ($term == 0x21) {
651
-				// After the given enddate
652
-				if ($occTimeRange) {
653
-					if ($occSingleDayRank) {
654
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s until %s from %s to %s."), $type, $start, $end, $startocc, $endocc);
655
-					}
656
-					else {
657
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s until %s from %s to %s."), $everyn, $type, $start, $end, $startocc, $endocc);
658
-					}
659
-				}
660
-				else {
661
-					if ($occSingleDayRank) {
662
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s until %s."), $type, $start, $end);
663
-					}
664
-					else {
665
-						$pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s until %s."), $everyn, $type, $start, $end);
666
-					}
667
-				}
668
-			}
669
-			if (!empty($pattern)) {
670
-				mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
671
-			}
672
-		}
673
-
674
-		/*
504
+        /**
505
+         * Generates and stores recurrence pattern string to recurring_pattern property.
506
+         */
507
+        public function saveRecurrencePattern() {
508
+            // Start formatting the properties in such a way we can apply
509
+            // them directly into the recurrence pattern.
510
+            $type = $this->recur['type'];
511
+            $everyn = $this->recur['everyn'];
512
+            $start = $this->recur['start'];
513
+            $end = $this->recur['end'];
514
+            $term = $this->recur['term'];
515
+            $numocc = isset($this->recur['numoccur']) ? $this->recur['numoccur'] : false;
516
+            $startocc = $this->recur['startocc'];
517
+            $endocc = $this->recur['endocc'];
518
+            $pattern = '';
519
+            $occSingleDayRank = false;
520
+            $occTimeRange = ($startocc != 0 && $endocc != 0);
521
+
522
+            switch ($type) {
523
+                // Daily
524
+                case 0x0A:
525
+                    if ($everyn == 1) {
526
+                        $type = dgettext("kopano", "workday");
527
+                        $occSingleDayRank = true;
528
+                    }
529
+                    elseif ($everyn == (24 * 60)) {
530
+                        $type = dgettext("kopano", "day");
531
+                        $occSingleDayRank = true;
532
+                    }
533
+                    else {
534
+                        $everyn /= (24 * 60);
535
+                        $type = dgettext("kopano", "days");
536
+                        $occSingleDayRank = false;
537
+                    }
538
+                    break;
539
+                // Weekly
540
+                case 0x0B:
541
+                    if ($everyn == 1) {
542
+                        $type = dgettext("kopano", "week");
543
+                        $occSingleDayRank = true;
544
+                    }
545
+                    else {
546
+                        $type = dgettext("kopano", "weeks");
547
+                        $occSingleDayRank = false;
548
+                    }
549
+                    break;
550
+                // Monthly
551
+                case 0x0C:
552
+                    if ($everyn == 1) {
553
+                        $type = dgettext("kopano", "month");
554
+                        $occSingleDayRank = true;
555
+                    }
556
+                    else {
557
+                        $type = dgettext("kopano", "months");
558
+                        $occSingleDayRank = false;
559
+                    }
560
+                    break;
561
+                // Yearly
562
+                case 0x0D:
563
+                    if ($everyn <= 12) {
564
+                        $everyn = 1;
565
+                        $type = dgettext("kopano", "year");
566
+                        $occSingleDayRank = true;
567
+                    }
568
+                    else {
569
+                        $everyn = $everyn / 12;
570
+                        $type = dgettext("kopano", "years");
571
+                        $occSingleDayRank = false;
572
+                    }
573
+                    break;
574
+            }
575
+
576
+            // get timings of the first occurrence
577
+            $firstoccstartdate = isset($startocc) ? $start + (((int) $startocc) * 60) : $start;
578
+            $firstoccenddate = isset($endocc) ? $end + (((int) $endocc) * 60) : $end;
579
+
580
+            $start = gmdate(dgettext("kopano", "d-m-Y"), $firstoccstartdate);
581
+            $end = gmdate(dgettext("kopano", "d-m-Y"), $firstoccenddate);
582
+            $startocc = gmdate(dgettext("kopano", "G:i"), $firstoccstartdate);
583
+            $endocc = gmdate(dgettext("kopano", "G:i"), $firstoccenddate);
584
+
585
+            // Based on the properties, we need to generate the recurrence pattern string.
586
+            // This is obviously very easy since we can simply concatenate a bunch of strings,
587
+            // however this messes up translations for languages which order their words
588
+            // differently.
589
+            // To improve translation quality we create a series of default strings, in which
590
+            // we only have to fill in the correct variables. The base string is thus selected
591
+            // based on the available properties.
592
+            if ($term == 0x23) {
593
+                // Never ends
594
+                if ($occTimeRange) {
595
+                    if ($occSingleDayRank) {
596
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s from %s to %s."), $type, $start, $startocc, $endocc);
597
+                    }
598
+                    else {
599
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s from %s to %s."), $everyn, $type, $start, $startocc, $endocc);
600
+                    }
601
+                }
602
+                else {
603
+                    if ($occSingleDayRank) {
604
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s."), $type, $start);
605
+                    }
606
+                    else {
607
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s."), $everyn, $type, $start);
608
+                    }
609
+                }
610
+            }
611
+            elseif ($term == 0x22) {
612
+                // After a number of times
613
+                if ($occTimeRange) {
614
+                    if ($occSingleDayRank) {
615
+                        $pattern = sprintf(dngettext(
616
+                            "kopano",
617
+                            "Occurs every %s effective %s for %s occurrence from %s to %s.",
618
+                            "Occurs every %s effective %s for %s occurrences from %s to %s.",
619
+                            $numocc
620
+                        ), $type, $start, $numocc, $startocc, $endocc);
621
+                    }
622
+                    else {
623
+                        $pattern = sprintf(dngettext(
624
+                            "kopano",
625
+                            "Occurs every %s %s effective %s for %s occurrence from %s to %s.",
626
+                            "Occurs every %s %s effective %s for %s occurrences %s to %s.",
627
+                            $numocc
628
+                        ), $everyn, $type, $start, $numocc, $startocc, $endocc);
629
+                    }
630
+                }
631
+                else {
632
+                    if ($occSingleDayRank) {
633
+                        $pattern = sprintf(dngettext(
634
+                            "kopano",
635
+                            "Occurs every %s effective %s for %s occurrence.",
636
+                            "Occurs every %s effective %s for %s occurrences.",
637
+                            $numocc
638
+                        ), $type, $start, $numocc);
639
+                    }
640
+                    else {
641
+                        $pattern = sprintf(dngettext(
642
+                            "kopano",
643
+                            "Occurs every %s %s effective %s for %s occurrence.",
644
+                            "Occurs every %s %s effective %s for %s occurrences.",
645
+                            $numocc
646
+                        ), $everyn, $type, $start, $numocc);
647
+                    }
648
+                }
649
+            }
650
+            elseif ($term == 0x21) {
651
+                // After the given enddate
652
+                if ($occTimeRange) {
653
+                    if ($occSingleDayRank) {
654
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s until %s from %s to %s."), $type, $start, $end, $startocc, $endocc);
655
+                    }
656
+                    else {
657
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s until %s from %s to %s."), $everyn, $type, $start, $end, $startocc, $endocc);
658
+                    }
659
+                }
660
+                else {
661
+                    if ($occSingleDayRank) {
662
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s effective %s until %s."), $type, $start, $end);
663
+                    }
664
+                    else {
665
+                        $pattern = sprintf(dgettext("kopano", "Occurs every %s %s effective %s until %s."), $everyn, $type, $start, $end);
666
+                    }
667
+                }
668
+            }
669
+            if (!empty($pattern)) {
670
+                mapi_setprops($this->message, [$this->proptags["recurring_pattern"] => $pattern]);
671
+            }
672
+        }
673
+
674
+        /*
675 675
 		 * Remove an exception by base_date. This is the base date in local daystart time
676 676
 		 */
677
-		public function deleteException($base_date) {
678
-			// Remove all exceptions on $base_date from the deleted and changed occurrences lists
679
-
680
-			// Remove all items in $todelete from deleted_occurrences
681
-			$new = [];
682
-
683
-			foreach ($this->recur["deleted_occurrences"] as $entry) {
684
-				if ($entry != $base_date) {
685
-					$new[] = $entry;
686
-				}
687
-			}
688
-
689
-			$this->recur["deleted_occurrences"] = $new;
690
-
691
-			$new = [];
692
-
693
-			foreach ($this->recur["changed_occurrences"] as $entry) {
694
-				if (!$this->isSameDay($entry["basedate"], $base_date)) {
695
-					$new[] = $entry;
696
-				}
697
-				else {
698
-					$this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
699
-				}
700
-			}
701
-
702
-			$this->recur["changed_occurrences"] = $new;
703
-		}
704
-
705
-		/**
706
-		 * Function which saves the exception data in an attachment.
707
-		 *
708
-		 * @param array        $exception_props  the exception data (like any other MAPI appointment)
709
-		 * @param array        $exception_recips list of recipients
710
-		 * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
711
-		 *
712
-		 * @return array properties of the exception
713
-		 */
714
-		public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) {
715
-			// Create new attachment.
716
-			$attachment = mapi_message_createattach($this->message);
717
-			$props = [];
718
-			$props[PR_ATTACHMENT_FLAGS] = 2;
719
-			$props[PR_ATTACHMENT_HIDDEN] = true;
720
-			$props[PR_ATTACHMENT_LINKID] = 0;
721
-			$props[PR_ATTACH_FLAGS] = 0;
722
-			$props[PR_ATTACH_METHOD] = 5;
723
-			$props[PR_DISPLAY_NAME] = "Exception";
724
-			$props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
725
-			$props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
726
-			mapi_setprops($attachment, $props);
727
-
728
-			$imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
729
-
730
-			if ($copy_attach_from) {
731
-				$attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
732
-				if ($attachmentTable) {
733
-					$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
734
-
735
-					foreach ($attachments as $attach_props) {
736
-						$attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
737
-						$attach_newResourceMsg = mapi_message_createattach($imessage);
738
-						mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
739
-						mapi_savechanges($attach_newResourceMsg);
740
-					}
741
-				}
742
-			}
743
-
744
-			$props = $props + $exception_props;
745
-
746
-			// FIXME: the following piece of code is written to fix the creation
747
-			// of an exception. This is only a quickfix as it is not yet possible
748
-			// to change an existing exception.
749
-			// remove mv properties when needed
750
-			foreach ($props as $propTag => $propVal) {
751
-				if (mapi_prop_type($propTag) & MV_FLAG && is_null($propVal)) {
752
-					unset($props[$propTag]);
753
-				}
754
-			}
755
-
756
-			mapi_setprops($imessage, $props);
757
-
758
-			$this->setExceptionRecipients($imessage, $exception_recips, true);
759
-
760
-			mapi_savechanges($imessage);
761
-			mapi_savechanges($attachment);
762
-		}
763
-
764
-		/**
765
-		 * Function which deletes the attachment of an exception.
766
-		 *
767
-		 * @param date $base_date base date of the attachment. Should be in GMT. The attachment
768
-		 *                        actually saves the real time of the original date, so we have
769
-		 *                        to check whether it's on the same day.
770
-		 */
771
-		public function deleteExceptionAttachment($base_date) {
772
-			$attachments = mapi_message_getattachmenttable($this->message);
773
-			$attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM]);
774
-
775
-			foreach ($attachTable as $attachRow) {
776
-				$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
777
-				$exception = mapi_attach_openobj($tempattach);
778
-
779
-				$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
780
-				if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
781
-					mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
782
-				}
783
-			}
784
-		}
785
-
786
-		/**
787
-		 * Function which deletes all attachments of a message.
788
-		 */
789
-		public function deleteAttachments() {
790
-			$attachments = mapi_message_getattachmenttable($this->message);
791
-			$attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
792
-
793
-			foreach ($attachTable as $attachRow) {
794
-				if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
795
-					mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
796
-				}
797
-			}
798
-		}
799
-
800
-		/**
801
-		 * Get an exception attachment based on its basedate.
802
-		 *
803
-		 * @param mixed $base_date
804
-		 */
805
-		public function getExceptionAttachment($base_date) {
806
-			// Retrieve only embedded messages
807
-			$attach_res = [RES_AND,
808
-				[
809
-					[RES_PROPERTY,
810
-						[RELOP => RELOP_EQ,
811
-							ULPROPTAG => PR_ATTACH_METHOD,
812
-							VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
813
-						],
814
-					],
815
-				],
816
-			];
817
-			$attachments = mapi_message_getattachmenttable($this->message);
818
-			$attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
819
-
820
-			if (is_array($attachRows)) {
821
-				foreach ($attachRows as $attachRow) {
822
-					$tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
823
-					$exception = mapi_attach_openobj($tempattach);
824
-
825
-					$data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
826
-
827
-					if (isset($data[$this->proptags["basedate"]]) && $this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) {
828
-						return $tempattach;
829
-					}
830
-				}
831
-			}
832
-
833
-			return false;
834
-		}
835
-
836
-		/**
837
-		 * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met:
838
-		 * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end>
839
-		 * - The ocurrence isn't specified as a deleted occurrence.
840
-		 *
841
-		 * @param array $items        reference to the array to be added to
842
-		 * @param date  $start        start of timeframe in GMT TIME
843
-		 * @param date  $end          end of timeframe in GMT TIME
844
-		 * @param date  $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
845
-		 * @param int   $startocc     start of occurrence since beginning of day in minutes
846
-		 * @param int   $endocc       end of occurrence since beginning of day in minutes
847
-		 * @param int   $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
848
-		 * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
849
-		 */
850
-		public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) {
851
-			$exception = $this->isException($basedate);
852
-			if ($exception) {
853
-				return false;
854
-			}
855
-			$occstart = $basedate + $startocc * 60;
856
-			$occend = $basedate + $endocc * 60;
857
-
858
-			// Convert to GMT
859
-			$occstart = $this->toGMT($tz, $occstart);
860
-			$occend = $this->toGMT($tz, $occend);
861
-
862
-			/**
863
-			 * FIRST PART : Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
864
-			 * see any part of the appointment. Partial overlaps DO match.
865
-			 *
866
-			 * SECOND PART : check if occurrence is not a zero duration occurrence which
867
-			 * starts at 00:00 and ends on 00:00. if it is so, then process
868
-			 * the occurrence and send it in response.
869
-			 */
870
-			if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) {
871
-				return;
872
-			}
873
-
874
-			// Properties for this occurrence are the same as the main object,
875
-			// With these properties overridden
876
-			$newitem = $this->messageprops;
877
-			$newitem[$this->proptags["startdate"]] = $occstart;
878
-			$newitem[$this->proptags["duedate"]] = $occend;
879
-			$newitem[$this->proptags["commonstart"]] = $occstart;
880
-			$newitem[$this->proptags["commonend"]] = $occend;
881
-			$newitem["basedate"] = $basedate;
882
-
883
-			// If reminderonly is set, only add reminders
884
-			if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) {
885
-				return;
886
-			}
887
-
888
-			$items[] = $newitem;
889
-		}
890
-
891
-		/**
892
-		 * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe.
893
-		 *
894
-		 * @param array $items reference to the array to be added to
895
-		 * @param date  $start start of timeframe in GMT TIME
896
-		 * @param date  $end   end of timeframe in GMT TIME
897
-		 */
898
-		public function processExceptionItems(&$items, $start, $end) {
899
-			$limit = 0;
900
-			foreach ($this->recur["changed_occurrences"] as $exception) {
901
-				// Convert to GMT
902
-				$occstart = $this->toGMT($this->tz, $exception["start"]);
903
-				$occend = $this->toGMT($this->tz, $exception["end"]);
904
-
905
-				// Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
906
-				// see any part of the appointment. Partial overlaps DO match.
907
-				if ($occstart >= $end || $occend <= $start) {
908
-					continue;
909
-				}
910
-
911
-				array_push($items, $this->getExceptionProperties($exception));
912
-				if ($limit && (count($items) == $limit)) {
913
-					break;
914
-				}
915
-			}
916
-		}
917
-
918
-		/**
919
-		 * Function which verifies if on the given date an exception, delete or change, occurs.
920
-		 *
921
-		 * @param date  $date     the date
922
-		 * @param mixed $basedate
923
-		 *
924
-		 * @return array the exception, true - if an occurrence is deleted on the given date, false - no exception occurs on the given date
925
-		 */
926
-		public function isException($basedate) {
927
-			if ($this->isDeleteException($basedate)) {
928
-				return true;
929
-			}
930
-
931
-			if ($this->getChangeException($basedate) != false) {
932
-				return true;
933
-			}
934
-
935
-			return false;
936
-		}
937
-
938
-		/**
939
-		 * Returns TRUE if there is a DELETE exception on the given base date.
940
-		 *
941
-		 * @param mixed $basedate
942
-		 */
943
-		public function isDeleteException($basedate) {
944
-			// Check if the occurrence is deleted on the specified date
945
-			foreach ($this->recur["deleted_occurrences"] as $deleted) {
946
-				if ($this->isSameDay($deleted, $basedate)) {
947
-					return true;
948
-				}
949
-			}
950
-
951
-			return false;
952
-		}
953
-
954
-		/**
955
-		 * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise.
956
-		 *
957
-		 * @param mixed $basedate
958
-		 */
959
-		public function getChangeException($basedate) {
960
-			// Check if the occurrence is modified on the specified date
961
-			foreach ($this->recur["changed_occurrences"] as $changed) {
962
-				if ($this->isSameDay($changed["basedate"], $basedate)) {
963
-					return $changed;
964
-				}
965
-			}
966
-
967
-			return false;
968
-		}
969
-
970
-		/**
971
-		 * Function to see if two dates are on the same day.
972
-		 *
973
-		 * @param date  $time1 date 1
974
-		 * @param date  $time2 date 2
975
-		 * @param mixed $date1
976
-		 * @param mixed $date2
977
-		 *
978
-		 * @return bool Returns TRUE when both dates are on the same day
979
-		 */
980
-		public function isSameDay($date1, $date2) {
981
-			$time1 = $this->gmtime($date1);
982
-			$time2 = $this->gmtime($date2);
983
-
984
-			return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
985
-		}
986
-
987
-		/**
988
-		 * Function to get all properties of a single changed exception.
989
-		 *
990
-		 * @param date  $date      base date of exception
991
-		 * @param mixed $exception
992
-		 *
993
-		 * @return array associative array of properties for the exception, compatible with
994
-		 */
995
-		public function getExceptionProperties($exception) {
996
-			// Exception has same properties as main object, with some properties overridden:
997
-			$item = $this->messageprops;
998
-
999
-			// Special properties
1000
-			$item["exception"] = true;
1001
-			$item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time !
1002
-
1003
-			// MAPI-compatible properties (you can handle an exception as a normal calendar item like this)
1004
-			$item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]);
1005
-			$item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]);
1006
-			$item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]];
1007
-			$item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]];
1008
-
1009
-			if (isset($exception["subject"])) {
1010
-				$item[$this->proptags["subject"]] = $exception["subject"];
1011
-			}
1012
-			if (isset($exception["label"])) {
1013
-				$item[$this->proptags["label"]] = $exception["label"];
1014
-			}
1015
-			if (isset($exception["alldayevent"])) {
1016
-				$item[$this->proptags["alldayevent"]] = $exception["alldayevent"];
1017
-			}
1018
-			if (isset($exception["location"])) {
1019
-				$item[$this->proptags["location"]] = $exception["location"];
1020
-			}
1021
-			if (isset($exception["remind_before"])) {
1022
-				$item[$this->proptags["reminder_minutes"]] = $exception["remind_before"];
1023
-			}
1024
-			if (isset($exception["reminder_set"])) {
1025
-				$item[$this->proptags["reminder"]] = $exception["reminder_set"];
1026
-			}
1027
-			if (isset($exception["busystatus"])) {
1028
-				$item[$this->proptags["busystatus"]] = $exception["busystatus"];
1029
-			}
1030
-
1031
-			return $item;
1032
-		}
1033
-
1034
-		/**
1035
-		 * Function which sets recipients for an exception.
1036
-		 *
1037
-		 * The $exception_recips can be provided in 2 ways:
1038
-		 *  - A delta which indicates which recipients must be added, removed or deleted.
1039
-		 *  - A complete array of the recipients which should be applied to the message.
1040
-		 *
1041
-		 * The first option is preferred as it will require less work to be executed.
1042
-		 *
1043
-		 * @param resource $message          exception attachment of recurring item
1044
-		 * @param array    $exception_recips list of recipients
1045
-		 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1046
-		 *                                   message to the attachment by default. False if only the $exception_recips changes should
1047
-		 *                                   be applied.
1048
-		 */
1049
-		public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) {
1050
-			if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1051
-				$this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1052
-			}
1053
-			else {
1054
-				$this->setAllExceptionRecipients($message, $exception_recips);
1055
-			}
1056
-		}
1057
-
1058
-		/**
1059
-		 * Function which applies the provided delta for recipients changes to the exception.
1060
-		 *
1061
-		 * The $exception_recips should be an array containing the following keys:
1062
-		 *  - "add": this contains an array of recipients which must be added
1063
-		 *  - "remove": This contains an array of recipients which must be removed
1064
-		 *  - "modify": This contains an array of recipients which must be modified
1065
-		 *
1066
-		 * @param resource $message          exception attachment of recurring item
1067
-		 * @param array    $exception_recips list of recipients
1068
-		 * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1069
-		 *                                   message to the attachment by default. False if only the $exception_recips changes should
1070
-		 *                                   be applied.
1071
-		 * @param mixed    $exception
1072
-		 */
1073
-		public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) {
1074
-			// Check if the recipients from the original message should be copied,
1075
-			// if so, open the recipient table of the parent message and apply all
1076
-			// rows on the target recipient.
1077
-			if ($copy_orig_recips === true) {
1078
-				$origTable = mapi_message_getrecipienttable($this->message);
1079
-				$recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
1080
-				mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
1081
-			}
1082
-
1083
-			// Add organizer to meeting only if it is not organized.
1084
-			$msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1085
-			if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1086
-				$this->addOrganizer($msgprops, $exception_recips['add']);
1087
-			}
1088
-
1089
-			// Remove all deleted recipients
1090
-			if (isset($exception_recips['remove'])) {
1091
-				foreach ($exception_recips['remove'] as &$recip) {
1092
-					if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1093
-						$recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1094
-					}
1095
-					else {
1096
-						$recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1097
-					}
1098
-					$recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;		// No Response required
1099
-				}
1100
-				unset($recip);
1101
-				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
1102
-			}
1103
-
1104
-			// Add all new recipients
1105
-			if (isset($exception_recips['add'])) {
1106
-				mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
1107
-			}
1108
-
1109
-			// Modify the existing recipients
1110
-			if (isset($exception_recips['modify'])) {
1111
-				mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
1112
-			}
1113
-		}
1114
-
1115
-		/**
1116
-		 * Function which applies the provided recipients to the exception, also checks for deleted recipients.
1117
-		 *
1118
-		 * The $exception_recips should be an array containing all recipients which must be applied
1119
-		 * to the exception. This will copy all recipients from the original message and then start filter
1120
-		 * out all recipients which are not provided by the $exception_recips list.
1121
-		 *
1122
-		 * @param resource $message          exception attachment of recurring item
1123
-		 * @param array    $exception_recips list of recipients
1124
-		 */
1125
-		public function setAllExceptionRecipients($message, $exception_recips) {
1126
-			$deletedRecipients = [];
1127
-			$useMessageRecipients = false;
1128
-
1129
-			$recipientTable = mapi_message_getrecipienttable($message);
1130
-			$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1131
-
1132
-			if (empty($recipientRows)) {
1133
-				$useMessageRecipients = true;
1134
-				$recipientTable = mapi_message_getrecipienttable($this->message);
1135
-				$recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1136
-			}
1137
-
1138
-			// Add organizer to meeting only if it is not organized.
1139
-			$msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1140
-			if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1141
-				$this->addOrganizer($msgprops, $exception_recips);
1142
-			}
1143
-
1144
-			if (!empty($exception_recips)) {
1145
-				foreach ($recipientRows as $key => $recipient) {
1146
-					$found = false;
1147
-					foreach ($exception_recips as $excep_recip) {
1148
-						if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) {
1149
-							$found = true;
1150
-						}
1151
-					}
1152
-
1153
-					if (!$found) {
1154
-						$foundInDeletedRecipients = false;
1155
-						// Look if the $recipient is in the list of deleted recipients
1156
-						if (!empty($deletedRecipients)) {
1157
-							foreach ($deletedRecipients as $recip) {
1158
-								if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) {
1159
-									$foundInDeletedRecipients = true;
1160
-
1161
-									break;
1162
-								}
1163
-							}
1164
-						}
1165
-
1166
-						// If recipient is not in list of deleted recipient, add him
1167
-						if (!$foundInDeletedRecipients) {
1168
-							if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1169
-								$recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1170
-							}
1171
-							else {
1172
-								$recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1173
-							}
1174
-							$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;	// No Response required
1175
-							$deletedRecipients[] = $recipient;
1176
-						}
1177
-					}
1178
-
1179
-					// When $message contains a non-empty recipienttable, we must delete the recipients
1180
-					// before re-adding them. However, when $message is doesn't contain any recipients,
1181
-					// we are using the recipient table of the original message ($this->message)
1182
-					// rather then $message. In that case, we don't need to remove the recipients
1183
-					// from the $message, as the recipient table is already empty, and
1184
-					// mapi_message_modifyrecipients() will throw an error.
1185
-					if ($useMessageRecipients === false) {
1186
-						mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1187
-					}
1188
-				}
1189
-				$exception_recips = array_merge($exception_recips, $deletedRecipients);
1190
-			}
1191
-			else {
1192
-				$exception_recips = $recipientRows;
1193
-			}
1194
-			if (!empty($exception_recips)) {
1195
-				// Set the new list of recipients on the exception message, this also removes the existing recipients
1196
-				mapi_message_modifyrecipients($message, 0, $exception_recips);
1197
-			}
1198
-		}
1199
-
1200
-		/**
1201
-		 * Function returns basedates of all changed occurrences.
1202
-		 *
1203
-		 *@return array array(
1204
-		 * 0 => 123459321
1205
-		 * )
1206
-		 */
1207
-		public function getAllExceptions() {
1208
-			$result = false;
1209
-			if (!empty($this->recur["changed_occurrences"])) {
1210
-				$result = [];
1211
-				foreach ($this->recur["changed_occurrences"] as $exception) {
1212
-					$result[] = $exception["basedate"];
1213
-				}
1214
-
1215
-				return $result;
1216
-			}
1217
-
1218
-			return $result;
1219
-		}
1220
-
1221
-		/**
1222
-		 *  Function which adds organizer to recipient list which is passed.
1223
-		 *  This function also checks if it has organizer.
1224
-		 *
1225
-		 * @param array $messageProps message properties
1226
-		 * @param array $recipients   recipients list of message
1227
-		 * @param bool  $isException  true if we are processing recipient of exception
1228
-		 */
1229
-		public function addOrganizer($messageProps, &$recipients, $isException = false) {
1230
-			$hasOrganizer = false;
1231
-			// Check if meeting already has an organizer.
1232
-			foreach ($recipients as $key => $recipient) {
1233
-				if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1234
-					$hasOrganizer = true;
1235
-				}
1236
-				elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1237
-					// Recipients for an occurrence
1238
-					$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1239
-				}
1240
-			}
1241
-
1242
-			if (!$hasOrganizer) {
1243
-				// Create organizer.
1244
-				$organizer = [];
1245
-				$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
1246
-				$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1247
-				$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1248
-				$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
1249
-				$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1250
-				$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
1251
-				$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
1252
-				$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1253
-				$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
1254
-
1255
-				// Add organizer to recipients list.
1256
-				array_unshift($recipients, $organizer);
1257
-			}
1258
-		}
1259
-	}
1260
-
1261
-	/*
677
+        public function deleteException($base_date) {
678
+            // Remove all exceptions on $base_date from the deleted and changed occurrences lists
679
+
680
+            // Remove all items in $todelete from deleted_occurrences
681
+            $new = [];
682
+
683
+            foreach ($this->recur["deleted_occurrences"] as $entry) {
684
+                if ($entry != $base_date) {
685
+                    $new[] = $entry;
686
+                }
687
+            }
688
+
689
+            $this->recur["deleted_occurrences"] = $new;
690
+
691
+            $new = [];
692
+
693
+            foreach ($this->recur["changed_occurrences"] as $entry) {
694
+                if (!$this->isSameDay($entry["basedate"], $base_date)) {
695
+                    $new[] = $entry;
696
+                }
697
+                else {
698
+                    $this->deleteExceptionAttachment($this->toGMT($this->tz, $base_date + $this->recur["startocc"] * 60));
699
+                }
700
+            }
701
+
702
+            $this->recur["changed_occurrences"] = $new;
703
+        }
704
+
705
+        /**
706
+         * Function which saves the exception data in an attachment.
707
+         *
708
+         * @param array        $exception_props  the exception data (like any other MAPI appointment)
709
+         * @param array        $exception_recips list of recipients
710
+         * @param mapi_message $copy_attach_from mapi message from which attachments should be copied
711
+         *
712
+         * @return array properties of the exception
713
+         */
714
+        public function createExceptionAttachment($exception_props, $exception_recips = [], $copy_attach_from = false) {
715
+            // Create new attachment.
716
+            $attachment = mapi_message_createattach($this->message);
717
+            $props = [];
718
+            $props[PR_ATTACHMENT_FLAGS] = 2;
719
+            $props[PR_ATTACHMENT_HIDDEN] = true;
720
+            $props[PR_ATTACHMENT_LINKID] = 0;
721
+            $props[PR_ATTACH_FLAGS] = 0;
722
+            $props[PR_ATTACH_METHOD] = 5;
723
+            $props[PR_DISPLAY_NAME] = "Exception";
724
+            $props[PR_EXCEPTION_STARTTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["startdate"]]);
725
+            $props[PR_EXCEPTION_ENDTIME] = $this->fromGMT($this->tz, $exception_props[$this->proptags["duedate"]]);
726
+            mapi_setprops($attachment, $props);
727
+
728
+            $imessage = mapi_attach_openobj($attachment, MAPI_CREATE | MAPI_MODIFY);
729
+
730
+            if ($copy_attach_from) {
731
+                $attachmentTable = mapi_message_getattachmenttable($copy_attach_from);
732
+                if ($attachmentTable) {
733
+                    $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD]);
734
+
735
+                    foreach ($attachments as $attach_props) {
736
+                        $attach_old = mapi_message_openattach($copy_attach_from, (int) $attach_props[PR_ATTACH_NUM]);
737
+                        $attach_newResourceMsg = mapi_message_createattach($imessage);
738
+                        mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
739
+                        mapi_savechanges($attach_newResourceMsg);
740
+                    }
741
+                }
742
+            }
743
+
744
+            $props = $props + $exception_props;
745
+
746
+            // FIXME: the following piece of code is written to fix the creation
747
+            // of an exception. This is only a quickfix as it is not yet possible
748
+            // to change an existing exception.
749
+            // remove mv properties when needed
750
+            foreach ($props as $propTag => $propVal) {
751
+                if (mapi_prop_type($propTag) & MV_FLAG && is_null($propVal)) {
752
+                    unset($props[$propTag]);
753
+                }
754
+            }
755
+
756
+            mapi_setprops($imessage, $props);
757
+
758
+            $this->setExceptionRecipients($imessage, $exception_recips, true);
759
+
760
+            mapi_savechanges($imessage);
761
+            mapi_savechanges($attachment);
762
+        }
763
+
764
+        /**
765
+         * Function which deletes the attachment of an exception.
766
+         *
767
+         * @param date $base_date base date of the attachment. Should be in GMT. The attachment
768
+         *                        actually saves the real time of the original date, so we have
769
+         *                        to check whether it's on the same day.
770
+         */
771
+        public function deleteExceptionAttachment($base_date) {
772
+            $attachments = mapi_message_getattachmenttable($this->message);
773
+            $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM]);
774
+
775
+            foreach ($attachTable as $attachRow) {
776
+                $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
777
+                $exception = mapi_attach_openobj($tempattach);
778
+
779
+                $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
780
+                if ($this->dayStartOf($this->fromGMT($this->tz, $data[$this->proptags["basedate"]])) == $this->dayStartOf($base_date)) {
781
+                    mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
782
+                }
783
+            }
784
+        }
785
+
786
+        /**
787
+         * Function which deletes all attachments of a message.
788
+         */
789
+        public function deleteAttachments() {
790
+            $attachments = mapi_message_getattachmenttable($this->message);
791
+            $attachTable = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM, PR_ATTACHMENT_HIDDEN]);
792
+
793
+            foreach ($attachTable as $attachRow) {
794
+                if (isset($attachRow[PR_ATTACHMENT_HIDDEN]) && $attachRow[PR_ATTACHMENT_HIDDEN]) {
795
+                    mapi_message_deleteattach($this->message, $attachRow[PR_ATTACH_NUM]);
796
+                }
797
+            }
798
+        }
799
+
800
+        /**
801
+         * Get an exception attachment based on its basedate.
802
+         *
803
+         * @param mixed $base_date
804
+         */
805
+        public function getExceptionAttachment($base_date) {
806
+            // Retrieve only embedded messages
807
+            $attach_res = [RES_AND,
808
+                [
809
+                    [RES_PROPERTY,
810
+                        [RELOP => RELOP_EQ,
811
+                            ULPROPTAG => PR_ATTACH_METHOD,
812
+                            VALUE => [PR_ATTACH_METHOD => ATTACH_EMBEDDED_MSG],
813
+                        ],
814
+                    ],
815
+                ],
816
+            ];
817
+            $attachments = mapi_message_getattachmenttable($this->message);
818
+            $attachRows = mapi_table_queryallrows($attachments, [PR_ATTACH_NUM], $attach_res);
819
+
820
+            if (is_array($attachRows)) {
821
+                foreach ($attachRows as $attachRow) {
822
+                    $tempattach = mapi_message_openattach($this->message, $attachRow[PR_ATTACH_NUM]);
823
+                    $exception = mapi_attach_openobj($tempattach);
824
+
825
+                    $data = mapi_message_getprops($exception, [$this->proptags["basedate"]]);
826
+
827
+                    if (isset($data[$this->proptags["basedate"]]) && $this->isSameDay($this->fromGMT($this->tz, $data[$this->proptags["basedate"]]), $base_date)) {
828
+                        return $tempattach;
829
+                    }
830
+                }
831
+            }
832
+
833
+            return false;
834
+        }
835
+
836
+        /**
837
+         * processOccurrenceItem, adds an item to a list of occurrences, but only if the following criteria are met:
838
+         * - The resulting occurrence (or exception) starts or ends in the interval <$start, $end>
839
+         * - The ocurrence isn't specified as a deleted occurrence.
840
+         *
841
+         * @param array $items        reference to the array to be added to
842
+         * @param date  $start        start of timeframe in GMT TIME
843
+         * @param date  $end          end of timeframe in GMT TIME
844
+         * @param date  $basedate     (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
845
+         * @param int   $startocc     start of occurrence since beginning of day in minutes
846
+         * @param int   $endocc       end of occurrence since beginning of day in minutes
847
+         * @param int   $tz           the timezone info for this occurrence ( applied to $basedate / $startocc / $endocc )
848
+         * @param bool  $reminderonly If TRUE, only add the item if the reminder is set
849
+         */
850
+        public function processOccurrenceItem(&$items, $start, $end, $basedate, $startocc, $endocc, $tz, $reminderonly) {
851
+            $exception = $this->isException($basedate);
852
+            if ($exception) {
853
+                return false;
854
+            }
855
+            $occstart = $basedate + $startocc * 60;
856
+            $occend = $basedate + $endocc * 60;
857
+
858
+            // Convert to GMT
859
+            $occstart = $this->toGMT($tz, $occstart);
860
+            $occend = $this->toGMT($tz, $occend);
861
+
862
+            /**
863
+             * FIRST PART : Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
864
+             * see any part of the appointment. Partial overlaps DO match.
865
+             *
866
+             * SECOND PART : check if occurrence is not a zero duration occurrence which
867
+             * starts at 00:00 and ends on 00:00. if it is so, then process
868
+             * the occurrence and send it in response.
869
+             */
870
+            if (($occstart >= $end || $occend <= $start) && !($occstart == $occend && $occstart == $start)) {
871
+                return;
872
+            }
873
+
874
+            // Properties for this occurrence are the same as the main object,
875
+            // With these properties overridden
876
+            $newitem = $this->messageprops;
877
+            $newitem[$this->proptags["startdate"]] = $occstart;
878
+            $newitem[$this->proptags["duedate"]] = $occend;
879
+            $newitem[$this->proptags["commonstart"]] = $occstart;
880
+            $newitem[$this->proptags["commonend"]] = $occend;
881
+            $newitem["basedate"] = $basedate;
882
+
883
+            // If reminderonly is set, only add reminders
884
+            if ($reminderonly && (!isset($newitem[$this->proptags["reminder"]]) || $newitem[$this->proptags["reminder"]] == false)) {
885
+                return;
886
+            }
887
+
888
+            $items[] = $newitem;
889
+        }
890
+
891
+        /**
892
+         * processExceptionItem, adds an all exception item to a list of occurrences, without any constraint on timeframe.
893
+         *
894
+         * @param array $items reference to the array to be added to
895
+         * @param date  $start start of timeframe in GMT TIME
896
+         * @param date  $end   end of timeframe in GMT TIME
897
+         */
898
+        public function processExceptionItems(&$items, $start, $end) {
899
+            $limit = 0;
900
+            foreach ($this->recur["changed_occurrences"] as $exception) {
901
+                // Convert to GMT
902
+                $occstart = $this->toGMT($this->tz, $exception["start"]);
903
+                $occend = $this->toGMT($this->tz, $exception["end"]);
904
+
905
+                // Check range criterium. Exact matches (eg when $occstart == $end), do NOT match since you cannot
906
+                // see any part of the appointment. Partial overlaps DO match.
907
+                if ($occstart >= $end || $occend <= $start) {
908
+                    continue;
909
+                }
910
+
911
+                array_push($items, $this->getExceptionProperties($exception));
912
+                if ($limit && (count($items) == $limit)) {
913
+                    break;
914
+                }
915
+            }
916
+        }
917
+
918
+        /**
919
+         * Function which verifies if on the given date an exception, delete or change, occurs.
920
+         *
921
+         * @param date  $date     the date
922
+         * @param mixed $basedate
923
+         *
924
+         * @return array the exception, true - if an occurrence is deleted on the given date, false - no exception occurs on the given date
925
+         */
926
+        public function isException($basedate) {
927
+            if ($this->isDeleteException($basedate)) {
928
+                return true;
929
+            }
930
+
931
+            if ($this->getChangeException($basedate) != false) {
932
+                return true;
933
+            }
934
+
935
+            return false;
936
+        }
937
+
938
+        /**
939
+         * Returns TRUE if there is a DELETE exception on the given base date.
940
+         *
941
+         * @param mixed $basedate
942
+         */
943
+        public function isDeleteException($basedate) {
944
+            // Check if the occurrence is deleted on the specified date
945
+            foreach ($this->recur["deleted_occurrences"] as $deleted) {
946
+                if ($this->isSameDay($deleted, $basedate)) {
947
+                    return true;
948
+                }
949
+            }
950
+
951
+            return false;
952
+        }
953
+
954
+        /**
955
+         * Returns the exception if there is a CHANGE exception on the given base date, or FALSE otherwise.
956
+         *
957
+         * @param mixed $basedate
958
+         */
959
+        public function getChangeException($basedate) {
960
+            // Check if the occurrence is modified on the specified date
961
+            foreach ($this->recur["changed_occurrences"] as $changed) {
962
+                if ($this->isSameDay($changed["basedate"], $basedate)) {
963
+                    return $changed;
964
+                }
965
+            }
966
+
967
+            return false;
968
+        }
969
+
970
+        /**
971
+         * Function to see if two dates are on the same day.
972
+         *
973
+         * @param date  $time1 date 1
974
+         * @param date  $time2 date 2
975
+         * @param mixed $date1
976
+         * @param mixed $date2
977
+         *
978
+         * @return bool Returns TRUE when both dates are on the same day
979
+         */
980
+        public function isSameDay($date1, $date2) {
981
+            $time1 = $this->gmtime($date1);
982
+            $time2 = $this->gmtime($date2);
983
+
984
+            return $time1["tm_mon"] == $time2["tm_mon"] && $time1["tm_year"] == $time2["tm_year"] && $time1["tm_mday"] == $time2["tm_mday"];
985
+        }
986
+
987
+        /**
988
+         * Function to get all properties of a single changed exception.
989
+         *
990
+         * @param date  $date      base date of exception
991
+         * @param mixed $exception
992
+         *
993
+         * @return array associative array of properties for the exception, compatible with
994
+         */
995
+        public function getExceptionProperties($exception) {
996
+            // Exception has same properties as main object, with some properties overridden:
997
+            $item = $this->messageprops;
998
+
999
+            // Special properties
1000
+            $item["exception"] = true;
1001
+            $item["basedate"] = $exception["basedate"]; // note that the basedate is always in local time !
1002
+
1003
+            // MAPI-compatible properties (you can handle an exception as a normal calendar item like this)
1004
+            $item[$this->proptags["startdate"]] = $this->toGMT($this->tz, $exception["start"]);
1005
+            $item[$this->proptags["duedate"]] = $this->toGMT($this->tz, $exception["end"]);
1006
+            $item[$this->proptags["commonstart"]] = $item[$this->proptags["startdate"]];
1007
+            $item[$this->proptags["commonend"]] = $item[$this->proptags["duedate"]];
1008
+
1009
+            if (isset($exception["subject"])) {
1010
+                $item[$this->proptags["subject"]] = $exception["subject"];
1011
+            }
1012
+            if (isset($exception["label"])) {
1013
+                $item[$this->proptags["label"]] = $exception["label"];
1014
+            }
1015
+            if (isset($exception["alldayevent"])) {
1016
+                $item[$this->proptags["alldayevent"]] = $exception["alldayevent"];
1017
+            }
1018
+            if (isset($exception["location"])) {
1019
+                $item[$this->proptags["location"]] = $exception["location"];
1020
+            }
1021
+            if (isset($exception["remind_before"])) {
1022
+                $item[$this->proptags["reminder_minutes"]] = $exception["remind_before"];
1023
+            }
1024
+            if (isset($exception["reminder_set"])) {
1025
+                $item[$this->proptags["reminder"]] = $exception["reminder_set"];
1026
+            }
1027
+            if (isset($exception["busystatus"])) {
1028
+                $item[$this->proptags["busystatus"]] = $exception["busystatus"];
1029
+            }
1030
+
1031
+            return $item;
1032
+        }
1033
+
1034
+        /**
1035
+         * Function which sets recipients for an exception.
1036
+         *
1037
+         * The $exception_recips can be provided in 2 ways:
1038
+         *  - A delta which indicates which recipients must be added, removed or deleted.
1039
+         *  - A complete array of the recipients which should be applied to the message.
1040
+         *
1041
+         * The first option is preferred as it will require less work to be executed.
1042
+         *
1043
+         * @param resource $message          exception attachment of recurring item
1044
+         * @param array    $exception_recips list of recipients
1045
+         * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1046
+         *                                   message to the attachment by default. False if only the $exception_recips changes should
1047
+         *                                   be applied.
1048
+         */
1049
+        public function setExceptionRecipients($message, $exception_recips, $copy_orig_recips = true) {
1050
+            if (isset($exception_recips['add']) || isset($exception_recips['remove']) || isset($exception_recips['modify'])) {
1051
+                $this->setDeltaExceptionRecipients($message, $exception_recips, $copy_orig_recips);
1052
+            }
1053
+            else {
1054
+                $this->setAllExceptionRecipients($message, $exception_recips);
1055
+            }
1056
+        }
1057
+
1058
+        /**
1059
+         * Function which applies the provided delta for recipients changes to the exception.
1060
+         *
1061
+         * The $exception_recips should be an array containing the following keys:
1062
+         *  - "add": this contains an array of recipients which must be added
1063
+         *  - "remove": This contains an array of recipients which must be removed
1064
+         *  - "modify": This contains an array of recipients which must be modified
1065
+         *
1066
+         * @param resource $message          exception attachment of recurring item
1067
+         * @param array    $exception_recips list of recipients
1068
+         * @param bool     $copy_orig_recips True to copy all recipients which are on the original
1069
+         *                                   message to the attachment by default. False if only the $exception_recips changes should
1070
+         *                                   be applied.
1071
+         * @param mixed    $exception
1072
+         */
1073
+        public function setDeltaExceptionRecipients($exception, $exception_recips, $copy_orig_recips) {
1074
+            // Check if the recipients from the original message should be copied,
1075
+            // if so, open the recipient table of the parent message and apply all
1076
+            // rows on the target recipient.
1077
+            if ($copy_orig_recips === true) {
1078
+                $origTable = mapi_message_getrecipienttable($this->message);
1079
+                $recipientRows = mapi_table_queryallrows($origTable, $this->recipprops);
1080
+                mapi_message_modifyrecipients($exception, MODRECIP_ADD, $recipientRows);
1081
+            }
1082
+
1083
+            // Add organizer to meeting only if it is not organized.
1084
+            $msgprops = mapi_getprops($exception, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1085
+            if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1086
+                $this->addOrganizer($msgprops, $exception_recips['add']);
1087
+            }
1088
+
1089
+            // Remove all deleted recipients
1090
+            if (isset($exception_recips['remove'])) {
1091
+                foreach ($exception_recips['remove'] as &$recip) {
1092
+                    if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recip[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1093
+                        $recip[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1094
+                    }
1095
+                    else {
1096
+                        $recip[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1097
+                    }
1098
+                    $recip[PR_RECIPIENT_TRACKSTATUS] = olResponseNone;		// No Response required
1099
+                }
1100
+                unset($recip);
1101
+                mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['remove']);
1102
+            }
1103
+
1104
+            // Add all new recipients
1105
+            if (isset($exception_recips['add'])) {
1106
+                mapi_message_modifyrecipients($exception, MODRECIP_ADD, $exception_recips['add']);
1107
+            }
1108
+
1109
+            // Modify the existing recipients
1110
+            if (isset($exception_recips['modify'])) {
1111
+                mapi_message_modifyrecipients($exception, MODRECIP_MODIFY, $exception_recips['modify']);
1112
+            }
1113
+        }
1114
+
1115
+        /**
1116
+         * Function which applies the provided recipients to the exception, also checks for deleted recipients.
1117
+         *
1118
+         * The $exception_recips should be an array containing all recipients which must be applied
1119
+         * to the exception. This will copy all recipients from the original message and then start filter
1120
+         * out all recipients which are not provided by the $exception_recips list.
1121
+         *
1122
+         * @param resource $message          exception attachment of recurring item
1123
+         * @param array    $exception_recips list of recipients
1124
+         */
1125
+        public function setAllExceptionRecipients($message, $exception_recips) {
1126
+            $deletedRecipients = [];
1127
+            $useMessageRecipients = false;
1128
+
1129
+            $recipientTable = mapi_message_getrecipienttable($message);
1130
+            $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1131
+
1132
+            if (empty($recipientRows)) {
1133
+                $useMessageRecipients = true;
1134
+                $recipientTable = mapi_message_getrecipienttable($this->message);
1135
+                $recipientRows = mapi_table_queryallrows($recipientTable, $this->recipprops);
1136
+            }
1137
+
1138
+            // Add organizer to meeting only if it is not organized.
1139
+            $msgprops = mapi_getprops($message, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY, $this->proptags['responsestatus']]);
1140
+            if (isset($msgprops[$this->proptags['responsestatus']]) && $msgprops[$this->proptags['responsestatus']] != olResponseOrganized) {
1141
+                $this->addOrganizer($msgprops, $exception_recips);
1142
+            }
1143
+
1144
+            if (!empty($exception_recips)) {
1145
+                foreach ($recipientRows as $key => $recipient) {
1146
+                    $found = false;
1147
+                    foreach ($exception_recips as $excep_recip) {
1148
+                        if (isset($recipient[PR_SEARCH_KEY], $excep_recip[PR_SEARCH_KEY]) && $recipient[PR_SEARCH_KEY] == $excep_recip[PR_SEARCH_KEY]) {
1149
+                            $found = true;
1150
+                        }
1151
+                    }
1152
+
1153
+                    if (!$found) {
1154
+                        $foundInDeletedRecipients = false;
1155
+                        // Look if the $recipient is in the list of deleted recipients
1156
+                        if (!empty($deletedRecipients)) {
1157
+                            foreach ($deletedRecipients as $recip) {
1158
+                                if ($recip[PR_SEARCH_KEY] == $recipient[PR_SEARCH_KEY]) {
1159
+                                    $foundInDeletedRecipients = true;
1160
+
1161
+                                    break;
1162
+                                }
1163
+                            }
1164
+                        }
1165
+
1166
+                        // If recipient is not in list of deleted recipient, add him
1167
+                        if (!$foundInDeletedRecipients) {
1168
+                            if (!isset($recipient[PR_RECIPIENT_FLAGS]) || $recipient[PR_RECIPIENT_FLAGS] != (recipReserved | recipExceptionalDeleted | recipSendable)) {
1169
+                                $recipient[PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalDeleted;
1170
+                            }
1171
+                            else {
1172
+                                $recipient[PR_RECIPIENT_FLAGS] = recipReserved | recipExceptionalDeleted | recipSendable;
1173
+                            }
1174
+                            $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;	// No Response required
1175
+                            $deletedRecipients[] = $recipient;
1176
+                        }
1177
+                    }
1178
+
1179
+                    // When $message contains a non-empty recipienttable, we must delete the recipients
1180
+                    // before re-adding them. However, when $message is doesn't contain any recipients,
1181
+                    // we are using the recipient table of the original message ($this->message)
1182
+                    // rather then $message. In that case, we don't need to remove the recipients
1183
+                    // from the $message, as the recipient table is already empty, and
1184
+                    // mapi_message_modifyrecipients() will throw an error.
1185
+                    if ($useMessageRecipients === false) {
1186
+                        mapi_message_modifyrecipients($message, MODRECIP_REMOVE, [$recipient]);
1187
+                    }
1188
+                }
1189
+                $exception_recips = array_merge($exception_recips, $deletedRecipients);
1190
+            }
1191
+            else {
1192
+                $exception_recips = $recipientRows;
1193
+            }
1194
+            if (!empty($exception_recips)) {
1195
+                // Set the new list of recipients on the exception message, this also removes the existing recipients
1196
+                mapi_message_modifyrecipients($message, 0, $exception_recips);
1197
+            }
1198
+        }
1199
+
1200
+        /**
1201
+         * Function returns basedates of all changed occurrences.
1202
+         *
1203
+         *@return array array(
1204
+         * 0 => 123459321
1205
+         * )
1206
+         */
1207
+        public function getAllExceptions() {
1208
+            $result = false;
1209
+            if (!empty($this->recur["changed_occurrences"])) {
1210
+                $result = [];
1211
+                foreach ($this->recur["changed_occurrences"] as $exception) {
1212
+                    $result[] = $exception["basedate"];
1213
+                }
1214
+
1215
+                return $result;
1216
+            }
1217
+
1218
+            return $result;
1219
+        }
1220
+
1221
+        /**
1222
+         *  Function which adds organizer to recipient list which is passed.
1223
+         *  This function also checks if it has organizer.
1224
+         *
1225
+         * @param array $messageProps message properties
1226
+         * @param array $recipients   recipients list of message
1227
+         * @param bool  $isException  true if we are processing recipient of exception
1228
+         */
1229
+        public function addOrganizer($messageProps, &$recipients, $isException = false) {
1230
+            $hasOrganizer = false;
1231
+            // Check if meeting already has an organizer.
1232
+            foreach ($recipients as $key => $recipient) {
1233
+                if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
1234
+                    $hasOrganizer = true;
1235
+                }
1236
+                elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
1237
+                    // Recipients for an occurrence
1238
+                    $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
1239
+                }
1240
+            }
1241
+
1242
+            if (!$hasOrganizer) {
1243
+                // Create organizer.
1244
+                $organizer = [];
1245
+                $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
1246
+                $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1247
+                $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1248
+                $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
1249
+                $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
1250
+                $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
1251
+                $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
1252
+                $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
1253
+                $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
1254
+
1255
+                // Add organizer to recipients list.
1256
+                array_unshift($recipients, $organizer);
1257
+            }
1258
+        }
1259
+    }
1260
+
1261
+    /*
1262 1262
 
1263 1263
 	From http://www.ohelp-one.com/new-6765483-3268.html:
1264 1264
 
Please login to merge, or discard this patch.
mapi/mapi.util.php 1 patch
Indentation   +254 added lines, -254 removed lines patch added patch discarded remove patch
@@ -22,7 +22,7 @@  discard block
 block discarded – undo
22 22
  * @param mixed $guid
23 23
  */
24 24
 function makeGuid($guid) {
25
-	return pack("vvvv", hexdec(substr($guid, 5, 4)), hexdec(substr($guid, 1, 4)), hexdec(substr($guid, 10, 4)), hexdec(substr($guid, 15, 4))) . hex2bin(substr($guid, 20, 4)) . hex2bin(substr($guid, 25, 12));
25
+    return pack("vvvv", hexdec(substr($guid, 5, 4)), hexdec(substr($guid, 1, 4)), hexdec(substr($guid, 10, 4)), hexdec(substr($guid, 15, 4))) . hex2bin(substr($guid, 20, 4)) . hex2bin(substr($guid, 25, 12));
26 26
 }
27 27
 
28 28
 /**
@@ -33,39 +33,39 @@  discard block
 block discarded – undo
33 33
  *@return string The defined name for the MAPI error code
34 34
  */
35 35
 function get_mapi_error_name($errcode = null) {
36
-	if ($errcode === null) {
37
-		$errcode = mapi_last_hresult();
38
-	}
36
+    if ($errcode === null) {
37
+        $errcode = mapi_last_hresult();
38
+    }
39 39
 
40
-	if ($errcode === 0) {
41
-		return "NOERROR";
42
-	}
40
+    if ($errcode === 0) {
41
+        return "NOERROR";
42
+    }
43 43
 
44
-	// get_defined_constants(true) is preferred, but crashes PHP
45
-	// https://bugs.php.net/bug.php?id=61156
46
-	$allConstants = get_defined_constants();
44
+    // get_defined_constants(true) is preferred, but crashes PHP
45
+    // https://bugs.php.net/bug.php?id=61156
46
+    $allConstants = get_defined_constants();
47 47
 
48
-	foreach ($allConstants as $key => $value) {
49
-		/*
48
+    foreach ($allConstants as $key => $value) {
49
+        /*
50 50
 		 * If PHP encounters a number beyond the bounds of the integer type,
51 51
 		 * it will be interpreted as a float instead, so when comparing these error codes
52 52
 		 * we have to manually typecast value to integer, so float will be converted in integer,
53 53
 		 * but still its out of bound for integer limit so it will be auto adjusted to minus value
54 54
 		 */
55
-		if ($errcode != (int) $value) {
56
-			continue;
57
-		}
58
-		// Check that we have an actual MAPI error or warning definition
59
-		$prefix = substr($key, 0, 7);
60
-		if ($prefix == "MAPI_E_" || $prefix == "MAPI_W_") {
61
-			return $key;
62
-		}
63
-	}
64
-
65
-	// error code not found, return hex value (this is a fix for 64-bit systems, we can't use the dechex() function for this)
66
-	$result = unpack("H*", pack("N", $errcode));
67
-
68
-	return "0x" . $result[1];
55
+        if ($errcode != (int) $value) {
56
+            continue;
57
+        }
58
+        // Check that we have an actual MAPI error or warning definition
59
+        $prefix = substr($key, 0, 7);
60
+        if ($prefix == "MAPI_E_" || $prefix == "MAPI_W_") {
61
+            return $key;
62
+        }
63
+    }
64
+
65
+    // error code not found, return hex value (this is a fix for 64-bit systems, we can't use the dechex() function for this)
66
+    $result = unpack("H*", pack("N", $errcode));
67
+
68
+    return "0x" . $result[1];
69 69
 }
70 70
 
71 71
 /**
@@ -79,65 +79,65 @@  discard block
 block discarded – undo
79 79
  * @param mixed $mapping
80 80
  */
81 81
 function getPropIdsFromStrings($store, $mapping) {
82
-	$props = [];
83
-
84
-	$ids = ["name" => [], "id" => [], "guid" => [], "type" => []]; // this array stores all the information needed to retrieve a named property
85
-	$num = 0;
86
-
87
-	// caching
88
-	$guids = [];
89
-
90
-	foreach ($mapping as $name => $val) {
91
-		if (!is_string($val)) {
92
-			// not a named property
93
-			$props[$name] = $val;
94
-
95
-			continue;
96
-		}
97
-		$split = explode(":", $val);
98
-		if (count($split) != 3) { // invalid string, ignore
99
-			trigger_error(sprintf("Invalid property: %s \"%s\"", $name, $val), E_USER_NOTICE);
100
-
101
-			continue;
102
-		}
103
-
104
-		if (substr($split[2], 0, 2) == "0x") {
105
-			$id = hexdec(substr($split[2], 2));
106
-		}
107
-		else {
108
-			$id = $split[2];
109
-		}
110
-
111
-		// have we used this guid before?
112
-		if (!defined($split[1])) {
113
-			if (!array_key_exists($split[1], $guids)) {
114
-				$guids[$split[1]] = makeguid($split[1]);
115
-			}
116
-			$guid = $guids[$split[1]];
117
-		}
118
-		else {
119
-			$guid = constant($split[1]);
120
-		}
121
-
122
-		// temp store info about named prop, so we have to call mapi_getidsfromnames just one time
123
-		$ids["name"][$num] = $name;
124
-		$ids["id"][$num] = $id;
125
-		$ids["guid"][$num] = $guid;
126
-		$ids["type"][$num] = $split[0];
127
-		++$num;
128
-	}
129
-
130
-	if (empty($ids["id"])) {
131
-		return $props;
132
-	}
133
-
134
-	// get the ids
135
-	$named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]);
136
-	foreach ($named as $num => $prop) {
137
-		$props[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop));
138
-	}
139
-
140
-	return $props;
82
+    $props = [];
83
+
84
+    $ids = ["name" => [], "id" => [], "guid" => [], "type" => []]; // this array stores all the information needed to retrieve a named property
85
+    $num = 0;
86
+
87
+    // caching
88
+    $guids = [];
89
+
90
+    foreach ($mapping as $name => $val) {
91
+        if (!is_string($val)) {
92
+            // not a named property
93
+            $props[$name] = $val;
94
+
95
+            continue;
96
+        }
97
+        $split = explode(":", $val);
98
+        if (count($split) != 3) { // invalid string, ignore
99
+            trigger_error(sprintf("Invalid property: %s \"%s\"", $name, $val), E_USER_NOTICE);
100
+
101
+            continue;
102
+        }
103
+
104
+        if (substr($split[2], 0, 2) == "0x") {
105
+            $id = hexdec(substr($split[2], 2));
106
+        }
107
+        else {
108
+            $id = $split[2];
109
+        }
110
+
111
+        // have we used this guid before?
112
+        if (!defined($split[1])) {
113
+            if (!array_key_exists($split[1], $guids)) {
114
+                $guids[$split[1]] = makeguid($split[1]);
115
+            }
116
+            $guid = $guids[$split[1]];
117
+        }
118
+        else {
119
+            $guid = constant($split[1]);
120
+        }
121
+
122
+        // temp store info about named prop, so we have to call mapi_getidsfromnames just one time
123
+        $ids["name"][$num] = $name;
124
+        $ids["id"][$num] = $id;
125
+        $ids["guid"][$num] = $guid;
126
+        $ids["type"][$num] = $split[0];
127
+        ++$num;
128
+    }
129
+
130
+    if (empty($ids["id"])) {
131
+        return $props;
132
+    }
133
+
134
+    // get the ids
135
+    $named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]);
136
+    foreach ($named as $num => $prop) {
137
+        $props[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop));
138
+    }
139
+
140
+    return $props;
141 141
 }
142 142
 
143 143
 /**
@@ -153,11 +153,11 @@  discard block
 block discarded – undo
153 153
  * @return mixed Gives back false when there is no error, if there is, gives the error
154 154
  */
155 155
 function propIsError($property, $propArray) {
156
-	if (array_key_exists(mapi_prop_tag(PT_ERROR, mapi_prop_id($property)), $propArray)) {
157
-		return $propArray[mapi_prop_tag(PT_ERROR, mapi_prop_id($property))];
158
-	}
156
+    if (array_key_exists(mapi_prop_tag(PT_ERROR, mapi_prop_id($property)), $propArray)) {
157
+        return $propArray[mapi_prop_tag(PT_ERROR, mapi_prop_id($property))];
158
+    }
159 159
 
160
-	return false;
160
+    return false;
161 161
 }
162 162
 
163 163
 /* Macro Functions for PR_DISPLAY_TYPE_EX values */
@@ -167,7 +167,7 @@  discard block
 block discarded – undo
167 167
  * @param mixed $value
168 168
  */
169 169
 function DTE_IS_REMOTE_VALID($value) {
170
-	return (bool) ($value & DTE_FLAG_REMOTE_VALID);
170
+    return (bool) ($value & DTE_FLAG_REMOTE_VALID);
171 171
 }
172 172
 
173 173
 /**
@@ -176,15 +176,15 @@  discard block
 block discarded – undo
176 176
  * @param mixed $value
177 177
  */
178 178
 function DTE_IS_ACL_CAPABLE($value) {
179
-	return (bool) ($value & DTE_FLAG_ACL_CAPABLE);
179
+    return (bool) ($value & DTE_FLAG_ACL_CAPABLE);
180 180
 }
181 181
 
182 182
 function DTE_REMOTE($value) {
183
-	return ($value & DTE_MASK_REMOTE) >> 8;
183
+    return ($value & DTE_MASK_REMOTE) >> 8;
184 184
 }
185 185
 
186 186
 function DTE_LOCAL($value) {
187
-	return $value & DTE_MASK_LOCAL;
187
+    return $value & DTE_MASK_LOCAL;
188 188
 }
189 189
 
190 190
 /**
@@ -204,86 +204,86 @@  discard block
 block discarded – undo
204 204
  *                    expanded so that it seems that there are only many single appointments in the table.
205 205
  */
206 206
 function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
207
-	$result = [];
208
-	$properties = getPropIdsFromStrings($store, ["duedate" => "PT_SYSTIME:PSETID_Appointment:0x820e",
209
-		"startdate" => "PT_SYSTIME:PSETID_Appointment:0x820d",
210
-		"enddate_recurring" => "PT_SYSTIME:PSETID_Appointment:0x8236",
211
-		"recurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
212
-		"recurring_data" => "PT_BINARY:PSETID_Appointment:0x8216",
213
-		"timezone_data" => "PT_BINARY:PSETID_Appointment:0x8233",
214
-		"label" => "PT_LONG:PSETID_Appointment:0x8214",
215
-	]);
216
-
217
-	// Create a restriction that will discard rows of appointments that are definitely not in our
218
-	// requested time frame
219
-
220
-	$table = mapi_folder_getcontentstable($calendar);
221
-
222
-	$restriction =
223
-		// OR
224
-		[RES_OR,
225
-			[
226
-				[RES_AND,    // Normal items: itemEnd must be after viewStart, itemStart must be before viewEnd
227
-					[
228
-						[RES_PROPERTY,
229
-							[RELOP => RELOP_GT,
230
-								ULPROPTAG => $properties["duedate"],
231
-								VALUE => $viewstart,
232
-							],
233
-						],
234
-						[RES_PROPERTY,
235
-							[RELOP => RELOP_LT,
236
-								ULPROPTAG => $properties["startdate"],
237
-								VALUE => $viewend,
238
-							],
239
-						],
240
-					],
241
-				],
242
-				// OR
243
-				[RES_PROPERTY,
244
-					[RELOP => RELOP_EQ,
245
-						ULPROPTAG => $properties["recurring"],
246
-						VALUE => true,
247
-					],
248
-				],
249
-			], // EXISTS OR
250
-		];        // global OR
251
-
252
-	// Get requested properties, plus whatever we need
253
-	$proplist = [PR_ENTRYID, $properties["recurring"], $properties["recurring_data"], $properties["timezone_data"]];
254
-	$proplist = array_merge($proplist, $propsrequested);
255
-	$propslist = array_unique($proplist);
256
-
257
-	$rows = mapi_table_queryallrows($table, $proplist, $restriction);
258
-
259
-	// $rows now contains all the items that MAY be in the window; a recurring item needs expansion before including in the output.
260
-
261
-	foreach ($rows as $row) {
262
-		$items = [];
263
-
264
-		if (isset($row[$properties["recurring"]]) && $row[$properties["recurring"]]) {
265
-			// Recurring item
266
-			$rec = new Recurrence($store, $row);
267
-
268
-			// GetItems guarantees that the item overlaps the interval <$viewstart, $viewend>
269
-			$occurrences = $rec->getItems($viewstart, $viewend);
270
-			foreach ($occurrences as $occurrence) {
271
-				// The occurrence takes all properties from the main row, but overrides some properties (like start and end obviously)
272
-				$item = $occurrence + $row;
273
-				array_push($items, $item);
274
-			}
275
-		}
276
-		else {
277
-			// Normal item, it matched the search criteria and therefore overlaps the interval <$viewstart, $viewend>
278
-			array_push($items, $row);
279
-		}
280
-
281
-		$result = array_merge($result, $items);
282
-	}
283
-
284
-	// All items are guaranteed to overlap the interval <$viewstart, $viewend>. Note that we may be returning a few extra
285
-	// properties that the caller did not request (recurring, etc). This shouldn't be a problem though.
286
-	return $result;
207
+    $result = [];
208
+    $properties = getPropIdsFromStrings($store, ["duedate" => "PT_SYSTIME:PSETID_Appointment:0x820e",
209
+        "startdate" => "PT_SYSTIME:PSETID_Appointment:0x820d",
210
+        "enddate_recurring" => "PT_SYSTIME:PSETID_Appointment:0x8236",
211
+        "recurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
212
+        "recurring_data" => "PT_BINARY:PSETID_Appointment:0x8216",
213
+        "timezone_data" => "PT_BINARY:PSETID_Appointment:0x8233",
214
+        "label" => "PT_LONG:PSETID_Appointment:0x8214",
215
+    ]);
216
+
217
+    // Create a restriction that will discard rows of appointments that are definitely not in our
218
+    // requested time frame
219
+
220
+    $table = mapi_folder_getcontentstable($calendar);
221
+
222
+    $restriction =
223
+        // OR
224
+        [RES_OR,
225
+            [
226
+                [RES_AND,    // Normal items: itemEnd must be after viewStart, itemStart must be before viewEnd
227
+                    [
228
+                        [RES_PROPERTY,
229
+                            [RELOP => RELOP_GT,
230
+                                ULPROPTAG => $properties["duedate"],
231
+                                VALUE => $viewstart,
232
+                            ],
233
+                        ],
234
+                        [RES_PROPERTY,
235
+                            [RELOP => RELOP_LT,
236
+                                ULPROPTAG => $properties["startdate"],
237
+                                VALUE => $viewend,
238
+                            ],
239
+                        ],
240
+                    ],
241
+                ],
242
+                // OR
243
+                [RES_PROPERTY,
244
+                    [RELOP => RELOP_EQ,
245
+                        ULPROPTAG => $properties["recurring"],
246
+                        VALUE => true,
247
+                    ],
248
+                ],
249
+            ], // EXISTS OR
250
+        ];        // global OR
251
+
252
+    // Get requested properties, plus whatever we need
253
+    $proplist = [PR_ENTRYID, $properties["recurring"], $properties["recurring_data"], $properties["timezone_data"]];
254
+    $proplist = array_merge($proplist, $propsrequested);
255
+    $propslist = array_unique($proplist);
256
+
257
+    $rows = mapi_table_queryallrows($table, $proplist, $restriction);
258
+
259
+    // $rows now contains all the items that MAY be in the window; a recurring item needs expansion before including in the output.
260
+
261
+    foreach ($rows as $row) {
262
+        $items = [];
263
+
264
+        if (isset($row[$properties["recurring"]]) && $row[$properties["recurring"]]) {
265
+            // Recurring item
266
+            $rec = new Recurrence($store, $row);
267
+
268
+            // GetItems guarantees that the item overlaps the interval <$viewstart, $viewend>
269
+            $occurrences = $rec->getItems($viewstart, $viewend);
270
+            foreach ($occurrences as $occurrence) {
271
+                // The occurrence takes all properties from the main row, but overrides some properties (like start and end obviously)
272
+                $item = $occurrence + $row;
273
+                array_push($items, $item);
274
+            }
275
+        }
276
+        else {
277
+            // Normal item, it matched the search criteria and therefore overlaps the interval <$viewstart, $viewend>
278
+            array_push($items, $row);
279
+        }
280
+
281
+        $result = array_merge($result, $items);
282
+    }
283
+
284
+    // All items are guaranteed to overlap the interval <$viewstart, $viewend>. Note that we may be returning a few extra
285
+    // properties that the caller did not request (recurring, etc). This shouldn't be a problem though.
286
+    return $result;
287 287
 }
288 288
 
289 289
 /**
@@ -292,14 +292,14 @@  discard block
 block discarded – undo
292 292
  * @param mixed $store
293 293
  */
294 294
 function getCalendar($store) {
295
-	$inbox = mapi_msgstore_getreceivefolder($store);
296
-	$inboxprops = mapi_getprops($inbox, [PR_IPM_APPOINTMENT_ENTRYID]);
295
+    $inbox = mapi_msgstore_getreceivefolder($store);
296
+    $inboxprops = mapi_getprops($inbox, [PR_IPM_APPOINTMENT_ENTRYID]);
297 297
 
298
-	if (!isset($inboxprops[PR_IPM_APPOINTMENT_ENTRYID])) {
299
-		return false;
300
-	}
298
+    if (!isset($inboxprops[PR_IPM_APPOINTMENT_ENTRYID])) {
299
+        return false;
300
+    }
301 301
 
302
-	return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]);
302
+    return mapi_msgstore_openentry($store, $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]);
303 303
 }
304 304
 
305 305
 /**
@@ -308,97 +308,97 @@  discard block
 block discarded – undo
308 308
  * @param mixed $session
309 309
  */
310 310
 function getDefaultStore($session) {
311
-	$msgstorestable = mapi_getmsgstorestable($session);
311
+    $msgstorestable = mapi_getmsgstorestable($session);
312 312
 
313
-	$msgstores = mapi_table_queryallrows($msgstorestable, [PR_DEFAULT_STORE, PR_ENTRYID]);
313
+    $msgstores = mapi_table_queryallrows($msgstorestable, [PR_DEFAULT_STORE, PR_ENTRYID]);
314 314
 
315
-	foreach ($msgstores as $row) {
316
-		if ($row[PR_DEFAULT_STORE]) {
317
-			$storeentryid = $row[PR_ENTRYID];
315
+    foreach ($msgstores as $row) {
316
+        if ($row[PR_DEFAULT_STORE]) {
317
+            $storeentryid = $row[PR_ENTRYID];
318 318
 
319
-			break;
320
-		}
321
-	}
319
+            break;
320
+        }
321
+    }
322 322
 
323
-	if (!$storeentryid) {
324
-		echo "Can't find default store\n";
323
+    if (!$storeentryid) {
324
+        echo "Can't find default store\n";
325 325
 
326
-		return false;
327
-	}
326
+        return false;
327
+    }
328 328
 
329
-	return mapi_openmsgstore($session, $storeentryid);
329
+    return mapi_openmsgstore($session, $storeentryid);
330 330
 }
331 331
 
332 332
 function forceUTF8($category) {
333
-	$old_locale = setlocale($category, "");
334
-	if (!isset($old_locale) || !$old_locale) {
335
-		echo "Unable to initialize locale\n";
336
-
337
-		exit(1);
338
-	}
339
-	$dot = strpos($old_locale, ".");
340
-	if ($dot) {
341
-		if (strrchr($old_locale, ".") == ".UTF-8" || strrchr($old_locale, ".") == ".utf8") {
342
-			return true;
343
-		}
344
-		$old_locale = substr($old_locale, 0, $dot);
345
-	}
346
-	$new_locale = $old_locale . ".UTF-8";
347
-	$old_locale = setlocale($category, $new_locale);
348
-	if (!$old_locale) {
349
-		$new_locale = "en_US.UTF-8";
350
-		$old_locale = setlocale($category, $new_locale);
351
-	}
352
-	if (!$old_locale) {
353
-		echo "Unable to set locale {$new_locale}\n";
354
-
355
-		exit(1);
356
-	}
357
-
358
-	return true;
333
+    $old_locale = setlocale($category, "");
334
+    if (!isset($old_locale) || !$old_locale) {
335
+        echo "Unable to initialize locale\n";
336
+
337
+        exit(1);
338
+    }
339
+    $dot = strpos($old_locale, ".");
340
+    if ($dot) {
341
+        if (strrchr($old_locale, ".") == ".UTF-8" || strrchr($old_locale, ".") == ".utf8") {
342
+            return true;
343
+        }
344
+        $old_locale = substr($old_locale, 0, $dot);
345
+    }
346
+    $new_locale = $old_locale . ".UTF-8";
347
+    $old_locale = setlocale($category, $new_locale);
348
+    if (!$old_locale) {
349
+        $new_locale = "en_US.UTF-8";
350
+        $old_locale = setlocale($category, $new_locale);
351
+    }
352
+    if (!$old_locale) {
353
+        echo "Unable to set locale {$new_locale}\n";
354
+
355
+        exit(1);
356
+    }
357
+
358
+    return true;
359 359
 }
360 360
 
361 361
 if (!function_exists('mapi_zarafa_getuser_by_name')) {
362
-	/**
363
-	 * Retrieve the user information.
364
-	 *
365
-	 * @param MAPIResource $store
366
-	 * @param string       $username
367
-	 *
368
-	 * @returns array
369
-	 */
370
-	function mapi_zarafa_getuser_by_name($store, $username) {
371
-		$userInfo = get_user_info_by_name($username);
372
-
373
-		if (!is_array($userInfo)) {
374
-			return false;
375
-		}
376
-
377
-		return [
378
-			'userid' => $userInfo['uid'],
379
-			'username' => $username,
380
-			'fullname' => $userInfo['real_name'],
381
-			'emailaddress' => $username,
382
-			'admin' => 0,
383
-			'nonactive' => 0,
384
-		];
385
-	}
362
+    /**
363
+     * Retrieve the user information.
364
+     *
365
+     * @param MAPIResource $store
366
+     * @param string       $username
367
+     *
368
+     * @returns array
369
+     */
370
+    function mapi_zarafa_getuser_by_name($store, $username) {
371
+        $userInfo = get_user_info_by_name($username);
372
+
373
+        if (!is_array($userInfo)) {
374
+            return false;
375
+        }
376
+
377
+        return [
378
+            'userid' => $userInfo['uid'],
379
+            'username' => $username,
380
+            'fullname' => $userInfo['real_name'],
381
+            'emailaddress' => $username,
382
+            'admin' => 0,
383
+            'nonactive' => 0,
384
+        ];
385
+    }
386 386
 }
387 387
 
388 388
 function getGoidFromUid($uid) {
389
-	return hex2bin("040000008200E00074C5B7101A82E0080000000000000000000000000000000000000000" .
390
-				bin2hex(pack("V", 12 + strlen($uid)) . "vCal-Uid" . pack("V", 1) . $uid));
389
+    return hex2bin("040000008200E00074C5B7101A82E0080000000000000000000000000000000000000000" .
390
+                bin2hex(pack("V", 12 + strlen($uid)) . "vCal-Uid" . pack("V", 1) . $uid));
391 391
 }
392 392
 
393 393
 function getUidFromGoid($goid) {
394
-	// check if "vCal-Uid" is somewhere in outlookid case-insensitive
395
-	$uid = stristr($goid, "vCal-Uid");
396
-	if ($uid !== false) {
397
-		// get the length of the ical id - go back 4 position from where "vCal-Uid" was found
398
-		$begin = unpack("V", substr($goid, strlen($uid) * (-1) - 4, 4));
399
-		// remove "vCal-Uid" and packed "1" and use the ical id length
400
-		return substr($uid, 12, ($begin[1] - 12));
401
-	}
402
-
403
-	return null;
394
+    // check if "vCal-Uid" is somewhere in outlookid case-insensitive
395
+    $uid = stristr($goid, "vCal-Uid");
396
+    if ($uid !== false) {
397
+        // get the length of the ical id - go back 4 position from where "vCal-Uid" was found
398
+        $begin = unpack("V", substr($goid, strlen($uid) * (-1) - 4, 4));
399
+        // remove "vCal-Uid" and packed "1" and use the ical id length
400
+        return substr($uid, 12, ($begin[1] - 12));
401
+    }
402
+
403
+    return null;
404 404
 }
Please login to merge, or discard this patch.
mapi/mapicode.php 1 patch
Indentation   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -27,7 +27,7 @@  discard block
 block discarded – undo
27 27
  * @param mixed $code
28 28
  */
29 29
 function make_mapi_e($code) {
30
-	return (int) mapi_make_scode(1, $code);
30
+    return (int) mapi_make_scode(1, $code);
31 31
 }
32 32
 
33 33
 /**
@@ -36,7 +36,7 @@  discard block
 block discarded – undo
36 36
  * @param mixed $code
37 37
  */
38 38
 function make_mapi_s($code) {
39
-	return (int) mapi_make_scode(0, $code);
39
+    return (int) mapi_make_scode(0, $code);
40 40
 }
41 41
 
42 42
 /* From mapicode.h */
Please login to merge, or discard this patch.
mapi/class.mapiexception.php 1 patch
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -5,62 +5,62 @@
 block discarded – undo
5 5
  * SPDX-FileCopyrightText: Copyright 2020 grommunio GmbH
6 6
  */
7 7
 
8
-	/**
9
-	 * MAPIException
10
-	 * if enabled using mapi_enable_exceptions then php-ext can throw exceptions when
11
-	 * any error occurs in mapi calls. this exception will only be thrown when severity bit is set in
12
-	 * error code that means it will be thrown only for mapi errors not for mapi warnings.
13
-	 */
14
-	class MAPIException extends BaseException {
15
-		/**
16
-		 * Function will return display message of exception if its set by the callee.
17
-		 * if it is not set then we are generating some default display messages based
18
-		 * on mapi error code.
19
-		 *
20
-		 * @return string returns error-message that should be sent to client to display
21
-		 */
22
-		public function getDisplayMessage() {
23
-			if (!empty($this->displayMessage)) {
24
-				return $this->displayMessage;
25
-			}
8
+    /**
9
+     * MAPIException
10
+     * if enabled using mapi_enable_exceptions then php-ext can throw exceptions when
11
+     * any error occurs in mapi calls. this exception will only be thrown when severity bit is set in
12
+     * error code that means it will be thrown only for mapi errors not for mapi warnings.
13
+     */
14
+    class MAPIException extends BaseException {
15
+        /**
16
+         * Function will return display message of exception if its set by the callee.
17
+         * if it is not set then we are generating some default display messages based
18
+         * on mapi error code.
19
+         *
20
+         * @return string returns error-message that should be sent to client to display
21
+         */
22
+        public function getDisplayMessage() {
23
+            if (!empty($this->displayMessage)) {
24
+                return $this->displayMessage;
25
+            }
26 26
 
27
-			switch ($this->getCode()) {
28
-			case MAPI_E_NO_ACCESS:
29
-				return dgettext("kopano", "You have insufficient privileges to open this object.");
27
+            switch ($this->getCode()) {
28
+            case MAPI_E_NO_ACCESS:
29
+                return dgettext("kopano", "You have insufficient privileges to open this object.");
30 30
 
31
-			case MAPI_E_LOGON_FAILED:
32
-			case MAPI_E_UNCONFIGURED:
33
-				return dgettext("kopano", "Logon Failed. Please check your username/password.");
31
+            case MAPI_E_LOGON_FAILED:
32
+            case MAPI_E_UNCONFIGURED:
33
+                return dgettext("kopano", "Logon Failed. Please check your username/password.");
34 34
 
35
-			case MAPI_E_NETWORK_ERROR:
36
-				return dgettext("kopano", "Can not connect to Kopano server.");
35
+            case MAPI_E_NETWORK_ERROR:
36
+                return dgettext("kopano", "Can not connect to Kopano server.");
37 37
 
38
-			case MAPI_E_UNKNOWN_ENTRYID:
39
-				return dgettext("kopano", "Can not open object with provided id.");
38
+            case MAPI_E_UNKNOWN_ENTRYID:
39
+                return dgettext("kopano", "Can not open object with provided id.");
40 40
 
41
-			case MAPI_E_NO_RECIPIENTS:
42
-				return dgettext("kopano", "There are no recipients in the message.");
41
+            case MAPI_E_NO_RECIPIENTS:
42
+                return dgettext("kopano", "There are no recipients in the message.");
43 43
 
44
-			case MAPI_E_NOT_FOUND:
45
-				return dgettext("kopano", "Can not find object.");
44
+            case MAPI_E_NOT_FOUND:
45
+                return dgettext("kopano", "Can not find object.");
46 46
 
47
-			case MAPI_E_INTERFACE_NOT_SUPPORTED:
48
-			case MAPI_E_INVALID_PARAMETER:
49
-			case MAPI_E_INVALID_ENTRYID:
50
-			case MAPI_E_INVALID_OBJECT:
51
-			case MAPI_E_TOO_COMPLEX:
52
-			case MAPI_E_CORRUPT_DATA:
53
-			case MAPI_E_END_OF_SESSION:
54
-			case MAPI_E_AMBIGUOUS_RECIP:
55
-			case MAPI_E_COLLISION:
56
-			case MAPI_E_UNCONFIGURED:
57
-			default:
58
-				return sprintf(dgettext("kopano", "Unknown MAPI Error: %s"), get_mapi_error_name($this->getCode()));
59
-			}
60
-		}
61
-	}
47
+            case MAPI_E_INTERFACE_NOT_SUPPORTED:
48
+            case MAPI_E_INVALID_PARAMETER:
49
+            case MAPI_E_INVALID_ENTRYID:
50
+            case MAPI_E_INVALID_OBJECT:
51
+            case MAPI_E_TOO_COMPLEX:
52
+            case MAPI_E_CORRUPT_DATA:
53
+            case MAPI_E_END_OF_SESSION:
54
+            case MAPI_E_AMBIGUOUS_RECIP:
55
+            case MAPI_E_COLLISION:
56
+            case MAPI_E_UNCONFIGURED:
57
+            default:
58
+                return sprintf(dgettext("kopano", "Unknown MAPI Error: %s"), get_mapi_error_name($this->getCode()));
59
+            }
60
+        }
61
+    }
62 62
 
63
-	// Tell the PHP extension which exception class to instantiate
64
-	if (function_exists('mapi_enable_exceptions')) {
65
-		mapi_enable_exceptions("mapiexception");
66
-	}
63
+    // Tell the PHP extension which exception class to instantiate
64
+    if (function_exists('mapi_enable_exceptions')) {
65
+        mapi_enable_exceptions("mapiexception");
66
+    }
Please login to merge, or discard this patch.
mapi/class.meetingrequest.php 1 patch
Indentation   +3250 added lines, -3250 removed lines patch added patch discarded remove patch
@@ -6,7 +6,7 @@  discard block
 block discarded – undo
6 6
  */
7 7
 
8 8
 class Meetingrequest {
9
-	/*
9
+    /*
10 10
 	 * NOTE
11 11
 	 *
12 12
 	 * This class is designed to modify and update meeting request properties
@@ -22,7 +22,7 @@  discard block
 block discarded – undo
22 22
 	 *
23 23
 	 */
24 24
 
25
-	/*
25
+    /*
26 26
 	 * How to use
27 27
 	 * ----------
28 28
 	 *
@@ -65,679 +65,679 @@  discard block
 block discarded – undo
65 65
 	 *     calendar and deletes the message
66 66
 	 */
67 67
 
68
-	// All properties for a recipient that are interesting
69
-	public $recipprops = [
70
-		PR_ENTRYID,
71
-		PR_DISPLAY_NAME,
72
-		PR_EMAIL_ADDRESS,
73
-		PR_RECIPIENT_ENTRYID,
74
-		PR_RECIPIENT_TYPE,
75
-		PR_SEND_INTERNET_ENCODING,
76
-		PR_SEND_RICH_INFO,
77
-		PR_RECIPIENT_DISPLAY_NAME,
78
-		PR_ADDRTYPE,
79
-		PR_DISPLAY_TYPE,
80
-		PR_RECIPIENT_TRACKSTATUS,
81
-		PR_RECIPIENT_TRACKSTATUS_TIME,
82
-		PR_RECIPIENT_FLAGS,
83
-		PR_ROWID,
84
-		PR_OBJECT_TYPE,
85
-		PR_SEARCH_KEY,
86
-	];
87
-
88
-	/**
89
-	 * Indication whether the setting of resources in a Meeting Request is success (false) or if it
90
-	 * has failed (integer).
91
-	 */
92
-	public $errorSetResource;
93
-
94
-	/**
95
-	 * Takes a store and a message. The message is an appointment item
96
-	 * that should be converted into a meeting request or an incoming
97
-	 * e-mail message that is a meeting request.
98
-	 *
99
-	 * The $session variable is optional, but required if the following features
100
-	 * are to be used:
101
-	 *
102
-	 * - Sending meeting requests for meetings that are not in your own store
103
-	 * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
104
-	 *
105
-	 * @param mixed $store
106
-	 * @param mixed $message
107
-	 * @param mixed $session
108
-	 * @param mixed $enableDirectBooking
109
-	 */
110
-	public function __construct($store, $message, $session = false, $enableDirectBooking = true) {
111
-		$this->store = $store;
112
-		$this->message = $message;
113
-		$this->session = $session;
114
-		// This variable string saves time information for the MR.
115
-		$this->meetingTimeInfo = false;
116
-		$this->enableDirectBooking = $enableDirectBooking;
117
-
118
-		$properties["goid"] = "PT_BINARY:PSETID_Meeting:0x3";
119
-		$properties["goid2"] = "PT_BINARY:PSETID_Meeting:0x23";
120
-		$properties["type"] = "PT_STRING8:PSETID_Meeting:0x24";
121
-		$properties["meetingrecurring"] = "PT_BOOLEAN:PSETID_Meeting:0x5";
122
-		$properties["unknown2"] = "PT_BOOLEAN:PSETID_Meeting:0xa";
123
-		$properties["attendee_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1";
124
-		$properties["owner_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1a";
125
-		$properties["meetingstatus"] = "PT_LONG:PSETID_Appointment:0x8217";
126
-		$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
127
-		$properties["unknown6"] = "PT_LONG:PSETID_Meeting:0x4";
128
-		$properties["replytime"] = "PT_SYSTIME:PSETID_Appointment:0x8220";
129
-		$properties["usetnef"] = "PT_BOOLEAN:PSETID_Common:0x8582";
130
-		$properties["recurrence_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
131
-		$properties["reminderminutes"] = "PT_LONG:PSETID_Common:0x8501";
132
-		$properties["reminderset"] = "PT_BOOLEAN:PSETID_Common:0x8503";
133
-		$properties["sendasical"] = "PT_BOOLEAN:PSETID_Appointment:0x8200";
134
-		$properties["updatecounter"] = "PT_LONG:PSETID_Appointment:0x8201";					// AppointmentSequenceNumber
135
-		$properties["last_updatecounter"] = "PT_LONG:PSETID_Appointment:0x8203";			// AppointmentLastSequence
136
-		$properties["unknown7"] = "PT_LONG:PSETID_Appointment:0x8202";
137
-		$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
138
-		$properties["intendedbusystatus"] = "PT_LONG:PSETID_Appointment:0x8224";
139
-		$properties["start"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
140
-		$properties["responselocation"] = "PT_STRING8:PSETID_Meeting:0x2";
141
-		$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
142
-		$properties["requestsent"] = "PT_BOOLEAN:PSETID_Appointment:0x8229";		// PidLidFInvited, MeetingRequestWasSent
143
-		$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
144
-		$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
145
-		$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
146
-		$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
147
-		$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
148
-		$properties["clipstart"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
149
-		$properties["clipend"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
150
-		$properties["start_recur_date"] = "PT_LONG:PSETID_Meeting:0xD";				// StartRecurTime
151
-		$properties["start_recur_time"] = "PT_LONG:PSETID_Meeting:0xE";				// StartRecurTime
152
-		$properties["end_recur_date"] = "PT_LONG:PSETID_Meeting:0xF";				// EndRecurDate
153
-		$properties["end_recur_time"] = "PT_LONG:PSETID_Meeting:0x10";				// EndRecurTime
154
-		$properties["is_exception"] = "PT_BOOLEAN:PSETID_Meeting:0xA";				// LID_IS_EXCEPTION
155
-		$properties["apptreplyname"] = "PT_STRING8:PSETID_Appointment:0x8230";
156
-		// Propose new time properties
157
-		$properties["proposed_start_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8250";
158
-		$properties["proposed_end_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8251";
159
-		$properties["proposed_duration"] = "PT_LONG:PSETID_Appointment:0x8256";
160
-		$properties["counter_proposal"] = "PT_BOOLEAN:PSETID_Appointment:0x8257";
161
-		$properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
162
-		$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
163
-		$properties["meetingtype"] = "PT_LONG:PSETID_Meeting:0x26";
164
-		$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
165
-		$properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
166
-		$properties["toattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823B";
167
-		$properties["ccattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823C";
168
-
169
-		$this->proptags = getPropIdsFromStrings($store, $properties);
170
-
171
-		$properties2["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
172
-		$this->proptags2 = getPropIdsFromStrings($store, $properties2);
173
-	}
174
-
175
-	/**
176
-	 * Sets the direct booking property. This is an alternative to the setting of the direct booking
177
-	 * property through the constructor. However, setting it in the constructor is preferred.
178
-	 *
179
-	 * @param bool $directBookingSetting
180
-	 */
181
-	public function setDirectBooking($directBookingSetting) {
182
-		$this->enableDirectBooking = $directBookingSetting;
183
-	}
184
-
185
-	/**
186
-	 * Returns TRUE if the message pointed to is an incoming meeting request and should
187
-	 * therefore be replied to with doAccept or doDecline().
188
-	 */
189
-	public function isMeetingRequest() {
190
-		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
191
-
192
-		if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request") {
193
-			return true;
194
-		}
195
-	}
196
-
197
-	/**
198
-	 * Returns TRUE if the message pointed to is a returning meeting request response.
199
-	 */
200
-	public function isMeetingRequestResponse() {
201
-		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
202
-
203
-		if (isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp") === 0) {
204
-			return true;
205
-		}
206
-	}
207
-
208
-	/**
209
-	 * Returns TRUE if the message pointed to is a cancellation request.
210
-	 */
211
-	public function isMeetingCancellation() {
212
-		$props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
213
-
214
-		if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Canceled") {
215
-			return true;
216
-		}
217
-	}
218
-
219
-	/**
220
-	 * Process an incoming meeting request response as Delegate. This will update the appointment
221
-	 * in the organizer's calendar.
222
-	 *
223
-	 * @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
224
-	 * of corresponding meeting in Calendar
225
-	 */
226
-	public function processMeetingRequestResponseAsDelegate() {
227
-		if (!$this->isMeetingRequestResponse()) {
228
-			return;
229
-		}
230
-
231
-		$messageprops = mapi_getprops($this->message);
232
-
233
-		$goid2 = $messageprops[$this->proptags['goid2']];
234
-
235
-		if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
236
-			return;
237
-		}
238
-
239
-		// Find basedate in GlobalID(0x3), this can be a response for an occurrence
240
-		$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
241
-
242
-		if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
243
-			$delegatorStore = $this->getDelegatorStore($messageprops);
244
-			$userStore = $delegatorStore['store'];
245
-			$calFolder = $delegatorStore['calFolder'];
246
-
247
-			if ($calFolder) {
248
-				$calendaritems = $this->findCalendarItems($goid2, $calFolder);
249
-
250
-				// $calendaritems now contains the ENTRYIDs of all the calendar items to which
251
-				// this meeting request points.
252
-
253
-				// Open the calendar items, and update all the recipients of the calendar item that match
254
-				// the email address of the response.
255
-				if (!empty($calendaritems)) {
256
-					return $this->processResponse($userStore, $calendaritems[0], $basedate, $messageprops);
257
-				}
258
-
259
-				return false;
260
-			}
261
-		}
262
-	}
263
-
264
-	/**
265
-	 * Process an incoming meeting request response. This updates the appointment
266
-	 * in your calendar to show whether the user has accepted or declined.
267
-	 *
268
-	 * @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
269
-	 * of corresponding meeting in Calendar
270
-	 */
271
-	public function processMeetingRequestResponse() {
272
-		if (!$this->isLocalOrganiser()) {
273
-			return;
274
-		}
275
-
276
-		if (!$this->isMeetingRequestResponse()) {
277
-			return;
278
-		}
279
-
280
-		// Get information we need from the response message
281
-		$messageprops = mapi_getprops($this->message, [
282
-			$this->proptags['goid'],
283
-			$this->proptags['goid2'],
284
-			PR_OWNER_APPT_ID,
285
-			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
286
-			PR_SENT_REPRESENTING_NAME,
287
-			PR_SENT_REPRESENTING_ADDRTYPE,
288
-			PR_SENT_REPRESENTING_ENTRYID,
289
-			PR_MESSAGE_DELIVERY_TIME,
290
-			PR_MESSAGE_CLASS,
291
-			PR_PROCESSED,
292
-			$this->proptags['proposed_start_whole'],
293
-			$this->proptags['proposed_end_whole'],
294
-			$this->proptags['proposed_duration'],
295
-			$this->proptags['counter_proposal'],
296
-			$this->proptags['attendee_critical_change'], ]);
297
-
298
-		$goid2 = $messageprops[$this->proptags['goid2']];
299
-
300
-		if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
301
-			return;
302
-		}
303
-
304
-		// Find basedate in GlobalID(0x3), this can be a response for an occurrence
305
-		$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
306
-
307
-		$calendaritems = $this->findCalendarItems($goid2);
308
-
309
-		// $calendaritems now contains the ENTRYIDs of all the calendar items to which
310
-		// this meeting request points.
311
-
312
-		// Open the calendar items, and update all the recipients of the calendar item that match
313
-		// the email address of the response.
314
-		if (!empty($calendaritems)) {
315
-			return $this->processResponse($this->store, $calendaritems[0], $basedate, $messageprops);
316
-		}
317
-
318
-		return false;
319
-	}
320
-
321
-	/**
322
-	 * Process every incoming MeetingRequest response.This updates the appointment
323
-	 * in your calendar to show whether the user has accepted or declined.
324
-	 *
325
-	 *@param resource $store contains the userStore in which the meeting is created
326
-	 *@param $entryid contains the ENTRYID of the calendar items to which this meeting request points
327
-	 *@param bool $basedate if present the create an exception
328
-	 *@param array $messageprops contains m3/17/2010essage properties
329
-	 *
330
-	 *@return entryids(storeid, parententryid, entryid, also basedate if response is occurrence) of corresponding meeting in Calendar
331
-	 */
332
-	public function processResponse($store, $entryid, $basedate, $messageprops) {
333
-		$data = [];
334
-		$senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
335
-		$messageclass = $messageprops[PR_MESSAGE_CLASS];
336
-		$deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
337
-
338
-		// Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
339
-		// the email address of the response.
340
-		$calendaritem = mapi_msgstore_openentry($store, $entryid);
341
-		$calendaritemProps = mapi_getprops($calendaritem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
342
-
343
-		$data["storeid"] = bin2hex($calendaritemProps[PR_STORE_ENTRYID]);
344
-		$data["parententryid"] = bin2hex($calendaritemProps[PR_PARENT_ENTRYID]);
345
-		$data["entryid"] = bin2hex($calendaritemProps[PR_ENTRYID]);
346
-		$data["basedate"] = $basedate;
347
-		$data["updatecounter"] = isset($calendaritemProps[$this->proptags['updatecounter']]) ? $calendaritemProps[$this->proptags['updatecounter']] : 0;
348
-
349
-		/* Check if meeting is updated or not in the organizer's calendar */
350
-		$data["meeting_updated"] = $this->isMeetingUpdated();
351
-
352
-		if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
353
-			// meeting is already processed
354
-			return $data;
355
-		}
356
-		mapi_setprops($this->message, [PR_PROCESSED => true]);
357
-		mapi_savechanges($this->message);
358
-
359
-		/* If the meeting is updated in the organizer's calendar, we do not need to process the old response. */
360
-		if ($data['meeting_updated'] === true) {
361
-			return $data;
362
-		}
363
-
364
-		// If basedate is found, then create/modify exception msg and do processing
365
-		if ($basedate && $calendaritemProps[$this->proptags['recurring']]) {
366
-			$recurr = new Recurrence($store, $calendaritem);
367
-
368
-			// Copy properties from meeting request
369
-			$exception_props = mapi_getprops($this->message, [PR_OWNER_APPT_ID,
370
-				$this->proptags['proposed_start_whole'],
371
-				$this->proptags['proposed_end_whole'],
372
-				$this->proptags['proposed_duration'],
373
-				$this->proptags['counter_proposal'],
374
-			]);
375
-
376
-			// Create/modify exception
377
-			if ($recurr->isException($basedate)) {
378
-				$recurr->modifyException($exception_props, $basedate);
379
-			}
380
-			else {
381
-				// When we are creating an exception we need copy recipients from main recurring item
382
-				$recipTable = mapi_message_getrecipienttable($calendaritem);
383
-				$recips = mapi_table_queryallrows($recipTable, $this->recipprops);
384
-
385
-				// Retrieve actual start/due dates from calendar item.
386
-				$exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
387
-				$exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
388
-
389
-				$recurr->createException($exception_props, $basedate, false, $recips);
390
-			}
391
-
392
-			mapi_savechanges($calendaritem);
393
-
394
-			$attach = $recurr->getExceptionAttachment($basedate);
395
-			if (!$attach) {
396
-				return $false;
397
-			}
398
-			$recurringItem = $calendaritem;
399
-			$calendaritem = mapi_attach_openobj($attach, MAPI_MODIFY);
400
-		}
401
-
402
-		// Get the recipients of the calendar item
403
-		$reciptable = mapi_message_getrecipienttable($calendaritem);
404
-		$recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
405
-
406
-		// FIXME we should look at the updatecounter property and compare it
407
-		// to the counter in the recipient to see if this update is actually
408
-		// newer than the status in the calendar item
409
-		$found = false;
410
-
411
-		$totalrecips = 0;
412
-		$acceptedrecips = 0;
413
-		foreach ($recipients as $recipient) {
414
-			++$totalrecips;
415
-			if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
416
-				$found = true;
417
-
418
-				/*
68
+    // All properties for a recipient that are interesting
69
+    public $recipprops = [
70
+        PR_ENTRYID,
71
+        PR_DISPLAY_NAME,
72
+        PR_EMAIL_ADDRESS,
73
+        PR_RECIPIENT_ENTRYID,
74
+        PR_RECIPIENT_TYPE,
75
+        PR_SEND_INTERNET_ENCODING,
76
+        PR_SEND_RICH_INFO,
77
+        PR_RECIPIENT_DISPLAY_NAME,
78
+        PR_ADDRTYPE,
79
+        PR_DISPLAY_TYPE,
80
+        PR_RECIPIENT_TRACKSTATUS,
81
+        PR_RECIPIENT_TRACKSTATUS_TIME,
82
+        PR_RECIPIENT_FLAGS,
83
+        PR_ROWID,
84
+        PR_OBJECT_TYPE,
85
+        PR_SEARCH_KEY,
86
+    ];
87
+
88
+    /**
89
+     * Indication whether the setting of resources in a Meeting Request is success (false) or if it
90
+     * has failed (integer).
91
+     */
92
+    public $errorSetResource;
93
+
94
+    /**
95
+     * Takes a store and a message. The message is an appointment item
96
+     * that should be converted into a meeting request or an incoming
97
+     * e-mail message that is a meeting request.
98
+     *
99
+     * The $session variable is optional, but required if the following features
100
+     * are to be used:
101
+     *
102
+     * - Sending meeting requests for meetings that are not in your own store
103
+     * - Sending meeting requests to resources, resource availability checking and resource freebusy updates
104
+     *
105
+     * @param mixed $store
106
+     * @param mixed $message
107
+     * @param mixed $session
108
+     * @param mixed $enableDirectBooking
109
+     */
110
+    public function __construct($store, $message, $session = false, $enableDirectBooking = true) {
111
+        $this->store = $store;
112
+        $this->message = $message;
113
+        $this->session = $session;
114
+        // This variable string saves time information for the MR.
115
+        $this->meetingTimeInfo = false;
116
+        $this->enableDirectBooking = $enableDirectBooking;
117
+
118
+        $properties["goid"] = "PT_BINARY:PSETID_Meeting:0x3";
119
+        $properties["goid2"] = "PT_BINARY:PSETID_Meeting:0x23";
120
+        $properties["type"] = "PT_STRING8:PSETID_Meeting:0x24";
121
+        $properties["meetingrecurring"] = "PT_BOOLEAN:PSETID_Meeting:0x5";
122
+        $properties["unknown2"] = "PT_BOOLEAN:PSETID_Meeting:0xa";
123
+        $properties["attendee_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1";
124
+        $properties["owner_critical_change"] = "PT_SYSTIME:PSETID_Meeting:0x1a";
125
+        $properties["meetingstatus"] = "PT_LONG:PSETID_Appointment:0x8217";
126
+        $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
127
+        $properties["unknown6"] = "PT_LONG:PSETID_Meeting:0x4";
128
+        $properties["replytime"] = "PT_SYSTIME:PSETID_Appointment:0x8220";
129
+        $properties["usetnef"] = "PT_BOOLEAN:PSETID_Common:0x8582";
130
+        $properties["recurrence_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
131
+        $properties["reminderminutes"] = "PT_LONG:PSETID_Common:0x8501";
132
+        $properties["reminderset"] = "PT_BOOLEAN:PSETID_Common:0x8503";
133
+        $properties["sendasical"] = "PT_BOOLEAN:PSETID_Appointment:0x8200";
134
+        $properties["updatecounter"] = "PT_LONG:PSETID_Appointment:0x8201";					// AppointmentSequenceNumber
135
+        $properties["last_updatecounter"] = "PT_LONG:PSETID_Appointment:0x8203";			// AppointmentLastSequence
136
+        $properties["unknown7"] = "PT_LONG:PSETID_Appointment:0x8202";
137
+        $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
138
+        $properties["intendedbusystatus"] = "PT_LONG:PSETID_Appointment:0x8224";
139
+        $properties["start"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
140
+        $properties["responselocation"] = "PT_STRING8:PSETID_Meeting:0x2";
141
+        $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
142
+        $properties["requestsent"] = "PT_BOOLEAN:PSETID_Appointment:0x8229";		// PidLidFInvited, MeetingRequestWasSent
143
+        $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
144
+        $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
145
+        $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
146
+        $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
147
+        $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
148
+        $properties["clipstart"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
149
+        $properties["clipend"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
150
+        $properties["start_recur_date"] = "PT_LONG:PSETID_Meeting:0xD";				// StartRecurTime
151
+        $properties["start_recur_time"] = "PT_LONG:PSETID_Meeting:0xE";				// StartRecurTime
152
+        $properties["end_recur_date"] = "PT_LONG:PSETID_Meeting:0xF";				// EndRecurDate
153
+        $properties["end_recur_time"] = "PT_LONG:PSETID_Meeting:0x10";				// EndRecurTime
154
+        $properties["is_exception"] = "PT_BOOLEAN:PSETID_Meeting:0xA";				// LID_IS_EXCEPTION
155
+        $properties["apptreplyname"] = "PT_STRING8:PSETID_Appointment:0x8230";
156
+        // Propose new time properties
157
+        $properties["proposed_start_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8250";
158
+        $properties["proposed_end_whole"] = "PT_SYSTIME:PSETID_Appointment:0x8251";
159
+        $properties["proposed_duration"] = "PT_LONG:PSETID_Appointment:0x8256";
160
+        $properties["counter_proposal"] = "PT_BOOLEAN:PSETID_Appointment:0x8257";
161
+        $properties["recurring_pattern"] = "PT_STRING8:PSETID_Appointment:0x8232";
162
+        $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
163
+        $properties["meetingtype"] = "PT_LONG:PSETID_Meeting:0x26";
164
+        $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
165
+        $properties["timezone"] = "PT_STRING8:PSETID_Appointment:0x8234";
166
+        $properties["toattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823B";
167
+        $properties["ccattendeesstring"] = "PT_STRING8:PSETID_Appointment:0x823C";
168
+
169
+        $this->proptags = getPropIdsFromStrings($store, $properties);
170
+
171
+        $properties2["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
172
+        $this->proptags2 = getPropIdsFromStrings($store, $properties2);
173
+    }
174
+
175
+    /**
176
+     * Sets the direct booking property. This is an alternative to the setting of the direct booking
177
+     * property through the constructor. However, setting it in the constructor is preferred.
178
+     *
179
+     * @param bool $directBookingSetting
180
+     */
181
+    public function setDirectBooking($directBookingSetting) {
182
+        $this->enableDirectBooking = $directBookingSetting;
183
+    }
184
+
185
+    /**
186
+     * Returns TRUE if the message pointed to is an incoming meeting request and should
187
+     * therefore be replied to with doAccept or doDecline().
188
+     */
189
+    public function isMeetingRequest() {
190
+        $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
191
+
192
+        if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request") {
193
+            return true;
194
+        }
195
+    }
196
+
197
+    /**
198
+     * Returns TRUE if the message pointed to is a returning meeting request response.
199
+     */
200
+    public function isMeetingRequestResponse() {
201
+        $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
202
+
203
+        if (isset($props[PR_MESSAGE_CLASS]) && strpos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp") === 0) {
204
+            return true;
205
+        }
206
+    }
207
+
208
+    /**
209
+     * Returns TRUE if the message pointed to is a cancellation request.
210
+     */
211
+    public function isMeetingCancellation() {
212
+        $props = mapi_getprops($this->message, [PR_MESSAGE_CLASS]);
213
+
214
+        if (isset($props[PR_MESSAGE_CLASS]) && $props[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Canceled") {
215
+            return true;
216
+        }
217
+    }
218
+
219
+    /**
220
+     * Process an incoming meeting request response as Delegate. This will update the appointment
221
+     * in the organizer's calendar.
222
+     *
223
+     * @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
224
+     * of corresponding meeting in Calendar
225
+     */
226
+    public function processMeetingRequestResponseAsDelegate() {
227
+        if (!$this->isMeetingRequestResponse()) {
228
+            return;
229
+        }
230
+
231
+        $messageprops = mapi_getprops($this->message);
232
+
233
+        $goid2 = $messageprops[$this->proptags['goid2']];
234
+
235
+        if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
236
+            return;
237
+        }
238
+
239
+        // Find basedate in GlobalID(0x3), this can be a response for an occurrence
240
+        $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
241
+
242
+        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
243
+            $delegatorStore = $this->getDelegatorStore($messageprops);
244
+            $userStore = $delegatorStore['store'];
245
+            $calFolder = $delegatorStore['calFolder'];
246
+
247
+            if ($calFolder) {
248
+                $calendaritems = $this->findCalendarItems($goid2, $calFolder);
249
+
250
+                // $calendaritems now contains the ENTRYIDs of all the calendar items to which
251
+                // this meeting request points.
252
+
253
+                // Open the calendar items, and update all the recipients of the calendar item that match
254
+                // the email address of the response.
255
+                if (!empty($calendaritems)) {
256
+                    return $this->processResponse($userStore, $calendaritems[0], $basedate, $messageprops);
257
+                }
258
+
259
+                return false;
260
+            }
261
+        }
262
+    }
263
+
264
+    /**
265
+     * Process an incoming meeting request response. This updates the appointment
266
+     * in your calendar to show whether the user has accepted or declined.
267
+     *
268
+     * @returns the entryids(storeid, parententryid, entryid, also basedate if response is occurrence)
269
+     * of corresponding meeting in Calendar
270
+     */
271
+    public function processMeetingRequestResponse() {
272
+        if (!$this->isLocalOrganiser()) {
273
+            return;
274
+        }
275
+
276
+        if (!$this->isMeetingRequestResponse()) {
277
+            return;
278
+        }
279
+
280
+        // Get information we need from the response message
281
+        $messageprops = mapi_getprops($this->message, [
282
+            $this->proptags['goid'],
283
+            $this->proptags['goid2'],
284
+            PR_OWNER_APPT_ID,
285
+            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
286
+            PR_SENT_REPRESENTING_NAME,
287
+            PR_SENT_REPRESENTING_ADDRTYPE,
288
+            PR_SENT_REPRESENTING_ENTRYID,
289
+            PR_MESSAGE_DELIVERY_TIME,
290
+            PR_MESSAGE_CLASS,
291
+            PR_PROCESSED,
292
+            $this->proptags['proposed_start_whole'],
293
+            $this->proptags['proposed_end_whole'],
294
+            $this->proptags['proposed_duration'],
295
+            $this->proptags['counter_proposal'],
296
+            $this->proptags['attendee_critical_change'], ]);
297
+
298
+        $goid2 = $messageprops[$this->proptags['goid2']];
299
+
300
+        if (!isset($goid2) || !isset($messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS])) {
301
+            return;
302
+        }
303
+
304
+        // Find basedate in GlobalID(0x3), this can be a response for an occurrence
305
+        $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
306
+
307
+        $calendaritems = $this->findCalendarItems($goid2);
308
+
309
+        // $calendaritems now contains the ENTRYIDs of all the calendar items to which
310
+        // this meeting request points.
311
+
312
+        // Open the calendar items, and update all the recipients of the calendar item that match
313
+        // the email address of the response.
314
+        if (!empty($calendaritems)) {
315
+            return $this->processResponse($this->store, $calendaritems[0], $basedate, $messageprops);
316
+        }
317
+
318
+        return false;
319
+    }
320
+
321
+    /**
322
+     * Process every incoming MeetingRequest response.This updates the appointment
323
+     * in your calendar to show whether the user has accepted or declined.
324
+     *
325
+     *@param resource $store contains the userStore in which the meeting is created
326
+     *@param $entryid contains the ENTRYID of the calendar items to which this meeting request points
327
+     *@param bool $basedate if present the create an exception
328
+     *@param array $messageprops contains m3/17/2010essage properties
329
+     *
330
+     *@return entryids(storeid, parententryid, entryid, also basedate if response is occurrence) of corresponding meeting in Calendar
331
+     */
332
+    public function processResponse($store, $entryid, $basedate, $messageprops) {
333
+        $data = [];
334
+        $senderentryid = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
335
+        $messageclass = $messageprops[PR_MESSAGE_CLASS];
336
+        $deliverytime = $messageprops[PR_MESSAGE_DELIVERY_TIME];
337
+
338
+        // Open the calendar item, find the sender in the recipient table and update all the recipients of the calendar item that match
339
+        // the email address of the response.
340
+        $calendaritem = mapi_msgstore_openentry($store, $entryid);
341
+        $calendaritemProps = mapi_getprops($calendaritem, [$this->proptags['recurring'], PR_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_ENTRYID, $this->proptags['updatecounter']]);
342
+
343
+        $data["storeid"] = bin2hex($calendaritemProps[PR_STORE_ENTRYID]);
344
+        $data["parententryid"] = bin2hex($calendaritemProps[PR_PARENT_ENTRYID]);
345
+        $data["entryid"] = bin2hex($calendaritemProps[PR_ENTRYID]);
346
+        $data["basedate"] = $basedate;
347
+        $data["updatecounter"] = isset($calendaritemProps[$this->proptags['updatecounter']]) ? $calendaritemProps[$this->proptags['updatecounter']] : 0;
348
+
349
+        /* Check if meeting is updated or not in the organizer's calendar */
350
+        $data["meeting_updated"] = $this->isMeetingUpdated();
351
+
352
+        if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
353
+            // meeting is already processed
354
+            return $data;
355
+        }
356
+        mapi_setprops($this->message, [PR_PROCESSED => true]);
357
+        mapi_savechanges($this->message);
358
+
359
+        /* If the meeting is updated in the organizer's calendar, we do not need to process the old response. */
360
+        if ($data['meeting_updated'] === true) {
361
+            return $data;
362
+        }
363
+
364
+        // If basedate is found, then create/modify exception msg and do processing
365
+        if ($basedate && $calendaritemProps[$this->proptags['recurring']]) {
366
+            $recurr = new Recurrence($store, $calendaritem);
367
+
368
+            // Copy properties from meeting request
369
+            $exception_props = mapi_getprops($this->message, [PR_OWNER_APPT_ID,
370
+                $this->proptags['proposed_start_whole'],
371
+                $this->proptags['proposed_end_whole'],
372
+                $this->proptags['proposed_duration'],
373
+                $this->proptags['counter_proposal'],
374
+            ]);
375
+
376
+            // Create/modify exception
377
+            if ($recurr->isException($basedate)) {
378
+                $recurr->modifyException($exception_props, $basedate);
379
+            }
380
+            else {
381
+                // When we are creating an exception we need copy recipients from main recurring item
382
+                $recipTable = mapi_message_getrecipienttable($calendaritem);
383
+                $recips = mapi_table_queryallrows($recipTable, $this->recipprops);
384
+
385
+                // Retrieve actual start/due dates from calendar item.
386
+                $exception_props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
387
+                $exception_props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
388
+
389
+                $recurr->createException($exception_props, $basedate, false, $recips);
390
+            }
391
+
392
+            mapi_savechanges($calendaritem);
393
+
394
+            $attach = $recurr->getExceptionAttachment($basedate);
395
+            if (!$attach) {
396
+                return $false;
397
+            }
398
+            $recurringItem = $calendaritem;
399
+            $calendaritem = mapi_attach_openobj($attach, MAPI_MODIFY);
400
+        }
401
+
402
+        // Get the recipients of the calendar item
403
+        $reciptable = mapi_message_getrecipienttable($calendaritem);
404
+        $recipients = mapi_table_queryallrows($reciptable, $this->recipprops);
405
+
406
+        // FIXME we should look at the updatecounter property and compare it
407
+        // to the counter in the recipient to see if this update is actually
408
+        // newer than the status in the calendar item
409
+        $found = false;
410
+
411
+        $totalrecips = 0;
412
+        $acceptedrecips = 0;
413
+        foreach ($recipients as $recipient) {
414
+            ++$totalrecips;
415
+            if (isset($recipient[PR_ENTRYID]) && $this->compareABEntryIDs($recipient[PR_ENTRYID], $senderentryid)) {
416
+                $found = true;
417
+
418
+                /*
419 419
 				 * If value of attendee_critical_change on meeting response mail is less than PR_RECIPIENT_TRACKSTATUS_TIME
420 420
 				 * on the corresponding recipientRow of meeting then we ignore this response mail.
421 421
 				 */
422
-				if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
423
-					continue;
424
-				}
425
-
426
-				// The email address matches, update the row
427
-				$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
428
-				$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
429
-
430
-				// If this is a counter proposal, set the proposal properties in the recipient row
431
-				if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
432
-					$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
433
-					$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
434
-					$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
435
-				}
436
-
437
-				mapi_message_modifyrecipients($calendaritem, MODRECIP_MODIFY, [$recipient]);
438
-			}
439
-			if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
440
-				++$acceptedrecips;
441
-			}
442
-		}
443
-
444
-		// If the recipient was not found in the original calendar item,
445
-		// then add the recpient as a new optional recipient
446
-		if (!$found) {
447
-			$recipient = [];
448
-			$recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
449
-			$recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
450
-			$recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
451
-			$recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
452
-			$recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
453
-			$recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
454
-			$recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
455
-			$recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
456
-
457
-			// If this is a counter proposal, set the proposal properties in the recipient row
458
-			if (isset($messageprops[$this->proptags['counter_proposal']])) {
459
-				$recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
460
-				$recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
461
-				$recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
462
-			}
463
-
464
-			mapi_message_modifyrecipients($calendaritem, MODRECIP_ADD, [$recipient]);
465
-			++$totalrecips;
466
-			if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
467
-				++$acceptedrecips;
468
-			}
469
-		}
470
-
471
-		// TODO: Update counter proposal number property on message
472
-		/*
422
+                if (isset($recipient[PR_RECIPIENT_TRACKSTATUS_TIME]) && ($messageprops[$this->proptags['attendee_critical_change']] < $recipient[PR_RECIPIENT_TRACKSTATUS_TIME])) {
423
+                    continue;
424
+                }
425
+
426
+                // The email address matches, update the row
427
+                $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
428
+                $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $messageprops[$this->proptags['attendee_critical_change']];
429
+
430
+                // If this is a counter proposal, set the proposal properties in the recipient row
431
+                if (isset($messageprops[$this->proptags['counter_proposal']]) && $messageprops[$this->proptags['counter_proposal']]) {
432
+                    $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
433
+                    $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
434
+                    $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
435
+                }
436
+
437
+                mapi_message_modifyrecipients($calendaritem, MODRECIP_MODIFY, [$recipient]);
438
+            }
439
+            if (isset($recipient[PR_RECIPIENT_TRACKSTATUS]) && $recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
440
+                ++$acceptedrecips;
441
+            }
442
+        }
443
+
444
+        // If the recipient was not found in the original calendar item,
445
+        // then add the recpient as a new optional recipient
446
+        if (!$found) {
447
+            $recipient = [];
448
+            $recipient[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
449
+            $recipient[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
450
+            $recipient[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
451
+            $recipient[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
452
+            $recipient[PR_RECIPIENT_TYPE] = MAPI_CC;
453
+            $recipient[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
454
+            $recipient[PR_RECIPIENT_TRACKSTATUS] = $this->getTrackStatus($messageclass);
455
+            $recipient[PR_RECIPIENT_TRACKSTATUS_TIME] = $deliverytime;
456
+
457
+            // If this is a counter proposal, set the proposal properties in the recipient row
458
+            if (isset($messageprops[$this->proptags['counter_proposal']])) {
459
+                $recipient[PR_PROPOSENEWTIME_START] = $messageprops[$this->proptags['proposed_start_whole']];
460
+                $recipient[PR_PROPOSENEWTIME_END] = $messageprops[$this->proptags['proposed_end_whole']];
461
+                $recipient[PR_PROPOSEDNEWTIME] = $messageprops[$this->proptags['counter_proposal']];
462
+            }
463
+
464
+            mapi_message_modifyrecipients($calendaritem, MODRECIP_ADD, [$recipient]);
465
+            ++$totalrecips;
466
+            if ($recipient[PR_RECIPIENT_TRACKSTATUS] == olRecipientTrackStatusAccepted) {
467
+                ++$acceptedrecips;
468
+            }
469
+        }
470
+
471
+        // TODO: Update counter proposal number property on message
472
+        /*
473 473
 		If it is the first time this attendee has proposed a new date/time, increment the value of the PidLidAppointmentProposalNumber property on the organizer's meeting object, by 0x00000001. If this property did not previously exist on the organizer's meeting object, it MUST be set with a value of 0x00000001.
474 474
 		*/
475
-		// If this is a counter proposal, set the counter proposal indicator boolean
476
-		if (isset($messageprops[$this->proptags['counter_proposal']])) {
477
-			$props = [];
478
-			if ($messageprops[$this->proptags['counter_proposal']]) {
479
-				$props[$this->proptags['counter_proposal']] = true;
480
-			}
481
-			else {
482
-				$props[$this->proptags['counter_proposal']] = false;
483
-			}
484
-			mapi_setprops($calendaritem, $props);
485
-		}
486
-
487
-		mapi_savechanges($calendaritem);
488
-		if (isset($attach)) {
489
-			mapi_savechanges($attach);
490
-			mapi_savechanges($recurringItem);
491
-		}
492
-
493
-		return $data;
494
-	}
495
-
496
-	/**
497
-	 * Process an incoming meeting request cancellation. This updates the
498
-	 * appointment in your calendar to show that the meeting has been cancelled.
499
-	 */
500
-	public function processMeetingCancellation() {
501
-		if ($this->isLocalOrganiser()) {
502
-			return;
503
-		}
504
-
505
-		if (!$this->isMeetingCancellation()) {
506
-			return;
507
-		}
508
-
509
-		if (!$this->isInCalendar()) {
510
-			return;
511
-		}
512
-
513
-		$listProperties = $this->proptags;
514
-		$listProperties['subject'] = PR_SUBJECT;
515
-		$listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
516
-		$listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
517
-		$listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
518
-		$listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
519
-		$listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
520
-		$listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
521
-		$messageprops = mapi_getprops($this->message, $listProperties);
522
-		$store = $this->store;
523
-
524
-		$goid = $messageprops[$this->proptags['goid']];	// GlobalID (0x3)
525
-		if (!isset($goid)) {
526
-			return;
527
-		}
528
-
529
-		if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
530
-			$delegatorStore = $this->getDelegatorStore($messageprops);
531
-			$store = $delegatorStore['store'];
532
-			$calFolder = $delegatorStore['calFolder'];
533
-		}
534
-		else {
535
-			$calFolder = $this->openDefaultCalendar();
536
-		}
537
-
538
-		// First, find the items in the calendar by GOID
539
-		$calendaritems = $this->findCalendarItems($goid, $calFolder);
540
-		$basedate = $this->getBasedateFromGlobalID($goid);
541
-
542
-		if ($basedate) {
543
-			// Calendaritems with GlobalID were not found, so find main recurring item using CleanGlobalID(0x23)
544
-			if (empty($calendaritems)) {
545
-				// This meeting req is of an occurrence
546
-				$goid2 = $messageprops[$this->proptags['goid2']];
547
-
548
-				// First, find the items in the calendar by GOID
549
-				$calendaritems = $this->findCalendarItems($goid2);
550
-				foreach ($calendaritems as $entryid) {
551
-					// Open each calendar item and set the properties of the cancellation object
552
-					$calendaritem = mapi_msgstore_openentry($store, $entryid);
553
-
554
-					if ($calendaritem) {
555
-						$calendaritemProps = mapi_getprops($calendaritem, [$this->proptags['recurring']]);
556
-						if ($calendaritemProps[$this->proptags['recurring']]) {
557
-							$recurr = new Recurrence($store, $calendaritem);
558
-
559
-							// Set message class
560
-							$messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
561
-
562
-							if ($recurr->isException($basedate)) {
563
-								$recurr->modifyException($messageprops, $basedate);
564
-							}
565
-							else {
566
-								$recurr->createException($messageprops, $basedate);
567
-							}
568
-						}
569
-						mapi_savechanges($calendaritem);
570
-					}
571
-				}
572
-			}
573
-		}
574
-
575
-		if (!isset($calendaritem)) {
576
-			foreach ($calendaritems as $entryid) {
577
-				// Open each calendar item and set the properties of the cancellation object
578
-				$calendaritem = mapi_msgstore_openentry($store, $entryid);
579
-				mapi_setprops($calendaritem, $messageprops);
580
-				mapi_savechanges($calendaritem);
581
-			}
582
-		}
583
-	}
584
-
585
-	/**
586
-	 * Returns true if the item is already in the calendar.
587
-	 */
588
-	public function isInCalendar() {
589
-		$messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME]);
590
-		$goid = $messageprops[$this->proptags['goid']];
591
-		if (isset($messageprops[$this->proptags['goid2']])) {
592
-			$goid2 = $messageprops[$this->proptags['goid2']];
593
-		}
594
-
595
-		$basedate = $this->getBasedateFromGlobalID($goid);
596
-
597
-		if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
598
-			$delegatorStore = $this->getDelegatorStore($messageprops);
599
-			$calFolder = $delegatorStore['calFolder'];
600
-		}
601
-		else {
602
-			$calFolder = $this->openDefaultCalendar();
603
-		}
604
-		/*
475
+        // If this is a counter proposal, set the counter proposal indicator boolean
476
+        if (isset($messageprops[$this->proptags['counter_proposal']])) {
477
+            $props = [];
478
+            if ($messageprops[$this->proptags['counter_proposal']]) {
479
+                $props[$this->proptags['counter_proposal']] = true;
480
+            }
481
+            else {
482
+                $props[$this->proptags['counter_proposal']] = false;
483
+            }
484
+            mapi_setprops($calendaritem, $props);
485
+        }
486
+
487
+        mapi_savechanges($calendaritem);
488
+        if (isset($attach)) {
489
+            mapi_savechanges($attach);
490
+            mapi_savechanges($recurringItem);
491
+        }
492
+
493
+        return $data;
494
+    }
495
+
496
+    /**
497
+     * Process an incoming meeting request cancellation. This updates the
498
+     * appointment in your calendar to show that the meeting has been cancelled.
499
+     */
500
+    public function processMeetingCancellation() {
501
+        if ($this->isLocalOrganiser()) {
502
+            return;
503
+        }
504
+
505
+        if (!$this->isMeetingCancellation()) {
506
+            return;
507
+        }
508
+
509
+        if (!$this->isInCalendar()) {
510
+            return;
511
+        }
512
+
513
+        $listProperties = $this->proptags;
514
+        $listProperties['subject'] = PR_SUBJECT;
515
+        $listProperties['sent_representing_name'] = PR_SENT_REPRESENTING_NAME;
516
+        $listProperties['sent_representing_address_type'] = PR_SENT_REPRESENTING_ADDRTYPE;
517
+        $listProperties['sent_representing_email_address'] = PR_SENT_REPRESENTING_EMAIL_ADDRESS;
518
+        $listProperties['sent_representing_entryid'] = PR_SENT_REPRESENTING_ENTRYID;
519
+        $listProperties['sent_representing_search_key'] = PR_SENT_REPRESENTING_SEARCH_KEY;
520
+        $listProperties['rcvd_representing_name'] = PR_RCVD_REPRESENTING_NAME;
521
+        $messageprops = mapi_getprops($this->message, $listProperties);
522
+        $store = $this->store;
523
+
524
+        $goid = $messageprops[$this->proptags['goid']];	// GlobalID (0x3)
525
+        if (!isset($goid)) {
526
+            return;
527
+        }
528
+
529
+        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
530
+            $delegatorStore = $this->getDelegatorStore($messageprops);
531
+            $store = $delegatorStore['store'];
532
+            $calFolder = $delegatorStore['calFolder'];
533
+        }
534
+        else {
535
+            $calFolder = $this->openDefaultCalendar();
536
+        }
537
+
538
+        // First, find the items in the calendar by GOID
539
+        $calendaritems = $this->findCalendarItems($goid, $calFolder);
540
+        $basedate = $this->getBasedateFromGlobalID($goid);
541
+
542
+        if ($basedate) {
543
+            // Calendaritems with GlobalID were not found, so find main recurring item using CleanGlobalID(0x23)
544
+            if (empty($calendaritems)) {
545
+                // This meeting req is of an occurrence
546
+                $goid2 = $messageprops[$this->proptags['goid2']];
547
+
548
+                // First, find the items in the calendar by GOID
549
+                $calendaritems = $this->findCalendarItems($goid2);
550
+                foreach ($calendaritems as $entryid) {
551
+                    // Open each calendar item and set the properties of the cancellation object
552
+                    $calendaritem = mapi_msgstore_openentry($store, $entryid);
553
+
554
+                    if ($calendaritem) {
555
+                        $calendaritemProps = mapi_getprops($calendaritem, [$this->proptags['recurring']]);
556
+                        if ($calendaritemProps[$this->proptags['recurring']]) {
557
+                            $recurr = new Recurrence($store, $calendaritem);
558
+
559
+                            // Set message class
560
+                            $messageprops[PR_MESSAGE_CLASS] = 'IPM.Appointment';
561
+
562
+                            if ($recurr->isException($basedate)) {
563
+                                $recurr->modifyException($messageprops, $basedate);
564
+                            }
565
+                            else {
566
+                                $recurr->createException($messageprops, $basedate);
567
+                            }
568
+                        }
569
+                        mapi_savechanges($calendaritem);
570
+                    }
571
+                }
572
+            }
573
+        }
574
+
575
+        if (!isset($calendaritem)) {
576
+            foreach ($calendaritems as $entryid) {
577
+                // Open each calendar item and set the properties of the cancellation object
578
+                $calendaritem = mapi_msgstore_openentry($store, $entryid);
579
+                mapi_setprops($calendaritem, $messageprops);
580
+                mapi_savechanges($calendaritem);
581
+            }
582
+        }
583
+    }
584
+
585
+    /**
586
+     * Returns true if the item is already in the calendar.
587
+     */
588
+    public function isInCalendar() {
589
+        $messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME]);
590
+        $goid = $messageprops[$this->proptags['goid']];
591
+        if (isset($messageprops[$this->proptags['goid2']])) {
592
+            $goid2 = $messageprops[$this->proptags['goid2']];
593
+        }
594
+
595
+        $basedate = $this->getBasedateFromGlobalID($goid);
596
+
597
+        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
598
+            $delegatorStore = $this->getDelegatorStore($messageprops);
599
+            $calFolder = $delegatorStore['calFolder'];
600
+        }
601
+        else {
602
+            $calFolder = $this->openDefaultCalendar();
603
+        }
604
+        /*
605 605
 		 * If basedate is found in globalID, then there are two possibilities.
606 606
 		 * case 1) User has only this occurrence OR
607 607
 		 * case 2) User has recurring item and has received an update for an occurrence
608 608
 		 */
609
-		if ($basedate) {
610
-			// First try with GlobalID(0x3) (case 1)
611
-			$entryid = $this->findCalendarItems($goid, $calFolder);
612
-			// If not found then try with CleanGlobalID(0x23) (case 2)
613
-			if (!is_array($entryid) && isset($goid2)) {
614
-				$entryid = $this->findCalendarItems($goid2, $calFolder);
615
-			}
616
-		}
617
-		elseif (isset($goid2)) {
618
-			$entryid = $this->findCalendarItems($goid2, $calFolder);
619
-		}
620
-		else {
621
-			return false;
622
-		}
623
-
624
-		return is_array($entryid);
625
-	}
626
-
627
-	/**
628
-	 * Accepts the meeting request by moving the item to the calendar
629
-	 * and sending a confirmation message back to the sender. If $tentative
630
-	 * is TRUE, then the item is accepted tentatively. After accepting, you
631
-	 * can't use this class instance any more. The message is closed. If you
632
-	 * specify TRUE for 'move', then the item is actually moved (from your
633
-	 * inbox probably) to the calendar. If you don't, it is copied into
634
-	 * your calendar.
635
-	 *
636
-	 *@param bool $tentative true if user as tentative accepted the meeting
637
-	 * @param bool $sendresponse true if a response has to be sent to organizer
638
-	 *@param bool $move true if the meeting request should be moved to the deleted items after processing
639
-	 *@param string $newProposedStartTime contains starttime if user has proposed other time
640
-	 *@param string $newProposedEndTime contains endtime if user has proposed other time
641
-	 *@param string $basedate start of day of occurrence for which user has accepted the recurrent meeting
642
-	 * @param mixed $body
643
-	 * @param mixed $userAction
644
-	 * @param mixed $store
645
-	 *
646
-	 *@return string $entryid entryid of item which created/updated in calendar
647
-	 */
648
-	public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false) {
649
-		if ($this->isLocalOrganiser()) {
650
-			return false;
651
-		}
652
-
653
-		// Remove any previous calendar items with this goid and appt id
654
-		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_OWNER_APPT_ID, $this->proptags['updatecounter'], PR_PROCESSED, $this->proptags['recurring'], $this->proptags['intendedbusystatus'], PR_RCVD_REPRESENTING_NAME]);
655
-
656
-		/*
609
+        if ($basedate) {
610
+            // First try with GlobalID(0x3) (case 1)
611
+            $entryid = $this->findCalendarItems($goid, $calFolder);
612
+            // If not found then try with CleanGlobalID(0x23) (case 2)
613
+            if (!is_array($entryid) && isset($goid2)) {
614
+                $entryid = $this->findCalendarItems($goid2, $calFolder);
615
+            }
616
+        }
617
+        elseif (isset($goid2)) {
618
+            $entryid = $this->findCalendarItems($goid2, $calFolder);
619
+        }
620
+        else {
621
+            return false;
622
+        }
623
+
624
+        return is_array($entryid);
625
+    }
626
+
627
+    /**
628
+     * Accepts the meeting request by moving the item to the calendar
629
+     * and sending a confirmation message back to the sender. If $tentative
630
+     * is TRUE, then the item is accepted tentatively. After accepting, you
631
+     * can't use this class instance any more. The message is closed. If you
632
+     * specify TRUE for 'move', then the item is actually moved (from your
633
+     * inbox probably) to the calendar. If you don't, it is copied into
634
+     * your calendar.
635
+     *
636
+     *@param bool $tentative true if user as tentative accepted the meeting
637
+     * @param bool $sendresponse true if a response has to be sent to organizer
638
+     *@param bool $move true if the meeting request should be moved to the deleted items after processing
639
+     *@param string $newProposedStartTime contains starttime if user has proposed other time
640
+     *@param string $newProposedEndTime contains endtime if user has proposed other time
641
+     *@param string $basedate start of day of occurrence for which user has accepted the recurrent meeting
642
+     * @param mixed $body
643
+     * @param mixed $userAction
644
+     * @param mixed $store
645
+     *
646
+     *@return string $entryid entryid of item which created/updated in calendar
647
+     */
648
+    public function doAccept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store = false, $basedate = false) {
649
+        if ($this->isLocalOrganiser()) {
650
+            return false;
651
+        }
652
+
653
+        // Remove any previous calendar items with this goid and appt id
654
+        $messageprops = mapi_getprops($this->message, [PR_ENTRYID, PR_MESSAGE_CLASS, $this->proptags['goid'], $this->proptags['goid2'], PR_OWNER_APPT_ID, $this->proptags['updatecounter'], PR_PROCESSED, $this->proptags['recurring'], $this->proptags['intendedbusystatus'], PR_RCVD_REPRESENTING_NAME]);
655
+
656
+        /*
657 657
 		 *	if this function is called automatically with meeting request object then there will be
658 658
 		 *	two possibilitites
659 659
 		 *	1) meeting request is opened first time, in this case make a tentative appointment in
660 660
 		 * the recipient's calendar
661 661
 		 *	2) after this every subsequent request to open meeting request will not do any processing
662 662
 		 */
663
-		if ($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request" && $userAction == false) {
664
-			if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
665
-				// if meeting request is already processed then don't do anything
666
-				return false;
667
-			}
668
-			mapi_setprops($this->message, [PR_PROCESSED => true]);
669
-			mapi_savechanges($this->message);
670
-		}
671
-
672
-		/* If this meeting request is received by a delegate, open the delegator's store. */
673
-		if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
674
-			$delegatorStore = $this->getDelegatorStore($messageprops);
675
-
676
-			$store = $delegatorStore['store'];
677
-			$calFolder = $delegatorStore['calFolder'];
678
-		}
679
-		else {
680
-			$calFolder = $this->openDefaultCalendar();
681
-			$store = $this->store;
682
-		}
683
-
684
-		return $this->accept($tentative, $sendresponse, $move, $newProposedStartTime, $newProposedEndTime, $body, $userAction, $store, $calFolder, $basedate);
685
-	}
686
-
687
-	public function accept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store, $calFolder, $basedate = false) {
688
-		$messageprops = mapi_getprops($this->message);
689
-		$isDelegate = false;
690
-
691
-		if (isset($messageprops[PR_DELEGATED_BY_RULE])) {
692
-			$isDelegate = true;
693
-		}
694
-
695
-		$goid = $messageprops[$this->proptags['goid2']];
696
-
697
-		// Retrieve basedate from globalID, if it is not received as argument
698
-		if (!$basedate) {
699
-			$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
700
-		}
701
-
702
-		if ($sendresponse) {
703
-			$this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $newProposedStartTime, $newProposedEndTime, $body, $store, $basedate, $calFolder);
704
-		}
705
-
706
-		$entryids = $this->findCalendarItems($goid, $calFolder);
707
-
708
-		if (is_array($entryids)) {
709
-			// Only check the first, there should only be one anyway...
710
-			$previtem = mapi_msgstore_openentry($store, $entryids[0]);
711
-			$prevcounterprops = mapi_getprops($previtem, [$this->proptags['updatecounter']]);
712
-
713
-			// Check if the existing item has an updatecounter that is lower than the request we are processing. If not, then we ignore this call, since the
714
-			// meeting request is out of date.
715
-			/*
663
+        if ($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request" && $userAction == false) {
664
+            if (isset($messageprops[PR_PROCESSED]) && $messageprops[PR_PROCESSED] == true) {
665
+                // if meeting request is already processed then don't do anything
666
+                return false;
667
+            }
668
+            mapi_setprops($this->message, [PR_PROCESSED => true]);
669
+            mapi_savechanges($this->message);
670
+        }
671
+
672
+        /* If this meeting request is received by a delegate, open the delegator's store. */
673
+        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
674
+            $delegatorStore = $this->getDelegatorStore($messageprops);
675
+
676
+            $store = $delegatorStore['store'];
677
+            $calFolder = $delegatorStore['calFolder'];
678
+        }
679
+        else {
680
+            $calFolder = $this->openDefaultCalendar();
681
+            $store = $this->store;
682
+        }
683
+
684
+        return $this->accept($tentative, $sendresponse, $move, $newProposedStartTime, $newProposedEndTime, $body, $userAction, $store, $calFolder, $basedate);
685
+    }
686
+
687
+    public function accept($tentative, $sendresponse, $move, $newProposedStartTime = false, $newProposedEndTime = false, $body = false, $userAction = false, $store, $calFolder, $basedate = false) {
688
+        $messageprops = mapi_getprops($this->message);
689
+        $isDelegate = false;
690
+
691
+        if (isset($messageprops[PR_DELEGATED_BY_RULE])) {
692
+            $isDelegate = true;
693
+        }
694
+
695
+        $goid = $messageprops[$this->proptags['goid2']];
696
+
697
+        // Retrieve basedate from globalID, if it is not received as argument
698
+        if (!$basedate) {
699
+            $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
700
+        }
701
+
702
+        if ($sendresponse) {
703
+            $this->createResponse($tentative ? olResponseTentative : olResponseAccepted, $newProposedStartTime, $newProposedEndTime, $body, $store, $basedate, $calFolder);
704
+        }
705
+
706
+        $entryids = $this->findCalendarItems($goid, $calFolder);
707
+
708
+        if (is_array($entryids)) {
709
+            // Only check the first, there should only be one anyway...
710
+            $previtem = mapi_msgstore_openentry($store, $entryids[0]);
711
+            $prevcounterprops = mapi_getprops($previtem, [$this->proptags['updatecounter']]);
712
+
713
+            // Check if the existing item has an updatecounter that is lower than the request we are processing. If not, then we ignore this call, since the
714
+            // meeting request is out of date.
715
+            /*
716 716
 				if(message_counter < appointment_counter) do_nothing
717 717
 				if(message_counter == appointment_counter) do_something_if_the_user_tells_us (userAction == true)
718 718
 				if(message_counter > appointment_counter) do_something_even_automatically
719 719
 			*/
720
-			if (isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] < $prevcounterprops[$this->proptags['updatecounter']]) {
721
-				return false;
722
-			}
723
-			if (isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] == $prevcounterprops[$this->proptags['updatecounter']]) {
724
-				if ($userAction == false && !$basedate) {
725
-					return false;
726
-				}
727
-			}
728
-		}
729
-
730
-		// set counter proposal properties in calendar item when proposing new time
731
-		// @FIXME this can be moved before call to createResponse function so that function doesn't need to recalculate duration
732
-		$proposeNewTimeProps = [];
733
-		if ($newProposedStartTime && $newProposedEndTime) {
734
-			$proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
735
-			$proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
736
-			$proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
737
-			$proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
738
-		}
739
-
740
-		/*
720
+            if (isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] < $prevcounterprops[$this->proptags['updatecounter']]) {
721
+                return false;
722
+            }
723
+            if (isset($prevcounterprops[$this->proptags['updatecounter']]) && $messageprops[$this->proptags['updatecounter']] == $prevcounterprops[$this->proptags['updatecounter']]) {
724
+                if ($userAction == false && !$basedate) {
725
+                    return false;
726
+                }
727
+            }
728
+        }
729
+
730
+        // set counter proposal properties in calendar item when proposing new time
731
+        // @FIXME this can be moved before call to createResponse function so that function doesn't need to recalculate duration
732
+        $proposeNewTimeProps = [];
733
+        if ($newProposedStartTime && $newProposedEndTime) {
734
+            $proposeNewTimeProps[$this->proptags['proposed_start_whole']] = $newProposedStartTime;
735
+            $proposeNewTimeProps[$this->proptags['proposed_end_whole']] = $newProposedEndTime;
736
+            $proposeNewTimeProps[$this->proptags['proposed_duration']] = round($newProposedEndTime - $newProposedStartTime) / 60;
737
+            $proposeNewTimeProps[$this->proptags['counter_proposal']] = true;
738
+        }
739
+
740
+        /*
741 741
 		 * Further processing depends on what user is receiving. User can receive recurring item, a single occurrence or a normal meeting.
742 742
 		 * 1) If meeting req is of recurrence then we find all the occurrence in calendar because in past user might have recivied one or few occurrences.
743 743
 		 * 2) If single occurrence then find occurrence itself using globalID and if item is not found then user cleanGlobalID to find main recurring item
@@ -747,2050 +747,2050 @@  discard block
 block discarded – undo
747 747
 		 * and that item is not found in calendar then item is move else item is opened and all properties, attachments and recipient are copied from meeting request.
748 748
 		 * If user is responding from calendar then item is opened and properties are set such as meetingstatus, responsestatus, busystatus etc.
749 749
 		 */
750
-		if ($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request") {
751
-			// While processing the item mark it as read.
752
-			mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
753
-
754
-			// This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
755
-			if ($messageprops[$this->proptags['recurring']] == true) {
756
-				$calendarItem = false;
757
-
758
-				// Find main recurring item based on GlobalID (0x3)
759
-				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
760
-				if (is_array($items)) {
761
-					foreach ($items as $key => $entryid) {
762
-						$calendarItem = mapi_msgstore_openentry($store, $entryid);
763
-					}
764
-				}
765
-
766
-				// Recurring item not found, so create new meeting in Calendar
767
-				if (!$calendarItem) {
768
-					$calendarItem = mapi_folder_createmessage($calFolder);
769
-				}
770
-
771
-				// Copy properties
772
-				$props = mapi_getprops($this->message);
773
-
774
-				// While we applying updates of MR then all local categories will be removed,
775
-				// So get the local categories of all occurrence before applying update from organiser.
776
-				$localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder);
777
-
778
-				$props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
779
-				$props[$this->proptags['meetingstatus']] = olMeetingReceived;
780
-				// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
781
-				$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
782
-
783
-				if (isset($props[$this->proptags['intendedbusystatus']])) {
784
-					if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
785
-						$props[$this->proptags['busystatus']] = $tentative;
786
-					}
787
-					else {
788
-						$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
789
-					}
790
-					// we already have intendedbusystatus value in $props so no need to copy it
791
-				}
792
-				else {
793
-					$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
794
-				}
795
-				if ($userAction) {
796
-					// if user has responded then set replytime
797
-					$props[$this->proptags['replytime']] = time();
798
-				}
799
-
800
-				mapi_setprops($calendarItem, $props);
801
-
802
-				// Copy attachments too
803
-				$this->replaceAttachments($this->message, $calendarItem);
804
-				// Copy recipients too
805
-				$this->replaceRecipients($this->message, $calendarItem, $isDelegate);
806
-
807
-				// Find all occurrences based on CleanGlobalID (0x23)
808
-				$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
809
-				if (is_array($items)) {
810
-					// Save all existing occurrence as exceptions
811
-					foreach ($items as $entryid) {
812
-						// Open occurrence
813
-						$occurrenceItem = mapi_msgstore_openentry($store, $entryid);
814
-
815
-						// Save occurrence into main recurring item as exception
816
-						if ($occurrenceItem) {
817
-							$occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]);
818
-
819
-							// Find basedate of occurrence item
820
-							$basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
821
-							if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) {
822
-								$this->acceptException($calendarItem, $occurrenceItem, $basedate, true, $tentative, $userAction, $store, $isDelegate);
823
-							}
824
-						}
825
-					}
826
-				}
827
-				mapi_savechanges($calendarItem);
828
-
829
-				// After applying update of organiser all local categories of occurrence was removed,
830
-				// So if local categories exist then apply it on respective occurrence.
831
-				if (!empty($localCategories)) {
832
-					$this->applyLocalCategories($calendarItem, $store, $localCategories);
833
-				}
834
-
835
-				if ($move) {
836
-					$wastebasket = $this->openDefaultWastebasket();
837
-					mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
838
-				}
839
-				$entryid = $props[PR_ENTRYID];
840
-			}
841
-			else {
842
-				/**
843
-				 * This meeting request is not recurring, so can be an exception or normal meeting.
844
-				 * If exception then find main recurring item and update exception
845
-				 * If main recurring item is not found then put exception into Calendar as normal meeting.
846
-				 */
847
-				$calendarItem = false;
848
-
849
-				// We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
850
-				if ($basedate) {
851
-					// Find main recurring item from CleanGlobalID of this meeting request
852
-					$items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
853
-					if (is_array($items)) {
854
-						foreach ($items as $key => $entryid) {
855
-							$calendarItem = mapi_msgstore_openentry($store, $entryid);
856
-						}
857
-					}
858
-
859
-					// Main recurring item is found, so now update exception
860
-					if ($calendarItem) {
861
-						$this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
862
-						$calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]);
863
-						$entryid = $calendarItemProps[PR_ENTRYID];
864
-					}
865
-				}
866
-
867
-				if (!$calendarItem) {
868
-					$items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
869
-
870
-					if (is_array($items)) {
871
-						// Get local categories before deleting MR.
872
-						$message = mapi_msgstore_openentry($store, $items[0]);
873
-						$localCategories = mapi_getprops($message, [$this->proptags2['categories']]);
874
-						mapi_folder_deletemessages($calFolder, $items);
875
-					}
876
-
877
-					if ($move) {
878
-						// All we have to do is open the default calendar,
879
-						// set the message class correctly to be an appointment item
880
-						// and move it to the calendar folder
881
-						$sourcefolder = $this->openParentFolder();
882
-
883
-						/* create a new calendar message, and copy the message to there,
750
+        if ($messageprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request") {
751
+            // While processing the item mark it as read.
752
+            mapi_message_setreadflag($this->message, SUPPRESS_RECEIPT);
753
+
754
+            // This meeting request item is recurring, so find all occurrences and saves them all as exceptions to this meeting request item.
755
+            if ($messageprops[$this->proptags['recurring']] == true) {
756
+                $calendarItem = false;
757
+
758
+                // Find main recurring item based on GlobalID (0x3)
759
+                $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
760
+                if (is_array($items)) {
761
+                    foreach ($items as $key => $entryid) {
762
+                        $calendarItem = mapi_msgstore_openentry($store, $entryid);
763
+                    }
764
+                }
765
+
766
+                // Recurring item not found, so create new meeting in Calendar
767
+                if (!$calendarItem) {
768
+                    $calendarItem = mapi_folder_createmessage($calFolder);
769
+                }
770
+
771
+                // Copy properties
772
+                $props = mapi_getprops($this->message);
773
+
774
+                // While we applying updates of MR then all local categories will be removed,
775
+                // So get the local categories of all occurrence before applying update from organiser.
776
+                $localCategories = $this->getLocalCategories($calendarItem, $store, $calFolder);
777
+
778
+                $props[PR_MESSAGE_CLASS] = 'IPM.Appointment';
779
+                $props[$this->proptags['meetingstatus']] = olMeetingReceived;
780
+                // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
781
+                $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
782
+
783
+                if (isset($props[$this->proptags['intendedbusystatus']])) {
784
+                    if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
785
+                        $props[$this->proptags['busystatus']] = $tentative;
786
+                    }
787
+                    else {
788
+                        $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
789
+                    }
790
+                    // we already have intendedbusystatus value in $props so no need to copy it
791
+                }
792
+                else {
793
+                    $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
794
+                }
795
+                if ($userAction) {
796
+                    // if user has responded then set replytime
797
+                    $props[$this->proptags['replytime']] = time();
798
+                }
799
+
800
+                mapi_setprops($calendarItem, $props);
801
+
802
+                // Copy attachments too
803
+                $this->replaceAttachments($this->message, $calendarItem);
804
+                // Copy recipients too
805
+                $this->replaceRecipients($this->message, $calendarItem, $isDelegate);
806
+
807
+                // Find all occurrences based on CleanGlobalID (0x23)
808
+                $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
809
+                if (is_array($items)) {
810
+                    // Save all existing occurrence as exceptions
811
+                    foreach ($items as $entryid) {
812
+                        // Open occurrence
813
+                        $occurrenceItem = mapi_msgstore_openentry($store, $entryid);
814
+
815
+                        // Save occurrence into main recurring item as exception
816
+                        if ($occurrenceItem) {
817
+                            $occurrenceItemProps = mapi_getprops($occurrenceItem, [$this->proptags['goid'], $this->proptags['recurring']]);
818
+
819
+                            // Find basedate of occurrence item
820
+                            $basedate = $this->getBasedateFromGlobalID($occurrenceItemProps[$this->proptags['goid']]);
821
+                            if ($basedate && $occurrenceItemProps[$this->proptags['recurring']] != true) {
822
+                                $this->acceptException($calendarItem, $occurrenceItem, $basedate, true, $tentative, $userAction, $store, $isDelegate);
823
+                            }
824
+                        }
825
+                    }
826
+                }
827
+                mapi_savechanges($calendarItem);
828
+
829
+                // After applying update of organiser all local categories of occurrence was removed,
830
+                // So if local categories exist then apply it on respective occurrence.
831
+                if (!empty($localCategories)) {
832
+                    $this->applyLocalCategories($calendarItem, $store, $localCategories);
833
+                }
834
+
835
+                if ($move) {
836
+                    $wastebasket = $this->openDefaultWastebasket();
837
+                    mapi_folder_copymessages($calFolder, [$props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
838
+                }
839
+                $entryid = $props[PR_ENTRYID];
840
+            }
841
+            else {
842
+                /**
843
+                 * This meeting request is not recurring, so can be an exception or normal meeting.
844
+                 * If exception then find main recurring item and update exception
845
+                 * If main recurring item is not found then put exception into Calendar as normal meeting.
846
+                 */
847
+                $calendarItem = false;
848
+
849
+                // We found basedate in GlobalID of this meeting request, so this meeting request if for an occurrence.
850
+                if ($basedate) {
851
+                    // Find main recurring item from CleanGlobalID of this meeting request
852
+                    $items = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
853
+                    if (is_array($items)) {
854
+                        foreach ($items as $key => $entryid) {
855
+                            $calendarItem = mapi_msgstore_openentry($store, $entryid);
856
+                        }
857
+                    }
858
+
859
+                    // Main recurring item is found, so now update exception
860
+                    if ($calendarItem) {
861
+                        $this->acceptException($calendarItem, $this->message, $basedate, $move, $tentative, $userAction, $store, $isDelegate);
862
+                        $calendarItemProps = mapi_getprops($calendarItem, [PR_ENTRYID]);
863
+                        $entryid = $calendarItemProps[PR_ENTRYID];
864
+                    }
865
+                }
866
+
867
+                if (!$calendarItem) {
868
+                    $items = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
869
+
870
+                    if (is_array($items)) {
871
+                        // Get local categories before deleting MR.
872
+                        $message = mapi_msgstore_openentry($store, $items[0]);
873
+                        $localCategories = mapi_getprops($message, [$this->proptags2['categories']]);
874
+                        mapi_folder_deletemessages($calFolder, $items);
875
+                    }
876
+
877
+                    if ($move) {
878
+                        // All we have to do is open the default calendar,
879
+                        // set the message class correctly to be an appointment item
880
+                        // and move it to the calendar folder
881
+                        $sourcefolder = $this->openParentFolder();
882
+
883
+                        /* create a new calendar message, and copy the message to there,
884 884
 						   since we want to delete (move to wastebasket) the original message */
885
-						$old_entryid = mapi_getprops($this->message, [PR_ENTRYID]);
886
-						$calmsg = mapi_folder_createmessage($calFolder);
887
-						mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
888
-						/* release old message */
889
-						$message = null;
890
-
891
-						// After creating new MR, If local categories exist then apply it on new MR.
892
-						if (!empty($localCategories)) {
893
-							mapi_setprops($calmsg, $localCategories);
894
-						}
895
-
896
-						$calItemProps = [];
897
-						$calItemProps[PR_MESSAGE_CLASS] = "IPM.Appointment";
898
-
899
-						if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
900
-							if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
901
-								$calItemProps[$this->proptags['busystatus']] = $tentative;
902
-							}
903
-							else {
904
-								$calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
905
-							}
906
-							$calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
907
-						}
908
-						else {
909
-							$calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
910
-						}
911
-
912
-						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
913
-						$calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
914
-						if ($userAction) {
915
-							// if user has responded then set replytime
916
-							$calItemProps[$this->proptags['replytime']] = time();
917
-						}
918
-
919
-						mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
920
-
921
-						// get properties which stores owner information in meeting request mails
922
-						$props = mapi_getprops($calmsg, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]);
923
-
924
-						// add owner to recipient table
925
-						$recips = [];
926
-						$this->addOrganizer($props, $recips);
927
-
928
-						if ($isDelegate) {
929
-							/*
885
+                        $old_entryid = mapi_getprops($this->message, [PR_ENTRYID]);
886
+                        $calmsg = mapi_folder_createmessage($calFolder);
887
+                        mapi_copyto($this->message, [], [], $calmsg); /* includes attachments and recipients */
888
+                        /* release old message */
889
+                        $message = null;
890
+
891
+                        // After creating new MR, If local categories exist then apply it on new MR.
892
+                        if (!empty($localCategories)) {
893
+                            mapi_setprops($calmsg, $localCategories);
894
+                        }
895
+
896
+                        $calItemProps = [];
897
+                        $calItemProps[PR_MESSAGE_CLASS] = "IPM.Appointment";
898
+
899
+                        if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
900
+                            if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
901
+                                $calItemProps[$this->proptags['busystatus']] = $tentative;
902
+                            }
903
+                            else {
904
+                                $calItemProps[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
905
+                            }
906
+                            $calItemProps[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
907
+                        }
908
+                        else {
909
+                            $calItemProps[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
910
+                        }
911
+
912
+                        // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
913
+                        $calItemProps[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
914
+                        if ($userAction) {
915
+                            // if user has responded then set replytime
916
+                            $calItemProps[$this->proptags['replytime']] = time();
917
+                        }
918
+
919
+                        mapi_setprops($calmsg, $proposeNewTimeProps + $calItemProps);
920
+
921
+                        // get properties which stores owner information in meeting request mails
922
+                        $props = mapi_getprops($calmsg, [PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE, PR_SENT_REPRESENTING_SEARCH_KEY]);
923
+
924
+                        // add owner to recipient table
925
+                        $recips = [];
926
+                        $this->addOrganizer($props, $recips);
927
+
928
+                        if ($isDelegate) {
929
+                            /*
930 930
 							 * If user is delegate then remove that user from recipienttable of the MR.
931 931
 							 * and delegate MR mail doesn't contain any of the attendees in recipient table.
932 932
 							 * So, other required and optional attendees are added from
933 933
 							 * toattendeesstring and ccattendeesstring properties.
934 934
 							 */
935
-							$this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
936
-							$this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
937
-							mapi_message_modifyrecipients($calmsg, 0, $recips);
938
-						}
939
-						else {
940
-							mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
941
-						}
942
-
943
-						mapi_savechanges($calmsg);
944
-
945
-						// Move the message to the wastebasket
946
-						$wastebasket = $this->openDefaultWastebasket();
947
-						mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
948
-
949
-						$messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
950
-						$entryid = $messageprops[PR_ENTRYID];
951
-					}
952
-					else {
953
-						// Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
954
-						$new = mapi_folder_createmessage($calFolder);
955
-						$props = mapi_getprops($this->message);
956
-
957
-						$props[PR_MESSAGE_CLASS] = "IPM.Appointment";
958
-
959
-						// After creating new MR, If local categories exist then apply it on new MR.
960
-						if (!empty($localCategories)) {
961
-							mapi_setprops($new, $localCategories);
962
-						}
963
-
964
-						// when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
965
-						$props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
966
-
967
-						if (isset($props[$this->proptags['intendedbusystatus']])) {
968
-							if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
969
-								$props[$this->proptags['busystatus']] = $tentative;
970
-							}
971
-							else {
972
-								$props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
973
-							}
974
-							// we already have intendedbusystatus value in $props so no need to copy it
975
-						}
976
-						else {
977
-							$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
978
-						}
979
-
980
-						// ZP-341 - we need to copy as well the attachments
981
-						// Copy attachments too
982
-						$this->replaceAttachments($this->message, $new);
983
-						// ZP-341 - end
984
-
985
-						if ($userAction) {
986
-							// if user has responded then set replytime
987
-							$props[$this->proptags['replytime']] = time();
988
-						}
989
-
990
-						mapi_setprops($new, $proposeNewTimeProps + $props);
991
-
992
-						$reciptable = mapi_message_getrecipienttable($this->message);
993
-
994
-						$recips = [];
995
-						if (!$isDelegate) {
996
-							$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
997
-						}
998
-
999
-						$this->addOrganizer($props, $recips);
1000
-
1001
-						if ($isDelegate) {
1002
-							/*
935
+                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
936
+                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
937
+                            mapi_message_modifyrecipients($calmsg, 0, $recips);
938
+                        }
939
+                        else {
940
+                            mapi_message_modifyrecipients($calmsg, MODRECIP_ADD, $recips);
941
+                        }
942
+
943
+                        mapi_savechanges($calmsg);
944
+
945
+                        // Move the message to the wastebasket
946
+                        $wastebasket = $this->openDefaultWastebasket();
947
+                        mapi_folder_copymessages($sourcefolder, [$old_entryid[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
948
+
949
+                        $messageprops = mapi_getprops($calmsg, [PR_ENTRYID]);
950
+                        $entryid = $messageprops[PR_ENTRYID];
951
+                    }
952
+                    else {
953
+                        // Create a new appointment with duplicate properties and recipient, but as an IPM.Appointment
954
+                        $new = mapi_folder_createmessage($calFolder);
955
+                        $props = mapi_getprops($this->message);
956
+
957
+                        $props[PR_MESSAGE_CLASS] = "IPM.Appointment";
958
+
959
+                        // After creating new MR, If local categories exist then apply it on new MR.
960
+                        if (!empty($localCategories)) {
961
+                            mapi_setprops($new, $localCategories);
962
+                        }
963
+
964
+                        // when we are automatically processing the meeting request set responsestatus to olResponseNotResponded
965
+                        $props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
966
+
967
+                        if (isset($props[$this->proptags['intendedbusystatus']])) {
968
+                            if ($tentative && $props[$this->proptags['intendedbusystatus']] !== fbFree) {
969
+                                $props[$this->proptags['busystatus']] = $tentative;
970
+                            }
971
+                            else {
972
+                                $props[$this->proptags['busystatus']] = $props[$this->proptags['intendedbusystatus']];
973
+                            }
974
+                            // we already have intendedbusystatus value in $props so no need to copy it
975
+                        }
976
+                        else {
977
+                            $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
978
+                        }
979
+
980
+                        // ZP-341 - we need to copy as well the attachments
981
+                        // Copy attachments too
982
+                        $this->replaceAttachments($this->message, $new);
983
+                        // ZP-341 - end
984
+
985
+                        if ($userAction) {
986
+                            // if user has responded then set replytime
987
+                            $props[$this->proptags['replytime']] = time();
988
+                        }
989
+
990
+                        mapi_setprops($new, $proposeNewTimeProps + $props);
991
+
992
+                        $reciptable = mapi_message_getrecipienttable($this->message);
993
+
994
+                        $recips = [];
995
+                        if (!$isDelegate) {
996
+                            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
997
+                        }
998
+
999
+                        $this->addOrganizer($props, $recips);
1000
+
1001
+                        if ($isDelegate) {
1002
+                            /*
1003 1003
 							 * If user is delegate then remove that user from recipienttable of the MR.
1004 1004
 							 * and delegate MR mail doesn't contain any of the attendees in recipient table.
1005 1005
 							 * So, other required and optional attendees are added from
1006 1006
 							 * toattendeesstring and ccattendeesstring properties.
1007 1007
 							 */
1008
-							$this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
1009
-							$this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
1010
-							mapi_message_modifyrecipients($new, 0, $recips);
1011
-						}
1012
-						else {
1013
-							mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
1014
-						}
1015
-
1016
-						mapi_savechanges($new);
1017
-
1018
-						$props = mapi_getprops($new, [PR_ENTRYID]);
1019
-						$entryid = $props[PR_ENTRYID];
1020
-					}
1021
-				}
1022
-			}
1023
-		}
1024
-		else {
1025
-			// Here only properties are set on calendaritem, because user is responding from calendar.
1026
-			$props = [];
1027
-			$props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1028
-
1029
-			if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1030
-				if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1031
-					$props[$this->proptags['busystatus']] = $tentative;
1032
-				}
1033
-				else {
1034
-					$props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1035
-				}
1036
-				$props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1037
-			}
1038
-			else {
1039
-				$props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1040
-			}
1041
-
1042
-			$props[$this->proptags['meetingstatus']] = olMeetingReceived;
1043
-			$props[$this->proptags['replytime']] = time();
1044
-
1045
-			if ($basedate) {
1046
-				$recurr = new Recurrence($store, $this->message);
1047
-
1048
-				// Copy recipients list
1049
-				$reciptable = mapi_message_getrecipienttable($this->message);
1050
-				$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1051
-
1052
-				if ($recurr->isException($basedate)) {
1053
-					$recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1054
-				}
1055
-				else {
1056
-					$props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1057
-					$props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1058
-
1059
-					$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1060
-					$props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1061
-					$props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1062
-					$props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1063
-					$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1064
-
1065
-					$recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1066
-				}
1067
-			}
1068
-			else {
1069
-				mapi_setprops($this->message, $proposeNewTimeProps + $props);
1070
-			}
1071
-			mapi_savechanges($this->message);
1072
-
1073
-			$entryid = $messageprops[PR_ENTRYID];
1074
-		}
1075
-
1076
-		return $entryid;
1077
-	}
1078
-
1079
-	/**
1080
-	 * Declines the meeting request by moving the item to the deleted
1081
-	 * items folder and sending a decline message. After declining, you
1082
-	 * can't use this class instance any more. The message is closed.
1083
-	 * When an occurrence is decline then false is returned because that
1084
-	 * occurrence is deleted not the recurring item.
1085
-	 *
1086
-	 *@param bool $sendresponse true if a response has to be sent to organizer
1087
-	 *@param resource $store MAPI_store of user
1088
-	 *@param string $basedate if specified contains starttime of day of an occurrence
1089
-	 * @param mixed $body
1090
-	 *
1091
-	 *@return bool true if item is deleted from Calendar else false
1092
-	 */
1093
-	public function doDecline($sendresponse, $store = false, $basedate = false, $body = false) {
1094
-		$result = true;
1095
-		$calendaritem = false;
1096
-		if ($this->isLocalOrganiser()) {
1097
-			return;
1098
-		}
1099
-
1100
-		// Remove any previous calendar items with this goid and appt id
1101
-		$messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME]);
1102
-
1103
-		/* If this meeting request is received by a delegate, open the delegator's store. */
1104
-		if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1105
-			$delegatorStore = $this->getDelegatorStore($messageprops);
1106
-
1107
-			$store = $delegatorStore['store'];
1108
-			$calFolder = $delegatorStore['calFolder'];
1109
-		}
1110
-		else {
1111
-			$calFolder = $this->openDefaultCalendar();
1112
-			$store = $this->store;
1113
-		}
1114
-
1115
-		$goid = $messageprops[$this->proptags['goid']];
1116
-
1117
-		// First, find the items in the calendar by GlobalObjid (0x3)
1118
-		$entryids = $this->findCalendarItems($goid, $calFolder);
1119
-
1120
-		if (!$basedate) {
1121
-			$basedate = $this->getBasedateFromGlobalID($goid);
1122
-		}
1123
-
1124
-		if ($sendresponse) {
1125
-			$this->createResponse(olResponseDeclined, false, false, $body, $store, $basedate, $calFolder);
1126
-		}
1127
-
1128
-		if ($basedate) {
1129
-			// use CleanGlobalObjid (0x23)
1130
-			$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1131
-
1132
-			foreach ($calendaritems as $entryid) {
1133
-				// Open each calendar item and set the properties of the cancellation object
1134
-				$calendaritem = mapi_msgstore_openentry($store, $entryid);
1135
-
1136
-				// Recurring item is found, now delete exception
1137
-				if ($calendaritem) {
1138
-					$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1139
-				}
1140
-			}
1141
-
1142
-			if ($this->isMeetingRequest()) {
1143
-				$calendaritem = false;
1144
-			}
1145
-			else {
1146
-				$result = false;
1147
-			}
1148
-		}
1149
-
1150
-		if (!$calendaritem) {
1151
-			$calendar = $this->openDefaultCalendar();
1152
-			if (!empty($entryids)) {
1153
-				mapi_folder_deletemessages($calendar, $entryids);
1154
-			}
1155
-
1156
-			// All we have to do to decline, is to move the item to the waste basket
1157
-			$wastebasket = $this->openDefaultWastebasket();
1158
-			$sourcefolder = $this->openParentFolder();
1159
-
1160
-			$messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1161
-
1162
-			// Release the message
1163
-			$this->message = null;
1164
-
1165
-			// Move the message to the waste basket
1166
-			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1167
-		}
1168
-
1169
-		return $result;
1170
-	}
1171
-
1172
-	/**
1173
-	 * Removes a meeting request from the calendar when the user presses the
1174
-	 * 'remove from calendar' button in response to a meeting cancellation.
1175
-	 *
1176
-	 * @param string $basedate if specified contains starttime of day of an occurrence
1177
-	 */
1178
-	public function doRemoveFromCalendar($basedate) {
1179
-		if ($this->isLocalOrganiser()) {
1180
-			return false;
1181
-		}
1182
-
1183
-		$store = $this->store;
1184
-		$messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_NAME, PR_MESSAGE_CLASS]);
1185
-		$goid = $messageprops[$this->proptags['goid']];
1186
-
1187
-		if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1188
-			$delegatorStore = $this->getDelegatorStore($messageprops);
1189
-			$store = $delegatorStore['store'];
1190
-			$calFolder = $delegatorStore['calFolder'];
1191
-		}
1192
-		else {
1193
-			$calFolder = $this->openDefaultCalendar();
1194
-		}
1195
-
1196
-		$wastebasket = $this->openDefaultWastebasket();
1197
-		$sourcefolder = $this->openParentFolder();
1198
-
1199
-		// Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1200
-		if (strpos($messageprops[PR_MESSAGE_CLASS], 'IPM.Schedule.Meeting') === 0) {
1201
-			/**
1202
-			 * 'Remove from calendar' option from previewpane then we have to check GlobalID of this meeting request.
1203
-			 * If basedate found then open meeting from calendar and delete that occurrence.
1204
-			 */
1205
-			$basedate = false;
1206
-			if ($goid) {
1207
-				// Retrieve GlobalID and find basedate in it.
1208
-				$basedate = $this->getBasedateFromGlobalID($goid);
1209
-
1210
-				// Basedate found, Now find item.
1211
-				if ($basedate) {
1212
-					$guid = $this->setBasedateInGlobalID($goid);
1213
-
1214
-					// First, find the items in the calendar by GOID
1215
-					$calendaritems = $this->findCalendarItems($guid, $calFolder);
1216
-					if (is_array($calendaritems)) {
1217
-						foreach ($calendaritems as $entryid) {
1218
-							// Open each calendar item and set the properties of the cancellation object
1219
-							$calendaritem = mapi_msgstore_openentry($store, $entryid);
1220
-							if ($calendaritem) {
1221
-								$this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1222
-							}
1223
-						}
1224
-					}
1225
-				}
1226
-			}
1227
-
1228
-			// It is normal/recurring meeting item.
1229
-			if (!$basedate) {
1230
-				if (!isset($calFolder)) {
1231
-					$calFolder = $this->openDefaultCalendar();
1232
-				}
1233
-
1234
-				$entryids = $this->findCalendarItems($goid, $calFolder);
1235
-				if (is_array($entryids)) {
1236
-					// Move the calendaritem to the waste basket
1237
-					mapi_folder_copymessages($sourcefolder, $entryids, $wastebasket, MESSAGE_MOVE);
1238
-				}
1239
-			}
1240
-
1241
-			// Release the message
1242
-			$this->message = null;
1243
-
1244
-			// Move the message to the waste basket
1245
-			mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1246
-		}
1247
-		else {
1248
-			// Here only properties are set on calendaritem, because user is responding from calendar.
1249
-			if ($basedate) { // remove the occurrence
1250
-				$this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1251
-			}
1252
-			else { // remove normal/recurring meeting item.
1253
-				// Move the message to the waste basket
1254
-				mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1255
-			}
1256
-		}
1257
-	}
1258
-
1259
-	/**
1260
-	 * Removes the meeting request by moving the item to the deleted
1261
-	 * items folder. After canceling, youcan't use this class instance
1262
-	 * any more. The message is closed.
1263
-	 */
1264
-	public function doCancel() {
1265
-		if ($this->isLocalOrganiser()) {
1266
-			return;
1267
-		}
1268
-		if (!$this->isMeetingCancellation()) {
1269
-			return;
1270
-		}
1271
-
1272
-		// Remove any previous calendar items with this goid and appt id
1273
-		$messageprops = mapi_getprops($this->message, [$this->proptags['goid']]);
1274
-		$goid = $messageprops[$this->proptags['goid']];
1275
-
1276
-		$entryids = $this->findCalendarItems($goid);
1277
-		$calendar = $this->openDefaultCalendar();
1278
-
1279
-		mapi_folder_deletemessages($calendar, $entryids);
1280
-
1281
-		// All we have to do to decline, is to move the item to the waste basket
1282
-
1283
-		$wastebasket = $this->openDefaultWastebasket();
1284
-		$sourcefolder = $this->openParentFolder();
1285
-
1286
-		$messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1287
-
1288
-		// Release the message
1289
-		$this->message = null;
1290
-
1291
-		// Move the message to the waste basket
1292
-		mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1293
-	}
1294
-
1295
-	/**
1296
-	 * Sets the properties in the message so that is can be sent
1297
-	 * as a meeting request. The caller has to submit the message. This
1298
-	 * is only used for new MeetingRequests. Pass the appointment item as $message
1299
-	 * in the constructor to do this.
1300
-	 *
1301
-	 * @param mixed $basedate
1302
-	 */
1303
-	public function setMeetingRequest($basedate = false) {
1304
-		$props = mapi_getprops($this->message, [$this->proptags['updatecounter']]);
1305
-
1306
-		// Create a new global id for this item
1307
-		$goid = pack("H*", "040000008200E00074C5B7101A82E00800000000");
1308
-		for ($i = 0; $i < 36; ++$i) {
1309
-			$goid .= chr(rand(0, 255));
1310
-		}
1311
-
1312
-		// Create a new appointment id for this item
1313
-		$apptid = rand();
1314
-
1315
-		$props[PR_OWNER_APPT_ID] = $apptid;
1316
-		$props[PR_ICON_INDEX] = 1026;
1317
-		$props[$this->proptags['goid']] = $goid;
1318
-		$props[$this->proptags['goid2']] = $goid;
1319
-
1320
-		if (!isset($props[$this->proptags['updatecounter']])) {
1321
-			$props[$this->proptags['updatecounter']] = 0;			// OL also starts sequence no with zero.
1322
-			$props[$this->proptags['last_updatecounter']] = 0;
1323
-		}
1324
-
1325
-		mapi_setprops($this->message, $props);
1326
-	}
1327
-
1328
-	/**
1329
-	 * Sends a meeting request by copying it to the outbox, converting
1330
-	 * the message class, adding some properties that are required only
1331
-	 * for sending the message and submitting the message. Set cancel to
1332
-	 * true if you wish to completely cancel the meeting request. You can
1333
-	 * specify an optional 'prefix' to prefix the sent message, which is normally
1334
-	 * 'Canceled: '.
1335
-	 *
1336
-	 * @param mixed $cancel
1337
-	 * @param mixed $prefix
1338
-	 * @param mixed $basedate
1339
-	 * @param mixed $deletedRecips
1340
-	 */
1341
-	public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $deletedRecips = false) {
1342
-		$this->includesResources = false;
1343
-		$this->nonAcceptingResources = [];
1344
-
1345
-		// Get the properties of the message
1346
-		$messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]);
1347
-
1348
-		/*
1008
+                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['toattendeesstring']], MAPI_TO);
1009
+                            $this->setRecipsFromString($recips, $messageprops[$this->proptags['ccattendeesstring']], MAPI_CC);
1010
+                            mapi_message_modifyrecipients($new, 0, $recips);
1011
+                        }
1012
+                        else {
1013
+                            mapi_message_modifyrecipients($new, MODRECIP_ADD, $recips);
1014
+                        }
1015
+
1016
+                        mapi_savechanges($new);
1017
+
1018
+                        $props = mapi_getprops($new, [PR_ENTRYID]);
1019
+                        $entryid = $props[PR_ENTRYID];
1020
+                    }
1021
+                }
1022
+            }
1023
+        }
1024
+        else {
1025
+            // Here only properties are set on calendaritem, because user is responding from calendar.
1026
+            $props = [];
1027
+            $props[$this->proptags['responsestatus']] = $tentative ? olResponseTentative : olResponseAccepted;
1028
+
1029
+            if (isset($messageprops[$this->proptags['intendedbusystatus']])) {
1030
+                if ($tentative && $messageprops[$this->proptags['intendedbusystatus']] !== fbFree) {
1031
+                    $props[$this->proptags['busystatus']] = $tentative;
1032
+                }
1033
+                else {
1034
+                    $props[$this->proptags['busystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1035
+                }
1036
+                $props[$this->proptags['intendedbusystatus']] = $messageprops[$this->proptags['intendedbusystatus']];
1037
+            }
1038
+            else {
1039
+                $props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
1040
+            }
1041
+
1042
+            $props[$this->proptags['meetingstatus']] = olMeetingReceived;
1043
+            $props[$this->proptags['replytime']] = time();
1044
+
1045
+            if ($basedate) {
1046
+                $recurr = new Recurrence($store, $this->message);
1047
+
1048
+                // Copy recipients list
1049
+                $reciptable = mapi_message_getrecipienttable($this->message);
1050
+                $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
1051
+
1052
+                if ($recurr->isException($basedate)) {
1053
+                    $recurr->modifyException($proposeNewTimeProps + $props, $basedate, $recips);
1054
+                }
1055
+                else {
1056
+                    $props[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1057
+                    $props[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1058
+
1059
+                    $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1060
+                    $props[PR_SENT_REPRESENTING_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1061
+                    $props[PR_SENT_REPRESENTING_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1062
+                    $props[PR_SENT_REPRESENTING_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1063
+                    $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1064
+
1065
+                    $recurr->createException($proposeNewTimeProps + $props, $basedate, false, $recips);
1066
+                }
1067
+            }
1068
+            else {
1069
+                mapi_setprops($this->message, $proposeNewTimeProps + $props);
1070
+            }
1071
+            mapi_savechanges($this->message);
1072
+
1073
+            $entryid = $messageprops[PR_ENTRYID];
1074
+        }
1075
+
1076
+        return $entryid;
1077
+    }
1078
+
1079
+    /**
1080
+     * Declines the meeting request by moving the item to the deleted
1081
+     * items folder and sending a decline message. After declining, you
1082
+     * can't use this class instance any more. The message is closed.
1083
+     * When an occurrence is decline then false is returned because that
1084
+     * occurrence is deleted not the recurring item.
1085
+     *
1086
+     *@param bool $sendresponse true if a response has to be sent to organizer
1087
+     *@param resource $store MAPI_store of user
1088
+     *@param string $basedate if specified contains starttime of day of an occurrence
1089
+     * @param mixed $body
1090
+     *
1091
+     *@return bool true if item is deleted from Calendar else false
1092
+     */
1093
+    public function doDecline($sendresponse, $store = false, $basedate = false, $body = false) {
1094
+        $result = true;
1095
+        $calendaritem = false;
1096
+        if ($this->isLocalOrganiser()) {
1097
+            return;
1098
+        }
1099
+
1100
+        // Remove any previous calendar items with this goid and appt id
1101
+        $messageprops = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME]);
1102
+
1103
+        /* If this meeting request is received by a delegate, open the delegator's store. */
1104
+        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1105
+            $delegatorStore = $this->getDelegatorStore($messageprops);
1106
+
1107
+            $store = $delegatorStore['store'];
1108
+            $calFolder = $delegatorStore['calFolder'];
1109
+        }
1110
+        else {
1111
+            $calFolder = $this->openDefaultCalendar();
1112
+            $store = $this->store;
1113
+        }
1114
+
1115
+        $goid = $messageprops[$this->proptags['goid']];
1116
+
1117
+        // First, find the items in the calendar by GlobalObjid (0x3)
1118
+        $entryids = $this->findCalendarItems($goid, $calFolder);
1119
+
1120
+        if (!$basedate) {
1121
+            $basedate = $this->getBasedateFromGlobalID($goid);
1122
+        }
1123
+
1124
+        if ($sendresponse) {
1125
+            $this->createResponse(olResponseDeclined, false, false, $body, $store, $basedate, $calFolder);
1126
+        }
1127
+
1128
+        if ($basedate) {
1129
+            // use CleanGlobalObjid (0x23)
1130
+            $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1131
+
1132
+            foreach ($calendaritems as $entryid) {
1133
+                // Open each calendar item and set the properties of the cancellation object
1134
+                $calendaritem = mapi_msgstore_openentry($store, $entryid);
1135
+
1136
+                // Recurring item is found, now delete exception
1137
+                if ($calendaritem) {
1138
+                    $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1139
+                }
1140
+            }
1141
+
1142
+            if ($this->isMeetingRequest()) {
1143
+                $calendaritem = false;
1144
+            }
1145
+            else {
1146
+                $result = false;
1147
+            }
1148
+        }
1149
+
1150
+        if (!$calendaritem) {
1151
+            $calendar = $this->openDefaultCalendar();
1152
+            if (!empty($entryids)) {
1153
+                mapi_folder_deletemessages($calendar, $entryids);
1154
+            }
1155
+
1156
+            // All we have to do to decline, is to move the item to the waste basket
1157
+            $wastebasket = $this->openDefaultWastebasket();
1158
+            $sourcefolder = $this->openParentFolder();
1159
+
1160
+            $messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1161
+
1162
+            // Release the message
1163
+            $this->message = null;
1164
+
1165
+            // Move the message to the waste basket
1166
+            mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1167
+        }
1168
+
1169
+        return $result;
1170
+    }
1171
+
1172
+    /**
1173
+     * Removes a meeting request from the calendar when the user presses the
1174
+     * 'remove from calendar' button in response to a meeting cancellation.
1175
+     *
1176
+     * @param string $basedate if specified contains starttime of day of an occurrence
1177
+     */
1178
+    public function doRemoveFromCalendar($basedate) {
1179
+        if ($this->isLocalOrganiser()) {
1180
+            return false;
1181
+        }
1182
+
1183
+        $store = $this->store;
1184
+        $messageprops = mapi_getprops($this->message, [PR_ENTRYID, $this->proptags['goid'], PR_RCVD_REPRESENTING_NAME, PR_MESSAGE_CLASS]);
1185
+        $goid = $messageprops[$this->proptags['goid']];
1186
+
1187
+        if (isset($messageprops[PR_RCVD_REPRESENTING_NAME])) {
1188
+            $delegatorStore = $this->getDelegatorStore($messageprops);
1189
+            $store = $delegatorStore['store'];
1190
+            $calFolder = $delegatorStore['calFolder'];
1191
+        }
1192
+        else {
1193
+            $calFolder = $this->openDefaultCalendar();
1194
+        }
1195
+
1196
+        $wastebasket = $this->openDefaultWastebasket();
1197
+        $sourcefolder = $this->openParentFolder();
1198
+
1199
+        // Check if the message is a meeting request in the inbox or a calendaritem by checking the message class
1200
+        if (strpos($messageprops[PR_MESSAGE_CLASS], 'IPM.Schedule.Meeting') === 0) {
1201
+            /**
1202
+             * 'Remove from calendar' option from previewpane then we have to check GlobalID of this meeting request.
1203
+             * If basedate found then open meeting from calendar and delete that occurrence.
1204
+             */
1205
+            $basedate = false;
1206
+            if ($goid) {
1207
+                // Retrieve GlobalID and find basedate in it.
1208
+                $basedate = $this->getBasedateFromGlobalID($goid);
1209
+
1210
+                // Basedate found, Now find item.
1211
+                if ($basedate) {
1212
+                    $guid = $this->setBasedateInGlobalID($goid);
1213
+
1214
+                    // First, find the items in the calendar by GOID
1215
+                    $calendaritems = $this->findCalendarItems($guid, $calFolder);
1216
+                    if (is_array($calendaritems)) {
1217
+                        foreach ($calendaritems as $entryid) {
1218
+                            // Open each calendar item and set the properties of the cancellation object
1219
+                            $calendaritem = mapi_msgstore_openentry($store, $entryid);
1220
+                            if ($calendaritem) {
1221
+                                $this->doRemoveExceptionFromCalendar($basedate, $calendaritem, $store);
1222
+                            }
1223
+                        }
1224
+                    }
1225
+                }
1226
+            }
1227
+
1228
+            // It is normal/recurring meeting item.
1229
+            if (!$basedate) {
1230
+                if (!isset($calFolder)) {
1231
+                    $calFolder = $this->openDefaultCalendar();
1232
+                }
1233
+
1234
+                $entryids = $this->findCalendarItems($goid, $calFolder);
1235
+                if (is_array($entryids)) {
1236
+                    // Move the calendaritem to the waste basket
1237
+                    mapi_folder_copymessages($sourcefolder, $entryids, $wastebasket, MESSAGE_MOVE);
1238
+                }
1239
+            }
1240
+
1241
+            // Release the message
1242
+            $this->message = null;
1243
+
1244
+            // Move the message to the waste basket
1245
+            mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1246
+        }
1247
+        else {
1248
+            // Here only properties are set on calendaritem, because user is responding from calendar.
1249
+            if ($basedate) { // remove the occurrence
1250
+                $this->doRemoveExceptionFromCalendar($basedate, $this->message, $store);
1251
+            }
1252
+            else { // remove normal/recurring meeting item.
1253
+                // Move the message to the waste basket
1254
+                mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1255
+            }
1256
+        }
1257
+    }
1258
+
1259
+    /**
1260
+     * Removes the meeting request by moving the item to the deleted
1261
+     * items folder. After canceling, youcan't use this class instance
1262
+     * any more. The message is closed.
1263
+     */
1264
+    public function doCancel() {
1265
+        if ($this->isLocalOrganiser()) {
1266
+            return;
1267
+        }
1268
+        if (!$this->isMeetingCancellation()) {
1269
+            return;
1270
+        }
1271
+
1272
+        // Remove any previous calendar items with this goid and appt id
1273
+        $messageprops = mapi_getprops($this->message, [$this->proptags['goid']]);
1274
+        $goid = $messageprops[$this->proptags['goid']];
1275
+
1276
+        $entryids = $this->findCalendarItems($goid);
1277
+        $calendar = $this->openDefaultCalendar();
1278
+
1279
+        mapi_folder_deletemessages($calendar, $entryids);
1280
+
1281
+        // All we have to do to decline, is to move the item to the waste basket
1282
+
1283
+        $wastebasket = $this->openDefaultWastebasket();
1284
+        $sourcefolder = $this->openParentFolder();
1285
+
1286
+        $messageprops = mapi_getprops($this->message, [PR_ENTRYID]);
1287
+
1288
+        // Release the message
1289
+        $this->message = null;
1290
+
1291
+        // Move the message to the waste basket
1292
+        mapi_folder_copymessages($sourcefolder, [$messageprops[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
1293
+    }
1294
+
1295
+    /**
1296
+     * Sets the properties in the message so that is can be sent
1297
+     * as a meeting request. The caller has to submit the message. This
1298
+     * is only used for new MeetingRequests. Pass the appointment item as $message
1299
+     * in the constructor to do this.
1300
+     *
1301
+     * @param mixed $basedate
1302
+     */
1303
+    public function setMeetingRequest($basedate = false) {
1304
+        $props = mapi_getprops($this->message, [$this->proptags['updatecounter']]);
1305
+
1306
+        // Create a new global id for this item
1307
+        $goid = pack("H*", "040000008200E00074C5B7101A82E00800000000");
1308
+        for ($i = 0; $i < 36; ++$i) {
1309
+            $goid .= chr(rand(0, 255));
1310
+        }
1311
+
1312
+        // Create a new appointment id for this item
1313
+        $apptid = rand();
1314
+
1315
+        $props[PR_OWNER_APPT_ID] = $apptid;
1316
+        $props[PR_ICON_INDEX] = 1026;
1317
+        $props[$this->proptags['goid']] = $goid;
1318
+        $props[$this->proptags['goid2']] = $goid;
1319
+
1320
+        if (!isset($props[$this->proptags['updatecounter']])) {
1321
+            $props[$this->proptags['updatecounter']] = 0;			// OL also starts sequence no with zero.
1322
+            $props[$this->proptags['last_updatecounter']] = 0;
1323
+        }
1324
+
1325
+        mapi_setprops($this->message, $props);
1326
+    }
1327
+
1328
+    /**
1329
+     * Sends a meeting request by copying it to the outbox, converting
1330
+     * the message class, adding some properties that are required only
1331
+     * for sending the message and submitting the message. Set cancel to
1332
+     * true if you wish to completely cancel the meeting request. You can
1333
+     * specify an optional 'prefix' to prefix the sent message, which is normally
1334
+     * 'Canceled: '.
1335
+     *
1336
+     * @param mixed $cancel
1337
+     * @param mixed $prefix
1338
+     * @param mixed $basedate
1339
+     * @param mixed $deletedRecips
1340
+     */
1341
+    public function sendMeetingRequest($cancel, $prefix = false, $basedate = false, $deletedRecips = false) {
1342
+        $this->includesResources = false;
1343
+        $this->nonAcceptingResources = [];
1344
+
1345
+        // Get the properties of the message
1346
+        $messageprops = mapi_getprops($this->message, [$this->proptags['recurring']]);
1347
+
1348
+        /*
1349 1349
 		 * Submit message to non-resource recipients
1350 1350
 		 */
1351
-		// Set BusyStatus to olTentative (1)
1352
-		// Set MeetingStatus to olMeetingReceived
1353
-		// Set ResponseStatus to olResponseNotResponded
1351
+        // Set BusyStatus to olTentative (1)
1352
+        // Set MeetingStatus to olMeetingReceived
1353
+        // Set ResponseStatus to olResponseNotResponded
1354 1354
 
1355
-		/*
1355
+        /*
1356 1356
 		 * While sending recurrence meeting exceptions are not send as attachments
1357 1357
 		 * because first all exceptions are sent and then recurrence meeting is sent.
1358 1358
 		 */
1359
-		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1360
-			// Book resource
1361
-			$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1362
-
1363
-			if (!$this->errorSetResource) {
1364
-				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1365
-
1366
-				// First send meetingrequest for recurring item
1367
-				$this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $deletedRecips);
1368
-
1369
-				// Then send all meeting request for all exceptions
1370
-				$exceptions = $recurr->getAllExceptions();
1371
-				if ($exceptions) {
1372
-					foreach ($exceptions as $exceptionBasedate) {
1373
-						$attach = $recurr->getExceptionAttachment($exceptionBasedate);
1374
-
1375
-						if (!$attach) {
1376
-							continue;
1377
-						}
1378
-						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1379
-						$this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $deletedRecips);
1380
-						mapi_savechanges($attach);
1381
-					}
1382
-				}
1383
-			}
1384
-		}
1385
-		else {
1386
-			// Basedate found, an exception is to be sent
1387
-			if ($basedate) {
1388
-				$recurr = new Recurrence($this->openDefaultStore(), $this->message);
1389
-
1390
-				if ($cancel) {
1391
-					/* @TODO: remove occurrence from the resource's calendar if resource was booked for whole series. */
1392
-					$this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1393
-				}
1394
-				else {
1395
-					$attach = $recurr->getExceptionAttachment($basedate);
1396
-
1397
-					if ($attach) {
1398
-						$occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1399
-
1400
-						// Book resource for this occurrence
1401
-						$resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1402
-
1403
-						if (!$this->errorSetResource) {
1404
-							// Save all previous changes
1405
-							mapi_savechanges($this->message);
1406
-
1407
-							$this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $deletedRecips);
1408
-							mapi_savechanges($occurrenceItem);
1409
-							mapi_savechanges($attach);
1410
-						}
1411
-					}
1412
-				}
1413
-			}
1414
-			else {
1415
-				// This is normal meeting
1416
-				$resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1417
-				if (!$this->errorSetResource) {
1418
-					$this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $deletedRecips);
1419
-				}
1420
-			}
1421
-		}
1422
-
1423
-		if (isset($this->errorSetResource) && $this->errorSetResource) {
1424
-			return [
1425
-				'error' => $this->errorSetResource,
1426
-				'displayname' => $this->recipientDisplayname,
1427
-			];
1428
-		}
1429
-
1430
-		return true;
1431
-	}
1432
-
1433
-	public function getFreeBusyInfo($entryID, $start, $end) {
1434
-		$result = [];
1435
-		$fbsupport = mapi_freebusysupport_open($this->session);
1436
-
1437
-		if (mapi_last_hresult() != NOERROR) {
1438
-			if (function_exists("dump")) {
1439
-				dump("Error in opening freebusysupport object.");
1440
-			}
1441
-
1442
-			return $result;
1443
-		}
1444
-
1445
-		$fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$entryID]);
1446
-
1447
-		if ($fbDataArray[0] != null) {
1448
-			foreach ($fbDataArray as $fbDataUser) {
1449
-				$rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
1450
-				if ($rangeuser1 == null) {
1451
-					return $result;
1452
-				}
1453
-
1454
-				$enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
1455
-				mapi_freebusyenumblock_reset($enumblock);
1456
-
1457
-				while (true) {
1458
-					$blocks = mapi_freebusyenumblock_next($enumblock, 100);
1459
-					if (!$blocks) {
1460
-						break;
1461
-					}
1462
-					foreach ($blocks as $blockItem) {
1463
-						$result[] = $blockItem;
1464
-					}
1465
-				}
1466
-			}
1467
-		}
1468
-
1469
-		mapi_freebusysupport_close($fbsupport);
1470
-
1471
-		return $result;
1472
-	}
1473
-
1474
-	/**
1475
-	 * Updates the message after an update has been performed (for example,
1476
-	 * changing the time of the meeting). This must be called before re-sending
1477
-	 * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1478
-	 * as it will automatically call setMeetingRequest on this object if it is the first
1479
-	 * call to this function.
1480
-	 *
1481
-	 * @param mixed $basedate
1482
-	 */
1483
-	public function updateMeetingRequest($basedate = false) {
1484
-		$messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
1485
-
1486
-		if (!isset($messageprops[$this->proptags['last_updatecounter']]) || !isset($messageprops[$this->proptags['goid']])) {
1487
-			$this->setMeetingRequest($basedate);
1488
-
1489
-			return;
1490
-		}
1491
-		$counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1492
-
1493
-		// increment value of last_updatecounter, last_updatecounter will be common for recurring series
1494
-		// so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1495
-		// this way we can make sure that every time we will be using a uniwue number for every operation
1496
-		mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
1497
-	}
1498
-
1499
-	/**
1500
-	 * Returns TRUE if we are the organiser of the meeting.
1501
-	 */
1502
-	public function isLocalOrganiser() {
1503
-		if ($this->isMeetingRequest() || $this->isMeetingRequestResponse()) {
1504
-			$messageid = $this->getAppointmentEntryID();
1505
-
1506
-			if (!isset($messageid)) {
1507
-				return false;
1508
-			}
1509
-
1510
-			$message = mapi_msgstore_openentry($this->store, $messageid);
1511
-
1512
-			$messageprops = mapi_getprops($this->message, [$this->proptags['goid']]);
1513
-			$basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
1514
-			if ($basedate) {
1515
-				$recurr = new Recurrence($this->store, $message);
1516
-				$attach = $recurr->getExceptionAttachment($basedate);
1517
-				if ($attach) {
1518
-					$occurItem = mapi_attach_openobj($attach);
1519
-					$occurItemProps = mapi_getprops($occurItem, [$this->proptags['responsestatus']]);
1520
-				}
1521
-			}
1522
-
1523
-			$messageprops = mapi_getprops($message, [$this->proptags['responsestatus']]);
1524
-		}
1525
-
1526
-		/*
1359
+        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']] && !$basedate) {
1360
+            // Book resource
1361
+            $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1362
+
1363
+            if (!$this->errorSetResource) {
1364
+                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
1365
+
1366
+                // First send meetingrequest for recurring item
1367
+                $this->submitMeetingRequest($this->message, $cancel, $prefix, false, $recurr, false, $deletedRecips);
1368
+
1369
+                // Then send all meeting request for all exceptions
1370
+                $exceptions = $recurr->getAllExceptions();
1371
+                if ($exceptions) {
1372
+                    foreach ($exceptions as $exceptionBasedate) {
1373
+                        $attach = $recurr->getExceptionAttachment($exceptionBasedate);
1374
+
1375
+                        if (!$attach) {
1376
+                            continue;
1377
+                        }
1378
+                        $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1379
+                        $this->submitMeetingRequest($occurrenceItem, $cancel, false, $exceptionBasedate, $recurr, false, $deletedRecips);
1380
+                        mapi_savechanges($attach);
1381
+                    }
1382
+                }
1383
+            }
1384
+        }
1385
+        else {
1386
+            // Basedate found, an exception is to be sent
1387
+            if ($basedate) {
1388
+                $recurr = new Recurrence($this->openDefaultStore(), $this->message);
1389
+
1390
+                if ($cancel) {
1391
+                    /* @TODO: remove occurrence from the resource's calendar if resource was booked for whole series. */
1392
+                    $this->submitMeetingRequest($this->message, $cancel, $prefix, $basedate, $recurr, false);
1393
+                }
1394
+                else {
1395
+                    $attach = $recurr->getExceptionAttachment($basedate);
1396
+
1397
+                    if ($attach) {
1398
+                        $occurrenceItem = mapi_attach_openobj($attach, MAPI_MODIFY);
1399
+
1400
+                        // Book resource for this occurrence
1401
+                        $resourceRecipData = $this->bookResources($occurrenceItem, $cancel, $prefix, $basedate);
1402
+
1403
+                        if (!$this->errorSetResource) {
1404
+                            // Save all previous changes
1405
+                            mapi_savechanges($this->message);
1406
+
1407
+                            $this->submitMeetingRequest($occurrenceItem, $cancel, $prefix, $basedate, $recurr, true, $deletedRecips);
1408
+                            mapi_savechanges($occurrenceItem);
1409
+                            mapi_savechanges($attach);
1410
+                        }
1411
+                    }
1412
+                }
1413
+            }
1414
+            else {
1415
+                // This is normal meeting
1416
+                $resourceRecipData = $this->bookResources($this->message, $cancel, $prefix);
1417
+                if (!$this->errorSetResource) {
1418
+                    $this->submitMeetingRequest($this->message, $cancel, $prefix, false, false, false, $deletedRecips);
1419
+                }
1420
+            }
1421
+        }
1422
+
1423
+        if (isset($this->errorSetResource) && $this->errorSetResource) {
1424
+            return [
1425
+                'error' => $this->errorSetResource,
1426
+                'displayname' => $this->recipientDisplayname,
1427
+            ];
1428
+        }
1429
+
1430
+        return true;
1431
+    }
1432
+
1433
+    public function getFreeBusyInfo($entryID, $start, $end) {
1434
+        $result = [];
1435
+        $fbsupport = mapi_freebusysupport_open($this->session);
1436
+
1437
+        if (mapi_last_hresult() != NOERROR) {
1438
+            if (function_exists("dump")) {
1439
+                dump("Error in opening freebusysupport object.");
1440
+            }
1441
+
1442
+            return $result;
1443
+        }
1444
+
1445
+        $fbDataArray = mapi_freebusysupport_loaddata($fbsupport, [$entryID]);
1446
+
1447
+        if ($fbDataArray[0] != null) {
1448
+            foreach ($fbDataArray as $fbDataUser) {
1449
+                $rangeuser1 = mapi_freebusydata_getpublishrange($fbDataUser);
1450
+                if ($rangeuser1 == null) {
1451
+                    return $result;
1452
+                }
1453
+
1454
+                $enumblock = mapi_freebusydata_enumblocks($fbDataUser, $start, $end);
1455
+                mapi_freebusyenumblock_reset($enumblock);
1456
+
1457
+                while (true) {
1458
+                    $blocks = mapi_freebusyenumblock_next($enumblock, 100);
1459
+                    if (!$blocks) {
1460
+                        break;
1461
+                    }
1462
+                    foreach ($blocks as $blockItem) {
1463
+                        $result[] = $blockItem;
1464
+                    }
1465
+                }
1466
+            }
1467
+        }
1468
+
1469
+        mapi_freebusysupport_close($fbsupport);
1470
+
1471
+        return $result;
1472
+    }
1473
+
1474
+    /**
1475
+     * Updates the message after an update has been performed (for example,
1476
+     * changing the time of the meeting). This must be called before re-sending
1477
+     * the meeting request. You can also call this function instead of 'setMeetingRequest()'
1478
+     * as it will automatically call setMeetingRequest on this object if it is the first
1479
+     * call to this function.
1480
+     *
1481
+     * @param mixed $basedate
1482
+     */
1483
+    public function updateMeetingRequest($basedate = false) {
1484
+        $messageprops = mapi_getprops($this->message, [$this->proptags['last_updatecounter'], $this->proptags['goid']]);
1485
+
1486
+        if (!isset($messageprops[$this->proptags['last_updatecounter']]) || !isset($messageprops[$this->proptags['goid']])) {
1487
+            $this->setMeetingRequest($basedate);
1488
+
1489
+            return;
1490
+        }
1491
+        $counter = $messageprops[$this->proptags['last_updatecounter']] + 1;
1492
+
1493
+        // increment value of last_updatecounter, last_updatecounter will be common for recurring series
1494
+        // so even if you sending an exception only you need to update the last_updatecounter in the recurring series message
1495
+        // this way we can make sure that every time we will be using a uniwue number for every operation
1496
+        mapi_setprops($this->message, [$this->proptags['last_updatecounter'] => $counter]);
1497
+    }
1498
+
1499
+    /**
1500
+     * Returns TRUE if we are the organiser of the meeting.
1501
+     */
1502
+    public function isLocalOrganiser() {
1503
+        if ($this->isMeetingRequest() || $this->isMeetingRequestResponse()) {
1504
+            $messageid = $this->getAppointmentEntryID();
1505
+
1506
+            if (!isset($messageid)) {
1507
+                return false;
1508
+            }
1509
+
1510
+            $message = mapi_msgstore_openentry($this->store, $messageid);
1511
+
1512
+            $messageprops = mapi_getprops($this->message, [$this->proptags['goid']]);
1513
+            $basedate = $this->getBasedateFromGlobalID($messageprops[$this->proptags['goid']]);
1514
+            if ($basedate) {
1515
+                $recurr = new Recurrence($this->store, $message);
1516
+                $attach = $recurr->getExceptionAttachment($basedate);
1517
+                if ($attach) {
1518
+                    $occurItem = mapi_attach_openobj($attach);
1519
+                    $occurItemProps = mapi_getprops($occurItem, [$this->proptags['responsestatus']]);
1520
+                }
1521
+            }
1522
+
1523
+            $messageprops = mapi_getprops($message, [$this->proptags['responsestatus']]);
1524
+        }
1525
+
1526
+        /*
1527 1527
 		 * User can send recurring meeting or any occurrences from a recurring appointment so
1528 1528
 		 * to be organizer 'responseStatus' property should be 'olResponseOrganized' on either
1529 1529
 		 * of the recurring item or occurrence item.
1530 1530
 		 */
1531
-		if ((isset($messageprops[$this->proptags['responsestatus']]) && $messageprops[$this->proptags['responsestatus']] == olResponseOrganized) ||
1532
-			(isset($occurItemProps[$this->proptags['responsestatus']]) && $occurItemProps[$this->proptags['responsestatus']] == olResponseOrganized)) {
1533
-			return true;
1534
-		}
1531
+        if ((isset($messageprops[$this->proptags['responsestatus']]) && $messageprops[$this->proptags['responsestatus']] == olResponseOrganized) ||
1532
+            (isset($occurItemProps[$this->proptags['responsestatus']]) && $occurItemProps[$this->proptags['responsestatus']] == olResponseOrganized)) {
1533
+            return true;
1534
+        }
1535 1535
 
1536
-		return false;
1537
-	}
1536
+        return false;
1537
+    }
1538 1538
 
1539
-	/**
1540
-	 * Returns the entryid of the appointment that this message points at. This is
1541
-	 * only used on messages that are not in the calendar.
1542
-	 */
1543
-	public function getAppointmentEntryID() {
1544
-		$messageprops = mapi_getprops($this->message, [$this->proptags['goid2']]);
1539
+    /**
1540
+     * Returns the entryid of the appointment that this message points at. This is
1541
+     * only used on messages that are not in the calendar.
1542
+     */
1543
+    public function getAppointmentEntryID() {
1544
+        $messageprops = mapi_getprops($this->message, [$this->proptags['goid2']]);
1545 1545
 
1546
-		$goid2 = $messageprops[$this->proptags['goid2']];
1546
+        $goid2 = $messageprops[$this->proptags['goid2']];
1547 1547
 
1548
-		$items = $this->findCalendarItems($goid2);
1548
+        $items = $this->findCalendarItems($goid2);
1549 1549
 
1550
-		if (empty($items)) {
1551
-			return;
1552
-		}
1550
+        if (empty($items)) {
1551
+            return;
1552
+        }
1553 1553
 
1554
-		// There should be just one item. If there are more, we just take the first one
1555
-		return $items[0];
1556
-	}
1554
+        // There should be just one item. If there are more, we just take the first one
1555
+        return $items[0];
1556
+    }
1557 1557
 
1558
-	/*
1558
+    /*
1559 1559
 	 * Support functions - INTERNAL ONLY
1560 1560
 	 ***************************************************************************************************
1561 1561
 	 */
1562 1562
 
1563
-	/**
1564
-	 * Return the tracking status of a recipient based on the IPM class (passed).
1565
-	 *
1566
-	 * @param mixed $class
1567
-	 */
1568
-	public function getTrackStatus($class) {
1569
-		$status = olRecipientTrackStatusNone;
1570
-
1571
-		switch ($class) {
1572
-			case "IPM.Schedule.Meeting.Resp.Pos":
1573
-				$status = olRecipientTrackStatusAccepted;
1574
-				break;
1575
-
1576
-			case "IPM.Schedule.Meeting.Resp.Tent":
1577
-				$status = olRecipientTrackStatusTentative;
1578
-				break;
1579
-
1580
-			case "IPM.Schedule.Meeting.Resp.Neg":
1581
-				$status = olRecipientTrackStatusDeclined;
1582
-				break;
1583
-		}
1584
-
1585
-		return $status;
1586
-	}
1587
-
1588
-	public function openParentFolder() {
1589
-		$messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1590
-
1591
-		return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1592
-	}
1593
-
1594
-	public function openDefaultCalendar() {
1595
-		return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID);
1596
-	}
1597
-
1598
-	public function openDefaultOutbox($store = false) {
1599
-		return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1600
-	}
1601
-
1602
-	public function openDefaultWastebasket() {
1603
-		return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID);
1604
-	}
1605
-
1606
-	public function getDefaultWastebasketEntryID() {
1607
-		return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID);
1608
-	}
1609
-
1610
-	public function getDefaultSentmailEntryID($store = false) {
1611
-		return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1612
-	}
1613
-
1614
-	public function getDefaultFolderEntryID($prop) {
1615
-		try {
1616
-			$inbox = mapi_msgstore_getreceivefolder($this->store);
1617
-		}
1618
-		catch (MAPIException $e) {
1619
-			// public store doesn't support this method
1620
-			if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1621
-				// don't propagate this error to parent handlers, if store doesn't support it
1622
-				$e->setHandled();
1623
-
1624
-				return;
1625
-			}
1626
-		}
1627
-
1628
-		$inboxprops = mapi_getprops($inbox, [$prop]);
1629
-		if (!isset($inboxprops[$prop])) {
1630
-			return;
1631
-		}
1632
-
1633
-		return $inboxprops[$prop];
1634
-	}
1635
-
1636
-	public function openDefaultFolder($prop) {
1637
-		$entryid = $this->getDefaultFolderEntryID($prop);
1638
-
1639
-		return mapi_msgstore_openentry($this->store, $entryid);
1640
-	}
1641
-
1642
-	public function getBaseEntryID($prop, $store = false) {
1643
-		$storeprops = mapi_getprops((($store) ? $store : $this->store), [$prop]);
1644
-		if (!isset($storeprops[$prop])) {
1645
-			return;
1646
-		}
1647
-
1648
-		return $storeprops[$prop];
1649
-	}
1650
-
1651
-	public function openBaseFolder($prop, $store = false) {
1652
-		$entryid = $this->getBaseEntryID($prop, $store);
1653
-
1654
-		return mapi_msgstore_openentry((($store) ? $store : $this->store), $entryid);
1655
-	}
1656
-
1657
-	/**
1658
-	 * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1659
-	 *
1660
-	 *@param int $status response status of attendee
1661
-	 *@param int $proposalStartTime proposed starttime by attendee
1662
-	 *@param int $proposalEndTime proposed endtime by attendee
1663
-	 *@param int $basedate date of occurrence which attendee has responded
1664
-	 * @param mixed $body
1665
-	 * @param mixed $store
1666
-	 * @param mixed $calFolder
1667
-	 */
1668
-	public function createResponse($status, $proposalStartTime = false, $proposalEndTime = false, $body = false, $store, $basedate = false, $calFolder) {
1669
-		$messageprops = mapi_getprops($this->message, [PR_SENT_REPRESENTING_ENTRYID,
1670
-			PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1671
-			PR_SENT_REPRESENTING_ADDRTYPE,
1672
-			PR_SENT_REPRESENTING_NAME,
1673
-			PR_SENT_REPRESENTING_SEARCH_KEY,
1674
-			$this->proptags['goid'],
1675
-			$this->proptags['goid2'],
1676
-			$this->proptags['location'],
1677
-			$this->proptags['startdate'],
1678
-			$this->proptags['duedate'],
1679
-			$this->proptags['recurring'],
1680
-			$this->proptags['recurring_pattern'],
1681
-			$this->proptags['recurrence_data'],
1682
-			$this->proptags['timezone_data'],
1683
-			$this->proptags['timezone'],
1684
-			$this->proptags['updatecounter'],
1685
-			PR_SUBJECT,
1686
-			PR_MESSAGE_CLASS,
1687
-			PR_OWNER_APPT_ID,
1688
-			$this->proptags['is_exception'],
1689
-		]);
1690
-
1691
-		if ($basedate && $messageprops[PR_MESSAGE_CLASS] != "IPM.Schedule.Meeting.Request") {
1692
-			// we are creating response from a recurring calendar item object
1693
-			// We found basedate,so opened occurrence and get properties.
1694
-			$recurr = new Recurrence($store, $this->message);
1695
-			$exception = $recurr->getExceptionAttachment($basedate);
1696
-
1697
-			if ($exception) {
1698
-				// Exception found, Now retrieve properties
1699
-				$imessage = mapi_attach_openobj($exception, 0);
1700
-				$imsgprops = mapi_getprops($imessage);
1701
-
1702
-				// If location is provided, copy it to the response
1703
-				if (isset($imsgprops[$this->proptags['location']])) {
1704
-					$messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1705
-				}
1706
-
1707
-				// Update $messageprops with timings of occurrence
1708
-				$messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1709
-				$messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1710
-
1711
-				// Meeting related properties
1712
-				$props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1713
-				$props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1714
-				$props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1715
-			}
1716
-			else {
1717
-				// Exceptions is deleted.
1718
-				// Update $messageprops with timings of occurrence
1719
-				$messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1720
-				$messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1721
-
1722
-				$props[$this->proptags['meetingstatus']] = olNonMeeting;
1723
-				$props[$this->proptags['responsestatus']] = olResponseNone;
1724
-			}
1725
-
1726
-			$props[$this->proptags['recurring']] = false;
1727
-			$props[$this->proptags['is_exception']] = true;
1728
-		}
1729
-		else {
1730
-			// we are creating a response from meeting request mail (it could be recurring or non-recurring)
1731
-			// Send all recurrence info in response, if this is a recurrence meeting.
1732
-			$isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
1733
-			$isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
1734
-			if ($isRecurring || $isException) {
1735
-				if ($isRecurring) {
1736
-					$props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
1737
-				}
1738
-				if ($isException) {
1739
-					$props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
1740
-				}
1741
-				$calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1742
-
1743
-				$calendaritem = mapi_msgstore_openentry($this->store, $calendaritems[0]);
1744
-				$recurr = new Recurrence($store, $calendaritem);
1745
-			}
1746
-		}
1747
-
1748
-		// we are sending a response for recurring meeting request (or exception), so set some required properties
1749
-		if (isset($recurr) && $recurr) {
1750
-			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
1751
-				$props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
1752
-			}
1753
-			if (!empty($messageprops[$this->proptags['recurrence_data']])) {
1754
-				$props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
1755
-			}
1756
-
1757
-			$props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
1758
-			$props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
1759
-
1760
-			$this->generateRecurDates($recurr, $messageprops, $props);
1761
-		}
1762
-
1763
-		// Create a response message
1764
-		$recip = [];
1765
-		$recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1766
-		$recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1767
-		$recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1768
-		$recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1769
-		$recip[PR_RECIPIENT_TYPE] = MAPI_TO;
1770
-		$recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1771
-
1772
-		switch ($status) {
1773
-			case olResponseAccepted:
1774
-				$classpostfix = "Pos";
1775
-				$subjectprefix = dgettext("kopano", "Accepted");
1776
-				break;
1777
-
1778
-			case olResponseDeclined:
1779
-				$classpostfix = "Neg";
1780
-				$subjectprefix = dgettext("kopano", "Declined");
1781
-				break;
1782
-
1783
-			case olResponseTentative:
1784
-				$classpostfix = "Tent";
1785
-				$subjectprefix = dgettext("kopano", "Tentatively accepted");
1786
-				break;
1787
-		}
1788
-
1789
-		if ($proposalStartTime && $proposalEndTime) {
1790
-			// if attendee has proposed new time then change subject prefix
1791
-			$subjectprefix = dgettext("kopano", "New Time Proposed");
1792
-		}
1793
-
1794
-		$props[PR_SUBJECT] = $subjectprefix . ": " . $messageprops[PR_SUBJECT];
1795
-
1796
-		$props[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Resp." . $classpostfix;
1797
-		if (isset($messageprops[PR_OWNER_APPT_ID])) {
1798
-			$props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
1799
-		}
1800
-
1801
-		// Set GLOBALID AND CLEANGLOBALID, if exception then also set basedate into GLOBALID(0x3).
1802
-		$props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
1803
-		$props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
1804
-		$props[$this->proptags['updatecounter']] = $messageprops[$this->proptags['updatecounter']];
1805
-
1806
-		// get the default store, in which we have to store the accepted email by delegate or normal user.
1807
-		$defaultStore = $this->openDefaultStore();
1808
-		$props[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($defaultStore);
1809
-
1810
-		if ($proposalStartTime && $proposalEndTime) {
1811
-			$props[$this->proptags['proposed_start_whole']] = $proposalStartTime;
1812
-			$props[$this->proptags['proposed_end_whole']] = $proposalEndTime;
1813
-			$props[$this->proptags['proposed_duration']] = round($proposalEndTime - $proposalStartTime) / 60;
1814
-			$props[$this->proptags['counter_proposal']] = true;
1815
-		}
1816
-
1817
-		// Set body message in Appointment
1818
-		if (isset($body)) {
1819
-			$props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
1820
-		}
1821
-
1822
-		// PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
1823
-		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
1824
-		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
1825
-
1826
-		// Set startdate and duedate in response mail.
1827
-		$props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
1828
-		$props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
1829
-
1830
-		// responselocation is used in the UI in Outlook on the response message
1831
-		if (isset($messageprops[$this->proptags['location']])) {
1832
-			$props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
1833
-			$props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
1834
-		}
1835
-
1836
-		// check if $store is set and it is not equal to $defaultStore (means its the delegation case)
1837
-		if (isset($store, $defaultStore)) {
1838
-			$storeProps = mapi_getprops($store, [PR_ENTRYID]);
1839
-			$defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
1840
-
1841
-			if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
1842
-				// get the properties of the other user (for which the logged in user is a delegate).
1843
-				$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
1844
-				$addrbook = mapi_openaddressbook($this->session);
1845
-				$addrbookitem = mapi_ab_openentry($addrbook, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
1846
-				$addrbookitemprops = mapi_getprops($addrbookitem, [PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
1847
-
1848
-				// setting the following properties will ensure that the delegation part of message.
1849
-				$props[PR_SENT_REPRESENTING_ENTRYID] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1850
-				$props[PR_SENT_REPRESENTING_NAME] = $addrbookitemprops[PR_DISPLAY_NAME];
1851
-				$props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $addrbookitemprops[PR_EMAIL_ADDRESS];
1852
-				$props[PR_SENT_REPRESENTING_ADDRTYPE] = "ZARAFA";
1853
-				$props[PR_SENT_REPRESENTING_SEARCH_KEY] = $addrbookitemprops[PR_SEARCH_KEY];
1854
-
1855
-				// set the following properties will ensure the sender's details, which will be the default user in this case.
1856
-				// the function returns array($name, $emailaddr, $addrtype, $entryid, $searchkey);
1857
-				$defaultUserDetails = $this->getOwnerAddress($defaultStore);
1858
-				$props[PR_SENDER_ENTRYID] = $defaultUserDetails[3];
1859
-				$props[PR_SENDER_EMAIL_ADDRESS] = $defaultUserDetails[1];
1860
-				$props[PR_SENDER_NAME] = $defaultUserDetails[0];
1861
-				$props[PR_SENDER_ADDRTYPE] = $defaultUserDetails[2];
1862
-				$props[PR_SENDER_SEARCH_KEY] = $defaultUserDetails[4];
1863
-			}
1864
-		}
1865
-
1866
-		// pass the default store to get the required store.
1867
-		$outbox = $this->openDefaultOutbox($defaultStore);
1868
-
1869
-		$message = mapi_folder_createmessage($outbox);
1870
-		mapi_setprops($message, $props);
1871
-		mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
1872
-		mapi_savechanges($message);
1873
-		mapi_message_submitmessage($message);
1874
-	}
1875
-
1876
-	/**
1877
-	 * Function which finds items in calendar based on specified parameters.
1878
-	 *
1879
-	 *@param binary $goid GlobalID(0x3) of item
1880
-	 *@param resource $calendar MAPI_folder of user
1881
-	 *@param bool $use_cleanGlobalID if true then search should be performed on cleanGlobalID(0x23) else globalID(0x3)
1882
-	 */
1883
-	public function findCalendarItems($goid, $calendar = false, $use_cleanGlobalID = false) {
1884
-		if (!$calendar) {
1885
-			// Open the Calendar
1886
-			$calendar = $this->openDefaultCalendar();
1887
-		}
1888
-
1889
-		// Find the item by restricting all items to the correct ID
1890
-		$restrict = [RES_AND, []];
1891
-
1892
-		array_push($restrict[1], [RES_PROPERTY,
1893
-			[RELOP => RELOP_EQ,
1894
-				ULPROPTAG => ($use_cleanGlobalID ? $this->proptags['goid2'] : $this->proptags['goid']),
1895
-				VALUE => $goid,
1896
-			],
1897
-		]);
1898
-
1899
-		$calendarcontents = mapi_folder_getcontentstable($calendar);
1900
-
1901
-		$rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
1902
-
1903
-		if (empty($rows)) {
1904
-			return;
1905
-		}
1906
-
1907
-		$calendaritems = [];
1908
-
1909
-		// In principle, there should only be one row, but we'll handle them all just in case
1910
-		foreach ($rows as $row) {
1911
-			$calendaritems[] = $row[PR_ENTRYID];
1912
-		}
1913
-
1914
-		return $calendaritems;
1915
-	}
1916
-
1917
-	// Returns TRUE if both entryids are equal. Equality is defined by both entryids pointing at the
1918
-	// same SMTP address when converted to SMTP
1919
-	public function compareABEntryIDs($entryid1, $entryid2) {
1920
-		// If the session was not passed, just do a 'normal' compare.
1921
-		if (!$this->session) {
1922
-			return $entryid1 == $entryid2;
1923
-		}
1924
-		$smtp1 = $this->getSMTPAddress($entryid1);
1925
-		$smtp2 = $this->getSMTPAddress($entryid2);
1926
-		if ($smtp1 == $smtp2) {
1927
-			return true;
1928
-		}
1929
-
1930
-		return false;
1931
-	}
1932
-
1933
-	// Gets the SMTP address of the passed addressbook entryid
1934
-	public function getSMTPAddress($entryid) {
1935
-		if (!$this->session) {
1936
-			return false;
1937
-		}
1938
-		$ab = mapi_openaddressbook($this->session);
1939
-
1940
-		$abitem = mapi_ab_openentry($ab, $entryid);
1941
-		if (!$abitem) {
1942
-			return "";
1943
-		}
1944
-
1945
-		$props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
1946
-		if ($props[PR_ADDRTYPE] == "SMTP") {
1947
-			return $props[PR_EMAIL_ADDRESS];
1948
-		}
1949
-
1950
-		return $props[PR_SMTP_ADDRESS];
1951
-	}
1952
-
1953
-	/**
1954
-	 * Gets the properties associated with the owner of the passed store:
1955
-	 * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
1956
-	 *
1957
-	 * @param $store message store
1958
-	 * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
1959
-	 * not used when passed store is public store. for public store we are always returning logged in user's info.
1960
-	 *
1961
-	 * @return properties of logged in user in an array in sequence of display_name, email address, address type,
1962
-	 *                    entryid and search key
1963
-	 */
1964
-	public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
1965
-		if (!$this->session) {
1966
-			return false;
1967
-		}
1968
-
1969
-		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
1970
-
1971
-		$ownerEntryId = false;
1972
-		if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
1973
-			$ownerEntryId = $storeProps[PR_USER_ENTRYID];
1974
-		}
1975
-		if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
1976
-			$ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1977
-		}
1978
-
1979
-		if ($ownerEntryId) {
1980
-			$ab = mapi_openaddressbook($this->session);
1981
-
1982
-			$kopanoUser = mapi_ab_openentry($ab, $ownerEntryId);
1983
-			if (!$kopanoUser) {
1984
-				return false;
1985
-			}
1986
-
1987
-			$ownerProps = mapi_getprops($kopanoUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
1988
-
1989
-			$addrType = $ownerProps[PR_ADDRTYPE];
1990
-			$name = $ownerProps[PR_DISPLAY_NAME];
1991
-			$emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
1992
-			$searchKey = $ownerProps[PR_SEARCH_KEY];
1993
-			$entryId = $ownerEntryId;
1994
-
1995
-			return [$name, $emailAddr, $addrType, $entryId, $searchKey];
1996
-		}
1997
-
1998
-		return false;
1999
-	}
2000
-
2001
-	// Opens this session's default message store
2002
-	public function openDefaultStore() {
2003
-		$storestable = mapi_getmsgstorestable($this->session);
2004
-		$rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
2005
-		$entry = false;
2006
-
2007
-		foreach ($rows as $row) {
2008
-			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2009
-				$entryid = $row[PR_ENTRYID];
2010
-
2011
-				break;
2012
-			}
2013
-		}
2014
-
2015
-		if (!$entryid) {
2016
-			return false;
2017
-		}
2018
-
2019
-		return mapi_openmsgstore($this->session, $entryid);
2020
-	}
2021
-
2022
-	/**
2023
-	 *  Function which adds organizer to recipient list which is passed.
2024
-	 *  This function also checks if it has organizer.
2025
-	 *
2026
-	 * @param array $messageProps message properties
2027
-	 * @param array $recipients   recipients list of message
2028
-	 * @param bool  $isException  true if we are processing recipient of exception
2029
-	 */
2030
-	public function addOrganizer($messageProps, &$recipients, $isException = false) {
2031
-		$hasOrganizer = false;
2032
-		// Check if meeting already has an organizer.
2033
-		foreach ($recipients as $key => $recipient) {
2034
-			if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2035
-				$hasOrganizer = true;
2036
-			}
2037
-			elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2038
-				// Recipients for an occurrence
2039
-				$recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2040
-			}
2041
-		}
2042
-
2043
-		if (!$hasOrganizer) {
2044
-			// Create organizer.
2045
-			$organizer = [];
2046
-			$organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
2047
-			$organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2048
-			$organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2049
-			$organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
2050
-			$organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2051
-			$organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2052
-			$organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2053
-			$organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2054
-			$organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2055
-
2056
-			// Add organizer to recipients list.
2057
-			array_unshift($recipients, $organizer);
2058
-		}
2059
-	}
2060
-
2061
-	/**
2062
-	 * Function adds recipients in recips array from the string.
2063
-	 *
2064
-	 * @param array  $recips      recipient array
2065
-	 * @param string $recipString recipient string attendees
2066
-	 * @param int    $type        type of the recipient, MAPI_TO/MAPI_CC
2067
-	 * @param mixed  $recipType
2068
-	 */
2069
-	public function setRecipsFromString(&$recips, $recipString, $recipType = MAPI_TO) {
2070
-		$extraRecipient = [];
2071
-		$recipArray = explode(";", $recipString);
2072
-
2073
-		foreach ($recipArray as $recip) {
2074
-			$recip = trim($recip);
2075
-			if (!empty($recip)) {
2076
-				$extraRecipient[PR_RECIPIENT_TYPE] = $recipType;
2077
-				$extraRecipient[PR_DISPLAY_NAME] = $recip;
2078
-				array_push($recips, $extraRecipient);
2079
-			}
2080
-		}
2081
-	}
2082
-
2083
-	/**
2084
-	 * Function which removes an exception/occurrence from recurrencing meeting
2085
-	 * when a meeting cancellation of an occurrence is processed.
2086
-	 *
2087
-	 *@param string $basedate basedate of an occurrence
2088
-	 *@param resource $message recurring item from which occurrence has to be deleted
2089
-	 *@param resource $store MAPI_MSG_Store which contains the item
2090
-	 */
2091
-	public function doRemoveExceptionFromCalendar($basedate, $message, $store) {
2092
-		$recurr = new Recurrence($store, $message);
2093
-		$recurr->createException([], $basedate, true);
2094
-		mapi_savechanges($message);
2095
-	}
2096
-
2097
-	/**
2098
-	 * Function which returns basedate of a changed occurrance from globalID of meeting request.
2099
-	 *
2100
-	 *@param binary $goid globalID
2101
-	 *
2102
-	 *@return bool true if basedate is found else false it not found
2103
-	 */
2104
-	public function getBasedateFromGlobalID($goid) {
2105
-		$hexguid = bin2hex($goid);
2106
-		$hexbase = substr($hexguid, 32, 8);
2107
-		$day = hexdec(substr($hexbase, 6, 2));
2108
-		$month = hexdec(substr($hexbase, 4, 2));
2109
-		$year = hexdec(substr($hexbase, 0, 4));
2110
-
2111
-		if ($day && $month && $year) {
2112
-			return gmmktime(0, 0, 0, $month, $day, $year);
2113
-		}
2114
-
2115
-		return false;
2116
-	}
2117
-
2118
-	/**
2119
-	 * Function which sets basedate in globalID of changed occurrance which is to be sent.
2120
-	 *
2121
-	 *@param binary $goid globalID
2122
-	 *@param string basedate of changed occurrance
2123
-	 * @param mixed $basedate
2124
-	 *
2125
-	 *@return binary globalID with basedate in it
2126
-	 */
2127
-	public function setBasedateInGlobalID($goid, $basedate = false) {
2128
-		$hexguid = bin2hex($goid);
2129
-		$year = $basedate ? sprintf('%04s', dechex(date('Y', $basedate))) : '0000';
2130
-		$month = $basedate ? sprintf('%02s', dechex(date('m', $basedate))) : '00';
2131
-		$day = $basedate ? sprintf('%02s', dechex(date('d', $basedate))) : '00';
2132
-
2133
-		return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2134
-	}
2135
-
2136
-	/**
2137
-	 * Function which replaces attachments with copy_from in copy_to.
2138
-	 *
2139
-	 *@param resource $copy_from MAPI_message from which attachments are to be copied
2140
-	 *@param resource $copy_to MAPI_message to which attachment are to be copied
2141
-	 *@param bool $copyExceptions if true then all exceptions should also be sent as attachments
2142
-	 */
2143
-	public function replaceAttachments($copy_from, $copy_to, $copyExceptions = true) {
2144
-		/* remove all old attachments */
2145
-		$attachmentTable = mapi_message_getattachmenttable($copy_to);
2146
-		if ($attachmentTable) {
2147
-			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2148
-
2149
-			foreach ($attachments as $attach_props) {
2150
-				/* remove exceptions too? */
2151
-				if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME])) {
2152
-					continue;
2153
-				}
2154
-				mapi_message_deleteattach($copy_to, $attach_props[PR_ATTACH_NUM]);
2155
-			}
2156
-		}
2157
-		$attachmentTable = false;
2158
-
2159
-		/* copy new attachments */
2160
-		$attachmentTable = mapi_message_getattachmenttable($copy_from);
2161
-		if ($attachmentTable) {
2162
-			$attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2163
-
2164
-			foreach ($attachments as $attach_props) {
2165
-				if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME])) {
2166
-					continue;
2167
-				}
2168
-
2169
-				$attach_old = mapi_message_openattach($copy_from, (int) $attach_props[PR_ATTACH_NUM]);
2170
-				$attach_newResourceMsg = mapi_message_createattach($copy_to);
2171
-				mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
2172
-				mapi_savechanges($attach_newResourceMsg);
2173
-			}
2174
-		}
2175
-	}
2176
-
2177
-	/**
2178
-	 * Function which replaces recipients in copy_to with recipients from copy_from.
2179
-	 *
2180
-	 *@param resource $copy_from MAPI_message from which recipients are to be copied
2181
-	 *@param resource $copy_to MAPI_message to which recipients are to be copied
2182
-	 * @param mixed $isDelegate
2183
-	 */
2184
-	public function replaceRecipients($copy_from, $copy_to, $isDelegate = false) {
2185
-		$recipienttable = mapi_message_getrecipienttable($copy_from);
2186
-
2187
-		// If delegate, then do not add the delegate in recipients
2188
-		if ($isDelegate) {
2189
-			$delegate = mapi_getprops($copy_from, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2190
-			$res = [RES_PROPERTY, [RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]]];
2191
-			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $res);
2192
-		}
2193
-		else {
2194
-			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2195
-		}
2196
-
2197
-		$copy_to_recipientTable = mapi_message_getrecipienttable($copy_to);
2198
-		$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
2199
-		foreach ($copy_to_recipientRows as $recipient) {
2200
-			mapi_message_modifyrecipients($copy_to, MODRECIP_REMOVE, [$recipient]);
2201
-		}
2202
-
2203
-		mapi_message_modifyrecipients($copy_to, MODRECIP_ADD, $recipients);
2204
-	}
2205
-
2206
-	/**
2207
-	 * Function creates meeting item in the resource's calendar.
2208
-	 *
2209
-	 *@param resource $message MAPI_message which is to be created in the resource's calendar
2210
-	 *@param bool $cancel cancel meeting
2211
-	 *@param string $prefix prefix for subject of meeting
2212
-	 * @param mixed $basedate
2213
-	 */
2214
-	public function bookResources($message, $cancel, $prefix, $basedate = false) {
2215
-		if (!$this->enableDirectBooking) {
2216
-			return [];
2217
-		}
2218
-
2219
-		// Get the properties of the message
2220
-		$messageprops = mapi_getprops($message);
2221
-
2222
-		if ($basedate) {
2223
-			$recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
2224
-
2225
-			$messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2226
-			$messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2227
-
2228
-			// Delete properties which are not needed.
2229
-			$deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
2230
-			foreach ($deleteProps as $propID) {
2231
-				if (isset($messageprops[$propID])) {
2232
-					unset($messageprops[$propID]);
2233
-				}
2234
-			}
2235
-
2236
-			if (isset($messageprops[$this->proptags['recurring']])) {
2237
-				$messageprops[$this->proptags['recurring']] = false;
2238
-			}
2239
-
2240
-			// Set Outlook properties
2241
-			$messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2242
-			$messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2243
-			$messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2244
-			$messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2245
-			$messageprops[$this->proptags['attendee_critical_change']] = time();
2246
-			$messageprops[$this->proptags['owner_critical_change']] = time();
2247
-		}
2248
-
2249
-		// Get resource recipients
2250
-		$getResourcesRestriction = [RES_AND,
2251
-			[[RES_PROPERTY,
2252
-				[RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2253
-					ULPROPTAG => PR_RECIPIENT_TYPE,
2254
-					VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2255
-				],
2256
-			]],
2257
-		];
2258
-		$recipienttable = mapi_message_getrecipienttable($message);
2259
-		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2260
-
2261
-		$this->errorSetResource = false;
2262
-		$resourceRecipData = [];
2263
-
2264
-		// Put appointment into store resource users
2265
-		$i = 0;
2266
-		$len = count($resourceRecipients);
2267
-		while (!$this->errorSetResource && $i < $len) {
2268
-			$request = [[PR_DISPLAY_NAME => $resourceRecipients[$i][PR_DISPLAY_NAME]]];
2269
-			$ab = mapi_openaddressbook($this->session);
2270
-			$ret = mapi_ab_resolvename($ab, $request, EMS_AB_ADDRESS_LOOKUP);
2271
-			$result = mapi_last_hresult();
2272
-			if ($result == NOERROR) {
2273
-				$result = $ret[0][PR_ENTRYID];
2274
-			}
2275
-			$resourceUsername = $ret[0][PR_EMAIL_ADDRESS];
2276
-			$resourceABEntryID = $ret[0][PR_ENTRYID];
2277
-
2278
-			// Get StoreEntryID by username
2279
-			$user_entryid = mapi_msgstore_createentryid($this->store, $resourceUsername);
2280
-
2281
-			// Open store of the user
2282
-			$userStore = mapi_openmsgstore($this->session, $user_entryid);
2283
-			// Open root folder
2284
-			$userRoot = mapi_msgstore_openentry($userStore, null);
2285
-			// Get calendar entryID
2286
-			$userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
2287
-
2288
-			// Open Calendar folder   [check hresult==0]
2289
-			$accessToFolder = false;
2290
-
2291
-			try {
2292
-				$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2293
-				if ($calFolder) {
2294
-					$calFolderProps = mapi_getprops($calFolder, [PR_ACCESS]);
2295
-					if (($calFolderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) !== 0) {
2296
-						$accessToFolder = true;
2297
-					}
2298
-				}
2299
-			}
2300
-			catch (MAPIException $e) {
2301
-				$e->setHandled();
2302
-				$this->errorSetResource = 1; // No access
2303
-			}
2304
-
2305
-			if ($accessToFolder) {
2306
-				/**
2307
-				 * Get the LocalFreebusy message that contains the properties that
2308
-				 * are set to accept or decline resource meeting requests.
2309
-				 */
2310
-				// Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
2311
-				$localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
2312
-				if ($localFreebusyMsg) {
2313
-					$props = mapi_getprops($localFreebusyMsg, [PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS]);
2314
-
2315
-					$acceptMeetingRequests = ($props[PR_PROCESS_MEETING_REQUESTS]) ? 1 : 0;
2316
-					$declineRecurringMeetingRequests = ($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? 1 : 0;
2317
-					$declineConflictingMeetingRequests = ($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? 1 : 0;
2318
-					if (!$acceptMeetingRequests) {
2319
-						/*
1563
+    /**
1564
+     * Return the tracking status of a recipient based on the IPM class (passed).
1565
+     *
1566
+     * @param mixed $class
1567
+     */
1568
+    public function getTrackStatus($class) {
1569
+        $status = olRecipientTrackStatusNone;
1570
+
1571
+        switch ($class) {
1572
+            case "IPM.Schedule.Meeting.Resp.Pos":
1573
+                $status = olRecipientTrackStatusAccepted;
1574
+                break;
1575
+
1576
+            case "IPM.Schedule.Meeting.Resp.Tent":
1577
+                $status = olRecipientTrackStatusTentative;
1578
+                break;
1579
+
1580
+            case "IPM.Schedule.Meeting.Resp.Neg":
1581
+                $status = olRecipientTrackStatusDeclined;
1582
+                break;
1583
+        }
1584
+
1585
+        return $status;
1586
+    }
1587
+
1588
+    public function openParentFolder() {
1589
+        $messageprops = mapi_getprops($this->message, [PR_PARENT_ENTRYID]);
1590
+
1591
+        return mapi_msgstore_openentry($this->store, $messageprops[PR_PARENT_ENTRYID]);
1592
+    }
1593
+
1594
+    public function openDefaultCalendar() {
1595
+        return $this->openDefaultFolder(PR_IPM_APPOINTMENT_ENTRYID);
1596
+    }
1597
+
1598
+    public function openDefaultOutbox($store = false) {
1599
+        return $this->openBaseFolder(PR_IPM_OUTBOX_ENTRYID, $store);
1600
+    }
1601
+
1602
+    public function openDefaultWastebasket() {
1603
+        return $this->openBaseFolder(PR_IPM_WASTEBASKET_ENTRYID);
1604
+    }
1605
+
1606
+    public function getDefaultWastebasketEntryID() {
1607
+        return $this->getBaseEntryID(PR_IPM_WASTEBASKET_ENTRYID);
1608
+    }
1609
+
1610
+    public function getDefaultSentmailEntryID($store = false) {
1611
+        return $this->getBaseEntryID(PR_IPM_SENTMAIL_ENTRYID, $store);
1612
+    }
1613
+
1614
+    public function getDefaultFolderEntryID($prop) {
1615
+        try {
1616
+            $inbox = mapi_msgstore_getreceivefolder($this->store);
1617
+        }
1618
+        catch (MAPIException $e) {
1619
+            // public store doesn't support this method
1620
+            if ($e->getCode() == MAPI_E_NO_SUPPORT) {
1621
+                // don't propagate this error to parent handlers, if store doesn't support it
1622
+                $e->setHandled();
1623
+
1624
+                return;
1625
+            }
1626
+        }
1627
+
1628
+        $inboxprops = mapi_getprops($inbox, [$prop]);
1629
+        if (!isset($inboxprops[$prop])) {
1630
+            return;
1631
+        }
1632
+
1633
+        return $inboxprops[$prop];
1634
+    }
1635
+
1636
+    public function openDefaultFolder($prop) {
1637
+        $entryid = $this->getDefaultFolderEntryID($prop);
1638
+
1639
+        return mapi_msgstore_openentry($this->store, $entryid);
1640
+    }
1641
+
1642
+    public function getBaseEntryID($prop, $store = false) {
1643
+        $storeprops = mapi_getprops((($store) ? $store : $this->store), [$prop]);
1644
+        if (!isset($storeprops[$prop])) {
1645
+            return;
1646
+        }
1647
+
1648
+        return $storeprops[$prop];
1649
+    }
1650
+
1651
+    public function openBaseFolder($prop, $store = false) {
1652
+        $entryid = $this->getBaseEntryID($prop, $store);
1653
+
1654
+        return mapi_msgstore_openentry((($store) ? $store : $this->store), $entryid);
1655
+    }
1656
+
1657
+    /**
1658
+     * Function which sends response to organizer when attendee accepts, declines or proposes new time to a received meeting request.
1659
+     *
1660
+     *@param int $status response status of attendee
1661
+     *@param int $proposalStartTime proposed starttime by attendee
1662
+     *@param int $proposalEndTime proposed endtime by attendee
1663
+     *@param int $basedate date of occurrence which attendee has responded
1664
+     * @param mixed $body
1665
+     * @param mixed $store
1666
+     * @param mixed $calFolder
1667
+     */
1668
+    public function createResponse($status, $proposalStartTime = false, $proposalEndTime = false, $body = false, $store, $basedate = false, $calFolder) {
1669
+        $messageprops = mapi_getprops($this->message, [PR_SENT_REPRESENTING_ENTRYID,
1670
+            PR_SENT_REPRESENTING_EMAIL_ADDRESS,
1671
+            PR_SENT_REPRESENTING_ADDRTYPE,
1672
+            PR_SENT_REPRESENTING_NAME,
1673
+            PR_SENT_REPRESENTING_SEARCH_KEY,
1674
+            $this->proptags['goid'],
1675
+            $this->proptags['goid2'],
1676
+            $this->proptags['location'],
1677
+            $this->proptags['startdate'],
1678
+            $this->proptags['duedate'],
1679
+            $this->proptags['recurring'],
1680
+            $this->proptags['recurring_pattern'],
1681
+            $this->proptags['recurrence_data'],
1682
+            $this->proptags['timezone_data'],
1683
+            $this->proptags['timezone'],
1684
+            $this->proptags['updatecounter'],
1685
+            PR_SUBJECT,
1686
+            PR_MESSAGE_CLASS,
1687
+            PR_OWNER_APPT_ID,
1688
+            $this->proptags['is_exception'],
1689
+        ]);
1690
+
1691
+        if ($basedate && $messageprops[PR_MESSAGE_CLASS] != "IPM.Schedule.Meeting.Request") {
1692
+            // we are creating response from a recurring calendar item object
1693
+            // We found basedate,so opened occurrence and get properties.
1694
+            $recurr = new Recurrence($store, $this->message);
1695
+            $exception = $recurr->getExceptionAttachment($basedate);
1696
+
1697
+            if ($exception) {
1698
+                // Exception found, Now retrieve properties
1699
+                $imessage = mapi_attach_openobj($exception, 0);
1700
+                $imsgprops = mapi_getprops($imessage);
1701
+
1702
+                // If location is provided, copy it to the response
1703
+                if (isset($imsgprops[$this->proptags['location']])) {
1704
+                    $messageprops[$this->proptags['location']] = $imsgprops[$this->proptags['location']];
1705
+                }
1706
+
1707
+                // Update $messageprops with timings of occurrence
1708
+                $messageprops[$this->proptags['startdate']] = $imsgprops[$this->proptags['startdate']];
1709
+                $messageprops[$this->proptags['duedate']] = $imsgprops[$this->proptags['duedate']];
1710
+
1711
+                // Meeting related properties
1712
+                $props[$this->proptags['meetingstatus']] = $imsgprops[$this->proptags['meetingstatus']];
1713
+                $props[$this->proptags['responsestatus']] = $imsgprops[$this->proptags['responsestatus']];
1714
+                $props[PR_SUBJECT] = $imsgprops[PR_SUBJECT];
1715
+            }
1716
+            else {
1717
+                // Exceptions is deleted.
1718
+                // Update $messageprops with timings of occurrence
1719
+                $messageprops[$this->proptags['startdate']] = $recurr->getOccurrenceStart($basedate);
1720
+                $messageprops[$this->proptags['duedate']] = $recurr->getOccurrenceEnd($basedate);
1721
+
1722
+                $props[$this->proptags['meetingstatus']] = olNonMeeting;
1723
+                $props[$this->proptags['responsestatus']] = olResponseNone;
1724
+            }
1725
+
1726
+            $props[$this->proptags['recurring']] = false;
1727
+            $props[$this->proptags['is_exception']] = true;
1728
+        }
1729
+        else {
1730
+            // we are creating a response from meeting request mail (it could be recurring or non-recurring)
1731
+            // Send all recurrence info in response, if this is a recurrence meeting.
1732
+            $isRecurring = isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']];
1733
+            $isException = isset($messageprops[$this->proptags['is_exception']]) && $messageprops[$this->proptags['is_exception']];
1734
+            if ($isRecurring || $isException) {
1735
+                if ($isRecurring) {
1736
+                    $props[$this->proptags['recurring']] = $messageprops[$this->proptags['recurring']];
1737
+                }
1738
+                if ($isException) {
1739
+                    $props[$this->proptags['is_exception']] = $messageprops[$this->proptags['is_exception']];
1740
+                }
1741
+                $calendaritems = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder);
1742
+
1743
+                $calendaritem = mapi_msgstore_openentry($this->store, $calendaritems[0]);
1744
+                $recurr = new Recurrence($store, $calendaritem);
1745
+            }
1746
+        }
1747
+
1748
+        // we are sending a response for recurring meeting request (or exception), so set some required properties
1749
+        if (isset($recurr) && $recurr) {
1750
+            if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
1751
+                $props[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
1752
+            }
1753
+            if (!empty($messageprops[$this->proptags['recurrence_data']])) {
1754
+                $props[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
1755
+            }
1756
+
1757
+            $props[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
1758
+            $props[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
1759
+
1760
+            $this->generateRecurDates($recurr, $messageprops, $props);
1761
+        }
1762
+
1763
+        // Create a response message
1764
+        $recip = [];
1765
+        $recip[PR_ENTRYID] = $messageprops[PR_SENT_REPRESENTING_ENTRYID];
1766
+        $recip[PR_EMAIL_ADDRESS] = $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
1767
+        $recip[PR_ADDRTYPE] = $messageprops[PR_SENT_REPRESENTING_ADDRTYPE];
1768
+        $recip[PR_DISPLAY_NAME] = $messageprops[PR_SENT_REPRESENTING_NAME];
1769
+        $recip[PR_RECIPIENT_TYPE] = MAPI_TO;
1770
+        $recip[PR_SEARCH_KEY] = $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY];
1771
+
1772
+        switch ($status) {
1773
+            case olResponseAccepted:
1774
+                $classpostfix = "Pos";
1775
+                $subjectprefix = dgettext("kopano", "Accepted");
1776
+                break;
1777
+
1778
+            case olResponseDeclined:
1779
+                $classpostfix = "Neg";
1780
+                $subjectprefix = dgettext("kopano", "Declined");
1781
+                break;
1782
+
1783
+            case olResponseTentative:
1784
+                $classpostfix = "Tent";
1785
+                $subjectprefix = dgettext("kopano", "Tentatively accepted");
1786
+                break;
1787
+        }
1788
+
1789
+        if ($proposalStartTime && $proposalEndTime) {
1790
+            // if attendee has proposed new time then change subject prefix
1791
+            $subjectprefix = dgettext("kopano", "New Time Proposed");
1792
+        }
1793
+
1794
+        $props[PR_SUBJECT] = $subjectprefix . ": " . $messageprops[PR_SUBJECT];
1795
+
1796
+        $props[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Resp." . $classpostfix;
1797
+        if (isset($messageprops[PR_OWNER_APPT_ID])) {
1798
+            $props[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
1799
+        }
1800
+
1801
+        // Set GLOBALID AND CLEANGLOBALID, if exception then also set basedate into GLOBALID(0x3).
1802
+        $props[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
1803
+        $props[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
1804
+        $props[$this->proptags['updatecounter']] = $messageprops[$this->proptags['updatecounter']];
1805
+
1806
+        // get the default store, in which we have to store the accepted email by delegate or normal user.
1807
+        $defaultStore = $this->openDefaultStore();
1808
+        $props[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($defaultStore);
1809
+
1810
+        if ($proposalStartTime && $proposalEndTime) {
1811
+            $props[$this->proptags['proposed_start_whole']] = $proposalStartTime;
1812
+            $props[$this->proptags['proposed_end_whole']] = $proposalEndTime;
1813
+            $props[$this->proptags['proposed_duration']] = round($proposalEndTime - $proposalStartTime) / 60;
1814
+            $props[$this->proptags['counter_proposal']] = true;
1815
+        }
1816
+
1817
+        // Set body message in Appointment
1818
+        if (isset($body)) {
1819
+            $props[PR_BODY] = $this->getMeetingTimeInfo() ? $this->getMeetingTimeInfo() : $body;
1820
+        }
1821
+
1822
+        // PR_START_DATE/PR_END_DATE is used in the UI in Outlook on the response message
1823
+        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
1824
+        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
1825
+
1826
+        // Set startdate and duedate in response mail.
1827
+        $props[$this->proptags['startdate']] = $messageprops[$this->proptags['startdate']];
1828
+        $props[$this->proptags['duedate']] = $messageprops[$this->proptags['duedate']];
1829
+
1830
+        // responselocation is used in the UI in Outlook on the response message
1831
+        if (isset($messageprops[$this->proptags['location']])) {
1832
+            $props[$this->proptags['responselocation']] = $messageprops[$this->proptags['location']];
1833
+            $props[$this->proptags['location']] = $messageprops[$this->proptags['location']];
1834
+        }
1835
+
1836
+        // check if $store is set and it is not equal to $defaultStore (means its the delegation case)
1837
+        if (isset($store, $defaultStore)) {
1838
+            $storeProps = mapi_getprops($store, [PR_ENTRYID]);
1839
+            $defaultStoreProps = mapi_getprops($defaultStore, [PR_ENTRYID]);
1840
+
1841
+            if ($storeProps[PR_ENTRYID] !== $defaultStoreProps[PR_ENTRYID]) {
1842
+                // get the properties of the other user (for which the logged in user is a delegate).
1843
+                $storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
1844
+                $addrbook = mapi_openaddressbook($this->session);
1845
+                $addrbookitem = mapi_ab_openentry($addrbook, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
1846
+                $addrbookitemprops = mapi_getprops($addrbookitem, [PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
1847
+
1848
+                // setting the following properties will ensure that the delegation part of message.
1849
+                $props[PR_SENT_REPRESENTING_ENTRYID] = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1850
+                $props[PR_SENT_REPRESENTING_NAME] = $addrbookitemprops[PR_DISPLAY_NAME];
1851
+                $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $addrbookitemprops[PR_EMAIL_ADDRESS];
1852
+                $props[PR_SENT_REPRESENTING_ADDRTYPE] = "ZARAFA";
1853
+                $props[PR_SENT_REPRESENTING_SEARCH_KEY] = $addrbookitemprops[PR_SEARCH_KEY];
1854
+
1855
+                // set the following properties will ensure the sender's details, which will be the default user in this case.
1856
+                // the function returns array($name, $emailaddr, $addrtype, $entryid, $searchkey);
1857
+                $defaultUserDetails = $this->getOwnerAddress($defaultStore);
1858
+                $props[PR_SENDER_ENTRYID] = $defaultUserDetails[3];
1859
+                $props[PR_SENDER_EMAIL_ADDRESS] = $defaultUserDetails[1];
1860
+                $props[PR_SENDER_NAME] = $defaultUserDetails[0];
1861
+                $props[PR_SENDER_ADDRTYPE] = $defaultUserDetails[2];
1862
+                $props[PR_SENDER_SEARCH_KEY] = $defaultUserDetails[4];
1863
+            }
1864
+        }
1865
+
1866
+        // pass the default store to get the required store.
1867
+        $outbox = $this->openDefaultOutbox($defaultStore);
1868
+
1869
+        $message = mapi_folder_createmessage($outbox);
1870
+        mapi_setprops($message, $props);
1871
+        mapi_message_modifyrecipients($message, MODRECIP_ADD, [$recip]);
1872
+        mapi_savechanges($message);
1873
+        mapi_message_submitmessage($message);
1874
+    }
1875
+
1876
+    /**
1877
+     * Function which finds items in calendar based on specified parameters.
1878
+     *
1879
+     *@param binary $goid GlobalID(0x3) of item
1880
+     *@param resource $calendar MAPI_folder of user
1881
+     *@param bool $use_cleanGlobalID if true then search should be performed on cleanGlobalID(0x23) else globalID(0x3)
1882
+     */
1883
+    public function findCalendarItems($goid, $calendar = false, $use_cleanGlobalID = false) {
1884
+        if (!$calendar) {
1885
+            // Open the Calendar
1886
+            $calendar = $this->openDefaultCalendar();
1887
+        }
1888
+
1889
+        // Find the item by restricting all items to the correct ID
1890
+        $restrict = [RES_AND, []];
1891
+
1892
+        array_push($restrict[1], [RES_PROPERTY,
1893
+            [RELOP => RELOP_EQ,
1894
+                ULPROPTAG => ($use_cleanGlobalID ? $this->proptags['goid2'] : $this->proptags['goid']),
1895
+                VALUE => $goid,
1896
+            ],
1897
+        ]);
1898
+
1899
+        $calendarcontents = mapi_folder_getcontentstable($calendar);
1900
+
1901
+        $rows = mapi_table_queryallrows($calendarcontents, [PR_ENTRYID], $restrict);
1902
+
1903
+        if (empty($rows)) {
1904
+            return;
1905
+        }
1906
+
1907
+        $calendaritems = [];
1908
+
1909
+        // In principle, there should only be one row, but we'll handle them all just in case
1910
+        foreach ($rows as $row) {
1911
+            $calendaritems[] = $row[PR_ENTRYID];
1912
+        }
1913
+
1914
+        return $calendaritems;
1915
+    }
1916
+
1917
+    // Returns TRUE if both entryids are equal. Equality is defined by both entryids pointing at the
1918
+    // same SMTP address when converted to SMTP
1919
+    public function compareABEntryIDs($entryid1, $entryid2) {
1920
+        // If the session was not passed, just do a 'normal' compare.
1921
+        if (!$this->session) {
1922
+            return $entryid1 == $entryid2;
1923
+        }
1924
+        $smtp1 = $this->getSMTPAddress($entryid1);
1925
+        $smtp2 = $this->getSMTPAddress($entryid2);
1926
+        if ($smtp1 == $smtp2) {
1927
+            return true;
1928
+        }
1929
+
1930
+        return false;
1931
+    }
1932
+
1933
+    // Gets the SMTP address of the passed addressbook entryid
1934
+    public function getSMTPAddress($entryid) {
1935
+        if (!$this->session) {
1936
+            return false;
1937
+        }
1938
+        $ab = mapi_openaddressbook($this->session);
1939
+
1940
+        $abitem = mapi_ab_openentry($ab, $entryid);
1941
+        if (!$abitem) {
1942
+            return "";
1943
+        }
1944
+
1945
+        $props = mapi_getprops($abitem, [PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS]);
1946
+        if ($props[PR_ADDRTYPE] == "SMTP") {
1947
+            return $props[PR_EMAIL_ADDRESS];
1948
+        }
1949
+
1950
+        return $props[PR_SMTP_ADDRESS];
1951
+    }
1952
+
1953
+    /**
1954
+     * Gets the properties associated with the owner of the passed store:
1955
+     * PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_ADDRTYPE, PR_ENTRYID, PR_SEARCH_KEY.
1956
+     *
1957
+     * @param $store message store
1958
+     * @param $fallbackToLoggedInUser if true then return properties of logged in user instead of mailbox owner
1959
+     * not used when passed store is public store. for public store we are always returning logged in user's info.
1960
+     *
1961
+     * @return properties of logged in user in an array in sequence of display_name, email address, address type,
1962
+     *                    entryid and search key
1963
+     */
1964
+    public function getOwnerAddress($store, $fallbackToLoggedInUser = true) {
1965
+        if (!$this->session) {
1966
+            return false;
1967
+        }
1968
+
1969
+        $storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID, PR_USER_ENTRYID]);
1970
+
1971
+        $ownerEntryId = false;
1972
+        if (isset($storeProps[PR_USER_ENTRYID]) && $storeProps[PR_USER_ENTRYID]) {
1973
+            $ownerEntryId = $storeProps[PR_USER_ENTRYID];
1974
+        }
1975
+        if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID]) && $storeProps[PR_MAILBOX_OWNER_ENTRYID] && !$fallbackToLoggedInUser) {
1976
+            $ownerEntryId = $storeProps[PR_MAILBOX_OWNER_ENTRYID];
1977
+        }
1978
+
1979
+        if ($ownerEntryId) {
1980
+            $ab = mapi_openaddressbook($this->session);
1981
+
1982
+            $kopanoUser = mapi_ab_openentry($ab, $ownerEntryId);
1983
+            if (!$kopanoUser) {
1984
+                return false;
1985
+            }
1986
+
1987
+            $ownerProps = mapi_getprops($kopanoUser, [PR_ADDRTYPE, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_SEARCH_KEY]);
1988
+
1989
+            $addrType = $ownerProps[PR_ADDRTYPE];
1990
+            $name = $ownerProps[PR_DISPLAY_NAME];
1991
+            $emailAddr = $ownerProps[PR_EMAIL_ADDRESS];
1992
+            $searchKey = $ownerProps[PR_SEARCH_KEY];
1993
+            $entryId = $ownerEntryId;
1994
+
1995
+            return [$name, $emailAddr, $addrType, $entryId, $searchKey];
1996
+        }
1997
+
1998
+        return false;
1999
+    }
2000
+
2001
+    // Opens this session's default message store
2002
+    public function openDefaultStore() {
2003
+        $storestable = mapi_getmsgstorestable($this->session);
2004
+        $rows = mapi_table_queryallrows($storestable, [PR_ENTRYID, PR_DEFAULT_STORE]);
2005
+        $entry = false;
2006
+
2007
+        foreach ($rows as $row) {
2008
+            if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE]) {
2009
+                $entryid = $row[PR_ENTRYID];
2010
+
2011
+                break;
2012
+            }
2013
+        }
2014
+
2015
+        if (!$entryid) {
2016
+            return false;
2017
+        }
2018
+
2019
+        return mapi_openmsgstore($this->session, $entryid);
2020
+    }
2021
+
2022
+    /**
2023
+     *  Function which adds organizer to recipient list which is passed.
2024
+     *  This function also checks if it has organizer.
2025
+     *
2026
+     * @param array $messageProps message properties
2027
+     * @param array $recipients   recipients list of message
2028
+     * @param bool  $isException  true if we are processing recipient of exception
2029
+     */
2030
+    public function addOrganizer($messageProps, &$recipients, $isException = false) {
2031
+        $hasOrganizer = false;
2032
+        // Check if meeting already has an organizer.
2033
+        foreach ($recipients as $key => $recipient) {
2034
+            if (isset($recipient[PR_RECIPIENT_FLAGS]) && $recipient[PR_RECIPIENT_FLAGS] == (recipSendable | recipOrganizer)) {
2035
+                $hasOrganizer = true;
2036
+            }
2037
+            elseif ($isException && !isset($recipient[PR_RECIPIENT_FLAGS])) {
2038
+                // Recipients for an occurrence
2039
+                $recipients[$key][PR_RECIPIENT_FLAGS] = recipSendable | recipExceptionalResponse;
2040
+            }
2041
+        }
2042
+
2043
+        if (!$hasOrganizer) {
2044
+            // Create organizer.
2045
+            $organizer = [];
2046
+            $organizer[PR_ENTRYID] = $messageProps[PR_SENT_REPRESENTING_ENTRYID];
2047
+            $organizer[PR_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2048
+            $organizer[PR_EMAIL_ADDRESS] = $messageProps[PR_SENT_REPRESENTING_EMAIL_ADDRESS];
2049
+            $organizer[PR_RECIPIENT_TYPE] = MAPI_TO;
2050
+            $organizer[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_SENT_REPRESENTING_NAME];
2051
+            $organizer[PR_ADDRTYPE] = empty($messageProps[PR_SENT_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_SENT_REPRESENTING_ADDRTYPE];
2052
+            $organizer[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
2053
+            $organizer[PR_RECIPIENT_FLAGS] = recipSendable | recipOrganizer;
2054
+            $organizer[PR_SEARCH_KEY] = $messageProps[PR_SENT_REPRESENTING_SEARCH_KEY];
2055
+
2056
+            // Add organizer to recipients list.
2057
+            array_unshift($recipients, $organizer);
2058
+        }
2059
+    }
2060
+
2061
+    /**
2062
+     * Function adds recipients in recips array from the string.
2063
+     *
2064
+     * @param array  $recips      recipient array
2065
+     * @param string $recipString recipient string attendees
2066
+     * @param int    $type        type of the recipient, MAPI_TO/MAPI_CC
2067
+     * @param mixed  $recipType
2068
+     */
2069
+    public function setRecipsFromString(&$recips, $recipString, $recipType = MAPI_TO) {
2070
+        $extraRecipient = [];
2071
+        $recipArray = explode(";", $recipString);
2072
+
2073
+        foreach ($recipArray as $recip) {
2074
+            $recip = trim($recip);
2075
+            if (!empty($recip)) {
2076
+                $extraRecipient[PR_RECIPIENT_TYPE] = $recipType;
2077
+                $extraRecipient[PR_DISPLAY_NAME] = $recip;
2078
+                array_push($recips, $extraRecipient);
2079
+            }
2080
+        }
2081
+    }
2082
+
2083
+    /**
2084
+     * Function which removes an exception/occurrence from recurrencing meeting
2085
+     * when a meeting cancellation of an occurrence is processed.
2086
+     *
2087
+     *@param string $basedate basedate of an occurrence
2088
+     *@param resource $message recurring item from which occurrence has to be deleted
2089
+     *@param resource $store MAPI_MSG_Store which contains the item
2090
+     */
2091
+    public function doRemoveExceptionFromCalendar($basedate, $message, $store) {
2092
+        $recurr = new Recurrence($store, $message);
2093
+        $recurr->createException([], $basedate, true);
2094
+        mapi_savechanges($message);
2095
+    }
2096
+
2097
+    /**
2098
+     * Function which returns basedate of a changed occurrance from globalID of meeting request.
2099
+     *
2100
+     *@param binary $goid globalID
2101
+     *
2102
+     *@return bool true if basedate is found else false it not found
2103
+     */
2104
+    public function getBasedateFromGlobalID($goid) {
2105
+        $hexguid = bin2hex($goid);
2106
+        $hexbase = substr($hexguid, 32, 8);
2107
+        $day = hexdec(substr($hexbase, 6, 2));
2108
+        $month = hexdec(substr($hexbase, 4, 2));
2109
+        $year = hexdec(substr($hexbase, 0, 4));
2110
+
2111
+        if ($day && $month && $year) {
2112
+            return gmmktime(0, 0, 0, $month, $day, $year);
2113
+        }
2114
+
2115
+        return false;
2116
+    }
2117
+
2118
+    /**
2119
+     * Function which sets basedate in globalID of changed occurrance which is to be sent.
2120
+     *
2121
+     *@param binary $goid globalID
2122
+     *@param string basedate of changed occurrance
2123
+     * @param mixed $basedate
2124
+     *
2125
+     *@return binary globalID with basedate in it
2126
+     */
2127
+    public function setBasedateInGlobalID($goid, $basedate = false) {
2128
+        $hexguid = bin2hex($goid);
2129
+        $year = $basedate ? sprintf('%04s', dechex(date('Y', $basedate))) : '0000';
2130
+        $month = $basedate ? sprintf('%02s', dechex(date('m', $basedate))) : '00';
2131
+        $day = $basedate ? sprintf('%02s', dechex(date('d', $basedate))) : '00';
2132
+
2133
+        return hex2bin(strtoupper(substr($hexguid, 0, 32) . $year . $month . $day . substr($hexguid, 40)));
2134
+    }
2135
+
2136
+    /**
2137
+     * Function which replaces attachments with copy_from in copy_to.
2138
+     *
2139
+     *@param resource $copy_from MAPI_message from which attachments are to be copied
2140
+     *@param resource $copy_to MAPI_message to which attachment are to be copied
2141
+     *@param bool $copyExceptions if true then all exceptions should also be sent as attachments
2142
+     */
2143
+    public function replaceAttachments($copy_from, $copy_to, $copyExceptions = true) {
2144
+        /* remove all old attachments */
2145
+        $attachmentTable = mapi_message_getattachmenttable($copy_to);
2146
+        if ($attachmentTable) {
2147
+            $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2148
+
2149
+            foreach ($attachments as $attach_props) {
2150
+                /* remove exceptions too? */
2151
+                if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME])) {
2152
+                    continue;
2153
+                }
2154
+                mapi_message_deleteattach($copy_to, $attach_props[PR_ATTACH_NUM]);
2155
+            }
2156
+        }
2157
+        $attachmentTable = false;
2158
+
2159
+        /* copy new attachments */
2160
+        $attachmentTable = mapi_message_getattachmenttable($copy_from);
2161
+        if ($attachmentTable) {
2162
+            $attachments = mapi_table_queryallrows($attachmentTable, [PR_ATTACH_NUM, PR_ATTACH_METHOD, PR_EXCEPTION_STARTTIME]);
2163
+
2164
+            foreach ($attachments as $attach_props) {
2165
+                if (!$copyExceptions && $attach_props[PR_ATTACH_METHOD] == 5 && isset($attach_props[PR_EXCEPTION_STARTTIME])) {
2166
+                    continue;
2167
+                }
2168
+
2169
+                $attach_old = mapi_message_openattach($copy_from, (int) $attach_props[PR_ATTACH_NUM]);
2170
+                $attach_newResourceMsg = mapi_message_createattach($copy_to);
2171
+                mapi_copyto($attach_old, [], [], $attach_newResourceMsg, 0);
2172
+                mapi_savechanges($attach_newResourceMsg);
2173
+            }
2174
+        }
2175
+    }
2176
+
2177
+    /**
2178
+     * Function which replaces recipients in copy_to with recipients from copy_from.
2179
+     *
2180
+     *@param resource $copy_from MAPI_message from which recipients are to be copied
2181
+     *@param resource $copy_to MAPI_message to which recipients are to be copied
2182
+     * @param mixed $isDelegate
2183
+     */
2184
+    public function replaceRecipients($copy_from, $copy_to, $isDelegate = false) {
2185
+        $recipienttable = mapi_message_getrecipienttable($copy_from);
2186
+
2187
+        // If delegate, then do not add the delegate in recipients
2188
+        if ($isDelegate) {
2189
+            $delegate = mapi_getprops($copy_from, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2190
+            $res = [RES_PROPERTY, [RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]]];
2191
+            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $res);
2192
+        }
2193
+        else {
2194
+            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2195
+        }
2196
+
2197
+        $copy_to_recipientTable = mapi_message_getrecipienttable($copy_to);
2198
+        $copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, [PR_ROWID]);
2199
+        foreach ($copy_to_recipientRows as $recipient) {
2200
+            mapi_message_modifyrecipients($copy_to, MODRECIP_REMOVE, [$recipient]);
2201
+        }
2202
+
2203
+        mapi_message_modifyrecipients($copy_to, MODRECIP_ADD, $recipients);
2204
+    }
2205
+
2206
+    /**
2207
+     * Function creates meeting item in the resource's calendar.
2208
+     *
2209
+     *@param resource $message MAPI_message which is to be created in the resource's calendar
2210
+     *@param bool $cancel cancel meeting
2211
+     *@param string $prefix prefix for subject of meeting
2212
+     * @param mixed $basedate
2213
+     */
2214
+    public function bookResources($message, $cancel, $prefix, $basedate = false) {
2215
+        if (!$this->enableDirectBooking) {
2216
+            return [];
2217
+        }
2218
+
2219
+        // Get the properties of the message
2220
+        $messageprops = mapi_getprops($message);
2221
+
2222
+        if ($basedate) {
2223
+            $recurrItemProps = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['timezone_data'], $this->proptags['timezone'], PR_OWNER_APPT_ID]);
2224
+
2225
+            $messageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($recurrItemProps[$this->proptags['goid']], $basedate);
2226
+            $messageprops[$this->proptags['goid2']] = $recurrItemProps[$this->proptags['goid2']];
2227
+
2228
+            // Delete properties which are not needed.
2229
+            $deleteProps = [$this->proptags['basedate'], PR_DISPLAY_NAME, PR_ATTACHMENT_FLAGS, PR_ATTACHMENT_HIDDEN, PR_ATTACHMENT_LINKID, PR_ATTACH_FLAGS, PR_ATTACH_METHOD];
2230
+            foreach ($deleteProps as $propID) {
2231
+                if (isset($messageprops[$propID])) {
2232
+                    unset($messageprops[$propID]);
2233
+                }
2234
+            }
2235
+
2236
+            if (isset($messageprops[$this->proptags['recurring']])) {
2237
+                $messageprops[$this->proptags['recurring']] = false;
2238
+            }
2239
+
2240
+            // Set Outlook properties
2241
+            $messageprops[$this->proptags['clipstart']] = $messageprops[$this->proptags['startdate']];
2242
+            $messageprops[$this->proptags['clipend']] = $messageprops[$this->proptags['duedate']];
2243
+            $messageprops[$this->proptags['timezone_data']] = $recurrItemProps[$this->proptags['timezone_data']];
2244
+            $messageprops[$this->proptags['timezone']] = $recurrItemProps[$this->proptags['timezone']];
2245
+            $messageprops[$this->proptags['attendee_critical_change']] = time();
2246
+            $messageprops[$this->proptags['owner_critical_change']] = time();
2247
+        }
2248
+
2249
+        // Get resource recipients
2250
+        $getResourcesRestriction = [RES_AND,
2251
+            [[RES_PROPERTY,
2252
+                [RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2253
+                    ULPROPTAG => PR_RECIPIENT_TYPE,
2254
+                    VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2255
+                ],
2256
+            ]],
2257
+        ];
2258
+        $recipienttable = mapi_message_getrecipienttable($message);
2259
+        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2260
+
2261
+        $this->errorSetResource = false;
2262
+        $resourceRecipData = [];
2263
+
2264
+        // Put appointment into store resource users
2265
+        $i = 0;
2266
+        $len = count($resourceRecipients);
2267
+        while (!$this->errorSetResource && $i < $len) {
2268
+            $request = [[PR_DISPLAY_NAME => $resourceRecipients[$i][PR_DISPLAY_NAME]]];
2269
+            $ab = mapi_openaddressbook($this->session);
2270
+            $ret = mapi_ab_resolvename($ab, $request, EMS_AB_ADDRESS_LOOKUP);
2271
+            $result = mapi_last_hresult();
2272
+            if ($result == NOERROR) {
2273
+                $result = $ret[0][PR_ENTRYID];
2274
+            }
2275
+            $resourceUsername = $ret[0][PR_EMAIL_ADDRESS];
2276
+            $resourceABEntryID = $ret[0][PR_ENTRYID];
2277
+
2278
+            // Get StoreEntryID by username
2279
+            $user_entryid = mapi_msgstore_createentryid($this->store, $resourceUsername);
2280
+
2281
+            // Open store of the user
2282
+            $userStore = mapi_openmsgstore($this->session, $user_entryid);
2283
+            // Open root folder
2284
+            $userRoot = mapi_msgstore_openentry($userStore, null);
2285
+            // Get calendar entryID
2286
+            $userRootProps = mapi_getprops($userRoot, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
2287
+
2288
+            // Open Calendar folder   [check hresult==0]
2289
+            $accessToFolder = false;
2290
+
2291
+            try {
2292
+                $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2293
+                if ($calFolder) {
2294
+                    $calFolderProps = mapi_getprops($calFolder, [PR_ACCESS]);
2295
+                    if (($calFolderProps[PR_ACCESS] & MAPI_ACCESS_CREATE_CONTENTS) !== 0) {
2296
+                        $accessToFolder = true;
2297
+                    }
2298
+                }
2299
+            }
2300
+            catch (MAPIException $e) {
2301
+                $e->setHandled();
2302
+                $this->errorSetResource = 1; // No access
2303
+            }
2304
+
2305
+            if ($accessToFolder) {
2306
+                /**
2307
+                 * Get the LocalFreebusy message that contains the properties that
2308
+                 * are set to accept or decline resource meeting requests.
2309
+                 */
2310
+                // Use PR_FREEBUSY_ENTRYIDS[1] to open folder the LocalFreeBusy msg
2311
+                $localFreebusyMsg = mapi_msgstore_openentry($userStore, $userRootProps[PR_FREEBUSY_ENTRYIDS][1]);
2312
+                if ($localFreebusyMsg) {
2313
+                    $props = mapi_getprops($localFreebusyMsg, [PR_PROCESS_MEETING_REQUESTS, PR_DECLINE_RECURRING_MEETING_REQUESTS, PR_DECLINE_CONFLICTING_MEETING_REQUESTS]);
2314
+
2315
+                    $acceptMeetingRequests = ($props[PR_PROCESS_MEETING_REQUESTS]) ? 1 : 0;
2316
+                    $declineRecurringMeetingRequests = ($props[PR_DECLINE_RECURRING_MEETING_REQUESTS]) ? 1 : 0;
2317
+                    $declineConflictingMeetingRequests = ($props[PR_DECLINE_CONFLICTING_MEETING_REQUESTS]) ? 1 : 0;
2318
+                    if (!$acceptMeetingRequests) {
2319
+                        /*
2320 2320
 						 * When a resource has not been set to automatically accept meeting requests,
2321 2321
 						 * the meeting request has to be sent to him rather than being put directly into
2322 2322
 						 * his calendar. No error should be returned.
2323 2323
 						 */
2324
-						// $errorSetResource = 2;
2325
-						$this->nonAcceptingResources[] = $resourceRecipients[$i];
2326
-					}
2327
-					else {
2328
-						if ($declineRecurringMeetingRequests && !$cancel) {
2329
-							// Check if appointment is recurring
2330
-							if ($messageprops[$this->proptags['recurring']]) {
2331
-								$this->errorSetResource = 3;
2332
-							}
2333
-						}
2334
-						if ($declineConflictingMeetingRequests && !$cancel) {
2335
-							// Check for conflicting items
2336
-							$conflicting = false;
2337
-
2338
-							// Open the calendar
2339
-							$calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2340
-
2341
-							if ($calFolder) {
2342
-								if ($this->isMeetingConflicting($message, $userStore, $calFolder, $messageprops)) {
2343
-									$conflicting = true;
2344
-								}
2345
-							}
2346
-							else {
2347
-								$this->errorSetResource = 1; // No access
2348
-							}
2349
-							if ($conflicting) {
2350
-								$this->errorSetResource = 4;
2351
-							} // Conflict
2352
-						}
2353
-					}
2354
-				}
2355
-			}
2356
-
2357
-			if (!$this->errorSetResource && $accessToFolder) {
2358
-				/**
2359
-				 * First search on GlobalID(0x3)
2360
-				 * If (recurring and occurrence) If Resource was booked for only this occurrence then Resource should have only this occurrence in Calendar and not whole series.
2361
-				 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2362
-				 */
2363
-				$rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2364
-
2365
-				/*
2324
+                        // $errorSetResource = 2;
2325
+                        $this->nonAcceptingResources[] = $resourceRecipients[$i];
2326
+                    }
2327
+                    else {
2328
+                        if ($declineRecurringMeetingRequests && !$cancel) {
2329
+                            // Check if appointment is recurring
2330
+                            if ($messageprops[$this->proptags['recurring']]) {
2331
+                                $this->errorSetResource = 3;
2332
+                            }
2333
+                        }
2334
+                        if ($declineConflictingMeetingRequests && !$cancel) {
2335
+                            // Check for conflicting items
2336
+                            $conflicting = false;
2337
+
2338
+                            // Open the calendar
2339
+                            $calFolder = mapi_msgstore_openentry($userStore, $userRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
2340
+
2341
+                            if ($calFolder) {
2342
+                                if ($this->isMeetingConflicting($message, $userStore, $calFolder, $messageprops)) {
2343
+                                    $conflicting = true;
2344
+                                }
2345
+                            }
2346
+                            else {
2347
+                                $this->errorSetResource = 1; // No access
2348
+                            }
2349
+                            if ($conflicting) {
2350
+                                $this->errorSetResource = 4;
2351
+                            } // Conflict
2352
+                        }
2353
+                    }
2354
+                }
2355
+            }
2356
+
2357
+            if (!$this->errorSetResource && $accessToFolder) {
2358
+                /**
2359
+                 * First search on GlobalID(0x3)
2360
+                 * If (recurring and occurrence) If Resource was booked for only this occurrence then Resource should have only this occurrence in Calendar and not whole series.
2361
+                 * If (normal meeting) then GlobalID(0x3) and CleanGlobalID(0x23) are same, so doesn't matter if search is based on GlobalID.
2362
+                 */
2363
+                $rows = $this->findCalendarItems($messageprops[$this->proptags['goid']], $calFolder);
2364
+
2365
+                /*
2366 2366
 				 * If no entry is found then
2367 2367
 				 * 1) Resource doesn't have meeting in Calendar. Seriously!!
2368 2368
 				 * OR
2369 2369
 				 * 2) We were looking for occurrence item but Resource has whole series
2370 2370
 				 */
2371
-				if (empty($rows)) {
2372
-					/**
2373
-					 * Now search on CleanGlobalID(0x23) WHY???
2374
-					 * Because we are looking recurring item.
2375
-					 *
2376
-					 * Possible results of this search
2377
-					 * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2378
-					 * 2) If Resource was booked for whole series then it should return series.
2379
-					 */
2380
-					$rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2381
-
2382
-					$newResourceMsg = false;
2383
-					if (!empty($rows)) {
2384
-						// Since we are looking for recurring item, open every result and check for 'recurring' property.
2385
-						foreach ($rows as $row) {
2386
-							$ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2387
-							$ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2388
-
2389
-							if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2390
-								$newResourceMsg = $ResourceMsg;
2391
-
2392
-								break;
2393
-							}
2394
-						}
2395
-					}
2396
-
2397
-					// Still no results found. I giveup, create new message.
2398
-					if (!$newResourceMsg) {
2399
-						$newResourceMsg = mapi_folder_createmessage($calFolder);
2400
-					}
2401
-				}
2402
-				else {
2403
-					$newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2404
-				}
2405
-
2406
-				// Prefix the subject if needed
2407
-				if ($prefix && isset($messageprops[PR_SUBJECT])) {
2408
-					$messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2409
-				}
2410
-
2411
-				// Set status to cancelled if needed
2412
-				$messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2413
-				if ($cancel) {
2414
-					$messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2415
-					$messageprops[$this->proptags['busystatus']] = fbFree; // Free
2416
-				}
2417
-				else {
2418
-					$messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2419
-				}
2420
-				$messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2421
-
2422
-				$messageprops[PR_MESSAGE_CLASS] = "IPM.Appointment";
2423
-
2424
-				// Remove the PR_ICON_INDEX as it is not needed in the sent message
2425
-				// and it also confuses webaccess
2426
-				$messageprops[PR_ICON_INDEX] = null;
2427
-				$messageprops[PR_RESPONSE_REQUESTED] = true;
2428
-
2429
-				$addrinfo = $this->getOwnerAddress($this->store);
2430
-
2431
-				if ($addrinfo) {
2432
-					list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2433
-
2434
-					$messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2435
-					$messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2436
-					$messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2437
-					$messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2438
-					$messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2439
-
2440
-					$messageprops[$this->proptags['apptreplyname']] = $ownername;
2441
-					$messageprops[$this->proptags['replytime']] = time();
2442
-				}
2443
-
2444
-				if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2445
-					$recurr = new Recurrence($userStore, $newResourceMsg);
2446
-
2447
-					// Copy recipients list
2448
-					$reciptable = mapi_message_getrecipienttable($message);
2449
-					$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2450
-					// add owner to recipient table
2451
-					$this->addOrganizer($messageprops, $recips, true);
2452
-
2453
-					// Update occurrence
2454
-					if ($recurr->isException($basedate)) {
2455
-						$recurr->modifyException($messageprops, $basedate, $recips);
2456
-					}
2457
-					else {
2458
-						$recurr->createException($messageprops, $basedate, false, $recips);
2459
-					}
2460
-				}
2461
-				else {
2462
-					mapi_setprops($newResourceMsg, $messageprops);
2463
-
2464
-					// Copy attachments
2465
-					$this->replaceAttachments($message, $newResourceMsg);
2466
-
2467
-					// Copy all recipients too
2468
-					$this->replaceRecipients($message, $newResourceMsg);
2469
-
2470
-					// Now add organizer also to recipient table
2471
-					$recips = [];
2472
-					$this->addOrganizer($messageprops, $recips);
2473
-					mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2474
-				}
2475
-
2476
-				mapi_savechanges($newResourceMsg);
2477
-
2478
-				$resourceRecipData[] = [
2479
-					'store' => $userStore,
2480
-					'folder' => $calFolder,
2481
-					'msg' => $newResourceMsg,
2482
-				];
2483
-				$this->includesResources = true;
2484
-			}
2485
-			else {
2486
-				/*
2371
+                if (empty($rows)) {
2372
+                    /**
2373
+                     * Now search on CleanGlobalID(0x23) WHY???
2374
+                     * Because we are looking recurring item.
2375
+                     *
2376
+                     * Possible results of this search
2377
+                     * 1) If Resource was booked for more than one occurrences then this search will return all those occurrence because search is perform on CleanGlobalID
2378
+                     * 2) If Resource was booked for whole series then it should return series.
2379
+                     */
2380
+                    $rows = $this->findCalendarItems($messageprops[$this->proptags['goid2']], $calFolder, true);
2381
+
2382
+                    $newResourceMsg = false;
2383
+                    if (!empty($rows)) {
2384
+                        // Since we are looking for recurring item, open every result and check for 'recurring' property.
2385
+                        foreach ($rows as $row) {
2386
+                            $ResourceMsg = mapi_msgstore_openentry($userStore, $row);
2387
+                            $ResourceMsgProps = mapi_getprops($ResourceMsg, [$this->proptags['recurring']]);
2388
+
2389
+                            if (isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2390
+                                $newResourceMsg = $ResourceMsg;
2391
+
2392
+                                break;
2393
+                            }
2394
+                        }
2395
+                    }
2396
+
2397
+                    // Still no results found. I giveup, create new message.
2398
+                    if (!$newResourceMsg) {
2399
+                        $newResourceMsg = mapi_folder_createmessage($calFolder);
2400
+                    }
2401
+                }
2402
+                else {
2403
+                    $newResourceMsg = mapi_msgstore_openentry($userStore, $rows[0]);
2404
+                }
2405
+
2406
+                // Prefix the subject if needed
2407
+                if ($prefix && isset($messageprops[PR_SUBJECT])) {
2408
+                    $messageprops[PR_SUBJECT] = $prefix . $messageprops[PR_SUBJECT];
2409
+                }
2410
+
2411
+                // Set status to cancelled if needed
2412
+                $messageprops[$this->proptags['busystatus']] = fbBusy; // The default status (Busy)
2413
+                if ($cancel) {
2414
+                    $messageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // The meeting has been canceled
2415
+                    $messageprops[$this->proptags['busystatus']] = fbFree; // Free
2416
+                }
2417
+                else {
2418
+                    $messageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2419
+                }
2420
+                $messageprops[$this->proptags['responsestatus']] = olResponseAccepted; // The resource automatically accepts the appointment
2421
+
2422
+                $messageprops[PR_MESSAGE_CLASS] = "IPM.Appointment";
2423
+
2424
+                // Remove the PR_ICON_INDEX as it is not needed in the sent message
2425
+                // and it also confuses webaccess
2426
+                $messageprops[PR_ICON_INDEX] = null;
2427
+                $messageprops[PR_RESPONSE_REQUESTED] = true;
2428
+
2429
+                $addrinfo = $this->getOwnerAddress($this->store);
2430
+
2431
+                if ($addrinfo) {
2432
+                    list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2433
+
2434
+                    $messageprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2435
+                    $messageprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2436
+                    $messageprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2437
+                    $messageprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2438
+                    $messageprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2439
+
2440
+                    $messageprops[$this->proptags['apptreplyname']] = $ownername;
2441
+                    $messageprops[$this->proptags['replytime']] = time();
2442
+                }
2443
+
2444
+                if ($basedate && isset($ResourceMsgProps[$this->proptags['recurring']]) && $ResourceMsgProps[$this->proptags['recurring']]) {
2445
+                    $recurr = new Recurrence($userStore, $newResourceMsg);
2446
+
2447
+                    // Copy recipients list
2448
+                    $reciptable = mapi_message_getrecipienttable($message);
2449
+                    $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2450
+                    // add owner to recipient table
2451
+                    $this->addOrganizer($messageprops, $recips, true);
2452
+
2453
+                    // Update occurrence
2454
+                    if ($recurr->isException($basedate)) {
2455
+                        $recurr->modifyException($messageprops, $basedate, $recips);
2456
+                    }
2457
+                    else {
2458
+                        $recurr->createException($messageprops, $basedate, false, $recips);
2459
+                    }
2460
+                }
2461
+                else {
2462
+                    mapi_setprops($newResourceMsg, $messageprops);
2463
+
2464
+                    // Copy attachments
2465
+                    $this->replaceAttachments($message, $newResourceMsg);
2466
+
2467
+                    // Copy all recipients too
2468
+                    $this->replaceRecipients($message, $newResourceMsg);
2469
+
2470
+                    // Now add organizer also to recipient table
2471
+                    $recips = [];
2472
+                    $this->addOrganizer($messageprops, $recips);
2473
+                    mapi_message_modifyrecipients($newResourceMsg, MODRECIP_ADD, $recips);
2474
+                }
2475
+
2476
+                mapi_savechanges($newResourceMsg);
2477
+
2478
+                $resourceRecipData[] = [
2479
+                    'store' => $userStore,
2480
+                    'folder' => $calFolder,
2481
+                    'msg' => $newResourceMsg,
2482
+                ];
2483
+                $this->includesResources = true;
2484
+            }
2485
+            else {
2486
+                /*
2487 2487
 				 * If no other errors occurred and you have no access to the
2488 2488
 				 * folder of the resource, throw an error=1.
2489 2489
 				 */
2490
-				if (!$this->errorSetResource) {
2491
-					$this->errorSetResource = 1;
2492
-				}
2493
-
2494
-				for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2495
-					// Get the EntryID
2496
-					$props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2497
-
2498
-					mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
2499
-				}
2500
-				$this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2501
-			}
2502
-			++$i;
2503
-		}
2504
-
2505
-		/*
2490
+                if (!$this->errorSetResource) {
2491
+                    $this->errorSetResource = 1;
2492
+                }
2493
+
2494
+                for ($j = 0, $len = count($resourceRecipData); $j < $len; ++$j) {
2495
+                    // Get the EntryID
2496
+                    $props = mapi_message_getprops($resourceRecipData[$j]['msg']);
2497
+
2498
+                    mapi_folder_deletemessages($resourceRecipData[$j]['folder'], [$props[PR_ENTRYID]], DELETE_HARD_DELETE);
2499
+                }
2500
+                $this->recipientDisplayname = $resourceRecipients[$i][PR_DISPLAY_NAME];
2501
+            }
2502
+            ++$i;
2503
+        }
2504
+
2505
+        /*
2506 2506
 		 * Set the BCC-recipients (resources) tackstatus to accepted.
2507 2507
 		 */
2508
-		// Get resource recipients
2509
-		$getResourcesRestriction = [RES_AND,
2510
-			[[RES_PROPERTY,
2511
-				[RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2512
-					ULPROPTAG => PR_RECIPIENT_TYPE,
2513
-					VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2514
-				],
2515
-			]],
2516
-		];
2517
-		$recipienttable = mapi_message_getrecipienttable($message);
2518
-		$resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2519
-		if (!empty($resourceRecipients)) {
2520
-			// Set Tracking status of resource recipients to olResponseAccepted (3)
2521
-			for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2522
-				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2523
-				$resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2524
-			}
2525
-			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2526
-		}
2527
-
2528
-		// Publish updated free/busy information
2529
-		if (!$this->errorSetResource) {
2530
-			for ($i = 0, $len = count($resourceRecipData); $i < $len; ++$i) {
2531
-				$storeProps = mapi_getprops($resourceRecipData[$i]['store'], [PR_MAILBOX_OWNER_ENTRYID]);
2532
-				if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) {
2533
-					$pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
2534
-					$pub->publishFB(time() - (7 * 24 * 60 * 60), 6 * 30 * 24 * 60 * 60); // publish from one week ago, 6 months ahead
2535
-				}
2536
-			}
2537
-		}
2538
-
2539
-		return $resourceRecipData;
2540
-	}
2541
-
2542
-	/**
2543
-	 * Function which save an exception into recurring item.
2544
-	 *
2545
-	 * @param resource $recurringItem  reference to MAPI_message of recurring item
2546
-	 * @param resource $occurrenceItem reference to MAPI_message of occurrence
2547
-	 * @param string   $basedate       basedate of occurrence
2548
-	 * @param bool     $move           if true then occurrence item is deleted
2549
-	 * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2550
-	 * @param bool     $userAction     true if user has manually responded to meeting request
2551
-	 * @param resource $store          user store
2552
-	 * @param bool     $isDelegate     true if delegate is processing this meeting request
2553
-	 */
2554
-	public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) {
2555
-		$recurr = new Recurrence($store, $recurringItem);
2556
-
2557
-		// Copy properties from meeting request
2558
-		$exception_props = mapi_getprops($occurrenceItem);
2559
-
2560
-		// Copy recipients list
2561
-		$reciptable = mapi_message_getrecipienttable($occurrenceItem);
2562
-		// If delegate, then do not add the delegate in recipients
2563
-		if ($isDelegate) {
2564
-			$delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2565
-			$res = [RES_PROPERTY, [RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]]];
2566
-			$recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2567
-		}
2568
-		else {
2569
-			$recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2570
-		}
2571
-
2572
-		// add owner to recipient table
2573
-		$this->addOrganizer($exception_props, $recips, true);
2574
-
2575
-		// add delegator to meetings
2576
-		if ($isDelegate) {
2577
-			$this->addDelegator($exception_props, $recips);
2578
-		}
2579
-
2580
-		$exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2581
-		$exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2582
-		// Set basedate property (ExceptionReplaceTime)
2583
-
2584
-		if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2585
-			if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2586
-				$exception_props[$this->proptags['busystatus']] = $tentative;
2587
-			}
2588
-			else {
2589
-				$exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2590
-			}
2591
-			// we already have intendedbusystatus value in $exception_props so no need to copy it
2592
-		}
2593
-		else {
2594
-			$exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2595
-		}
2596
-
2597
-		if ($userAction) {
2598
-			// if user has responded then set replytime
2599
-			$exception_props[$this->proptags['replytime']] = time();
2600
-		}
2601
-		if ($recurr->isException($basedate)) {
2602
-			$recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2603
-		}
2604
-		else {
2605
-			$recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2606
-		}
2607
-
2608
-		// Move the occurrenceItem to the waste basket
2609
-		if ($move) {
2610
-			$wastebasket = $this->openDefaultWastebasket();
2611
-			$sourcefolder = mapi_msgstore_openentry($this->store, $exception_props[PR_PARENT_ENTRYID]);
2612
-			mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2613
-		}
2614
-
2615
-		mapi_savechanges($recurringItem);
2616
-	}
2617
-
2618
-	/**
2619
-	 * Function which submits meeting request based on arguments passed to it.
2620
-	 *
2621
-	 * @param resource $message MAPI_message whose meeting request is to be sent
2622
-	 *@param bool $cancel if true send request, else send cancellation
2623
-	 *@param string $prefix subject prefix
2624
-	 *@param int $basedate basedate for an occurrence
2625
-	 *@param object $recurObject recurrence object of mr
2626
-	 *@param bool $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
2627
-	 * @param mixed $deletedRecips
2628
-	 */
2629
-	public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false) {
2630
-		$newmessageprops = $messageprops = mapi_getprops($this->message);
2631
-		$new = $this->createOutgoingMessage();
2632
-
2633
-		// Copy the entire message into the new meeting request message
2634
-		if ($basedate) {
2635
-			// messageprops contains properties of whole recurring series
2636
-			// and newmessageprops contains properties of exception item
2637
-			$newmessageprops = mapi_getprops($message);
2638
-
2639
-			// Ensure that the correct basedate is set in the new message
2640
-			$newmessageprops[$this->proptags['basedate']] = $basedate;
2641
-
2642
-			// Set isRecurring to false, because this is an exception
2643
-			$newmessageprops[$this->proptags['recurring']] = false;
2644
-
2645
-			// set LID_IS_EXCEPTION to true
2646
-			$newmessageprops[$this->proptags['is_exception']] = true;
2647
-
2648
-			// Set to high importance
2649
-			if ($cancel) {
2650
-				$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2651
-			}
2652
-
2653
-			// Set startdate and enddate of exception
2654
-			if ($cancel && $recurObject) {
2655
-				$newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2656
-				$newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2657
-			}
2658
-
2659
-			// Set basedate in guid (0x3)
2660
-			$newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2661
-			$newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2662
-			$newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2663
-
2664
-			// Get deleted recipiets from exception msg
2665
-			$restriction = [RES_AND,
2666
-				[
2667
-					[RES_BITMASK,
2668
-						[ULTYPE => BMR_NEZ,
2669
-							ULPROPTAG => PR_RECIPIENT_FLAGS,
2670
-							ULMASK => recipExceptionalDeleted,
2671
-						],
2672
-					],
2673
-					[RES_BITMASK,
2674
-						[ULTYPE => BMR_EQZ,
2675
-							ULPROPTAG => PR_RECIPIENT_FLAGS,
2676
-							ULMASK => recipOrganizer,
2677
-						],
2678
-					],
2679
-				],
2680
-			];
2681
-
2682
-			// In direct-booking mode, we don't need to send cancellations to resources
2683
-			if ($this->enableDirectBooking) {
2684
-				$restriction[1][] = [RES_PROPERTY,
2685
-					[RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2686
-						ULPROPTAG => PR_RECIPIENT_TYPE,
2687
-						VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2688
-					],
2689
-				];
2690
-			}
2691
-
2692
-			$recipienttable = mapi_message_getrecipienttable($message);
2693
-			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2694
-
2695
-			if (!$deletedRecips) {
2696
-				$deletedRecips = array_merge([], $recipients);
2697
-			}
2698
-			else {
2699
-				$deletedRecips = array_merge($deletedRecips, $recipients);
2700
-			}
2701
-		}
2702
-
2703
-		// Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
2704
-		// confuses webaccess
2705
-		$newmessageprops[PR_ICON_INDEX] = null;
2706
-		$newmessageprops[PR_RESPONSE_REQUESTED] = true;
2707
-
2708
-		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2709
-		$newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
2710
-		$newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
2711
-
2712
-		// Set updatecounter/AppointmentSequenceNumber
2713
-		// get the value of latest updatecounter for the whole series and use it
2714
-		$newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
2715
-
2716
-		$meetingTimeInfo = $this->getMeetingTimeInfo();
2717
-
2718
-		if ($meetingTimeInfo) {
2719
-			$newmessageprops[PR_BODY] = $meetingTimeInfo;
2720
-		}
2721
-
2722
-		// Send all recurrence info in mail, if this is a recurrence meeting.
2723
-		if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
2724
-			if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2725
-				$newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2726
-			}
2727
-			$newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2728
-			$newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2729
-			$newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2730
-			if ($recurObject) {
2731
-				$this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
2732
-			}
2733
-		}
2734
-		if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
2735
-			unset($newmessageprops[$this->proptags['counter_proposal']]);
2736
-		}
2737
-
2738
-		// Prefix the subject if needed
2739
-		if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
2740
-			$newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
2741
-		}
2742
-
2743
-		mapi_setprops($new, $newmessageprops);
2744
-
2745
-		// Copy attachments
2746
-		$this->replaceAttachments($message, $new, $copyExceptions);
2747
-
2748
-		// Retrieve only those recipient who should receive this meeting request.
2749
-		$stripResourcesRestriction = [RES_AND,
2750
-			[
2751
-				[RES_BITMASK,
2752
-					[ULTYPE => BMR_EQZ,
2753
-						ULPROPTAG => PR_RECIPIENT_FLAGS,
2754
-						ULMASK => recipExceptionalDeleted,
2755
-					],
2756
-				],
2757
-				[RES_BITMASK,
2758
-					[ULTYPE => BMR_EQZ,
2759
-						ULPROPTAG => PR_RECIPIENT_FLAGS,
2760
-						ULMASK => recipOrganizer,
2761
-					],
2762
-				],
2763
-			],
2764
-		];
2765
-
2766
-		// In direct-booking mode, resources do not receive a meeting request
2767
-		if ($this->enableDirectBooking) {
2768
-			$stripResourcesRestriction[1][] =
2769
-									[RES_PROPERTY,
2770
-										[RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2771
-											ULPROPTAG => PR_RECIPIENT_TYPE,
2772
-											VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2773
-										],
2774
-									];
2775
-		}
2776
-
2777
-		$recipienttable = mapi_message_getrecipienttable($message);
2778
-		$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2779
-
2780
-		if ($basedate && empty($recipients)) {
2781
-			// Retrieve full list
2782
-			$recipienttable = mapi_message_getrecipienttable($this->message);
2783
-			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2784
-
2785
-			// Save recipients in exceptions
2786
-			mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients);
2787
-
2788
-			// Now retrieve only those recipient who should receive this meeting request.
2789
-			$recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2790
-		}
2791
-
2792
-		// @TODO: handle nonAcceptingResources
2793
-		/*
2508
+        // Get resource recipients
2509
+        $getResourcesRestriction = [RES_AND,
2510
+            [[RES_PROPERTY,
2511
+                [RELOP => RELOP_EQ,	// Equals recipient type 3: Resource
2512
+                    ULPROPTAG => PR_RECIPIENT_TYPE,
2513
+                    VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2514
+                ],
2515
+            ]],
2516
+        ];
2517
+        $recipienttable = mapi_message_getrecipienttable($message);
2518
+        $resourceRecipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $getResourcesRestriction);
2519
+        if (!empty($resourceRecipients)) {
2520
+            // Set Tracking status of resource recipients to olResponseAccepted (3)
2521
+            for ($i = 0, $len = count($resourceRecipients); $i < $len; ++$i) {
2522
+                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusAccepted;
2523
+                $resourceRecipients[$i][PR_RECIPIENT_TRACKSTATUS_TIME] = time();
2524
+            }
2525
+            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, $resourceRecipients);
2526
+        }
2527
+
2528
+        // Publish updated free/busy information
2529
+        if (!$this->errorSetResource) {
2530
+            for ($i = 0, $len = count($resourceRecipData); $i < $len; ++$i) {
2531
+                $storeProps = mapi_getprops($resourceRecipData[$i]['store'], [PR_MAILBOX_OWNER_ENTRYID]);
2532
+                if (isset($storeProps[PR_MAILBOX_OWNER_ENTRYID])) {
2533
+                    $pub = new FreeBusyPublish($this->session, $resourceRecipData[$i]['store'], $resourceRecipData[$i]['folder'], $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
2534
+                    $pub->publishFB(time() - (7 * 24 * 60 * 60), 6 * 30 * 24 * 60 * 60); // publish from one week ago, 6 months ahead
2535
+                }
2536
+            }
2537
+        }
2538
+
2539
+        return $resourceRecipData;
2540
+    }
2541
+
2542
+    /**
2543
+     * Function which save an exception into recurring item.
2544
+     *
2545
+     * @param resource $recurringItem  reference to MAPI_message of recurring item
2546
+     * @param resource $occurrenceItem reference to MAPI_message of occurrence
2547
+     * @param string   $basedate       basedate of occurrence
2548
+     * @param bool     $move           if true then occurrence item is deleted
2549
+     * @param bool     $tentative      true if user has tentatively accepted it or false if user has accepted it
2550
+     * @param bool     $userAction     true if user has manually responded to meeting request
2551
+     * @param resource $store          user store
2552
+     * @param bool     $isDelegate     true if delegate is processing this meeting request
2553
+     */
2554
+    public function acceptException(&$recurringItem, &$occurrenceItem, $basedate, $move = false, $tentative, $userAction = false, $store, $isDelegate = false) {
2555
+        $recurr = new Recurrence($store, $recurringItem);
2556
+
2557
+        // Copy properties from meeting request
2558
+        $exception_props = mapi_getprops($occurrenceItem);
2559
+
2560
+        // Copy recipients list
2561
+        $reciptable = mapi_message_getrecipienttable($occurrenceItem);
2562
+        // If delegate, then do not add the delegate in recipients
2563
+        if ($isDelegate) {
2564
+            $delegate = mapi_getprops($this->message, [PR_RECEIVED_BY_EMAIL_ADDRESS]);
2565
+            $res = [RES_PROPERTY, [RELOP => RELOP_NE, ULPROPTAG => PR_EMAIL_ADDRESS, VALUE => $delegate[PR_RECEIVED_BY_EMAIL_ADDRESS]]];
2566
+            $recips = mapi_table_queryallrows($reciptable, $this->recipprops, $res);
2567
+        }
2568
+        else {
2569
+            $recips = mapi_table_queryallrows($reciptable, $this->recipprops);
2570
+        }
2571
+
2572
+        // add owner to recipient table
2573
+        $this->addOrganizer($exception_props, $recips, true);
2574
+
2575
+        // add delegator to meetings
2576
+        if ($isDelegate) {
2577
+            $this->addDelegator($exception_props, $recips);
2578
+        }
2579
+
2580
+        $exception_props[$this->proptags['meetingstatus']] = olMeetingReceived;
2581
+        $exception_props[$this->proptags['responsestatus']] = $userAction ? ($tentative ? olResponseTentative : olResponseAccepted) : olResponseNotResponded;
2582
+        // Set basedate property (ExceptionReplaceTime)
2583
+
2584
+        if (isset($exception_props[$this->proptags['intendedbusystatus']])) {
2585
+            if ($tentative && $exception_props[$this->proptags['intendedbusystatus']] !== fbFree) {
2586
+                $exception_props[$this->proptags['busystatus']] = $tentative;
2587
+            }
2588
+            else {
2589
+                $exception_props[$this->proptags['busystatus']] = $exception_props[$this->proptags['intendedbusystatus']];
2590
+            }
2591
+            // we already have intendedbusystatus value in $exception_props so no need to copy it
2592
+        }
2593
+        else {
2594
+            $exception_props[$this->proptags['busystatus']] = $tentative ? fbTentative : fbBusy;
2595
+        }
2596
+
2597
+        if ($userAction) {
2598
+            // if user has responded then set replytime
2599
+            $exception_props[$this->proptags['replytime']] = time();
2600
+        }
2601
+        if ($recurr->isException($basedate)) {
2602
+            $recurr->modifyException($exception_props, $basedate, $recips, $occurrenceItem);
2603
+        }
2604
+        else {
2605
+            $recurr->createException($exception_props, $basedate, false, $recips, $occurrenceItem);
2606
+        }
2607
+
2608
+        // Move the occurrenceItem to the waste basket
2609
+        if ($move) {
2610
+            $wastebasket = $this->openDefaultWastebasket();
2611
+            $sourcefolder = mapi_msgstore_openentry($this->store, $exception_props[PR_PARENT_ENTRYID]);
2612
+            mapi_folder_copymessages($sourcefolder, [$exception_props[PR_ENTRYID]], $wastebasket, MESSAGE_MOVE);
2613
+        }
2614
+
2615
+        mapi_savechanges($recurringItem);
2616
+    }
2617
+
2618
+    /**
2619
+     * Function which submits meeting request based on arguments passed to it.
2620
+     *
2621
+     * @param resource $message MAPI_message whose meeting request is to be sent
2622
+     *@param bool $cancel if true send request, else send cancellation
2623
+     *@param string $prefix subject prefix
2624
+     *@param int $basedate basedate for an occurrence
2625
+     *@param object $recurObject recurrence object of mr
2626
+     *@param bool $copyExceptions When sending update mail for recurring item then we dont send exceptions in attachments
2627
+     * @param mixed $deletedRecips
2628
+     */
2629
+    public function submitMeetingRequest($message, $cancel, $prefix, $basedate = false, $recurObject = false, $copyExceptions = true, $deletedRecips = false) {
2630
+        $newmessageprops = $messageprops = mapi_getprops($this->message);
2631
+        $new = $this->createOutgoingMessage();
2632
+
2633
+        // Copy the entire message into the new meeting request message
2634
+        if ($basedate) {
2635
+            // messageprops contains properties of whole recurring series
2636
+            // and newmessageprops contains properties of exception item
2637
+            $newmessageprops = mapi_getprops($message);
2638
+
2639
+            // Ensure that the correct basedate is set in the new message
2640
+            $newmessageprops[$this->proptags['basedate']] = $basedate;
2641
+
2642
+            // Set isRecurring to false, because this is an exception
2643
+            $newmessageprops[$this->proptags['recurring']] = false;
2644
+
2645
+            // set LID_IS_EXCEPTION to true
2646
+            $newmessageprops[$this->proptags['is_exception']] = true;
2647
+
2648
+            // Set to high importance
2649
+            if ($cancel) {
2650
+                $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;
2651
+            }
2652
+
2653
+            // Set startdate and enddate of exception
2654
+            if ($cancel && $recurObject) {
2655
+                $newmessageprops[$this->proptags['startdate']] = $recurObject->getOccurrenceStart($basedate);
2656
+                $newmessageprops[$this->proptags['duedate']] = $recurObject->getOccurrenceEnd($basedate);
2657
+            }
2658
+
2659
+            // Set basedate in guid (0x3)
2660
+            $newmessageprops[$this->proptags['goid']] = $this->setBasedateInGlobalID($messageprops[$this->proptags['goid2']], $basedate);
2661
+            $newmessageprops[$this->proptags['goid2']] = $messageprops[$this->proptags['goid2']];
2662
+            $newmessageprops[PR_OWNER_APPT_ID] = $messageprops[PR_OWNER_APPT_ID];
2663
+
2664
+            // Get deleted recipiets from exception msg
2665
+            $restriction = [RES_AND,
2666
+                [
2667
+                    [RES_BITMASK,
2668
+                        [ULTYPE => BMR_NEZ,
2669
+                            ULPROPTAG => PR_RECIPIENT_FLAGS,
2670
+                            ULMASK => recipExceptionalDeleted,
2671
+                        ],
2672
+                    ],
2673
+                    [RES_BITMASK,
2674
+                        [ULTYPE => BMR_EQZ,
2675
+                            ULPROPTAG => PR_RECIPIENT_FLAGS,
2676
+                            ULMASK => recipOrganizer,
2677
+                        ],
2678
+                    ],
2679
+                ],
2680
+            ];
2681
+
2682
+            // In direct-booking mode, we don't need to send cancellations to resources
2683
+            if ($this->enableDirectBooking) {
2684
+                $restriction[1][] = [RES_PROPERTY,
2685
+                    [RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2686
+                        ULPROPTAG => PR_RECIPIENT_TYPE,
2687
+                        VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2688
+                    ],
2689
+                ];
2690
+            }
2691
+
2692
+            $recipienttable = mapi_message_getrecipienttable($message);
2693
+            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $restriction);
2694
+
2695
+            if (!$deletedRecips) {
2696
+                $deletedRecips = array_merge([], $recipients);
2697
+            }
2698
+            else {
2699
+                $deletedRecips = array_merge($deletedRecips, $recipients);
2700
+            }
2701
+        }
2702
+
2703
+        // Remove the PR_ICON_INDEX as it is not needed in the sent message and it also
2704
+        // confuses webaccess
2705
+        $newmessageprops[PR_ICON_INDEX] = null;
2706
+        $newmessageprops[PR_RESPONSE_REQUESTED] = true;
2707
+
2708
+        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2709
+        $newmessageprops[PR_START_DATE] = $newmessageprops[$this->proptags['startdate']];
2710
+        $newmessageprops[PR_END_DATE] = $newmessageprops[$this->proptags['duedate']];
2711
+
2712
+        // Set updatecounter/AppointmentSequenceNumber
2713
+        // get the value of latest updatecounter for the whole series and use it
2714
+        $newmessageprops[$this->proptags['updatecounter']] = $messageprops[$this->proptags['last_updatecounter']];
2715
+
2716
+        $meetingTimeInfo = $this->getMeetingTimeInfo();
2717
+
2718
+        if ($meetingTimeInfo) {
2719
+            $newmessageprops[PR_BODY] = $meetingTimeInfo;
2720
+        }
2721
+
2722
+        // Send all recurrence info in mail, if this is a recurrence meeting.
2723
+        if (isset($messageprops[$this->proptags['recurring']]) && $messageprops[$this->proptags['recurring']]) {
2724
+            if (!empty($messageprops[$this->proptags['recurring_pattern']])) {
2725
+                $newmessageprops[$this->proptags['recurring_pattern']] = $messageprops[$this->proptags['recurring_pattern']];
2726
+            }
2727
+            $newmessageprops[$this->proptags['recurrence_data']] = $messageprops[$this->proptags['recurrence_data']];
2728
+            $newmessageprops[$this->proptags['timezone_data']] = $messageprops[$this->proptags['timezone_data']];
2729
+            $newmessageprops[$this->proptags['timezone']] = $messageprops[$this->proptags['timezone']];
2730
+            if ($recurObject) {
2731
+                $this->generateRecurDates($recurObject, $messageprops, $newmessageprops);
2732
+            }
2733
+        }
2734
+        if (isset($newmessageprops[$this->proptags['counter_proposal']])) {
2735
+            unset($newmessageprops[$this->proptags['counter_proposal']]);
2736
+        }
2737
+
2738
+        // Prefix the subject if needed
2739
+        if ($prefix && isset($newmessageprops[PR_SUBJECT])) {
2740
+            $newmessageprops[PR_SUBJECT] = $prefix . $newmessageprops[PR_SUBJECT];
2741
+        }
2742
+
2743
+        mapi_setprops($new, $newmessageprops);
2744
+
2745
+        // Copy attachments
2746
+        $this->replaceAttachments($message, $new, $copyExceptions);
2747
+
2748
+        // Retrieve only those recipient who should receive this meeting request.
2749
+        $stripResourcesRestriction = [RES_AND,
2750
+            [
2751
+                [RES_BITMASK,
2752
+                    [ULTYPE => BMR_EQZ,
2753
+                        ULPROPTAG => PR_RECIPIENT_FLAGS,
2754
+                        ULMASK => recipExceptionalDeleted,
2755
+                    ],
2756
+                ],
2757
+                [RES_BITMASK,
2758
+                    [ULTYPE => BMR_EQZ,
2759
+                        ULPROPTAG => PR_RECIPIENT_FLAGS,
2760
+                        ULMASK => recipOrganizer,
2761
+                    ],
2762
+                ],
2763
+            ],
2764
+        ];
2765
+
2766
+        // In direct-booking mode, resources do not receive a meeting request
2767
+        if ($this->enableDirectBooking) {
2768
+            $stripResourcesRestriction[1][] =
2769
+                                    [RES_PROPERTY,
2770
+                                        [RELOP => RELOP_NE,	// Does not equal recipient type: MAPI_BCC (Resource)
2771
+                                            ULPROPTAG => PR_RECIPIENT_TYPE,
2772
+                                            VALUE => [PR_RECIPIENT_TYPE => MAPI_BCC],
2773
+                                        ],
2774
+                                    ];
2775
+        }
2776
+
2777
+        $recipienttable = mapi_message_getrecipienttable($message);
2778
+        $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2779
+
2780
+        if ($basedate && empty($recipients)) {
2781
+            // Retrieve full list
2782
+            $recipienttable = mapi_message_getrecipienttable($this->message);
2783
+            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops);
2784
+
2785
+            // Save recipients in exceptions
2786
+            mapi_message_modifyrecipients($message, MODRECIP_ADD, $recipients);
2787
+
2788
+            // Now retrieve only those recipient who should receive this meeting request.
2789
+            $recipients = mapi_table_queryallrows($recipienttable, $this->recipprops, $stripResourcesRestriction);
2790
+        }
2791
+
2792
+        // @TODO: handle nonAcceptingResources
2793
+        /*
2794 2794
 		 * Add resource recipients that did not automatically accept the meeting request.
2795 2795
 		 * (note: meaning that they did not decline the meeting request)
2796 2796
 		 */ /*
@@ -2798,603 +2798,603 @@  discard block
 block discarded – undo
2798 2798
 			$recipients[] = $this->nonAcceptingResources[$i];
2799 2799
 		}*/
2800 2800
 
2801
-		if (!empty($recipients)) {
2802
-			// Strip out the sender/"owner" recipient
2803
-			mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
2804
-
2805
-			// Set some properties that are different in the sent request than
2806
-			// in the item in our calendar
2807
-
2808
-			// we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
2809
-			// should always be fbTentative
2810
-			$newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
2811
-			$newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
2812
-			$newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
2813
-			$newmessageprops[$this->proptags['attendee_critical_change']] = time();
2814
-			$newmessageprops[$this->proptags['owner_critical_change']] = time();
2815
-			$newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
2816
-
2817
-			if ($cancel) {
2818
-				$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
2819
-				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2820
-				$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2821
-			}
2822
-			else {
2823
-				$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request";
2824
-				$newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2825
-			}
2826
-
2827
-			mapi_setprops($new, $newmessageprops);
2828
-			mapi_savechanges($new);
2829
-
2830
-			// Submit message to non-resource recipients
2831
-			mapi_message_submitmessage($new);
2832
-		}
2833
-
2834
-		// Send cancellation to deleted attendees
2835
-		if ($deletedRecips && !empty($deletedRecips)) {
2836
-			$new = $this->createOutgoingMessage();
2837
-
2838
-			mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
2839
-
2840
-			$newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
2841
-			$newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2842
-			$newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2843
-			$newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;	// HIGH Importance
2844
-			if (isset($newmessageprops[PR_SUBJECT])) {
2845
-				$newmessageprops[PR_SUBJECT] = dgettext("kopano", "Canceled") . ": " . $newmessageprops[PR_SUBJECT];
2846
-			}
2847
-
2848
-			mapi_setprops($new, $newmessageprops);
2849
-			mapi_savechanges($new);
2850
-
2851
-			// Submit message to non-resource recipients
2852
-			mapi_message_submitmessage($new);
2853
-		}
2854
-
2855
-		// Set properties on meeting object in calendar
2856
-		// Set requestsent to 'true' (turns on 'tracking', etc)
2857
-		$props = [];
2858
-		$props[$this->proptags['meetingstatus']] = olMeeting;
2859
-		$props[$this->proptags['responsestatus']] = olResponseOrganized;
2860
-		$props[$this->proptags['requestsent']] = (!empty($recipients)) || ($this->includesResources && !$this->errorSetResource);
2861
-		$props[$this->proptags['attendee_critical_change']] = time();
2862
-		$props[$this->proptags['owner_critical_change']] = time();
2863
-		$props[$this->proptags['meetingtype']] = mtgRequest;
2864
-		// save the new updatecounter to exception/recurring series/normal meeting
2865
-		$props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
2866
-
2867
-		// PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2868
-		$props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
2869
-		$props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
2870
-
2871
-		mapi_setprops($message, $props);
2872
-
2873
-		// saving of these properties on calendar item should be handled by caller function
2874
-		// based on sending meeting request was successful or not.
2875
-	}
2876
-
2877
-	/**
2878
-	 * OL2007 uses these 4 properties to specify occurrence that should be updated.
2879
-	 * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
2880
-	 * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
2881
-	 * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
2882
-	 * also additionally we are sending these properties.
2883
-	 * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
2884
-	 *
2885
-	 * @param object $recurObject     instance of recurrence class for this message
2886
-	 * @param array  $messageprops    properties of meeting object that is going to be sent
2887
-	 * @param array  $newmessageprops properties of meeting request/response that is going to be sent
2888
-	 */
2889
-	public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) {
2890
-		if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
2891
-			$startDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
2892
-			$endDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
2893
-
2894
-			$startDate = explode(":", $startDate);
2895
-			$endDate = explode(":", $endDate);
2896
-
2897
-			// [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
2898
-			// RecurStartDate = year * 512 + month_number * 32 + day_number
2899
-			$newmessageprops[$this->proptags["start_recur_date"]] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
2900
-			// RecurStartTime = hour * 4096 + minutes * 64 + seconds
2901
-			$newmessageprops[$this->proptags["start_recur_time"]] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
2902
-
2903
-			$newmessageprops[$this->proptags["end_recur_date"]] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
2904
-			$newmessageprops[$this->proptags["end_recur_time"]] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
2905
-		}
2906
-	}
2907
-
2908
-	public function createOutgoingMessage() {
2909
-		$sentprops = [];
2910
-		$outbox = $this->openDefaultOutbox($this->openDefaultStore());
2911
-
2912
-		$outgoing = mapi_folder_createmessage($outbox);
2913
-		if (!$outgoing) {
2914
-			return false;
2915
-		}
2916
-
2917
-		$addrinfo = $this->getOwnerAddress($this->store);
2918
-		if ($addrinfo) {
2919
-			list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2920
-			$sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2921
-			$sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2922
-			$sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2923
-			$sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2924
-			$sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2925
-		}
2926
-
2927
-		$sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($this->openDefaultStore());
2928
-
2929
-		mapi_setprops($outgoing, $sentprops);
2930
-
2931
-		return $outgoing;
2932
-	}
2933
-
2934
-	/**
2935
-	 * Function which checks received meeting request is either old(outofdate) or new.
2936
-	 *
2937
-	 * @return bool true if meeting request is outofdate else false if it is new
2938
-	 */
2939
-	public function isMeetingOutOfDate() {
2940
-		$result = false;
2941
-		$store = $this->store;
2942
-		$props = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
2943
-
2944
-		if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
2945
-			return true;
2946
-		}
2947
-
2948
-		// get the basedate to check for exception
2949
-		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
2950
-
2951
-		$calendarItems = $this->getCorrespondedCalendarItems();
2952
-
2953
-		foreach ($calendarItems as $calendarItem) {
2954
-			if ($calendarItem) {
2955
-				$calendarItemProps = mapi_getprops($calendarItem, [
2956
-					$this->proptags['owner_critical_change'],
2957
-					$this->proptags['updatecounter'],
2958
-					$this->proptags['recurring'],
2959
-				]);
2960
-
2961
-				// If these items is recurring and basedate is found then open exception to compare it with meeting request
2962
-				if (isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] && $basedate) {
2963
-					$recurr = new Recurrence($store, $calendarItem);
2964
-
2965
-					if ($recurr->isException($basedate)) {
2966
-						$attach = $recurr->getExceptionAttachment($basedate);
2967
-						$exception = mapi_attach_openobj($attach, 0);
2968
-						$occurrenceItemProps = mapi_getprops($exception, [
2969
-							$this->proptags['owner_critical_change'],
2970
-							$this->proptags['updatecounter'],
2971
-						]);
2972
-					}
2973
-
2974
-					// we found the exception, compare with it
2975
-					if (isset($occurrenceItemProps)) {
2976
-						if ((isset($occurrenceItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $occurrenceItemProps[$this->proptags['updatecounter']]) ||
2977
-							(isset($occurrenceItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $occurrenceItemProps[$this->proptags['owner_critical_change']])) {
2978
-							mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
2979
-							mapi_savechanges($this->message);
2980
-							$result = true;
2981
-						}
2982
-					}
2983
-					elseif ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) ||
2984
-						(isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
2985
-						// we are not able to find exception, could mean that a significant change has occurred on series
2986
-						// and it deleted all exceptions, so compare with series
2987
-
2988
-						mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
2989
-						mapi_savechanges($this->message);
2990
-						$result = true;
2991
-					}
2992
-				}
2993
-				elseif ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) ||
2994
-					(isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
2995
-					// normal / recurring series
2996
-
2997
-					mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
2998
-					mapi_savechanges($this->message);
2999
-					$result = true;
3000
-				}
3001
-			}
3002
-		}
3003
-
3004
-		return $result;
3005
-	}
3006
-
3007
-	/**
3008
-	 * Function which checks received meeting request is updated later or not.
3009
-	 *
3010
-	 * @return bool true if meeting request is updated later
3011
-	 * @TODO: Implement handling for recurrings and exceptions.
3012
-	 */
3013
-	public function isMeetingUpdated() {
3014
-		$result = false;
3015
-		$store = $this->store;
3016
-		$props = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['owner_critical_change'], $this->proptags['updatecounter']]);
3017
-
3018
-		$calendarItems = $this->getCorrespondedCalendarItems();
3019
-
3020
-		foreach ($calendarItems as $calendarItem) {
3021
-			if ($calendarItem) {
3022
-				$calendarItemProps = mapi_getprops($calendarItem, [
3023
-					$this->proptags['updatecounter'],
3024
-					$this->proptags['recurring'],
3025
-				]);
3026
-
3027
-				if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']]) && $calendarItemProps[$this->proptags['updatecounter']] > $props[$this->proptags['updatecounter']]) {
3028
-					$result = true;
3029
-				}
3030
-			}
3031
-		}
3032
-
3033
-		return $result;
3034
-	}
3035
-
3036
-	/**
3037
-	 * Checks if there has been any significant changes on appointment/meeting item.
3038
-	 * Significant changes be:
3039
-	 * 1) startdate has been changed
3040
-	 * 2) duedate has been changed OR
3041
-	 * 3) recurrence pattern has been created, modified or removed.
3042
-	 *
3043
-	 * @param array oldProps old props before an update
3044
-	 * @param Number basedate basedate
3045
-	 * @param bool isRecurrenceChanged for change in recurrence pattern.
3046
-	 * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
3047
-	 * @param mixed $oldProps
3048
-	 * @param mixed $basedate
3049
-	 * @param mixed $isRecurrenceChanged
3050
-	 */
3051
-	public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3052
-		$message = null;
3053
-		$attach = null;
3054
-
3055
-		// If basedate is specified then we need to open exception message to clear recipient responses
3056
-		if ($basedate) {
3057
-			$recurrence = new Recurrence($this->store, $this->message);
3058
-			if ($recurrence->isException($basedate)) {
3059
-				$attach = $recurrence->getExceptionAttachment($basedate);
3060
-				if ($attach) {
3061
-					$message = mapi_attach_openobj($attach, MAPI_MODIFY);
3062
-				}
3063
-			}
3064
-		}
3065
-		else {
3066
-			// use normal message or recurring series message
3067
-			$message = $this->message;
3068
-		}
3069
-
3070
-		if (!$message) {
3071
-			return;
3072
-		}
3073
-
3074
-		$newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
3075
-
3076
-		// Check whether message is updated or not.
3077
-		if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3078
-			return;
3079
-		}
3080
-
3081
-		if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3082
-			($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3083
-			$isRecurrenceChanged) {
3084
-			$this->clearRecipientResponse($message);
3085
-
3086
-			mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
3087
-
3088
-			mapi_savechanges($message);
3089
-			if ($attach) { // Also save attachment Object.
3090
-				mapi_savechanges($attach);
3091
-			}
3092
-		}
3093
-	}
3094
-
3095
-	/**
3096
-	 * Clear responses of all attendees who have replied in past.
3097
-	 *
3098
-	 * @param MAPI_MESSAGE $message on which responses should be cleared
3099
-	 */
3100
-	public function clearRecipientResponse($message) {
3101
-		$recipTable = mapi_message_getrecipienttable($message);
3102
-		$recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3103
-
3104
-		foreach ($recipsRows as $recipient) {
3105
-			if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
3106
-				// Recipient is attendee, set the trackstatus to "Not Responded"
3107
-				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3108
-			}
3109
-			else {
3110
-				// Recipient is organizer, this is not possible, but for safety
3111
-				// it is best to clear the trackstatus for him as well by setting
3112
-				// the trackstatus to "Organized".
3113
-				$recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3114
-			}
3115
-			mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
3116
-		}
3117
-	}
3118
-
3119
-	/**
3120
-	 * Function returns corresponded calendar items attached with
3121
-	 * the meeting request.
3122
-	 *
3123
-	 * @return array array of correlated calendar items
3124
-	 */
3125
-	public function getCorrespondedCalendarItems() {
3126
-		$store = $this->store;
3127
-		$props = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME]);
3128
-
3129
-		$basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3130
-
3131
-		/* If the delegate is processing MR for the delegator, retrieve the delegator's store and calendar. */
3132
-		if (isset($props[PR_RCVD_REPRESENTING_NAME])) {
3133
-			$delegatorStore = $this->getDelegatorStore($props);
3134
-			$store = $delegatorStore['store'];
3135
-			$calFolder = $delegatorStore['calFolder'];
3136
-		}
3137
-		else {
3138
-			$calFolder = $this->openDefaultCalendar();
3139
-		}
3140
-
3141
-		// Finding item in calendar with GlobalID(0x3), not necessary that attendee is having recurring item, he/she can also have only a occurrence
3142
-		$entryids = $this->findCalendarItems($props[$this->proptags['goid']], $calFolder);
3143
-
3144
-		// Basedate found, so this meeting request is an update of an occurrence.
3145
-		if ($basedate) {
3146
-			if (!$entryids) {
3147
-				// Find main recurring item in calendar with GlobalID(0x23)
3148
-				$entryids = $this->findCalendarItems($props[$this->proptags['goid2']], $calFolder);
3149
-			}
3150
-		}
3151
-
3152
-		$calendarItems = [];
3153
-		if ($entryids) {
3154
-			foreach ($entryids as $entryid) {
3155
-				$calendarItems[] = mapi_msgstore_openentry($store, $entryid);
3156
-			}
3157
-		}
3158
-
3159
-		return $calendarItems;
3160
-	}
3161
-
3162
-	/**
3163
-	 * Function which checks whether received meeting request is either conflicting with other appointments or not.
3164
-	 *
3165
-	 * @param mixed $message
3166
-	 * @param mixed $userStore
3167
-	 * @param mixed $calFolder
3168
-	 * @param mixed $msgprops
3169
-	 *
3170
-	 *@return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3171
-	 * conflict of recurring meeting and false if meeting is not conflicting
3172
-	 */
3173
-	public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false, $msgprops = false) {
3174
-		$returnValue = false;
3175
-		$conflicting = false;
3176
-		$noOfInstances = 0;
3177
-
3178
-		if (!$message) {
3179
-			$message = $this->message;
3180
-		}
3181
-
3182
-		if (!$userStore) {
3183
-			$userStore = $this->store;
3184
-		}
3185
-
3186
-		if (!$calFolder) {
3187
-			$root = mapi_msgstore_openentry($userStore);
3188
-			$rootprops = mapi_getprops($root, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
3189
-			if (!isset($rootprops[PR_IPM_APPOINTMENT_ENTRYID])) {
3190
-				return;
3191
-			}
3192
-			$calFolder = mapi_msgstore_openentry($userStore, $rootprops[PR_IPM_APPOINTMENT_ENTRYID]);
3193
-		}
3194
-
3195
-		if (!$msgprops) {
3196
-			$msgprops = mapi_getprops($message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['recurring'], $this->proptags['clipstart'], $this->proptags['clipend']]);
3197
-		}
3198
-
3199
-		if ($calFolder) {
3200
-			// Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3201
-			if (isset($msgprops[$this->proptags['recurring']]) && $msgprops[$this->proptags['recurring']]) {
3202
-				// Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3203
-				$recurr = new Recurrence($userStore, $message);
3204
-				$items = $recurr->getItems($msgprops[$this->proptags['clipstart']], $msgprops[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3205
-
3206
-				foreach ($items as $item) {
3207
-					// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3208
-					$calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID]);
3209
-
3210
-					foreach ($calendarItems as $calendarItem) {
3211
-						if ($calendarItem[$this->proptags['busystatus']] == fbFree) {
3212
-							continue;
3213
-						}
3214
-						/*
2801
+        if (!empty($recipients)) {
2802
+            // Strip out the sender/"owner" recipient
2803
+            mapi_message_modifyrecipients($new, MODRECIP_ADD, $recipients);
2804
+
2805
+            // Set some properties that are different in the sent request than
2806
+            // in the item in our calendar
2807
+
2808
+            // we should store busystatus value to intendedbusystatus property, because busystatus for outgoing meeting request
2809
+            // should always be fbTentative
2810
+            $newmessageprops[$this->proptags['intendedbusystatus']] = isset($newmessageprops[$this->proptags['busystatus']]) ? $newmessageprops[$this->proptags['busystatus']] : $messageprops[$this->proptags['busystatus']];
2811
+            $newmessageprops[$this->proptags['busystatus']] = fbTentative; // The default status when not accepted
2812
+            $newmessageprops[$this->proptags['responsestatus']] = olResponseNotResponded; // The recipient has not responded yet
2813
+            $newmessageprops[$this->proptags['attendee_critical_change']] = time();
2814
+            $newmessageprops[$this->proptags['owner_critical_change']] = time();
2815
+            $newmessageprops[$this->proptags['meetingtype']] = mtgRequest;
2816
+
2817
+            if ($cancel) {
2818
+                $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
2819
+                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2820
+                $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2821
+            }
2822
+            else {
2823
+                $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Request";
2824
+                $newmessageprops[$this->proptags['meetingstatus']] = olMeetingReceived; // The recipient is receiving the request
2825
+            }
2826
+
2827
+            mapi_setprops($new, $newmessageprops);
2828
+            mapi_savechanges($new);
2829
+
2830
+            // Submit message to non-resource recipients
2831
+            mapi_message_submitmessage($new);
2832
+        }
2833
+
2834
+        // Send cancellation to deleted attendees
2835
+        if ($deletedRecips && !empty($deletedRecips)) {
2836
+            $new = $this->createOutgoingMessage();
2837
+
2838
+            mapi_message_modifyrecipients($new, MODRECIP_ADD, $deletedRecips);
2839
+
2840
+            $newmessageprops[PR_MESSAGE_CLASS] = "IPM.Schedule.Meeting.Canceled";
2841
+            $newmessageprops[$this->proptags['meetingstatus']] = olMeetingCanceled; // It's a cancel request
2842
+            $newmessageprops[$this->proptags['busystatus']] = fbFree; // set the busy status as free
2843
+            $newmessageprops[PR_IMPORTANCE] = IMPORTANCE_HIGH;	// HIGH Importance
2844
+            if (isset($newmessageprops[PR_SUBJECT])) {
2845
+                $newmessageprops[PR_SUBJECT] = dgettext("kopano", "Canceled") . ": " . $newmessageprops[PR_SUBJECT];
2846
+            }
2847
+
2848
+            mapi_setprops($new, $newmessageprops);
2849
+            mapi_savechanges($new);
2850
+
2851
+            // Submit message to non-resource recipients
2852
+            mapi_message_submitmessage($new);
2853
+        }
2854
+
2855
+        // Set properties on meeting object in calendar
2856
+        // Set requestsent to 'true' (turns on 'tracking', etc)
2857
+        $props = [];
2858
+        $props[$this->proptags['meetingstatus']] = olMeeting;
2859
+        $props[$this->proptags['responsestatus']] = olResponseOrganized;
2860
+        $props[$this->proptags['requestsent']] = (!empty($recipients)) || ($this->includesResources && !$this->errorSetResource);
2861
+        $props[$this->proptags['attendee_critical_change']] = time();
2862
+        $props[$this->proptags['owner_critical_change']] = time();
2863
+        $props[$this->proptags['meetingtype']] = mtgRequest;
2864
+        // save the new updatecounter to exception/recurring series/normal meeting
2865
+        $props[$this->proptags['updatecounter']] = $newmessageprops[$this->proptags['updatecounter']];
2866
+
2867
+        // PR_START_DATE and PR_END_DATE will be used by outlook to show the position in the calendar
2868
+        $props[PR_START_DATE] = $messageprops[$this->proptags['startdate']];
2869
+        $props[PR_END_DATE] = $messageprops[$this->proptags['duedate']];
2870
+
2871
+        mapi_setprops($message, $props);
2872
+
2873
+        // saving of these properties on calendar item should be handled by caller function
2874
+        // based on sending meeting request was successful or not.
2875
+    }
2876
+
2877
+    /**
2878
+     * OL2007 uses these 4 properties to specify occurrence that should be updated.
2879
+     * ical generates RECURRENCE-ID property based on exception's basedate (PidLidExceptionReplaceTime),
2880
+     * but OL07 doesn't send this property, so ical will generate RECURRENCE-ID property based on date
2881
+     * from GlobalObjId and time from StartRecurTime property, so we are sending basedate property and
2882
+     * also additionally we are sending these properties.
2883
+     * Ref: MS-OXCICAL 2.2.1.20.20 Property: RECURRENCE-ID.
2884
+     *
2885
+     * @param object $recurObject     instance of recurrence class for this message
2886
+     * @param array  $messageprops    properties of meeting object that is going to be sent
2887
+     * @param array  $newmessageprops properties of meeting request/response that is going to be sent
2888
+     */
2889
+    public function generateRecurDates($recurObject, $messageprops, &$newmessageprops) {
2890
+        if ($messageprops[$this->proptags['startdate']] && $messageprops[$this->proptags['duedate']]) {
2891
+            $startDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['startdate']]));
2892
+            $endDate = date("Y:n:j:G:i:s", $recurObject->fromGMT($recurObject->tz, $messageprops[$this->proptags['duedate']]));
2893
+
2894
+            $startDate = explode(":", $startDate);
2895
+            $endDate = explode(":", $endDate);
2896
+
2897
+            // [0] => year, [1] => month, [2] => day, [3] => hour, [4] => minutes, [5] => seconds
2898
+            // RecurStartDate = year * 512 + month_number * 32 + day_number
2899
+            $newmessageprops[$this->proptags["start_recur_date"]] = (((int) $startDate[0]) * 512) + (((int) $startDate[1]) * 32) + ((int) $startDate[2]);
2900
+            // RecurStartTime = hour * 4096 + minutes * 64 + seconds
2901
+            $newmessageprops[$this->proptags["start_recur_time"]] = (((int) $startDate[3]) * 4096) + (((int) $startDate[4]) * 64) + ((int) $startDate[5]);
2902
+
2903
+            $newmessageprops[$this->proptags["end_recur_date"]] = (((int) $endDate[0]) * 512) + (((int) $endDate[1]) * 32) + ((int) $endDate[2]);
2904
+            $newmessageprops[$this->proptags["end_recur_time"]] = (((int) $endDate[3]) * 4096) + (((int) $endDate[4]) * 64) + ((int) $endDate[5]);
2905
+        }
2906
+    }
2907
+
2908
+    public function createOutgoingMessage() {
2909
+        $sentprops = [];
2910
+        $outbox = $this->openDefaultOutbox($this->openDefaultStore());
2911
+
2912
+        $outgoing = mapi_folder_createmessage($outbox);
2913
+        if (!$outgoing) {
2914
+            return false;
2915
+        }
2916
+
2917
+        $addrinfo = $this->getOwnerAddress($this->store);
2918
+        if ($addrinfo) {
2919
+            list($ownername, $owneremailaddr, $owneraddrtype, $ownerentryid, $ownersearchkey) = $addrinfo;
2920
+            $sentprops[PR_SENT_REPRESENTING_EMAIL_ADDRESS] = $owneremailaddr;
2921
+            $sentprops[PR_SENT_REPRESENTING_NAME] = $ownername;
2922
+            $sentprops[PR_SENT_REPRESENTING_ADDRTYPE] = $owneraddrtype;
2923
+            $sentprops[PR_SENT_REPRESENTING_ENTRYID] = $ownerentryid;
2924
+            $sentprops[PR_SENT_REPRESENTING_SEARCH_KEY] = $ownersearchkey;
2925
+        }
2926
+
2927
+        $sentprops[PR_SENTMAIL_ENTRYID] = $this->getDefaultSentmailEntryID($this->openDefaultStore());
2928
+
2929
+        mapi_setprops($outgoing, $sentprops);
2930
+
2931
+        return $outgoing;
2932
+    }
2933
+
2934
+    /**
2935
+     * Function which checks received meeting request is either old(outofdate) or new.
2936
+     *
2937
+     * @return bool true if meeting request is outofdate else false if it is new
2938
+     */
2939
+    public function isMeetingOutOfDate() {
2940
+        $result = false;
2941
+        $store = $this->store;
2942
+        $props = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['meetingtype'], $this->proptags['owner_critical_change']]);
2943
+
2944
+        if (isset($props[$this->proptags['meetingtype']]) && ($props[$this->proptags['meetingtype']] & mtgOutOfDate) == mtgOutOfDate) {
2945
+            return true;
2946
+        }
2947
+
2948
+        // get the basedate to check for exception
2949
+        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
2950
+
2951
+        $calendarItems = $this->getCorrespondedCalendarItems();
2952
+
2953
+        foreach ($calendarItems as $calendarItem) {
2954
+            if ($calendarItem) {
2955
+                $calendarItemProps = mapi_getprops($calendarItem, [
2956
+                    $this->proptags['owner_critical_change'],
2957
+                    $this->proptags['updatecounter'],
2958
+                    $this->proptags['recurring'],
2959
+                ]);
2960
+
2961
+                // If these items is recurring and basedate is found then open exception to compare it with meeting request
2962
+                if (isset($calendarItemProps[$this->proptags['recurring']]) && $calendarItemProps[$this->proptags['recurring']] && $basedate) {
2963
+                    $recurr = new Recurrence($store, $calendarItem);
2964
+
2965
+                    if ($recurr->isException($basedate)) {
2966
+                        $attach = $recurr->getExceptionAttachment($basedate);
2967
+                        $exception = mapi_attach_openobj($attach, 0);
2968
+                        $occurrenceItemProps = mapi_getprops($exception, [
2969
+                            $this->proptags['owner_critical_change'],
2970
+                            $this->proptags['updatecounter'],
2971
+                        ]);
2972
+                    }
2973
+
2974
+                    // we found the exception, compare with it
2975
+                    if (isset($occurrenceItemProps)) {
2976
+                        if ((isset($occurrenceItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $occurrenceItemProps[$this->proptags['updatecounter']]) ||
2977
+                            (isset($occurrenceItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $occurrenceItemProps[$this->proptags['owner_critical_change']])) {
2978
+                            mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
2979
+                            mapi_savechanges($this->message);
2980
+                            $result = true;
2981
+                        }
2982
+                    }
2983
+                    elseif ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) ||
2984
+                        (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
2985
+                        // we are not able to find exception, could mean that a significant change has occurred on series
2986
+                        // and it deleted all exceptions, so compare with series
2987
+
2988
+                        mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
2989
+                        mapi_savechanges($this->message);
2990
+                        $result = true;
2991
+                    }
2992
+                }
2993
+                elseif ((isset($calendarItemProps[$this->proptags['updatecounter']]) && $props[$this->proptags['updatecounter']] < $calendarItemProps[$this->proptags['updatecounter']]) ||
2994
+                    (isset($calendarItemProps[$this->proptags['owner_critical_change']]) && $props[$this->proptags['owner_critical_change']] < $calendarItemProps[$this->proptags['owner_critical_change']])) {
2995
+                    // normal / recurring series
2996
+
2997
+                    mapi_setprops($this->message, [$this->proptags['meetingtype'] => mtgOutOfDate, PR_ICON_INDEX => 1033]);
2998
+                    mapi_savechanges($this->message);
2999
+                    $result = true;
3000
+                }
3001
+            }
3002
+        }
3003
+
3004
+        return $result;
3005
+    }
3006
+
3007
+    /**
3008
+     * Function which checks received meeting request is updated later or not.
3009
+     *
3010
+     * @return bool true if meeting request is updated later
3011
+     * @TODO: Implement handling for recurrings and exceptions.
3012
+     */
3013
+    public function isMeetingUpdated() {
3014
+        $result = false;
3015
+        $store = $this->store;
3016
+        $props = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['updatecounter'], $this->proptags['owner_critical_change'], $this->proptags['updatecounter']]);
3017
+
3018
+        $calendarItems = $this->getCorrespondedCalendarItems();
3019
+
3020
+        foreach ($calendarItems as $calendarItem) {
3021
+            if ($calendarItem) {
3022
+                $calendarItemProps = mapi_getprops($calendarItem, [
3023
+                    $this->proptags['updatecounter'],
3024
+                    $this->proptags['recurring'],
3025
+                ]);
3026
+
3027
+                if (isset($calendarItemProps[$this->proptags['updatecounter']], $props[$this->proptags['updatecounter']]) && $calendarItemProps[$this->proptags['updatecounter']] > $props[$this->proptags['updatecounter']]) {
3028
+                    $result = true;
3029
+                }
3030
+            }
3031
+        }
3032
+
3033
+        return $result;
3034
+    }
3035
+
3036
+    /**
3037
+     * Checks if there has been any significant changes on appointment/meeting item.
3038
+     * Significant changes be:
3039
+     * 1) startdate has been changed
3040
+     * 2) duedate has been changed OR
3041
+     * 3) recurrence pattern has been created, modified or removed.
3042
+     *
3043
+     * @param array oldProps old props before an update
3044
+     * @param Number basedate basedate
3045
+     * @param bool isRecurrenceChanged for change in recurrence pattern.
3046
+     * isRecurrenceChanged true means Recurrence pattern has been changed, so clear all attendees response
3047
+     * @param mixed $oldProps
3048
+     * @param mixed $basedate
3049
+     * @param mixed $isRecurrenceChanged
3050
+     */
3051
+    public function checkSignificantChanges($oldProps, $basedate, $isRecurrenceChanged = false) {
3052
+        $message = null;
3053
+        $attach = null;
3054
+
3055
+        // If basedate is specified then we need to open exception message to clear recipient responses
3056
+        if ($basedate) {
3057
+            $recurrence = new Recurrence($this->store, $this->message);
3058
+            if ($recurrence->isException($basedate)) {
3059
+                $attach = $recurrence->getExceptionAttachment($basedate);
3060
+                if ($attach) {
3061
+                    $message = mapi_attach_openobj($attach, MAPI_MODIFY);
3062
+                }
3063
+            }
3064
+        }
3065
+        else {
3066
+            // use normal message or recurring series message
3067
+            $message = $this->message;
3068
+        }
3069
+
3070
+        if (!$message) {
3071
+            return;
3072
+        }
3073
+
3074
+        $newProps = mapi_getprops($message, [$this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['updatecounter']]);
3075
+
3076
+        // Check whether message is updated or not.
3077
+        if (isset($newProps[$this->proptags['updatecounter']]) && $newProps[$this->proptags['updatecounter']] == 0) {
3078
+            return;
3079
+        }
3080
+
3081
+        if (($newProps[$this->proptags['startdate']] != $oldProps[$this->proptags['startdate']]) ||
3082
+            ($newProps[$this->proptags['duedate']] != $oldProps[$this->proptags['duedate']]) ||
3083
+            $isRecurrenceChanged) {
3084
+            $this->clearRecipientResponse($message);
3085
+
3086
+            mapi_setprops($message, [$this->proptags['owner_critical_change'] => time()]);
3087
+
3088
+            mapi_savechanges($message);
3089
+            if ($attach) { // Also save attachment Object.
3090
+                mapi_savechanges($attach);
3091
+            }
3092
+        }
3093
+    }
3094
+
3095
+    /**
3096
+     * Clear responses of all attendees who have replied in past.
3097
+     *
3098
+     * @param MAPI_MESSAGE $message on which responses should be cleared
3099
+     */
3100
+    public function clearRecipientResponse($message) {
3101
+        $recipTable = mapi_message_getrecipienttable($message);
3102
+        $recipsRows = mapi_table_queryallrows($recipTable, $this->recipprops);
3103
+
3104
+        foreach ($recipsRows as $recipient) {
3105
+            if (($recipient[PR_RECIPIENT_FLAGS] & recipOrganizer) != recipOrganizer) {
3106
+                // Recipient is attendee, set the trackstatus to "Not Responded"
3107
+                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3108
+            }
3109
+            else {
3110
+                // Recipient is organizer, this is not possible, but for safety
3111
+                // it is best to clear the trackstatus for him as well by setting
3112
+                // the trackstatus to "Organized".
3113
+                $recipient[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3114
+            }
3115
+            mapi_message_modifyrecipients($message, MODRECIP_MODIFY, [$recipient]);
3116
+        }
3117
+    }
3118
+
3119
+    /**
3120
+     * Function returns corresponded calendar items attached with
3121
+     * the meeting request.
3122
+     *
3123
+     * @return array array of correlated calendar items
3124
+     */
3125
+    public function getCorrespondedCalendarItems() {
3126
+        $store = $this->store;
3127
+        $props = mapi_getprops($this->message, [$this->proptags['goid'], $this->proptags['goid2'], PR_RCVD_REPRESENTING_NAME]);
3128
+
3129
+        $basedate = $this->getBasedateFromGlobalID($props[$this->proptags['goid']]);
3130
+
3131
+        /* If the delegate is processing MR for the delegator, retrieve the delegator's store and calendar. */
3132
+        if (isset($props[PR_RCVD_REPRESENTING_NAME])) {
3133
+            $delegatorStore = $this->getDelegatorStore($props);
3134
+            $store = $delegatorStore['store'];
3135
+            $calFolder = $delegatorStore['calFolder'];
3136
+        }
3137
+        else {
3138
+            $calFolder = $this->openDefaultCalendar();
3139
+        }
3140
+
3141
+        // Finding item in calendar with GlobalID(0x3), not necessary that attendee is having recurring item, he/she can also have only a occurrence
3142
+        $entryids = $this->findCalendarItems($props[$this->proptags['goid']], $calFolder);
3143
+
3144
+        // Basedate found, so this meeting request is an update of an occurrence.
3145
+        if ($basedate) {
3146
+            if (!$entryids) {
3147
+                // Find main recurring item in calendar with GlobalID(0x23)
3148
+                $entryids = $this->findCalendarItems($props[$this->proptags['goid2']], $calFolder);
3149
+            }
3150
+        }
3151
+
3152
+        $calendarItems = [];
3153
+        if ($entryids) {
3154
+            foreach ($entryids as $entryid) {
3155
+                $calendarItems[] = mapi_msgstore_openentry($store, $entryid);
3156
+            }
3157
+        }
3158
+
3159
+        return $calendarItems;
3160
+    }
3161
+
3162
+    /**
3163
+     * Function which checks whether received meeting request is either conflicting with other appointments or not.
3164
+     *
3165
+     * @param mixed $message
3166
+     * @param mixed $userStore
3167
+     * @param mixed $calFolder
3168
+     * @param mixed $msgprops
3169
+     *
3170
+     *@return mixed(boolean/integer) true if normal meeting is conflicting or an integer which specifies no of instances
3171
+     * conflict of recurring meeting and false if meeting is not conflicting
3172
+     */
3173
+    public function isMeetingConflicting($message = false, $userStore = false, $calFolder = false, $msgprops = false) {
3174
+        $returnValue = false;
3175
+        $conflicting = false;
3176
+        $noOfInstances = 0;
3177
+
3178
+        if (!$message) {
3179
+            $message = $this->message;
3180
+        }
3181
+
3182
+        if (!$userStore) {
3183
+            $userStore = $this->store;
3184
+        }
3185
+
3186
+        if (!$calFolder) {
3187
+            $root = mapi_msgstore_openentry($userStore);
3188
+            $rootprops = mapi_getprops($root, [PR_STORE_ENTRYID, PR_IPM_APPOINTMENT_ENTRYID, PR_FREEBUSY_ENTRYIDS]);
3189
+            if (!isset($rootprops[PR_IPM_APPOINTMENT_ENTRYID])) {
3190
+                return;
3191
+            }
3192
+            $calFolder = mapi_msgstore_openentry($userStore, $rootprops[PR_IPM_APPOINTMENT_ENTRYID]);
3193
+        }
3194
+
3195
+        if (!$msgprops) {
3196
+            $msgprops = mapi_getprops($message, [$this->proptags['goid'], $this->proptags['goid2'], $this->proptags['startdate'], $this->proptags['duedate'], $this->proptags['recurring'], $this->proptags['clipstart'], $this->proptags['clipend']]);
3197
+        }
3198
+
3199
+        if ($calFolder) {
3200
+            // Meeting request is recurring, so get all occurrence and check for each occurrence whether it conflicts with other appointments in Calendar.
3201
+            if (isset($msgprops[$this->proptags['recurring']]) && $msgprops[$this->proptags['recurring']]) {
3202
+                // Apply recurrence class and retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3203
+                $recurr = new Recurrence($userStore, $message);
3204
+                $items = $recurr->getItems($msgprops[$this->proptags['clipstart']], $msgprops[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3205
+
3206
+                foreach ($items as $item) {
3207
+                    // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3208
+                    $calendarItems = $recurr->getCalendarItems($userStore, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID]);
3209
+
3210
+                    foreach ($calendarItems as $calendarItem) {
3211
+                        if ($calendarItem[$this->proptags['busystatus']] == fbFree) {
3212
+                            continue;
3213
+                        }
3214
+                        /*
3215 3215
 						 * Only meeting requests have globalID, normal appointments do not have globalID
3216 3216
 						 * so if any normal appointment if found then it is assumed to be conflict.
3217 3217
 						 */
3218
-						if (!isset($calendarItem[$this->proptags['goid']])) {
3219
-							++$noOfInstances;
3220
-
3221
-							break;
3222
-						}
3223
-						if ($calendarItem[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) {
3224
-							++$noOfInstances;
3225
-
3226
-							break;
3227
-						}
3228
-					}
3229
-				}
3230
-
3231
-				$returnValue = $noOfInstances;
3232
-			}
3233
-			else {
3234
-				// Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3235
-				$items = getCalendarItems($userStore, $calFolder, $msgprops[$this->proptags['startdate']], $msgprops[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID]);
3236
-
3237
-				foreach ($items as $item) {
3238
-					if ($item[$this->proptags['busystatus']] == fbFree) {
3239
-						continue;
3240
-					}
3241
-					if (!isset($item[$this->proptags['goid']])) {
3242
-						$conflicting = true;
3243
-
3244
-						break;
3245
-					}
3246
-					if (($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) &&
3247
-						($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid2']])) {
3248
-						$conflicting = true;
3249
-
3250
-						break;
3251
-					}
3252
-				}
3253
-
3254
-				if ($conflicting) {
3255
-					$returnValue = true;
3256
-				}
3257
-			}
3258
-		}
3259
-
3260
-		return $returnValue;
3261
-	}
3262
-
3263
-	/**
3264
-	 *  Function which adds organizer to recipient list which is passed.
3265
-	 *  This function also checks if it has organizer.
3266
-	 *
3267
-	 * @param array $messageProps message properties
3268
-	 * @param array $recipients   recipients list of message
3269
-	 * @param bool  $isException  true if we are processing recipient of exception
3270
-	 */
3271
-	public function addDelegator($messageProps, &$recipients) {
3272
-		$hasDelegator = false;
3273
-		// Check if meeting already has an organizer.
3274
-		foreach ($recipients as $key => $recipient) {
3275
-			if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
3276
-				$hasDelegator = true;
3277
-			}
3278
-		}
3279
-
3280
-		if (!$hasDelegator) {
3281
-			// Create delegator.
3282
-			$delegator = [];
3283
-			$delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3284
-			$delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3285
-			$delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3286
-			$delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3287
-			$delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3288
-			$delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3289
-			$delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3290
-			$delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3291
-			$delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
3292
-
3293
-			// Add organizer to recipients list.
3294
-			array_unshift($recipients, $delegator);
3295
-		}
3296
-	}
3297
-
3298
-	public function getDelegatorStore($messageprops) {
3299
-		// Find the organiser of appointment in addressbook
3300
-		$delegatorName = [[PR_DISPLAY_NAME => $messageprops[PR_RCVD_REPRESENTING_NAME]]];
3301
-		$ab = mapi_openaddressbook($this->session);
3302
-		$user = mapi_ab_resolvename($ab, $delegatorName, EMS_AB_ADDRESS_LOOKUP);
3303
-
3304
-		// Get StoreEntryID by username
3305
-		$delegatorEntryid = mapi_msgstore_createentryid($this->store, $user[0][PR_EMAIL_ADDRESS]);
3306
-		// Open store of the delegator
3307
-		$delegatorStore = mapi_openmsgstore($this->session, $delegatorEntryid);
3308
-		// Open root folder
3309
-		$delegatorRoot = mapi_msgstore_openentry($delegatorStore, null);
3310
-		// Get calendar entryID
3311
-		$delegatorRootProps = mapi_getprops($delegatorRoot, [PR_IPM_APPOINTMENT_ENTRYID]);
3312
-		// Open the calendar Folder
3313
-		$calFolder = mapi_msgstore_openentry($delegatorStore, $delegatorRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
3314
-
3315
-		return ['store' => $delegatorStore, 'calFolder' => $calFolder];
3316
-	}
3317
-
3318
-	/**
3319
-	 * Function returns extra info about meeting timing along with message body
3320
-	 * which will be included in body while sending meeting request/response.
3321
-	 *
3322
-	 * @return string $meetingTimeInfo info about meeting timing along with message body
3323
-	 */
3324
-	public function getMeetingTimeInfo() {
3325
-		return $this->meetingTimeInfo;
3326
-	}
3327
-
3328
-	/**
3329
-	 * Function sets extra info about meeting timing along with message body
3330
-	 * which will be included in body while sending meeting request/response.
3331
-	 *
3332
-	 * @param string $meetingTimeInfo info about meeting timing along with message body
3333
-	 */
3334
-	public function setMeetingTimeInfo($meetingTimeInfo) {
3335
-		$this->meetingTimeInfo = $meetingTimeInfo;
3336
-	}
3337
-
3338
-	/**
3339
-	 * Helper function which is use to get local categories of all occurrence.
3340
-	 *
3341
-	 * @param MAPIMessage $calendarItem meeting request item
3342
-	 * @param MAPIStore   $store        store containing calendar folder
3343
-	 * @param MAPIFolder  $calFolder    calendar folder
3344
-	 *
3345
-	 * @return array $localCategories which contain array of basedate along with categories
3346
-	 */
3347
-	public function getLocalCategories($calendarItem, $store, $calFolder) {
3348
-		$calendarItemProps = mapi_getprops($calendarItem);
3349
-		$recurrence = new Recurrence($store, $calendarItem);
3350
-
3351
-		// Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3352
-		$items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3353
-		$localCategories = [];
3354
-
3355
-		foreach ($items as $item) {
3356
-			$recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags2['categories']]);
3357
-			foreach ($recurrenceItems as $recurrenceItem) {
3358
-				// Check if occurrence is exception then get the local categories of that occurrence.
3359
-				if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3360
-					$exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3361
-
3362
-					if ($exceptionAttach) {
3363
-						$exception = mapi_attach_openobj($exceptionAttach, 0);
3364
-						$exceptionProps = mapi_getprops($exception, [$this->proptags2['categories']]);
3365
-						if (isset($exceptionProps[$this->proptags2['categories']])) {
3366
-							$localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags2['categories']];
3367
-						}
3368
-					}
3369
-				}
3370
-			}
3371
-		}
3372
-
3373
-		return $localCategories;
3374
-	}
3375
-
3376
-	/**
3377
-	 * Helper function which is use to apply local categories on respective occurrences.
3378
-	 *
3379
-	 * @param MAPIMessage $calendarItem    meeting request item
3380
-	 * @param MAPIStore   $store           store containing calendar folder
3381
-	 * @param array       $localCategories array contains basedate and array of categories
3382
-	 */
3383
-	public function applyLocalCategories($calendarItem, $store, $localCategories) {
3384
-		$calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
3385
-		$message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3386
-		$recurrence = new Recurrence($store, $message);
3387
-
3388
-		// Check for all occurrence if it is exception then modify the exception by setting up categories,
3389
-		// Otherwise create new exception with categories.
3390
-		foreach ($localCategories as $key => $value) {
3391
-			if ($recurrence->isException($key)) {
3392
-				$recurrence->modifyException([$this->proptags2['categories'] => $value], $key);
3393
-			}
3394
-			else {
3395
-				$recurrence->createException([$this->proptags2['categories'] => $value], $key, false);
3396
-			}
3397
-			mapi_savechanges($message);
3398
-		}
3399
-	}
3218
+                        if (!isset($calendarItem[$this->proptags['goid']])) {
3219
+                            ++$noOfInstances;
3220
+
3221
+                            break;
3222
+                        }
3223
+                        if ($calendarItem[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) {
3224
+                            ++$noOfInstances;
3225
+
3226
+                            break;
3227
+                        }
3228
+                    }
3229
+                }
3230
+
3231
+                $returnValue = $noOfInstances;
3232
+            }
3233
+            else {
3234
+                // Get all items in the timeframe that we want to book, and get the goid and busystatus for each item
3235
+                $items = getCalendarItems($userStore, $calFolder, $msgprops[$this->proptags['startdate']], $msgprops[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], PR_OWNER_APPT_ID]);
3236
+
3237
+                foreach ($items as $item) {
3238
+                    if ($item[$this->proptags['busystatus']] == fbFree) {
3239
+                        continue;
3240
+                    }
3241
+                    if (!isset($item[$this->proptags['goid']])) {
3242
+                        $conflicting = true;
3243
+
3244
+                        break;
3245
+                    }
3246
+                    if (($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid']]) &&
3247
+                        ($item[$this->proptags['goid']] !== $msgprops[$this->proptags['goid2']])) {
3248
+                        $conflicting = true;
3249
+
3250
+                        break;
3251
+                    }
3252
+                }
3253
+
3254
+                if ($conflicting) {
3255
+                    $returnValue = true;
3256
+                }
3257
+            }
3258
+        }
3259
+
3260
+        return $returnValue;
3261
+    }
3262
+
3263
+    /**
3264
+     *  Function which adds organizer to recipient list which is passed.
3265
+     *  This function also checks if it has organizer.
3266
+     *
3267
+     * @param array $messageProps message properties
3268
+     * @param array $recipients   recipients list of message
3269
+     * @param bool  $isException  true if we are processing recipient of exception
3270
+     */
3271
+    public function addDelegator($messageProps, &$recipients) {
3272
+        $hasDelegator = false;
3273
+        // Check if meeting already has an organizer.
3274
+        foreach ($recipients as $key => $recipient) {
3275
+            if (isset($messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) && $recipient[PR_EMAIL_ADDRESS] == $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS]) {
3276
+                $hasDelegator = true;
3277
+            }
3278
+        }
3279
+
3280
+        if (!$hasDelegator) {
3281
+            // Create delegator.
3282
+            $delegator = [];
3283
+            $delegator[PR_ENTRYID] = $messageProps[PR_RCVD_REPRESENTING_ENTRYID];
3284
+            $delegator[PR_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3285
+            $delegator[PR_EMAIL_ADDRESS] = $messageProps[PR_RCVD_REPRESENTING_EMAIL_ADDRESS];
3286
+            $delegator[PR_RECIPIENT_TYPE] = MAPI_TO;
3287
+            $delegator[PR_RECIPIENT_DISPLAY_NAME] = $messageProps[PR_RCVD_REPRESENTING_NAME];
3288
+            $delegator[PR_ADDRTYPE] = empty($messageProps[PR_RCVD_REPRESENTING_ADDRTYPE]) ? 'SMTP' : $messageProps[PR_RCVD_REPRESENTING_ADDRTYPE];
3289
+            $delegator[PR_RECIPIENT_TRACKSTATUS] = olRecipientTrackStatusNone;
3290
+            $delegator[PR_RECIPIENT_FLAGS] = recipSendable;
3291
+            $delegator[PR_SEARCH_KEY] = $messageProps[PR_RCVD_REPRESENTING_SEARCH_KEY];
3292
+
3293
+            // Add organizer to recipients list.
3294
+            array_unshift($recipients, $delegator);
3295
+        }
3296
+    }
3297
+
3298
+    public function getDelegatorStore($messageprops) {
3299
+        // Find the organiser of appointment in addressbook
3300
+        $delegatorName = [[PR_DISPLAY_NAME => $messageprops[PR_RCVD_REPRESENTING_NAME]]];
3301
+        $ab = mapi_openaddressbook($this->session);
3302
+        $user = mapi_ab_resolvename($ab, $delegatorName, EMS_AB_ADDRESS_LOOKUP);
3303
+
3304
+        // Get StoreEntryID by username
3305
+        $delegatorEntryid = mapi_msgstore_createentryid($this->store, $user[0][PR_EMAIL_ADDRESS]);
3306
+        // Open store of the delegator
3307
+        $delegatorStore = mapi_openmsgstore($this->session, $delegatorEntryid);
3308
+        // Open root folder
3309
+        $delegatorRoot = mapi_msgstore_openentry($delegatorStore, null);
3310
+        // Get calendar entryID
3311
+        $delegatorRootProps = mapi_getprops($delegatorRoot, [PR_IPM_APPOINTMENT_ENTRYID]);
3312
+        // Open the calendar Folder
3313
+        $calFolder = mapi_msgstore_openentry($delegatorStore, $delegatorRootProps[PR_IPM_APPOINTMENT_ENTRYID]);
3314
+
3315
+        return ['store' => $delegatorStore, 'calFolder' => $calFolder];
3316
+    }
3317
+
3318
+    /**
3319
+     * Function returns extra info about meeting timing along with message body
3320
+     * which will be included in body while sending meeting request/response.
3321
+     *
3322
+     * @return string $meetingTimeInfo info about meeting timing along with message body
3323
+     */
3324
+    public function getMeetingTimeInfo() {
3325
+        return $this->meetingTimeInfo;
3326
+    }
3327
+
3328
+    /**
3329
+     * Function sets extra info about meeting timing along with message body
3330
+     * which will be included in body while sending meeting request/response.
3331
+     *
3332
+     * @param string $meetingTimeInfo info about meeting timing along with message body
3333
+     */
3334
+    public function setMeetingTimeInfo($meetingTimeInfo) {
3335
+        $this->meetingTimeInfo = $meetingTimeInfo;
3336
+    }
3337
+
3338
+    /**
3339
+     * Helper function which is use to get local categories of all occurrence.
3340
+     *
3341
+     * @param MAPIMessage $calendarItem meeting request item
3342
+     * @param MAPIStore   $store        store containing calendar folder
3343
+     * @param MAPIFolder  $calFolder    calendar folder
3344
+     *
3345
+     * @return array $localCategories which contain array of basedate along with categories
3346
+     */
3347
+    public function getLocalCategories($calendarItem, $store, $calFolder) {
3348
+        $calendarItemProps = mapi_getprops($calendarItem);
3349
+        $recurrence = new Recurrence($store, $calendarItem);
3350
+
3351
+        // Retrieve all occurrences(max: 30 occurrence because recurrence can also be set as 'no end date')
3352
+        $items = $recurrence->getItems($calendarItemProps[$this->proptags['clipstart']], $calendarItemProps[$this->proptags['clipend']] * (24 * 24 * 60), 30);
3353
+        $localCategories = [];
3354
+
3355
+        foreach ($items as $item) {
3356
+            $recurrenceItems = $recurrence->getCalendarItems($store, $calFolder, $item[$this->proptags['startdate']], $item[$this->proptags['duedate']], [$this->proptags['goid'], $this->proptags['busystatus'], $this->proptags2['categories']]);
3357
+            foreach ($recurrenceItems as $recurrenceItem) {
3358
+                // Check if occurrence is exception then get the local categories of that occurrence.
3359
+                if (isset($recurrenceItem[$this->proptags['goid']]) && $recurrenceItem[$this->proptags['goid']] == $calendarItemProps[$this->proptags['goid']]) {
3360
+                    $exceptionAttach = $recurrence->getExceptionAttachment($recurrenceItem['basedate']);
3361
+
3362
+                    if ($exceptionAttach) {
3363
+                        $exception = mapi_attach_openobj($exceptionAttach, 0);
3364
+                        $exceptionProps = mapi_getprops($exception, [$this->proptags2['categories']]);
3365
+                        if (isset($exceptionProps[$this->proptags2['categories']])) {
3366
+                            $localCategories[$recurrenceItem['basedate']] = $exceptionProps[$this->proptags2['categories']];
3367
+                        }
3368
+                    }
3369
+                }
3370
+            }
3371
+        }
3372
+
3373
+        return $localCategories;
3374
+    }
3375
+
3376
+    /**
3377
+     * Helper function which is use to apply local categories on respective occurrences.
3378
+     *
3379
+     * @param MAPIMessage $calendarItem    meeting request item
3380
+     * @param MAPIStore   $store           store containing calendar folder
3381
+     * @param array       $localCategories array contains basedate and array of categories
3382
+     */
3383
+    public function applyLocalCategories($calendarItem, $store, $localCategories) {
3384
+        $calendarItemProps = mapi_getprops($calendarItem, [PR_PARENT_ENTRYID, PR_ENTRYID]);
3385
+        $message = mapi_msgstore_openentry($store, $calendarItemProps[PR_ENTRYID]);
3386
+        $recurrence = new Recurrence($store, $message);
3387
+
3388
+        // Check for all occurrence if it is exception then modify the exception by setting up categories,
3389
+        // Otherwise create new exception with categories.
3390
+        foreach ($localCategories as $key => $value) {
3391
+            if ($recurrence->isException($key)) {
3392
+                $recurrence->modifyException([$this->proptags2['categories'] => $value], $key);
3393
+            }
3394
+            else {
3395
+                $recurrence->createException([$this->proptags2['categories'] => $value], $key, false);
3396
+            }
3397
+            mapi_savechanges($message);
3398
+        }
3399
+    }
3400 3400
 }
Please login to merge, or discard this patch.