@@ -21,7 +21,7 @@ discard block |
||
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 |
||
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 |
||
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 |
||
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'); |
@@ -18,121 +18,121 @@ |
||
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 | } |
@@ -6,271 +6,271 @@ |
||
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 | } |
@@ -5,409 +5,409 @@ |
||
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 | + } |
@@ -5,14 +5,14 @@ discard block |
||
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 |
||
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 |
@@ -22,7 +22,7 @@ discard block |
||
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 |
||
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 |
||
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 |
||
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 |
||
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 |
||
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 |
||
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 |
||
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 |
||
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 | } |
@@ -27,7 +27,7 @@ discard block |
||
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 |
||
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 */ |
@@ -5,62 +5,62 @@ |
||
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 | + } |
@@ -6,7 +6,7 @@ discard block |
||
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 |
||
22 | 22 | * |
23 | 23 | */ |
24 | 24 | |
25 | - /* |
|
25 | + /* |
|
26 | 26 | * How to use |
27 | 27 | * ---------- |
28 | 28 | * |
@@ -65,679 +65,679 @@ discard block |
||
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 |
||
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 |
||
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 | } |