1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Reminder Module. |
5
|
|
|
* |
6
|
|
|
* TODO: add description |
7
|
|
|
*/ |
8
|
|
|
class ReminderListModule extends ListModule { |
9
|
|
|
private $reminderEntryId; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* Constructor. |
13
|
|
|
* |
14
|
|
|
* @param int $id unique id |
15
|
|
|
* @param array $data list of all actions |
16
|
|
|
*/ |
17
|
|
|
public function __construct($id, $data) { |
18
|
|
|
parent::__construct($id, $data); |
19
|
|
|
|
20
|
|
|
$this->properties = $GLOBALS["properties"]->getReminderProperties(); |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
#[Override] |
24
|
|
|
public function execute() { |
25
|
|
|
foreach ($this->data as $actionType => $action) { |
26
|
|
|
$store = $GLOBALS["mapisession"]->getDefaultMessageStore(); |
27
|
|
|
$this->reminderEntryId = $this->getReminderFolderEntryId($store); |
28
|
|
|
|
29
|
|
|
if (isset($actionType)) { |
30
|
|
|
try { |
31
|
|
|
match ($actionType) { |
32
|
|
|
"list" => $this->getReminders(), |
33
|
|
|
default => $this->handleUnknownActionType($actionType), |
|
|
|
|
34
|
|
|
}; |
35
|
|
|
} |
36
|
|
|
catch (MAPIException $e) { |
37
|
|
|
$this->processException($e, $actionType, $store, null, null, $action); |
38
|
|
|
} |
39
|
|
|
} |
40
|
|
|
} |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
public function getReminderFolderEntryId($store) { |
44
|
|
|
$root = mapi_msgstore_openentry($store); |
45
|
|
|
$rootProps = mapi_getprops($root, [PR_REM_ONLINE_ENTRYID]); |
46
|
|
|
|
47
|
|
|
// Reminder folder didn't exist, create one |
48
|
|
|
return $rootProps[PR_REM_ONLINE_ENTRYID] ?? $this->createReminderFolder($store); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
public function createReminderFolder($store) { |
52
|
|
|
$storeProps = mapi_getprops($store, [PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SUBTREE_ENTRYID]); |
53
|
|
|
$root = mapi_msgstore_openentry($store); |
54
|
|
|
$rootProps = mapi_getprops($root, [PR_ADDITIONAL_REN_ENTRYIDS, PR_IPM_DRAFTS_ENTRYID]); |
55
|
|
|
|
56
|
|
|
$folders = []; |
57
|
|
|
/* The list of forbidden folders (MS-OXORMDR v14 §1.1) */ |
58
|
|
|
if (isset($storeProps[PR_IPM_WASTEBASKET_ENTRYID])) { |
59
|
|
|
$folders[] = $storeProps[PR_IPM_WASTEBASKET_ENTRYID]; |
60
|
|
|
} |
61
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][4])) { |
62
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][4]; |
63
|
|
|
} // junk mail |
64
|
|
|
if (isset($rootProps[PR_IPM_DRAFTS_ENTRYID])) { |
65
|
|
|
$folders[] = $rootProps[PR_IPM_DRAFTS_ENTRYID]; |
66
|
|
|
} |
67
|
|
|
if (isset($storeProps[PR_IPM_OUTBOX_ENTRYID])) { |
68
|
|
|
$folders[] = $storeProps[PR_IPM_OUTBOX_ENTRYID]; |
69
|
|
|
} |
70
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][0])) { |
71
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][0]; |
72
|
|
|
} // conflicts |
73
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][2])) { |
74
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][2]; |
75
|
|
|
} // local failures |
76
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][3])) { |
77
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][3]; |
78
|
|
|
} // server failures |
79
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][1])) { |
80
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][1]; |
81
|
|
|
} // sync issues |
82
|
|
|
|
83
|
|
|
$folderRestriction = []; |
84
|
|
|
foreach ($folders as $folder) { |
85
|
|
|
$folderRestriction[] = [RES_PROPERTY, |
86
|
|
|
[ |
87
|
|
|
RELOP => RELOP_NE, |
88
|
|
|
ULPROPTAG => $this->properties["parent_entryid"], |
89
|
|
|
VALUE => [$this->properties["parent_entryid"] => $folder], |
90
|
|
|
], |
91
|
|
|
]; |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
$res = |
95
|
|
|
[RES_AND, |
96
|
|
|
[ |
97
|
|
|
[RES_AND, |
98
|
|
|
$folderRestriction, |
99
|
|
|
], |
100
|
|
|
[RES_AND, |
101
|
|
|
[ |
102
|
|
|
[RES_NOT, |
103
|
|
|
[ |
104
|
|
|
[RES_AND, |
105
|
|
|
[ |
106
|
|
|
[RES_EXIST, |
107
|
|
|
[ |
108
|
|
|
ULPROPTAG => $this->properties["message_class"], |
109
|
|
|
], |
110
|
|
|
], |
111
|
|
|
[RES_CONTENT, |
112
|
|
|
[ |
113
|
|
|
FUZZYLEVEL => FL_PREFIX, |
114
|
|
|
ULPROPTAG => $this->properties["message_class"], |
115
|
|
|
VALUE => [$this->properties["message_class"] => "IPM.Schedule"], |
116
|
|
|
], |
117
|
|
|
], |
118
|
|
|
], |
119
|
|
|
], |
120
|
|
|
], |
121
|
|
|
], |
122
|
|
|
[RES_BITMASK, |
123
|
|
|
[ |
124
|
|
|
ULTYPE => BMR_EQZ, |
125
|
|
|
ULPROPTAG => $this->properties["message_flags"], |
126
|
|
|
ULMASK => MSGFLAG_SUBMIT, |
127
|
|
|
], |
128
|
|
|
], |
129
|
|
|
[RES_OR, |
130
|
|
|
[ |
131
|
|
|
[RES_PROPERTY, |
132
|
|
|
[ |
133
|
|
|
RELOP => RELOP_EQ, |
134
|
|
|
ULPROPTAG => $this->properties["reminder"], |
135
|
|
|
VALUE => [$this->properties["reminder"] => true], |
136
|
|
|
], |
137
|
|
|
], |
138
|
|
|
[RES_AND, |
139
|
|
|
[ |
140
|
|
|
[RES_EXIST, |
141
|
|
|
[ |
142
|
|
|
ULPROPTAG => $this->properties["recurring"], |
143
|
|
|
], |
144
|
|
|
], |
145
|
|
|
[RES_PROPERTY, |
146
|
|
|
[ |
147
|
|
|
RELOP => RELOP_EQ, |
148
|
|
|
ULPROPTAG => $this->properties["recurring"], |
149
|
|
|
VALUE => [$this->properties["recurring"] => true], |
150
|
|
|
], |
151
|
|
|
], |
152
|
|
|
], |
153
|
|
|
], |
154
|
|
|
], |
155
|
|
|
], |
156
|
|
|
], |
157
|
|
|
], |
158
|
|
|
], |
159
|
|
|
]; |
160
|
|
|
|
161
|
|
|
$folder = mapi_folder_createfolder($root, _("Reminders"), "", OPEN_IF_EXISTS, FOLDER_SEARCH); |
162
|
|
|
mapi_setprops($folder, [PR_CONTAINER_CLASS => "Outlook.Reminder"]); |
163
|
|
|
mapi_savechanges($folder); |
164
|
|
|
|
165
|
|
|
mapi_folder_setsearchcriteria($folder, $res, [$storeProps[PR_IPM_SUBTREE_ENTRYID]], RECURSIVE_SEARCH); |
166
|
|
|
$folderProps = mapi_getprops($folder, [PR_ENTRYID]); |
167
|
|
|
|
168
|
|
|
mapi_setprops($root, [PR_REM_ONLINE_ENTRYID => $folderProps[PR_ENTRYID]]); |
169
|
|
|
mapi_savechanges($root); |
170
|
|
|
|
171
|
|
|
return $folderProps[PR_ENTRYID]; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
public function getReminders() { |
175
|
|
|
$data = []; |
176
|
|
|
|
177
|
|
|
$store = $GLOBALS["mapisession"]->getDefaultMessageStore(); |
178
|
|
|
|
179
|
|
|
$restriction = [RES_AND, |
180
|
|
|
[ |
181
|
|
|
[RES_PROPERTY, |
182
|
|
|
[ |
183
|
|
|
RELOP => RELOP_LT, |
184
|
|
|
ULPROPTAG => $this->properties["flagdueby"], |
185
|
|
|
VALUE => [$this->properties["flagdueby"] => time()], |
186
|
|
|
], |
187
|
|
|
], |
188
|
|
|
[RES_PROPERTY, |
189
|
|
|
[ |
190
|
|
|
RELOP => RELOP_EQ, |
191
|
|
|
ULPROPTAG => $this->properties["reminder"], |
192
|
|
|
VALUE => true, |
193
|
|
|
], |
194
|
|
|
], |
195
|
|
|
[RES_PROPERTY, |
196
|
|
|
[ |
197
|
|
|
RELOP => RELOP_NE, |
198
|
|
|
ULPROPTAG => $this->properties["message_class"], |
199
|
|
|
VALUE => "IPM.TaskRequest", |
200
|
|
|
], |
201
|
|
|
], |
202
|
|
|
[RES_PROPERTY, |
203
|
|
|
[ |
204
|
|
|
RELOP => RELOP_NE, |
205
|
|
|
ULPROPTAG => $this->properties["message_class"], |
206
|
|
|
VALUE => "IPM.TaskRequest.Cancel", |
207
|
|
|
], |
208
|
|
|
], |
209
|
|
|
[RES_PROPERTY, |
210
|
|
|
[ |
211
|
|
|
RELOP => RELOP_NE, |
212
|
|
|
ULPROPTAG => $this->properties["message_class"], |
213
|
|
|
VALUE => "IPM.TaskRequest.Accept", |
214
|
|
|
], |
215
|
|
|
], |
216
|
|
|
[RES_PROPERTY, |
217
|
|
|
[ |
218
|
|
|
RELOP => RELOP_NE, |
219
|
|
|
ULPROPTAG => $this->properties["message_class"], |
220
|
|
|
VALUE => "IPM.TaskRequest.Update", |
221
|
|
|
], |
222
|
|
|
], |
223
|
|
|
[RES_PROPERTY, |
224
|
|
|
[ |
225
|
|
|
RELOP => RELOP_NE, |
226
|
|
|
ULPROPTAG => $this->properties["message_class"], |
227
|
|
|
VALUE => "IPM.TaskRequest.Complete", |
228
|
|
|
], |
229
|
|
|
], |
230
|
|
|
], |
231
|
|
|
]; |
232
|
|
|
|
233
|
|
|
try { |
234
|
|
|
$reminderfolder = mapi_msgstore_openentry($store, $this->reminderEntryId); |
235
|
|
|
} |
236
|
|
|
catch (MAPIException $e) { |
237
|
|
|
// if the reminder folder does not exist, try to recreate it. |
238
|
|
|
if ($e->getCode() == MAPI_E_NOT_FOUND) { |
239
|
|
|
$e->setHandled(); |
240
|
|
|
|
241
|
|
|
$this->reminderEntryId = $this->createReminderFolder($store); |
242
|
|
|
$reminderfolder = mapi_msgstore_openentry($store, $this->reminderEntryId); |
243
|
|
|
} |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
$remindertable = mapi_folder_getcontentstable($reminderfolder, MAPI_DEFERRED_ERRORS); |
247
|
|
|
if (!$remindertable) { |
248
|
|
|
return false; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
mapi_table_restrict($remindertable, $restriction, TBL_BATCH); |
252
|
|
|
mapi_table_sort($remindertable, [$this->properties["flagdueby"] => TABLE_SORT_DESCEND], TBL_BATCH); |
253
|
|
|
|
254
|
|
|
// reminder store hold only 99 records as |
255
|
|
|
// we show 99 notification on client side. |
256
|
|
|
$rows = mapi_table_queryrows($remindertable, $this->properties, 0, MAX_NUM_REMINDERS); |
257
|
|
|
$data["item"] = []; |
258
|
|
|
|
259
|
|
|
foreach ($rows as $row) { |
260
|
|
|
if (isset($row[$this->properties["appointment_recurring"]]) && $row[$this->properties["appointment_recurring"]]) { |
261
|
|
|
$recur = new Recurrence($store, $row); |
|
|
|
|
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* FlagDueBy == PidLidReminderSignalTime. |
265
|
|
|
* FlagDueBy handles whether we should be showing the item; if now() is after FlagDueBy, then we should show a reminder |
266
|
|
|
* for this recurrence. However, the item we will show is either the last passed occurrence (overdue), or the next occurrence, depending |
267
|
|
|
* on whether we have reached the next occurrence yet (the reminder_time of the next item is ignored). |
268
|
|
|
* |
269
|
|
|
* The way we handle this is to get all occurrences between the 'flagdueby' moment and the current time. This will |
270
|
|
|
* yield N items (may be a lot of it was not dismissed for a long time). We can then take the last item in this list, and this is the item |
271
|
|
|
* we will show to the user. The idea here is: |
272
|
|
|
* |
273
|
|
|
* The item we want to show is the last item in that list (new occurrences that have started uptil now should override old ones) |
274
|
|
|
* |
275
|
|
|
* Add the reminder_minutes (default 15 minutes for calendar, 0 for tasks) to check over the gap between FlagDueBy and the start time of the |
276
|
|
|
* occurrence, if "now" would be in between these values. |
277
|
|
|
*/ |
278
|
|
|
$remindertimeinseconds = $row[$this->properties["reminder_minutes"]] * 60; |
279
|
|
|
$occurrences = $recur->getItems($row[$this->properties["flagdueby"]], time() + $remindertimeinseconds, 0, true); |
280
|
|
|
|
281
|
|
|
if (empty($occurrences)) { |
282
|
|
|
continue; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
// More than one occurrence, use the last one instead of the first one after flagdueby |
286
|
|
|
$occ = $occurrences[count($occurrences) - 1]; |
287
|
|
|
|
288
|
|
|
// Bydefault, on occurrence reminder is true but if reminder value is set to false then we don't send popup reminder for this occurrence |
289
|
|
|
if (!(isset($occ[$this->properties['reminder']]) && $occ[$this->properties['reminder']] == 0)) { |
290
|
|
|
$row[$this->properties["reminder_time"]] = $occ[$this->properties["appointment_startdate"]]; |
291
|
|
|
$row[$this->properties["appointment_startdate"]] = $occ[$this->properties["appointment_startdate"]]; |
292
|
|
|
$row[$this->properties["appointment_enddate"]] = $occ[$this->properties["appointment_startdate"]]; |
293
|
|
|
} |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
// Add the non-bogus rows |
297
|
|
|
array_push($data["item"], Conversion::mapMAPI2XML($this->properties, $row)); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
$this->addActionData("list", $data); |
301
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData()); |
302
|
|
|
|
303
|
|
|
// Trigger the newmailnotifier |
304
|
|
|
$GLOBALS["bus"]->notify(REQUEST_ENTRYID, HIERARCHY_UPDATE, ['', '']); |
305
|
|
|
|
306
|
|
|
return true; |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.