|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* Reminder Module. |
|
5
|
|
|
* |
|
6
|
|
|
* TODO: add description |
|
7
|
|
|
*/ |
|
8
|
|
|
class ReminderListModule extends ListModule { |
|
9
|
|
|
/** |
|
10
|
|
|
* Constructor. |
|
11
|
|
|
* |
|
12
|
|
|
* @param int $id unique id |
|
13
|
|
|
* @param array $data list of all actions |
|
14
|
|
|
*/ |
|
15
|
|
|
public function __construct($id, $data) { |
|
16
|
|
|
parent::__construct($id, $data); |
|
17
|
|
|
|
|
18
|
|
|
$this->properties = $GLOBALS["properties"]->getReminderProperties(); |
|
19
|
|
|
} |
|
20
|
|
|
|
|
21
|
|
|
public function execute() { |
|
22
|
|
|
foreach ($this->data as $actionType => $action) { |
|
23
|
|
|
$store = $GLOBALS["mapisession"]->getDefaultMessageStore(); |
|
24
|
|
|
$this->reminderEntryId = $this->getReminderFolderEntryId($store); |
|
|
|
|
|
|
25
|
|
|
|
|
26
|
|
|
if (isset($actionType)) { |
|
27
|
|
|
try { |
|
28
|
|
|
switch ($actionType) { |
|
29
|
|
|
case "list": |
|
30
|
|
|
$this->getReminders(); |
|
31
|
|
|
break; |
|
32
|
|
|
|
|
33
|
|
|
default: |
|
34
|
|
|
$this->handleUnknownActionType($actionType); |
|
35
|
|
|
} |
|
36
|
|
|
} |
|
37
|
|
|
catch (MAPIException $e) { |
|
38
|
|
|
$this->processException($e, $actionType, $store, null, null, $action); |
|
39
|
|
|
} |
|
40
|
|
|
} |
|
41
|
|
|
} |
|
42
|
|
|
} |
|
43
|
|
|
|
|
44
|
|
|
public function getReminderFolderEntryId($store) { |
|
45
|
|
|
$root = mapi_msgstore_openentry($store, null); |
|
46
|
|
|
$rootProps = mapi_getprops($root, [PR_REM_ONLINE_ENTRYID]); |
|
47
|
|
|
if (isset($rootProps[PR_REM_ONLINE_ENTRYID])) { |
|
48
|
|
|
return $rootProps[PR_REM_ONLINE_ENTRYID]; |
|
49
|
|
|
} |
|
50
|
|
|
|
|
51
|
|
|
// Reminder folder didn't exist, create one |
|
52
|
|
|
return $this->createReminderFolder($store); |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
public function createReminderFolder($store) { |
|
56
|
|
|
$storeProps = mapi_getprops($store, [PR_IPM_OUTBOX_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_IPM_SUBTREE_ENTRYID]); |
|
57
|
|
|
$root = mapi_msgstore_openentry($store, null); |
|
58
|
|
|
$rootProps = mapi_getprops($root, [PR_ADDITIONAL_REN_ENTRYIDS, PR_IPM_DRAFTS_ENTRYID]); |
|
59
|
|
|
|
|
60
|
|
|
$folders = []; |
|
61
|
|
|
if (isset($storeProps[PR_IPM_WASTEBASKET_ENTRYID])) { |
|
62
|
|
|
$folders[] = $storeProps[PR_IPM_WASTEBASKET_ENTRYID]; |
|
63
|
|
|
} |
|
64
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][4])) { |
|
65
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][4]; |
|
66
|
|
|
} // junk mail |
|
67
|
|
|
if (isset($rootProps[PR_IPM_DRAFTS_ENTRYID])) { |
|
68
|
|
|
$folders[] = $rootProps[PR_IPM_DRAFTS_ENTRYID]; |
|
69
|
|
|
} |
|
70
|
|
|
if (isset($storeProps[PR_IPM_OUTBOX_ENTRYID])) { |
|
71
|
|
|
$folders[] = $storeProps[PR_IPM_OUTBOX_ENTRYID]; |
|
72
|
|
|
} |
|
73
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][0])) { |
|
74
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][0]; |
|
75
|
|
|
} // conflicts |
|
76
|
|
|
if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && !empty($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][2])) { |
|
77
|
|
|
$folders[] = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS][2]; |
|
78
|
|
|
} // local 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
|
|
|
], |
|
139
|
|
|
], |
|
140
|
|
|
], |
|
141
|
|
|
], |
|
142
|
|
|
], |
|
143
|
|
|
]; |
|
144
|
|
|
|
|
145
|
|
|
$folder = mapi_folder_createfolder($root, _("Reminders"), "", OPEN_IF_EXISTS, FOLDER_SEARCH); |
|
146
|
|
|
mapi_setprops($folder, [PR_CONTAINER_CLASS => "Outlook.Reminder"]); |
|
147
|
|
|
mapi_savechanges($folder); |
|
148
|
|
|
|
|
149
|
|
|
mapi_folder_setsearchcriteria($folder, $res, [$storeProps[PR_IPM_SUBTREE_ENTRYID]], RECURSIVE_SEARCH); |
|
150
|
|
|
$folderProps = mapi_getprops($folder, [PR_ENTRYID]); |
|
151
|
|
|
|
|
152
|
|
|
mapi_setprops($root, [PR_REM_ONLINE_ENTRYID => $folderProps[PR_ENTRYID]]); |
|
153
|
|
|
mapi_savechanges($root); |
|
154
|
|
|
|
|
155
|
|
|
return $folderProps[PR_ENTRYID]; |
|
156
|
|
|
} |
|
157
|
|
|
|
|
158
|
|
|
public function getReminders() { |
|
159
|
|
|
$data = []; |
|
160
|
|
|
|
|
161
|
|
|
$store = $GLOBALS["mapisession"]->getDefaultMessageStore(); |
|
162
|
|
|
|
|
163
|
|
|
$restriction = [RES_AND, |
|
164
|
|
|
[ |
|
165
|
|
|
[RES_PROPERTY, |
|
166
|
|
|
[ |
|
167
|
|
|
RELOP => RELOP_LT, |
|
168
|
|
|
ULPROPTAG => $this->properties["flagdueby"], |
|
169
|
|
|
VALUE => [$this->properties["flagdueby"] => time()], |
|
170
|
|
|
], |
|
171
|
|
|
], |
|
172
|
|
|
[RES_PROPERTY, |
|
173
|
|
|
[ |
|
174
|
|
|
RELOP => RELOP_EQ, |
|
175
|
|
|
ULPROPTAG => $this->properties["reminder"], |
|
176
|
|
|
VALUE => true, |
|
177
|
|
|
], |
|
178
|
|
|
], |
|
179
|
|
|
[RES_PROPERTY, |
|
180
|
|
|
[ |
|
181
|
|
|
RELOP => RELOP_NE, |
|
182
|
|
|
ULPROPTAG => $this->properties["message_class"], |
|
183
|
|
|
VALUE => "IPM.TaskRequest", |
|
184
|
|
|
], |
|
185
|
|
|
], |
|
186
|
|
|
[RES_PROPERTY, |
|
187
|
|
|
[ |
|
188
|
|
|
RELOP => RELOP_NE, |
|
189
|
|
|
ULPROPTAG => $this->properties["message_class"], |
|
190
|
|
|
VALUE => "IPM.TaskRequest.Cancel", |
|
191
|
|
|
], |
|
192
|
|
|
], |
|
193
|
|
|
[RES_PROPERTY, |
|
194
|
|
|
[ |
|
195
|
|
|
RELOP => RELOP_NE, |
|
196
|
|
|
ULPROPTAG => $this->properties["message_class"], |
|
197
|
|
|
VALUE => "IPM.TaskRequest.Accept", |
|
198
|
|
|
], |
|
199
|
|
|
], |
|
200
|
|
|
[RES_PROPERTY, |
|
201
|
|
|
[ |
|
202
|
|
|
RELOP => RELOP_NE, |
|
203
|
|
|
ULPROPTAG => $this->properties["message_class"], |
|
204
|
|
|
VALUE => "IPM.TaskRequest.Update", |
|
205
|
|
|
], |
|
206
|
|
|
], |
|
207
|
|
|
[RES_PROPERTY, |
|
208
|
|
|
[ |
|
209
|
|
|
RELOP => RELOP_NE, |
|
210
|
|
|
ULPROPTAG => $this->properties["message_class"], |
|
211
|
|
|
VALUE => "IPM.TaskRequest.Complete", |
|
212
|
|
|
], |
|
213
|
|
|
], |
|
214
|
|
|
], |
|
215
|
|
|
]; |
|
216
|
|
|
|
|
217
|
|
|
try { |
|
218
|
|
|
$reminderfolder = mapi_msgstore_openentry($store, $this->reminderEntryId); |
|
219
|
|
|
} |
|
220
|
|
|
catch (MAPIException $e) { |
|
221
|
|
|
// if the reminder folder does not exist, try to recreate it. |
|
222
|
|
|
if ($e->getCode() == MAPI_E_NOT_FOUND) { |
|
223
|
|
|
$e->setHandled(); |
|
224
|
|
|
|
|
225
|
|
|
$this->reminderEntryId = $this->createReminderFolder($store); |
|
|
|
|
|
|
226
|
|
|
$reminderfolder = mapi_msgstore_openentry($store, $this->reminderEntryId); |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
$remindertable = mapi_folder_getcontentstable($reminderfolder, MAPI_DEFERRED_ERRORS); |
|
231
|
|
|
if (!$remindertable) { |
|
232
|
|
|
return false; |
|
233
|
|
|
} |
|
234
|
|
|
|
|
235
|
|
|
mapi_table_restrict($remindertable, $restriction, TBL_BATCH); |
|
236
|
|
|
mapi_table_sort($remindertable, [$this->properties["flagdueby"] => TABLE_SORT_DESCEND], TBL_BATCH); |
|
237
|
|
|
|
|
238
|
|
|
// reminder store hold only 99 records as |
|
239
|
|
|
// we show 99 notification on client side. |
|
240
|
|
|
$rows = mapi_table_queryrows($remindertable, $this->properties, 0, MAX_NUM_REMINDERS); |
|
241
|
|
|
$data["item"] = []; |
|
242
|
|
|
|
|
243
|
|
|
foreach ($rows as $row) { |
|
244
|
|
|
if (isset($row[$this->properties["appointment_recurring"]]) && $row[$this->properties["appointment_recurring"]]) { |
|
245
|
|
|
$recur = new Recurrence($store, $row); |
|
246
|
|
|
|
|
247
|
|
|
/** |
|
248
|
|
|
* FlagDueBy == PidLidReminderSignalTime. |
|
249
|
|
|
* FlagDueBy handles whether we should be showing the item; if now() is after FlagDueBy, then we should show a reminder |
|
250
|
|
|
* for this recurrence. However, the item we will show is either the last passed occurrence (overdue), or the next occurrence, depending |
|
251
|
|
|
* on whether we have reached the next occurrence yet (the reminder_time of the next item is ignored). |
|
252
|
|
|
* |
|
253
|
|
|
* The way we handle this is to get all occurrences between the 'flagdueby' moment and the current time. This will |
|
254
|
|
|
* 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 |
|
255
|
|
|
* we will show to the user. The idea here is: |
|
256
|
|
|
* |
|
257
|
|
|
* The item we want to show is the last item in that list (new occurrences that have started uptil now should override old ones) |
|
258
|
|
|
* |
|
259
|
|
|
* 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 |
|
260
|
|
|
* occurrence, if "now" would be in between these values. |
|
261
|
|
|
*/ |
|
262
|
|
|
$remindertimeinseconds = $row[$this->properties["reminder_minutes"]] * 60; |
|
263
|
|
|
$occurrences = $recur->getItems($row[$this->properties["flagdueby"]], time() + ($remindertimeinseconds), 0, true); |
|
|
|
|
|
|
264
|
|
|
|
|
265
|
|
|
if (empty($occurrences)) { |
|
266
|
|
|
continue; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
// More than one occurrence, use the last one instead of the first one after flagdueby |
|
270
|
|
|
$occ = $occurrences[count($occurrences) - 1]; |
|
271
|
|
|
|
|
272
|
|
|
// Bydefault, on occurrence reminder is true but if reminder value is set to false then we don't send popup reminder for this occurrence |
|
273
|
|
|
if (!(isset($occ[$this->properties['reminder']]) && $occ[$this->properties['reminder']] == 0)) { |
|
274
|
|
|
$row[$this->properties["reminder_time"]] = $occ[$this->properties["appointment_startdate"]]; |
|
275
|
|
|
$row[$this->properties["appointment_startdate"]] = $occ[$this->properties["appointment_startdate"]]; |
|
276
|
|
|
$row[$this->properties["appointment_enddate"]] = $occ[$this->properties["appointment_startdate"]]; |
|
277
|
|
|
} |
|
278
|
|
|
} |
|
279
|
|
|
|
|
280
|
|
|
// Add the non-bogus rows |
|
281
|
|
|
array_push($data["item"], Conversion::mapMAPI2XML($this->properties, $row)); |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
$this->addActionData("list", $data); |
|
285
|
|
|
$GLOBALS["bus"]->addData($this->getResponseData()); |
|
286
|
|
|
|
|
287
|
|
|
// Trigger the newmailnotifier |
|
288
|
|
|
$GLOBALS["bus"]->notify(REQUEST_ENTRYID, HIERARCHY_UPDATE, ['', '']); |
|
289
|
|
|
|
|
290
|
|
|
return true; |
|
291
|
|
|
} |
|
292
|
|
|
} |
|
293
|
|
|
|