1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CalDav calendar file. |
4
|
|
|
* |
5
|
|
|
* @package Integration |
6
|
|
|
* |
7
|
|
|
* @see https://tools.ietf.org/html/rfc5545 |
8
|
|
|
* |
9
|
|
|
* @package Integration |
10
|
|
|
* |
11
|
|
|
* @copyright YetiForce S.A. |
12
|
|
|
* @license YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com) |
13
|
|
|
* @author Mariusz Krzaczkowski <[email protected]> |
14
|
|
|
*/ |
15
|
|
|
|
16
|
|
|
namespace App\Integrations\Dav; |
17
|
|
|
|
18
|
|
|
use Sabre\VObject; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* CalDav calendar class. |
22
|
|
|
*/ |
23
|
|
|
class Calendar |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* Record model instance. |
27
|
|
|
* |
28
|
|
|
* @var \Vtiger_Record_Model[] |
29
|
|
|
*/ |
30
|
|
|
private $records = []; |
31
|
|
|
/** |
32
|
|
|
* Record data. |
33
|
|
|
* |
34
|
|
|
* @var \Vtiger_Record_Model |
35
|
|
|
*/ |
36
|
|
|
private $record = []; |
37
|
|
|
/** |
38
|
|
|
* VCalendar object. |
39
|
|
|
* |
40
|
|
|
* @var \Sabre\VObject\Component\VCalendar |
41
|
|
|
*/ |
42
|
|
|
private $vcalendar; |
43
|
|
|
/** |
44
|
|
|
* @var \Sabre\VObject\Component |
45
|
|
|
*/ |
46
|
|
|
private $vcomponent; |
47
|
|
|
/** |
48
|
|
|
* Optimization for creating a time zone. |
49
|
|
|
* |
50
|
|
|
* @var bool |
51
|
|
|
*/ |
52
|
|
|
private $createdTimeZone = false; |
53
|
|
|
/** |
54
|
|
|
* Custom values. |
55
|
|
|
* |
56
|
|
|
* @var string[] |
57
|
|
|
*/ |
58
|
|
|
protected static $customValues = [ |
59
|
|
|
'X-GOOGLE-CONFERENCE' => 'meeting_url', |
60
|
|
|
'X-MS-OLK-MWSURL' => 'meeting_url', |
61
|
|
|
'X-MICROSOFT-SKYPETEAMSMEETINGURL' => 'meeting_url', |
62
|
|
|
'X-MICROSOFT-ONLINEMEETINGCONFLINK' => 'meeting_url', |
63
|
|
|
'X-MICROSOFT-ONLINEMEETINGEXTERNALLINK' => 'meeting_url', |
64
|
|
|
]; |
65
|
|
|
/** |
66
|
|
|
* Max date. |
67
|
|
|
* |
68
|
|
|
* @var string |
69
|
|
|
*/ |
70
|
|
|
const MAX_DATE = '2038-01-01'; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Delete calendar event by crm id. |
74
|
|
|
* |
75
|
|
|
* @param int $id |
76
|
|
|
* |
77
|
|
|
* @throws \yii\db\Exception |
78
|
|
|
*/ |
79
|
|
|
public static function deleteByCrmId(int $id) |
80
|
|
|
{ |
81
|
|
|
$dbCommand = \App\Db::getInstance()->createCommand(); |
82
|
|
|
$dataReader = (new \App\Db\Query())->select(['calendarid'])->from('dav_calendarobjects')->where(['crmid' => $id])->createCommand()->query(); |
83
|
|
|
$dbCommand->delete('dav_calendarobjects', ['crmid' => $id])->execute(); |
84
|
|
|
while ($calendarId = $dataReader->readColumn(0)) { |
85
|
|
|
static::addChange($calendarId, $id . '.vcf', 3); |
86
|
|
|
} |
87
|
|
|
$dataReader->close(); |
88
|
|
|
} |
89
|
|
|
|
90
|
1 |
|
/** |
91
|
|
|
* Dav delete. |
92
|
1 |
|
* |
93
|
1 |
|
* @param array $calendar |
94
|
1 |
|
* |
95
|
1 |
|
* @throws \yii\db\Exception |
96
|
1 |
|
*/ |
97
|
1 |
|
public static function delete(array $calendar) |
98
|
1 |
|
{ |
99
|
1 |
|
static::addChange($calendar['calendarid'], $calendar['uri'], 3); |
100
|
1 |
|
\App\Db::getInstance()->createCommand()->delete('dav_calendarobjects', ['id' => $calendar['id']])->execute(); |
101
|
1 |
|
} |
102
|
1 |
|
|
103
|
1 |
|
/** |
104
|
1 |
|
* Add change to calendar. |
105
|
|
|
* |
106
|
|
|
* @param int $calendarId |
107
|
|
|
* @param string $uri |
108
|
|
|
* @param int $operation |
109
|
|
|
* |
110
|
|
|
* @throws \yii\db\Exception |
111
|
|
|
*/ |
112
|
|
|
public static function addChange(int $calendarId, string $uri, int $operation) |
113
|
1 |
|
{ |
114
|
|
|
$dbCommand = \App\Db::getInstance()->createCommand(); |
115
|
1 |
|
$calendar = static::getCalendar($calendarId); |
116
|
|
|
$dbCommand->insert('dav_calendarchanges', [ |
117
|
|
|
'uri' => $uri, |
118
|
|
|
'synctoken' => (int) $calendar['synctoken'], |
119
|
|
|
'calendarid' => $calendarId, |
120
|
|
|
'operation' => $operation, |
121
|
|
|
])->execute(); |
122
|
|
|
$dbCommand->update('dav_calendars', [ |
123
|
|
|
'synctoken' => ((int) $calendar['synctoken']) + 1, |
124
|
|
|
], ['id' => $calendarId])->execute(); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Get calendar. |
129
|
|
|
* |
130
|
|
|
* @param int $id |
131
|
|
|
* |
132
|
|
|
* @return array |
133
|
|
|
*/ |
134
|
|
|
public static function getCalendar(int $id) |
135
|
|
|
{ |
136
|
|
|
return (new \App\Db\Query())->from('dav_calendars')->where(['id' => $id])->one(); |
|
|
|
|
137
|
|
|
} |
138
|
|
|
|
139
|
1 |
|
/** |
140
|
|
|
* Create instance from dav data. |
141
|
1 |
|
* |
142
|
1 |
|
* @param string $calendar |
143
|
1 |
|
* |
144
|
1 |
|
* @return \App\Integrations\Dav\Calendar |
145
|
1 |
|
*/ |
146
|
|
|
public static function loadFromDav(string $calendar) |
147
|
|
|
{ |
148
|
|
|
$instance = new self(); |
149
|
|
|
$instance->record = \Vtiger_Record_Model::getCleanInstance('Calendar'); |
150
|
|
|
$instance->vcalendar = VObject\Reader::read($calendar, \Sabre\VObject\Reader::OPTION_FORGIVING); |
151
|
|
|
foreach ($instance->vcalendar->children() as $child) { |
152
|
|
|
if (!$child instanceof VObject\Component) { |
153
|
1 |
|
continue; |
154
|
|
|
} |
155
|
1 |
|
if ('VTIMEZONE' === $child->name) { |
156
|
1 |
|
continue; |
157
|
|
|
} |
158
|
|
|
if (empty($instance->vcomponent)) { |
159
|
|
|
$instance->vcomponent = $child; |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
return $instance; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Create empty instance. |
167
|
|
|
* |
168
|
|
|
* @return \App\Integrations\Dav\Calendar |
169
|
|
|
*/ |
170
|
|
|
public static function createEmptyInstance() |
171
|
|
|
{ |
172
|
|
|
$instance = new self(); |
173
|
|
|
$instance->record = \Vtiger_Record_Model::getCleanInstance('Calendar'); |
174
|
|
|
$instance->vcalendar = new VObject\Component\VCalendar(); |
175
|
|
|
$instance->vcalendar->PRODID = '-//YetiForce//YetiForceCRM V' . \App\Version::get() . '//'; |
176
|
|
|
return $instance; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Load record data. |
181
|
|
|
* |
182
|
|
|
* @param array $data |
183
|
|
|
*/ |
184
|
|
|
public function loadFromArray(array $data) |
185
|
|
|
{ |
186
|
|
|
$this->record->setData($data); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Create a class instance by crm id. |
191
|
|
|
* |
192
|
|
|
* @param int $record |
193
|
|
|
* @param string $uid |
194
|
|
|
* |
195
|
|
|
* @return bool |
196
|
|
|
*/ |
197
|
|
|
public function getByRecordId(int $record, string $uid) |
198
|
|
|
{ |
199
|
1 |
|
\App\Log::trace($record, __METHOD__); |
200
|
|
|
if ($record) { |
201
|
1 |
|
$this->records[$uid] = \Vtiger_Record_Model::getInstanceById($record, 'Calendar'); |
202
|
|
|
} |
203
|
|
|
return $this->getByRecordInstance()[$uid] ?? false; |
|
|
|
|
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Create a class instance from vcalendar content. |
208
|
|
|
* |
209
|
|
|
* @param string $content |
210
|
|
|
* @param \Vtiger_Record_Model|null $recordModel |
211
|
|
|
* @param ?string $uid |
212
|
|
|
* |
213
|
|
|
* @return \App\Integrations\Dav\Calendar |
214
|
|
|
*/ |
215
|
|
|
public static function loadFromContent(string $content, ?\Vtiger_Record_Model $recordModel = null, ?string $uid = null) |
216
|
|
|
{ |
217
|
|
|
$instance = new self(); |
218
|
|
|
$instance->vcalendar = VObject\Reader::read($content, \Sabre\VObject\Reader::OPTION_FORGIVING); |
219
|
|
|
if ($recordModel && $uid) { |
220
|
|
|
$instance->records[$uid] = $recordModel; |
221
|
|
|
} |
222
|
|
|
return $instance; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Get VCalendar instance. |
227
|
|
|
* |
228
|
|
|
* @return \Sabre\VObject\Component\VCalendar |
229
|
|
|
*/ |
230
|
|
|
public function getVCalendar() |
231
|
|
|
{ |
232
|
|
|
return $this->vcalendar; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Get calendar component instance. |
237
|
|
|
* |
238
|
|
|
* @return \Sabre\VObject\Component |
239
|
|
|
*/ |
240
|
|
|
public function getComponent() |
241
|
|
|
{ |
242
|
|
|
return $this->vcomponent; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Get record instance. |
247
|
|
|
* |
248
|
|
|
* @return \Vtiger_Record_Model[] |
249
|
|
|
*/ |
250
|
|
|
public function getRecordInstance() |
251
|
|
|
{ |
252
|
|
|
foreach ($this->vcalendar->getBaseComponents() ?: $this->vcalendar->getComponents() as $component) { |
253
|
|
|
$type = (string) $component->name; |
254
|
|
|
if ('VTODO' === $type || 'VEVENT' === $type) { |
255
|
|
|
$this->vcomponent = $component; |
256
|
|
|
$this->parseComponent(); |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
return $this->records; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Parse component. |
264
|
|
|
* |
265
|
|
|
* @return void |
266
|
|
|
*/ |
267
|
|
|
private function parseComponent(): void |
268
|
|
|
{ |
269
|
|
|
$uid = (string) $this->vcomponent->UID; |
270
|
|
|
if (isset($this->records[$uid])) { |
271
|
|
|
$this->record = $this->records[$uid]; |
272
|
|
|
} else { |
273
|
|
|
$this->record = $this->records[$uid] = \Vtiger_Record_Model::getCleanInstance('Calendar'); |
274
|
|
|
} |
275
|
|
|
$this->parseText('subject', 'SUMMARY'); |
276
|
|
|
$this->parseText('location', 'LOCATION'); |
277
|
|
|
$this->parseText('description', 'DESCRIPTION'); |
278
|
|
|
$this->parseStatus(); |
279
|
|
|
$this->parsePriority(); |
280
|
|
|
$this->parseVisibility(); |
281
|
|
|
$this->parseState(); |
282
|
|
|
$this->parseType(); |
283
|
|
|
$this->parseDateTime(); |
284
|
|
|
$this->parseCustomValues(); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Parse simple text. |
289
|
|
|
* |
290
|
|
|
* @param string $fieldName |
291
|
|
|
* @param string $davName |
292
|
|
|
* @param \Vtiger_Record_Model $recordModel |
293
|
|
|
* |
294
|
|
|
* @return void |
295
|
|
|
*/ |
296
|
|
|
private function parseText(string $fieldName, string $davName): void |
297
|
|
|
{ |
298
|
|
|
$separator = '-::~:~::~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~:~::~:~::-'; |
299
|
|
|
$value = (string) $this->vcomponent->{$davName}; |
300
|
|
|
if (false !== strpos($value, $separator)) { |
301
|
|
|
[$html,$text] = explode($separator, $value, 2); |
302
|
|
|
$value = trim(strip_tags($html)) . "\n" . \trim(\str_replace($separator, '', $text)); |
303
|
|
|
} else { |
304
|
|
|
$value = trim(\str_replace('\n', PHP_EOL, $value)); |
305
|
|
|
} |
306
|
|
|
$value = \App\Purifier::decodeHtml(\App\Purifier::purify($value)); |
307
|
|
|
if ($length = $this->record->getField($fieldName)->getMaxValue()) { |
308
|
|
|
$value = \App\TextUtils::textTruncate($value, $length, false); |
309
|
|
|
} |
310
|
|
|
$this->record->set($fieldName, \trim($value)); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Parse status. |
315
|
|
|
* |
316
|
|
|
* @return void |
317
|
|
|
*/ |
318
|
|
|
private function parseStatus(): void |
319
|
|
|
{ |
320
|
|
|
$davValue = null; |
321
|
|
|
if (isset($this->vcomponent->STATUS)) { |
322
|
|
|
$davValue = strtoupper($this->vcomponent->STATUS->getValue()); |
|
|
|
|
323
|
|
|
} |
324
|
|
|
if ('VEVENT' === (string) $this->vcomponent->name) { |
325
|
|
|
$values = [ |
326
|
|
|
'TENTATIVE' => 'PLL_PLANNED', |
327
|
|
|
'CANCELLED' => 'PLL_CANCELLED', |
328
|
|
|
'CONFIRMED' => 'PLL_PLANNED', |
329
|
|
|
]; |
330
|
|
|
} else { |
331
|
|
|
$values = [ |
332
|
|
|
'NEEDS-ACTION' => 'PLL_PLANNED', |
333
|
|
|
'IN-PROCESS' => 'PLL_IN_REALIZATION', |
334
|
|
|
'CANCELLED' => 'PLL_CANCELLED', |
335
|
|
|
'COMPLETED' => 'PLL_COMPLETED', |
336
|
|
|
]; |
337
|
|
|
} |
338
|
|
|
$value = reset($values); |
339
|
|
|
if ($davValue && isset($values[$davValue])) { |
340
|
|
|
$value = $values[$davValue]; |
341
|
|
|
} |
342
|
|
|
$this->record->set('activitystatus', $value); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Parse visibility. |
347
|
|
|
* |
348
|
|
|
* @return void |
349
|
|
|
*/ |
350
|
|
|
private function parseVisibility(): void |
351
|
|
|
{ |
352
|
|
|
$davValue = null; |
|
|
|
|
353
|
|
|
$value = 'Private'; |
354
|
|
|
if (isset($this->vcomponent->CLASS)) { |
355
|
|
|
$davValue = strtoupper($this->vcomponent->CLASS->getValue()); |
|
|
|
|
356
|
|
|
$values = [ |
357
|
|
|
'PUBLIC' => 'Public', |
358
|
|
|
'PRIVATE' => 'Private', |
359
|
|
|
]; |
360
|
|
|
if ($davValue && isset($values[$davValue])) { |
361
|
|
|
$value = $values[$davValue]; |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
$this->record->set('visibility', $value); |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
/** |
368
|
|
|
* Parse state. |
369
|
|
|
* |
370
|
|
|
* @return void |
371
|
|
|
*/ |
372
|
|
|
private function parseState(): void |
373
|
|
|
{ |
374
|
|
|
$davValue = null; |
|
|
|
|
375
|
|
|
$value = ''; |
376
|
|
|
if (isset($this->vcomponent->TRANSP)) { |
377
|
|
|
$davValue = strtoupper($this->vcomponent->TRANSP->getValue()); |
|
|
|
|
378
|
|
|
$values = [ |
379
|
|
|
'OPAQUE' => 'PLL_OPAQUE', |
380
|
|
|
'TRANSPARENT' => 'PLL_TRANSPARENT', |
381
|
|
|
]; |
382
|
|
|
if ($davValue && isset($values[$davValue])) { |
383
|
|
|
$value = $values[$davValue]; |
384
|
|
|
} |
385
|
|
|
} |
386
|
|
|
$this->record->set('state', $value); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* Parse priority. |
391
|
|
|
* |
392
|
|
|
* @return void |
393
|
|
|
*/ |
394
|
|
|
private function parsePriority(): void |
395
|
|
|
{ |
396
|
|
|
$davValue = null; |
|
|
|
|
397
|
|
|
$value = 'Medium'; |
398
|
|
|
if (isset($this->vcomponent->PRIORITY)) { |
399
|
|
|
$davValue = strtoupper($this->vcomponent->PRIORITY->getValue()); |
|
|
|
|
400
|
|
|
$values = [ |
401
|
|
|
1 => 'High', |
402
|
|
|
5 => 'Medium', |
403
|
|
|
9 => 'Low', |
404
|
|
|
]; |
405
|
|
|
if ($davValue && isset($values[$davValue])) { |
406
|
|
|
$value = $values[$davValue]; |
407
|
|
|
} |
408
|
|
|
} |
409
|
|
|
$this->record->set('taskpriority', $value); |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* Parse type. |
414
|
|
|
* |
415
|
|
|
* @return void |
416
|
|
|
*/ |
417
|
|
|
private function parseType(): void |
418
|
|
|
{ |
419
|
|
|
if ($this->record->isEmpty('activitytype')) { |
420
|
|
|
$this->record->set('activitytype', 'VTODO' === (string) $this->vcomponent->name ? 'Task' : 'Meeting'); |
421
|
|
|
} |
422
|
|
|
} |
423
|
1 |
|
|
424
|
|
|
/** |
425
|
1 |
|
* Parse date time. |
426
|
1 |
|
* |
427
|
1 |
|
* @return void |
428
|
1 |
|
*/ |
429
|
1 |
|
private function parseDateTime(): void |
430
|
1 |
|
{ |
431
|
|
|
$allDay = 0; |
432
|
|
|
$endHasTime = $startHasTime = false; |
433
|
|
|
$endField = 'VEVENT' === ((string) $this->vcomponent->name) ? 'DTEND' : 'DUE'; |
434
|
|
|
if (isset($this->vcomponent->DTSTART)) { |
435
|
|
|
$timeStamp = $this->vcomponent->DTSTART->getDateTime()->getTimeStamp(); |
|
|
|
|
436
|
|
|
$dateStart = date('Y-m-d', $timeStamp); |
437
|
|
|
$timeStart = date('H:i:s', $timeStamp); |
438
|
1 |
|
$startHasTime = $this->vcomponent->DTSTART->hasTime(); |
|
|
|
|
439
|
|
|
} else { |
440
|
1 |
|
$timeStamp = $this->vcomponent->DTSTAMP->getDateTime()->getTimeStamp(); |
|
|
|
|
441
|
1 |
|
$dateStart = date('Y-m-d', $timeStamp); |
442
|
1 |
|
$timeStart = date('H:i:s', $timeStamp); |
443
|
1 |
|
} |
444
|
1 |
|
if (isset($this->vcomponent->{$endField})) { |
445
|
1 |
|
$timeStamp = $this->vcomponent->{$endField}->getDateTime()->getTimeStamp(); |
446
|
1 |
|
$endHasTime = $this->vcomponent->{$endField}->hasTime(); |
447
|
1 |
|
$dueDate = date('Y-m-d', $timeStamp); |
448
|
1 |
|
$timeEnd = date('H:i:s', $timeStamp); |
449
|
1 |
|
if (!$endHasTime) { |
450
|
1 |
|
$endTime = strtotime('-1 day', strtotime("$dueDate $timeEnd")); |
451
|
1 |
|
$dueDate = date('Y-m-d', $endTime); |
452
|
|
|
$timeEnd = date('H:i:s', $endTime); |
453
|
1 |
|
} |
454
|
1 |
|
} else { |
455
|
|
|
$endTime = strtotime('+1 day', strtotime("$dateStart $timeStart")); |
456
|
|
|
$dueDate = date('Y-m-d', $endTime); |
457
|
|
|
$timeEnd = date('H:i:s', $endTime); |
458
|
1 |
|
} |
459
|
|
|
if (!$startHasTime && !$endHasTime && \App\User::getCurrentUserId()) { |
460
|
|
|
$allDay = 1; |
461
|
|
|
$currentUser = \App\User::getCurrentUserModel(); |
462
|
|
|
$userTimeZone = new \DateTimeZone($currentUser->getDetail('time_zone')); |
463
|
|
|
$sysTimeZone = new \DateTimeZone(\App\Fields\DateTime::getTimeZone()); |
464
|
|
|
[$hour , $minute] = explode(':', $currentUser->getDetail('start_hour')); |
465
|
|
|
$date = new \DateTime('now', $userTimeZone); |
466
|
|
|
$date->setTime($hour, $minute); |
|
|
|
|
467
|
|
|
$date->setTimezone($sysTimeZone); |
468
|
1 |
|
$timeStart = $date->format('H:i:s'); |
469
|
|
|
|
470
|
1 |
|
$date->setTimezone($userTimeZone); |
471
|
1 |
|
[$hour , $minute] = explode(':', $currentUser->getDetail('end_hour')); |
472
|
|
|
$date->setTime($hour, $minute); |
473
|
|
|
$date->setTimezone($sysTimeZone); |
474
|
|
|
$timeEnd = $date->format('H:i:s'); |
475
|
|
|
} |
476
|
|
|
$this->record->set('allday', $allDay); |
477
|
1 |
|
$this->record->set('date_start', $dateStart); |
478
|
1 |
|
$this->record->set('due_date', $dueDate); |
479
|
|
|
$this->record->set('time_start', $timeStart); |
480
|
1 |
|
$this->record->set('time_end', $timeEnd); |
481
|
|
|
} |
482
|
|
|
|
483
|
|
|
/** |
484
|
|
|
* Parse parse custom values. |
485
|
1 |
|
* |
486
|
|
|
* @return void |
487
|
1 |
|
*/ |
488
|
1 |
|
private function parseCustomValues(): void |
489
|
|
|
{ |
490
|
1 |
|
foreach (self::$customValues as $key => $fieldName) { |
491
|
|
|
if (isset($this->vcomponent->{$key})) { |
492
|
|
|
$this->record->set($fieldName, (string) $this->vcomponent->{$key}); |
493
|
|
|
} |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
|
497
|
|
|
/** |
498
|
1 |
|
* Create calendar entry component. |
499
|
|
|
* |
500
|
|
|
* @return \Sabre\VObject\Component |
501
|
|
|
*/ |
502
|
|
|
public function createComponent() |
503
|
|
|
{ |
504
|
|
|
$componentType = 'Task' === $this->record->get('activitytype') ? 'VTODO' : 'VEVENT'; |
505
|
|
|
$this->vcomponent = $this->vcalendar->createComponent($componentType); |
506
|
1 |
|
$this->vcomponent->UID = \str_replace('sabre-vobject', 'YetiForceCRM', (string) $this->vcomponent->UID); |
507
|
1 |
|
$this->updateComponent(); |
508
|
|
|
$this->vcalendar->add($this->vcomponent); |
509
|
|
|
return $this->vcomponent; |
510
|
|
|
} |
511
|
1 |
|
|
512
|
|
|
/** |
513
|
|
|
* Update calendar entry component. |
514
|
1 |
|
* |
515
|
|
|
* @throws \Sabre\VObject\InvalidDataException |
516
|
1 |
|
*/ |
517
|
|
|
public function updateComponent() |
518
|
|
|
{ |
519
|
|
|
$this->createDateTime(); |
520
|
|
|
$this->createText('subject', 'SUMMARY'); |
521
|
1 |
|
$this->createText('location', 'LOCATION'); |
522
|
|
|
$this->createText('description', 'DESCRIPTION'); |
523
|
1 |
|
$this->createStatus(); |
524
|
|
|
$this->createVisibility(); |
525
|
1 |
|
$this->createState(); |
526
|
|
|
$this->createPriority(); |
527
|
|
|
if (empty($this->vcomponent->CREATED)) { |
528
|
1 |
|
$createdTime = new \DateTime(); |
529
|
1 |
|
$createdTime->setTimezone(new \DateTimeZone('UTC')); |
530
|
1 |
|
$this->vcomponent->add($this->vcalendar->createProperty('CREATED', $createdTime)); |
531
|
|
|
} |
532
|
1 |
|
if (empty($this->vcomponent->SEQUENCE)) { |
533
|
|
|
$this->vcomponent->add($this->vcalendar->createProperty('SEQUENCE', 1)); |
534
|
|
|
} else { |
535
|
1 |
|
$this->vcomponent->SEQUENCE = $this->vcomponent->SEQUENCE->getValue() + 1; |
536
|
|
|
} |
537
|
|
|
} |
538
|
1 |
|
|
539
|
|
|
/** |
540
|
1 |
|
* Create a text value for dav. |
541
|
|
|
* |
542
|
|
|
* @param string $fieldName |
543
|
|
|
* @param string $davName |
544
|
|
|
* |
545
|
1 |
|
* @throws \Sabre\VObject\InvalidDataException |
546
|
|
|
*/ |
547
|
1 |
|
private function createText(string $fieldName, string $davName) |
548
|
|
|
{ |
549
|
1 |
|
$empty = $this->record->isEmpty($fieldName); |
550
|
|
|
if (isset($this->vcomponent->{$davName})) { |
551
|
|
|
if ($empty) { |
552
|
1 |
|
$this->vcomponent->remove($davName); |
553
|
1 |
|
} else { |
554
|
1 |
|
$this->vcomponent->{$davName} = $this->record->get($fieldName); |
555
|
|
|
} |
556
|
|
|
} elseif (!$empty) { |
557
|
1 |
|
$this->vcomponent->add($this->vcalendar->createProperty($davName, $this->record->get($fieldName))); |
558
|
|
|
} |
559
|
|
|
} |
560
|
|
|
|
561
|
|
|
/** |
562
|
1 |
|
* Create status value for dav. |
563
|
|
|
*/ |
564
|
|
|
private function createStatus() |
565
|
|
|
{ |
566
|
|
|
$status = $this->record->get('activitystatus'); |
567
|
1 |
|
if ('VEVENT' === (string) $this->vcomponent->name) { |
568
|
|
|
$values = [ |
569
|
1 |
|
'PLL_PLANNED' => 'TENTATIVE', |
570
|
|
|
'PLL_OVERDUE' => 'TENTATIVE', |
571
|
1 |
|
'PLL_POSTPONED' => 'CANCELLED', |
572
|
|
|
'PLL_CANCELLED' => 'CANCELLED', |
573
|
|
|
'PLL_COMPLETED' => 'CONFIRMED', |
574
|
|
|
]; |
575
|
1 |
|
} else { |
576
|
1 |
|
$values = [ |
577
|
1 |
|
'PLL_PLANNED' => 'NEEDS-ACTION', |
578
|
|
|
'PLL_IN_REALIZATION' => 'IN-PROCESS', |
579
|
1 |
|
'PLL_OVERDUE' => 'NEEDS-ACTION', |
580
|
|
|
'PLL_POSTPONED' => 'CANCELLED', |
581
|
|
|
'PLL_CANCELLED' => 'CANCELLED', |
582
|
1 |
|
'PLL_COMPLETED' => 'COMPLETED', |
583
|
|
|
]; |
584
|
1 |
|
} |
585
|
|
|
if ($status && isset($values[$status])) { |
586
|
|
|
$value = $values[$status]; |
587
|
|
|
} else { |
588
|
|
|
$value = reset($values); |
589
|
1 |
|
} |
590
|
|
|
if (isset($this->vcomponent->STATUS)) { |
591
|
1 |
|
$this->vcomponent->STATUS = $value; |
592
|
1 |
|
} else { |
593
|
1 |
|
$this->vcomponent->add($this->vcalendar->createProperty('STATUS', $value)); |
594
|
1 |
|
} |
595
|
1 |
|
} |
596
|
1 |
|
|
597
|
1 |
|
/** |
598
|
1 |
|
* Create visibility value for dav. |
599
|
1 |
|
*/ |
600
|
1 |
|
private function createVisibility() |
601
|
|
|
{ |
602
|
1 |
|
$visibility = $this->record->get('visibility'); |
603
|
1 |
|
$values = [ |
604
|
1 |
|
'Public' => 'PUBLIC', |
605
|
1 |
|
'Private' => 'PRIVATE', |
606
|
1 |
|
]; |
607
|
1 |
|
$value = 'Private'; |
608
|
|
|
if ($visibility && isset($values[$visibility])) { |
609
|
|
|
$value = $values[$visibility]; |
610
|
1 |
|
} |
611
|
1 |
|
if (false !== \App\Config::component('Dav', 'CALDAV_DEFAULT_VISIBILITY_FROM_DAV')) { |
612
|
1 |
|
$value = \App\Config::component('Dav', 'CALDAV_DEFAULT_VISIBILITY_FROM_DAV'); |
613
|
|
|
} |
614
|
|
|
if (isset($this->vcomponent->CLASS)) { |
615
|
|
|
$this->vcomponent->CLASS = $value; |
616
|
|
|
} else { |
617
|
|
|
$this->vcomponent->add($this->vcalendar->createProperty('CLASS', $value)); |
618
|
|
|
} |
619
|
|
|
} |
620
|
|
|
|
621
|
|
|
/** |
622
|
|
|
* Create visibility value for dav. |
623
|
|
|
*/ |
624
|
|
|
private function createState() |
625
|
1 |
|
{ |
626
|
|
|
$state = $this->record->get('state'); |
627
|
1 |
|
$values = [ |
628
|
|
|
'PLL_OPAQUE' => 'OPAQUE', |
629
|
|
|
'PLL_TRANSPARENT' => 'TRANSPARENT', |
630
|
1 |
|
]; |
631
|
|
|
if ($state && isset($values[$state])) { |
632
|
|
|
$value = $values[$state]; |
633
|
|
|
if (isset($this->vcomponent->TRANSP)) { |
634
|
1 |
|
$this->vcomponent->TRANSP = $value; |
635
|
|
|
} else { |
636
|
|
|
$this->vcomponent->add($this->vcalendar->createProperty('TRANSP', $value)); |
637
|
|
|
} |
638
|
|
|
} elseif (isset($this->vcomponent->TRANSP)) { |
639
|
1 |
|
$this->vcomponent->remove('TRANSP'); |
640
|
1 |
|
} |
641
|
1 |
|
} |
642
|
1 |
|
|
643
|
1 |
|
/** |
644
|
1 |
|
* Create priority value for dav. |
645
|
1 |
|
*/ |
646
|
1 |
|
private function createPriority() |
647
|
1 |
|
{ |
648
|
|
|
$priority = $this->record->get('taskpriority'); |
649
|
1 |
|
$values = [ |
650
|
1 |
|
'High' => 1, |
651
|
1 |
|
'Medium' => 5, |
652
|
|
|
'Low' => 9, |
653
|
|
|
]; |
654
|
1 |
|
$value = 5; |
655
|
1 |
|
if ($priority && isset($values[$priority])) { |
656
|
1 |
|
$value = $values[$priority]; |
657
|
1 |
|
} |
658
|
1 |
|
if (isset($this->vcomponent->PRIORITY)) { |
659
|
|
|
$this->vcomponent->PRIORITY = $value; |
660
|
1 |
|
} else { |
661
|
1 |
|
$this->vcomponent->add($this->vcalendar->createProperty('PRIORITY', $value)); |
662
|
1 |
|
} |
663
|
1 |
|
} |
664
|
|
|
|
665
|
1 |
|
/** |
666
|
1 |
|
* Create date and time values for dav. |
667
|
1 |
|
*/ |
668
|
1 |
|
private function createDateTime() |
669
|
|
|
{ |
670
|
1 |
|
$end = $this->record->get('due_date') . ' ' . $this->record->get('time_end'); |
671
|
1 |
|
$endField = 'VEVENT' == (string) $this->vcomponent->name ? 'DTEND' : 'DUE'; |
672
|
|
|
$start = new \DateTime($this->record->get('date_start') . ' ' . $this->record->get('time_start')); |
673
|
1 |
|
$startProperty = $this->vcalendar->createProperty('DTSTART', $start); |
674
|
1 |
|
if ($this->record->get('allday')) { |
675
|
1 |
|
$end = new \DateTime($end); |
676
|
1 |
|
$end->modify('+1 day'); |
677
|
|
|
$endProperty = $this->vcalendar->createProperty($endField, $end); |
678
|
|
|
$endProperty['VALUE'] = 'DATE'; |
679
|
1 |
|
$startProperty['VALUE'] = 'DATE'; |
680
|
1 |
|
} else { |
681
|
|
|
$end = new \DateTime($end); |
682
|
|
|
$endProperty = $this->vcalendar->createProperty($endField, $end); |
683
|
|
|
if (!$this->createdTimeZone) { |
684
|
1 |
|
unset($this->vcalendar->VTIMEZONE); |
685
|
1 |
|
$this->vcalendar->add($this->createTimeZone(date_default_timezone_get(), $start->getTimestamp(), $end->getTimestamp())); |
686
|
|
|
$this->createdTimeZone = true; |
687
|
|
|
} |
688
|
1 |
|
} |
689
|
|
|
$this->vcomponent->DTSTART = $startProperty; |
690
|
|
|
$this->vcomponent->{$endField} = $endProperty; |
691
|
|
|
} |
692
|
|
|
|
693
|
|
|
/** |
694
|
|
|
* Create time zone. |
695
|
|
|
* |
696
|
|
|
* @param string $tzid |
697
|
|
|
* @param int $from |
698
|
1 |
|
* @param int $to |
699
|
|
|
* |
700
|
1 |
|
* @throws \Exception |
701
|
1 |
|
* |
702
|
1 |
|
* @return \Sabre\VObject\Component |
703
|
|
|
*/ |
704
|
|
|
public function createTimeZone($tzid, $from = 0, $to = 0) |
705
|
|
|
{ |
706
|
|
|
if (!$from) { |
707
|
1 |
|
$from = time(); |
708
|
|
|
} |
709
|
|
|
if (!$to) { |
710
|
|
|
$to = $from; |
711
|
|
|
} |
712
|
|
|
try { |
713
|
|
|
$tz = new \DateTimeZone($tzid); |
714
|
|
|
} catch (\Exception $e) { |
715
|
|
|
return false; |
|
|
|
|
716
|
|
|
} |
717
|
|
|
// get all transitions for one year back/ahead |
718
|
|
|
$year = 86400 * 360; |
719
|
|
|
$transitions = $tz->getTransitions($from - $year, $to + $year); |
720
|
|
|
$vt = $this->vcalendar->createComponent('VTIMEZONE'); |
721
|
|
|
$vt->TZID = $tz->getName(); |
722
|
|
|
$vt->TZURL = 'http://tzurl.org/zoneinfo/' . $tz->getName(); |
723
|
|
|
$vt->add('X-LIC-LOCATION', $tz->getName()); |
724
|
|
|
$dst = $std = null; |
725
|
|
|
foreach ($transitions as $i => $trans) { |
726
|
|
|
$cmp = null; |
727
|
|
|
// skip the first entry... |
728
|
|
|
if (0 == $i) { // ... but remember the offset for the next TZOFFSETFROM value |
729
|
|
|
$tzfrom = $trans['offset'] / 3600; |
730
|
|
|
continue; |
731
|
|
|
} |
732
|
|
|
// daylight saving time definition |
733
|
|
|
if ($trans['isdst']) { |
734
|
|
|
$t_dst = $trans['ts']; |
735
|
|
|
$dst = $this->vcalendar->createComponent('DAYLIGHT'); |
736
|
|
|
$cmp = $dst; |
737
|
|
|
$cmpName = 'DAYLIGHT'; |
738
|
|
|
} else { // standard time definition |
739
|
|
|
$t_std = $trans['ts']; |
740
|
|
|
$std = $this->vcalendar->createComponent('STANDARD'); |
741
|
|
|
$cmp = $std; |
742
|
|
|
$cmpName = 'STANDARD'; |
743
|
|
|
} |
744
|
|
|
if ($cmp && empty($vt->select($cmpName))) { |
745
|
|
|
$offset = $trans['offset'] / 3600; |
746
|
|
|
$cmp->TZOFFSETFROM = sprintf('%s%02d%02d', $tzfrom >= 0 ? '+' : '', floor($tzfrom), ($tzfrom - floor($tzfrom)) * 60); |
|
|
|
|
747
|
|
|
$cmp->TZOFFSETTO = sprintf('%s%02d%02d', $offset >= 0 ? '+' : '', floor($offset), ($offset - floor($offset)) * 60); |
748
|
|
|
// add abbreviated timezone name if available |
749
|
|
|
if (!empty($trans['abbr'])) { |
750
|
|
|
$cmp->TZNAME = $trans['abbr']; |
751
|
|
|
} |
752
|
|
|
$dt = new \DateTime($trans['time']); |
753
|
|
|
$cmp->DTSTART = $dt->format('Ymd\THis'); |
754
|
|
|
$tzfrom = $offset; |
755
|
|
|
$vt->add($cmp); |
756
|
|
|
} |
757
|
|
|
// we covered the entire date range |
758
|
|
|
if ($std && $dst && min($t_std, $t_dst) < $from && max($t_std, $t_dst) > $to) { |
|
|
|
|
759
|
|
|
break; |
760
|
|
|
} |
761
|
|
|
} |
762
|
|
|
// add X-MICROSOFT-CDO-TZID if available |
763
|
|
|
$microsoftExchangeMap = array_flip(VObject\TimeZoneUtil::$microsoftExchangeMap); |
764
|
|
|
if (\array_key_exists($tz->getName(), $microsoftExchangeMap)) { |
765
|
|
|
$vt->add('X-MICROSOFT-CDO-TZID', $microsoftExchangeMap[$tz->getName()]); |
766
|
|
|
} |
767
|
|
|
return $vt; |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
/** |
771
|
|
|
* Get invitations for record id. |
772
|
|
|
* |
773
|
|
|
* @param int $recordId |
774
|
|
|
* |
775
|
|
|
* @return array |
776
|
|
|
*/ |
777
|
|
|
public function getInvitations(int $recordId): array |
778
|
|
|
{ |
779
|
|
|
$invities = []; |
780
|
|
|
$dataReader = (new \App\Db\Query())->from('u_#__activity_invitation')->where(['activityid' => $recordId])->createCommand()->query(); |
781
|
1 |
|
while ($row = $dataReader->read()) { |
782
|
|
|
if (!empty($row['email'])) { |
783
|
1 |
|
$invities[$row['email']] = $row; |
784
|
1 |
|
} |
785
|
1 |
|
} |
786
|
1 |
|
return $invities; |
787
|
1 |
|
} |
788
|
|
|
|
789
|
|
|
/** |
790
|
|
|
* Record save attendee. |
791
|
|
|
* |
792
|
|
|
* @param Vtiger_Record_Model $record |
|
|
|
|
793
|
|
|
*/ |
794
|
|
|
public function recordSaveAttendee(\Vtiger_Record_Model $record) |
795
|
|
|
{ |
796
|
|
|
if ('VEVENT' === (string) $this->vcomponent->name) { |
797
|
|
|
$invities = $this->getInvitations($record->getId()); |
798
|
|
|
$time = VObject\DateTimeParser::parse($this->vcomponent->DTSTAMP); |
799
|
|
|
$timeFormated = $time->format('Y-m-d H:i:s'); |
800
|
|
|
$dbCommand = \App\Db::getInstance()->createCommand(); |
801
|
|
|
$attendees = $this->vcomponent->select('ATTENDEE'); |
802
|
|
|
foreach ($attendees as &$attendee) { |
803
|
|
|
$nameAttendee = isset($attendee->parameters['CN']) ? $attendee->parameters['CN']->getValue() : null; |
804
|
|
|
$value = $attendee->getValue(); |
805
|
|
|
if (0 === stripos($value, 'mailto:')) { |
806
|
|
|
$value = substr($value, 7, \strlen($value) - 7); |
807
|
|
|
} |
808
|
|
|
if ($value && \App\TextUtils::getTextLength($value) > 100 || !\App\Validator::email($value)) { |
|
|
|
|
809
|
|
|
throw new \Sabre\DAV\Exception\BadRequest('Invalid email: ' . $value); |
810
|
1 |
|
} |
811
|
|
|
if (isset($attendee['ROLE']) && 'CHAIR' === $attendee['ROLE']->getValue()) { |
812
|
|
|
$users = $this->findRecordByEmail($value, ['Users']); |
813
|
|
|
if (!empty($users)) { |
814
|
|
|
continue; |
815
|
|
|
} |
816
|
|
|
} |
817
|
|
|
$crmid = 0; |
818
|
1 |
|
$records = $this->findRecordByEmail($value, array_keys(array_merge(\App\ModuleHierarchy::getModulesByLevel(0), \App\ModuleHierarchy::getModulesByLevel(4)))); |
819
|
|
|
if (!empty($records)) { |
820
|
|
|
$recordCrm = current($records); |
821
|
|
|
$crmid = $recordCrm['crmid']; |
822
|
|
|
} |
823
|
|
|
$status = $this->getAttendeeStatus(isset($attendee['PARTSTAT']) ? $attendee['PARTSTAT']->getValue() : ''); |
824
|
|
|
if (isset($invities[$value])) { |
825
|
|
|
$row = $invities[$value]; |
826
|
|
|
if ($row['status'] !== $status || $row['name'] !== $nameAttendee) { |
827
|
|
|
$dbCommand->update('u_#__activity_invitation', [ |
828
|
|
|
'status' => $status, |
829
|
|
|
'time' => $timeFormated, |
830
|
|
|
'name' => \App\TextUtils::textTruncate($nameAttendee, 500, false), |
831
|
|
|
], ['activityid' => $record->getId(), 'email' => $value] |
832
|
|
|
)->execute(); |
833
|
|
|
} |
834
|
|
|
unset($invities[$value]); |
835
|
|
|
} else { |
836
|
|
|
$params = [ |
837
|
|
|
'email' => $value, |
838
|
|
|
'crmid' => $crmid, |
839
|
|
|
'status' => $status, |
840
|
|
|
'name' => \App\TextUtils::textTruncate($nameAttendee, 500, false), |
841
|
|
|
'activityid' => $record->getId(), |
842
|
|
|
]; |
843
|
|
|
if ($status) { |
844
|
|
|
$params['time'] = $timeFormated; |
845
|
|
|
} |
846
|
|
|
$dbCommand->insert('u_#__activity_invitation', $params)->execute(); |
847
|
|
|
} |
848
|
|
|
} |
849
|
|
|
foreach ($invities as &$invitation) { |
850
|
|
|
$dbCommand->delete('u_#__activity_invitation', ['inviteesid' => $invitation['inviteesid']])->execute(); |
851
|
|
|
} |
852
|
|
|
} |
853
|
|
|
} |
854
|
|
|
|
855
|
|
|
/** |
856
|
|
|
* Dav save attendee. |
857
|
|
|
* |
858
|
|
|
* @param array $record |
859
|
1 |
|
*/ |
860
|
|
|
public function davSaveAttendee(array $record) |
861
|
1 |
|
{ |
862
|
1 |
|
$owner = \Users_Privileges_Model::getInstanceById($record['assigned_user_id']); |
863
|
1 |
|
$invities = $this->getInvitations($record['id']); |
864
|
1 |
|
$attendees = $this->vcomponent->select('ATTENDEE'); |
865
|
1 |
|
if (empty($attendees)) { |
866
|
1 |
|
if (!empty($invities)) { |
867
|
1 |
|
$organizer = $this->vcalendar->createProperty('ORGANIZER', 'mailto:' . $owner->get('email1')); |
868
|
|
|
$organizer->add('CN', $owner->getName()); |
869
|
|
|
$this->vcomponent->add($organizer); |
870
|
1 |
|
$attendee = $this->vcalendar->createProperty('ATTENDEE', 'mailto:' . $owner->get('email1')); |
871
|
|
|
$attendee->add('CN', $owner->getName()); |
872
|
|
|
$attendee->add('ROLE', 'CHAIR'); |
873
|
1 |
|
$attendee->add('PARTSTAT', 'ACCEPTED'); |
874
|
1 |
|
$attendee->add('RSVP', 'false'); |
875
|
|
|
$this->vcomponent->add($attendee); |
876
|
1 |
|
} |
877
|
1 |
|
} else { |
878
|
1 |
|
foreach ($attendees as &$attendee) { |
879
|
|
|
$value = ltrim($attendee->getValue(), 'mailto:'); |
880
|
|
|
if (isset($invities[$value])) { |
881
|
|
|
$row = $invities[$value]; |
882
|
|
|
if (isset($attendee['PARTSTAT'])) { |
883
|
|
|
$attendee['PARTSTAT']->setValue($this->getAttendeeStatus($row['status'], false)); |
884
|
|
|
} else { |
885
|
|
|
$attendee->add('PARTSTAT', $this->getAttendeeStatus($row['status'])); |
886
|
|
|
} |
887
|
|
|
unset($invities[$value]); |
888
|
|
|
} else { |
889
|
|
|
$this->vcomponent->remove($attendee); |
890
|
|
|
} |
891
|
|
|
} |
892
|
|
|
} |
893
|
|
|
foreach ($invities as &$row) { |
894
|
|
|
$attendee = $this->vcalendar->createProperty('ATTENDEE', 'mailto:' . $row['email']); |
895
|
|
|
$attendee->add('CN', empty($row['crmid']) ? $row['name'] : \App\Record::getLabel($row['crmid'])); |
896
|
|
|
$attendee->add('ROLE', 'REQ-PARTICIPANT'); |
897
|
|
|
$attendee->add('PARTSTAT', $this->getAttendeeStatus($row['status'], false)); |
898
|
|
|
$attendee->add('RSVP', '0' == $row['status'] ? 'true' : 'false'); |
899
|
|
|
$this->vcomponent->add($attendee); |
900
|
|
|
} |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
/** |
904
|
|
|
* Get attendee status. |
905
|
1 |
|
* |
906
|
|
|
* @param string $value |
907
|
|
|
* @param bool $toCrm |
908
|
1 |
|
* |
909
|
|
|
* @return false|string |
910
|
|
|
*/ |
911
|
|
|
public function getAttendeeStatus(string $value, bool $toCrm = true) |
912
|
|
|
{ |
913
|
1 |
|
$statuses = ['NEEDS-ACTION', 'ACCEPTED', 'DECLINED']; |
914
|
|
|
if ($toCrm) { |
915
|
1 |
|
$status = false; |
916
|
1 |
|
$statuses = array_flip($statuses); |
917
|
1 |
|
} else { |
918
|
1 |
|
$status = 'NEEDS-ACTION'; |
919
|
1 |
|
} |
920
|
1 |
|
if (isset($statuses[$value])) { |
921
|
|
|
$status = $statuses[$value]; |
922
|
|
|
} |
923
|
|
|
return $status; |
924
|
|
|
} |
925
|
|
|
|
926
|
|
|
/** |
927
|
|
|
* Parses some information from calendar objects, used for optimized |
928
|
|
|
* calendar-queries. |
929
|
|
|
* |
930
|
|
|
* Returns an array with the following keys: |
931
|
|
|
* * etag - An md5 checksum of the object without the quotes. |
932
|
|
|
* * size - Size of the object in bytes |
933
|
|
|
* * componentType - VEVENT, VTODO or VJOURNAL |
934
|
|
|
* * firstOccurence |
935
|
|
|
* * lastOccurence |
936
|
|
|
* * uid - value of the UID property |
937
|
|
|
* |
938
|
|
|
* @param string $calendarData |
939
|
|
|
* |
940
|
|
|
* @return array |
941
|
|
|
* |
942
|
|
|
* @see Sabre\CalDAV\Backend\PDO::getDenormalizedData |
943
|
|
|
*/ |
944
|
|
|
public function getDenormalizedData($calendarData) |
945
|
|
|
{ |
946
|
|
|
$vObject = VObject\Reader::read($calendarData); |
947
|
|
|
$uid = $lastOccurence = $firstOccurence = $component = $componentType = null; |
948
|
|
|
foreach ($vObject->getComponents() as $component) { |
949
|
|
|
if ('VTIMEZONE' !== $component->name) { |
950
|
|
|
$componentType = $component->name; |
951
|
|
|
$uid = (string) $component->UID; |
952
|
|
|
break; |
953
|
|
|
} |
954
|
|
|
} |
955
|
|
|
if (!$componentType) { |
956
|
|
|
throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); |
957
|
|
|
} |
958
|
|
|
if ('VEVENT' === $componentType) { |
959
|
|
|
$firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); |
960
|
|
|
// Finding the last occurence is a bit harder |
961
|
|
|
if (!isset($component->RRULE)) { |
962
|
|
|
if (isset($component->DTEND)) { |
963
|
|
|
$lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); |
964
|
|
|
} elseif (isset($component->DURATION)) { |
965
|
|
|
$endDate = clone $component->DTSTART->getDateTime(); |
966
|
|
|
$endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); |
967
|
|
|
$lastOccurence = $endDate->getTimeStamp(); |
968
|
|
|
} elseif (!$component->DTSTART->hasTime()) { |
969
|
|
|
$endDate = clone $component->DTSTART->getDateTime(); |
970
|
|
|
$endDate = $endDate->modify('+1 day'); |
971
|
|
|
$lastOccurence = $endDate->getTimeStamp(); |
972
|
|
|
} else { |
973
|
|
|
$lastOccurence = $firstOccurence; |
974
|
|
|
} |
975
|
|
|
} else { |
976
|
|
|
$it = new VObject\Recur\EventIterator($vObject, (string) $component->UID); |
977
|
|
|
$maxDate = new \DateTime(self::MAX_DATE); |
978
|
|
|
if ($it->isInfinite()) { |
979
|
|
|
$lastOccurence = $maxDate->getTimeStamp(); |
980
|
|
|
} else { |
981
|
|
|
$end = $it->getDtEnd(); |
982
|
|
|
while ($it->valid() && $end < $maxDate) { |
983
|
|
|
$end = $it->getDtEnd(); |
984
|
|
|
$it->next(); |
985
|
|
|
} |
986
|
|
|
$lastOccurence = $end->getTimeStamp(); |
987
|
|
|
} |
988
|
|
|
} |
989
|
|
|
// Ensure Occurence values are positive |
990
|
|
|
if ($firstOccurence < 0) { |
991
|
|
|
$firstOccurence = 0; |
992
|
|
|
} |
993
|
|
|
if ($lastOccurence < 0) { |
994
|
|
|
$lastOccurence = 0; |
995
|
|
|
} |
996
|
|
|
} |
997
|
|
|
// Destroy circular references to PHP will GC the object. |
998
|
|
|
$vObject->destroy(); |
999
|
|
|
return [ |
1000
|
|
|
'etag' => md5($calendarData), |
1001
|
|
|
'size' => \strlen($calendarData), |
1002
|
|
|
'componentType' => $componentType, |
1003
|
|
|
'firstOccurence' => $firstOccurence, |
1004
|
|
|
'lastOccurence' => $lastOccurence, |
1005
|
|
|
'uid' => $uid, |
1006
|
|
|
]; |
1007
|
|
|
} |
1008
|
|
|
|
1009
|
|
|
/** |
1010
|
|
|
* Find crm id by email. |
1011
|
|
|
* |
1012
|
|
|
* @param int|string $value |
1013
|
|
|
* @param array $allowedModules |
1014
|
|
|
* @param array $skipModules |
1015
|
|
|
* |
1016
|
|
|
* @return array |
1017
|
|
|
*/ |
1018
|
|
|
public function findRecordByEmail($value, array $allowedModules = [], array $skipModules = []) |
1019
|
|
|
{ |
1020
|
|
|
$db = \App\Db::getInstance(); |
1021
|
|
|
$rows = $fields = []; |
|
|
|
|
1022
|
|
|
$dataReader = (new \App\Db\Query())->select(['vtiger_field.columnname', 'vtiger_field.tablename', 'vtiger_field.fieldlabel', 'vtiger_field.tabid', 'vtiger_tab.name']) |
1023
|
|
|
->from('vtiger_field')->innerJoin('vtiger_tab', 'vtiger_field.tabid = vtiger_tab.tabid') |
1024
|
|
|
->where(['vtiger_tab.presence' => 0]) |
1025
|
|
|
->andWhere(['<>', 'vtiger_field.presence', 1]) |
1026
|
|
|
->andWhere(['or', ['uitype' => 13], ['uitype' => 104]])->createCommand()->query(); |
1027
|
|
|
while ($row = $dataReader->read()) { |
1028
|
|
|
$fields[$row['name']][$row['tablename']][$row['columnname']] = $row; |
1029
|
|
|
} |
1030
|
|
|
$queryUnion = null; |
1031
|
|
|
foreach ($fields as $moduleName => $moduleFields) { |
1032
|
|
|
if (($allowedModules && !\in_array($moduleName, $allowedModules)) || \in_array($moduleName, $skipModules)) { |
|
|
|
|
1033
|
|
|
continue; |
1034
|
|
|
} |
1035
|
|
|
$instance = \CRMEntity::getInstance($moduleName); |
1036
|
|
|
$isEntityType = isset($instance->tab_name_index['vtiger_crmentity']); |
1037
|
|
|
foreach ($moduleFields as $tablename => $columns) { |
1038
|
|
|
$tableIndex = $instance->tab_name_index[$tablename]; |
1039
|
|
|
$query = (new \App\Db\Query())->select(['crmid' => $tableIndex, 'modules' => new \yii\db\Expression($db->quoteValue($moduleName))]) |
1040
|
|
|
->from($tablename); |
1041
|
|
|
if ($isEntityType) { |
1042
|
|
|
$query->innerJoin('vtiger_crmentity', "vtiger_crmentity.crmid = {$tablename}.{$tableIndex}")->where(['vtiger_crmentity.deleted' => 0]); |
1043
|
|
|
} |
1044
|
|
|
$orWhere = ['or']; |
1045
|
|
|
foreach ($columns as $row) { |
1046
|
|
|
$orWhere[] = ["{$row['tablename']}.{$row['columnname']}" => $value]; |
1047
|
|
|
} |
1048
|
|
|
$query->andWhere($orWhere); |
1049
|
|
|
if ($queryUnion) { |
1050
|
|
|
$queryUnion->union($query); |
1051
|
|
|
} else { |
1052
|
|
|
$queryUnion = $query; |
1053
|
|
|
} |
1054
|
|
|
} |
1055
|
|
|
} |
1056
|
|
|
$rows = $queryUnion->all(); |
|
|
|
|
1057
|
|
|
$labels = \App\Record::getLabel(array_column($rows, 'crmid')); |
1058
|
|
|
foreach ($rows as &$row) { |
1059
|
|
|
$row['label'] = $labels[$row['crmid']]; |
1060
|
|
|
} |
1061
|
|
|
return $rows; |
1062
|
|
|
} |
1063
|
|
|
} |
1064
|
|
|
|
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.