1<?php
2
3class GoSyncUtils {
4
5
6
7
8	/**
9	 * Get the Group-Office Sync settings for the given user
10	 * If no user is given then it will take the settings for the current user
11	 *
12	 * @return \GO\Sync\Model\Settings
13	 */
14	public static function getUserSettings($user = false) {
15
16		if (empty($user))
17			$user = \GO::user();
18
19		return \GO\Sync\Model\Settings::model()->findForUser($user);
20	}
21
22//	public static function getEntityModSeq($entity) {
23//		$entityModSeq = go()->getDbConnection()
24//						->selectSingleValue("MAX(modSeq)")
25//						->from("core_change")
26//						->where([
27//								"entityTypeId" => $entity->getType()->getId(),
28//								"entityId" => $entity->id
29//										])
30//						->single();
31//
32//		if(empty($entityModSeq)) {
33//			return false;
34//		}
35//
36//		$userModSeq = go()->getDbConnection()
37//						->selectSingleValue("MAX(modSeq)")
38//						->from("core_change_user")
39//						->where([
40//								"entityTypeId" => $entity->getType()->getId(),
41//								"entityId" => $entity->id,
42//								"userId" => go()->getUserId()
43//										])
44//						->single();
45//
46//		if(empty($entityModSeq)) {
47//			return false;
48//		}
49//
50//		return $entityModSeq.':'.$userModSeq;
51//
52//	}
53
54	/**
55	 * Returns the best match of preferred body preference types.
56	 *
57	 * @param array $bpTypes
58	 *
59	 * @access private
60	 * @return int
61	 */
62	public static function getBodyPreferenceMatch($bpTypes, $supported = array(SYNC_BODYPREFERENCE_PLAIN, SYNC_BODYPREFERENCE_HTML)) {
63
64		ZLog::Write(LOGLEVEL_DEBUG, 'GoSyncUtils->getBodyPreferenceMatch() ~~ bpTypes = ' . var_export($bpTypes, true));
65
66		if (is_array($bpTypes)) {
67
68			// The best choice is RTF, then HTML and then MIME in order to save bandwidth
69			// because MIME is a complete message including the headers and attachments
70			if (in_array(SYNC_BODYPREFERENCE_RTF, $bpTypes) && in_array(SYNC_BODYPREFERENCE_RTF, $supported))
71				return SYNC_BODYPREFERENCE_RTF;
72			if (in_array(SYNC_BODYPREFERENCE_HTML, $bpTypes) && in_array(SYNC_BODYPREFERENCE_HTML, $supported))
73				return SYNC_BODYPREFERENCE_HTML;
74			if (in_array(SYNC_BODYPREFERENCE_MIME, $bpTypes) && in_array(SYNC_BODYPREFERENCE_MIME, $supported))
75				return SYNC_BODYPREFERENCE_MIME;
76		}
77
78		if (defined("BACKEND_GO_DEFAULT_BODY_PREFENCE")) {
79			return BACKEND_GO_DEFAULT_BODY_PREFENCE;
80		}
81
82		return SYNC_BODYPREFERENCE_PLAIN;
83	}
84
85	/**
86	 * Get the correct formatted \SyncBaseBody from an attribute of a model from GO.
87	 *
88	 * @param \GO\Base\Db\ActiveRecord $model
89	 * @param StringHelper $attribute
90	 * @param int $sbReturnType
91	 * @return \SyncBaseBody
92	 */
93	public static function createASBodyForMessage($model, $attribute, $sbReturnType = SYNC_BODYPREFERENCE_HTML) {
94
95		$sbBody = new SyncBaseBody();
96
97		$asBodyData = \GO\Base\Util\StringHelper::normalizeCrlf($model->$attribute);
98
99		if(!isset($asBodyData)) {
100			$asBodyData = "";
101		}
102
103		if ($sbReturnType == SYNC_BODYPREFERENCE_HTML) {
104
105			ZLog::Write(LOGLEVEL_DEBUG, 'SYNCUTILS HTML');
106
107			$sbBody->type = SYNC_BODYPREFERENCE_HTML;
108
109			$asBodyData = \GO\Base\Util\StringHelper::text_to_html($asBodyData);
110		} else {
111
112			$sbBody->type = SYNC_BODYPREFERENCE_PLAIN;
113		}
114		ZLog::Write(LOGLEVEL_DEBUG, $asBodyData);
115
116		ZLog::Write(LOGLEVEL_DEBUG, 'SYNCUTILS END');
117
118		$sbBody->estimatedDataSize = strlen($asBodyData);
119		$sbBody->data = StringStreamWrapper::Open($asBodyData);
120		$sbBody->truncated = 0;
121
122		return $sbBody;
123	}
124
125	/**
126	 * Get the body text of the message
127	 *
128	 * @param \SyncObject $message
129	 * @return StringHelper
130	 */
131	public static function getBodyFromMessage($message) {
132
133		if (Request::GetProtocolVersion() >= 12.0) {
134			return isset($message->asbody) && isset($message->asbody->data) ? stream_get_contents($message->asbody->data) : "";
135		} else {
136			if (!empty($message->body))
137				return $message->body;
138
139			if (isset($message->rtf)) {
140				$rtfParser = new \GO\Base\Util\Rtf();
141				$rtfParser->output('ascii');
142				$rtfParser->loadrtf(base64_decode($message->rtf));
143				$rtfParser->parse();
144				return (string) $rtfParser->out;
145			}
146		}
147		return "";
148	}
149
150	/* Translates recurrence information in ActiveSync format to the rrule field
151	 * for the tasks table or calendar event table.
152	 */
153
154	public static function importRecurrence($recur, $eventStartTime) {
155		$rrule = '';
156		$freq = "";
157		switch ($recur->type) {
158			case 0:
159				$freq = "DAILY";
160				break;
161			case 1:
162				$freq = "WEEKLY";
163				break;
164			case 2:
165				$freq = "MONTHLY";
166				break;
167			case 3:
168				$freq = "MONTHLY";
169				break;
170			case 5:
171			case 6:
172				$freq = "YEARLY";
173				break;
174		}
175
176		if ($freq) {
177
178			$rrule = new \GO\Base\Util\Icalendar\Rrule();
179			$rrule->eventStartTime = $eventStartTime;
180			$rrule->freq = $freq;
181			$rrule->interval = $recur->interval;
182			if (!empty($recur->until))
183				$rrule->until = $recur->until;
184
185			$rrule->byday = self::aSync2weekday($recur->dayofweek);
186			if (!empty($recur->weekofmonth))
187				$rrule->bysetpos = $recur->weekofmonth;
188
189//			$rrule->shiftDays(true);
190
191			return $rrule->createRrule();
192		}else {
193			return false;
194		}
195	}
196
197	public static function aSync2weekday($number) {
198		$weekdays = array();
199		if ($number >= 128 || $number < 0) {
200			throw new \Exception('The way the recurrence days were coded, is corrupted!');
201		}
202		if ($number >= 64) {
203			$number -=64;
204			$weekdays[] = 'SA';
205		}
206		if ($number >= 32) {
207			$number -=32;
208			$weekdays[] = 'FR';
209		}
210		if ($number >= 16) {
211			$number -=16;
212			$weekdays[] = 'TH';
213		}
214		if ($number >= 8) {
215			$number -=8;
216			$weekdays[] = 'WE';
217		}
218		if ($number >= 4) {
219			$number -=4;
220			$weekdays[] = 'TU';
221		}
222		if ($number >= 2) {
223			$number -=2;
224			$weekdays[] = 'MO';
225		}
226		if ($number >= 1) {
227			$number -=1;
228			$weekdays[] = 'SU';
229		}
230		return $weekdays;
231	}
232
233	public static function weekday2ASync($weekdays) {
234		//ZLog::Write(LOGLEVEL_DEBUG, var_export($weekdays, true));
235		$ASyncDay = 0;
236		foreach ($weekdays as $weekday) {
237			switch ($weekday) {
238				case 'MO':
239					$ASyncDay += 2;
240					break;
241				case 'TU':
242					$ASyncDay += 4;
243					break;
244				case 'WE':
245					$ASyncDay += 8;
246					break;
247				case 'TH':
248					$ASyncDay += 16;
249					break;
250				case 'FR':
251					$ASyncDay += 32;
252					break;
253				case 'SA':
254					$ASyncDay += 64;
255					break;
256				case 'SU':
257					$ASyncDay += 1;
258					break;
259			}
260		}
261		return $ASyncDay;
262	}
263
264	public static function getTimeZoneForClient() {
265
266		if (!isset(\GO::session()->values['activesync_timezone'])) {
267			$old = date_default_timezone_get();
268			date_default_timezone_set(\GO::user()->timezone);
269
270			$tz = new DateTimeZone(\GO::user()->timezone);
271			$transitions = $tz->getTransitions();
272			$start_of_year = mktime(0, 0, 0, 1, 1);
273
274			for ($i = 0, $max = count($transitions); $i < $max; $i++) {
275				if ($transitions[$i]['ts'] > $start_of_year) {
276					$dst_end = $transitions[$i];
277					$dst_start = $transitions[$i + 1];
278					break;
279				}
280			}
281
282			if (!isset($dst_end)) {
283				$astz['format'] = "la64vvvvvvvv" . "la64vvvvvvvv" . "l";
284				$astz['bias'] = 0;
285				$astz['stdname'] = $tz->getName();
286				$astz['stdyear'] = 0;
287				$astz['stdmonth'] = 0;
288				$astz['stdday'] = 0;
289
290				$astz['stdweek'] = 0;
291				$astz['stdhour'] = 0;
292				$astz['stdminute'] = 0;
293				$astz['stdmillis'] = 0;
294				$astz['stdsecond'] = 0;
295				$astz['stdbias'] = 0;
296
297				$astz['dstname'] = "";
298				$astz['dstyear'] = 0;
299				$astz['dstmonth'] = 0;
300				$astz['dstday'] = 0;
301				$astz['dstweek'] = 0;
302				$astz['dsthour'] = 0;
303				$astz['dstminute'] = 0;
304				$astz['dstsecond'] = 0;
305				$astz['dstdmillis'] = 0;
306				$astz['dstbias'] = 0;
307			} else {
308				$astz['format'] = "la64vvvvvvvv" . "la64vvvvvvvv" . "l";
309				$astz['bias'] = $dst_start['offset'] / -60;
310				$astz['stdname'] = $tz->getName();
311				$astz['stdyear'] = 0;
312				$astz['stdmonth'] = date('n', $dst_start['ts']);
313				$astz['stdday'] = date('w', $dst_start['ts']);
314				$stdweek = \GO\Base\Util\Date::get_occurring_number_of_day_in_month($dst_start['ts']);
315				if ($stdweek == 4) {
316					$stdweek = 5;
317				}
318
319				$astz['stdweek'] = $stdweek;
320				$astz['stdhour'] = date('G', $dst_start['ts']);
321				$astz['stdminute'] = intval(date('i', $dst_start['ts']));
322				$astz['stdmillis'] = 0;
323				$astz['stdsecond'] = 0;
324				$astz['stdbias'] = 0;
325
326				$astz['dstname'] = "";
327				$astz['dstyear'] = 0;
328				$astz['dstmonth'] = date('n', $dst_end['ts']);
329				$astz['dstday'] = date('w', $dst_end['ts']);
330				$dstweek = \GO\Base\Util\Date::get_occurring_number_of_day_in_month($dst_end['ts']);
331				if ($dstweek == 4) {
332					$dstweek = 5;
333				}
334				$astz['dstweek'] = $dstweek;
335				$astz['dsthour'] = date('G', $dst_end['ts']);
336				$astz['dstminute'] = intval(date('i', $dst_end['ts']));
337				$astz['dstsecond'] = 0;
338				$astz['dstdmillis'] = 0;
339				$astz['dstbias'] = ($dst_end['offset'] / -60) - $astz['bias'];
340			}
341			date_default_timezone_set($old);
342			\GO::session()->values['activesync_timezone'] = base64_encode(call_user_func_array('pack', $astz));
343		}
344
345		/* $timezone = base64_encode(
346		  pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
347		  -60, //bias, the standard timezone UTC offset in minutes, in this case +2 hour
348
349		  "Europe/Amsterdam", //stdname, we could give this timezone a name, like EET
350		  0, //stdyear, the year off the timezone, 0 means every year
351		  10, //stdmonth, the month the dst ends, 10 equals october
352		  0, //stdday, the day the dst ends, 0 equeals sunday
353		  5, //stdweek, weeknumber in the month the dst ends, where 1 will give the first dstendday of dstendmonth, 5 is the last dstendday of dstendmonth
354		  2, //stdhour, the hour the dst ends
355		  0, //stdminute
356		  0, //stdsecond
357		  0, //stdmillis
358		  0, //stdbias, the difference between timezone and std in minutes usually 0
359
360		  "", //dstname, name of dst version, like EEST
361		  0, //dstyear, the year off the timezone, 0 means every year
362		  3, //dstmonth, the month the dst start, 3 equals march
363		  0, //dstday, the day the dst starts, 0 equeals sunday
364		  5, //dstweek, weeknumber in the month the dst starts, where 1 will give the first dstendday of dstendmonth, 5 is the last dstendday of dstendmonth
365		  3, //dsthour, the hour the dst starts
366		  0, //dstminute
367		  0, //dstsecond
368		  0, //dstmillis
369		  -60 //dstbias, the difference between timezone and dst in minutes usually -60
370		  ));
371		  return $timezone; */
372
373		//test n900
374		//return 'xP///0UAdQByAG8AcABlAC8AQQBtAHMAdABlAHIAZABhAG0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAIAAAAAAAAAAAAAAEUAdQByAG8AcABlAC8AQQBtAHMAdABlAHIAZABhAG0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAMAAAAAAAAAxP///w==';
375		//GMT
376		/* return base64_encode(
377		  pack("la64vvvvvvvv" . "la64vvvvvvvv" . "l",
378		  0, "", 0, 0, 0, 0, 0, 0, 0, 0,
379		  0, "", 0, 0, 0, 0, 0, 0, 0, 0,
380		  0
381		  )); */
382
383		return \GO::session()->values['activesync_timezone'];
384	}
385
386	/* Translates rrule field, repeat_end_time field, and start_time field from
387	 * the calendar events table to a format understandable for ActiveSync.
388	 */
389
390	public static function exportRecurrence($model) {
391
392		$old = date_default_timezone_get();
393		date_default_timezone_set($model->timezone ?? \GO::user()->timezone);
394
395		if ($model instanceof \GO\Tasks\Model\Task)
396			$recur = new SyncTaskRecurrence();
397		else
398			$recur = new SyncRecurrence();
399
400		$rrule = new \GO\Base\Util\Icalendar\Rrule();
401		$rrule->readIcalendarRruleString($model->start_time, $model->rrule, false);
402
403		$recur->interval = $rrule->interval;
404		if ($model->repeat_end_time > 0) {
405			$recur->until = $rrule->until; //\GO\Base\Util\Date::date_add($model->repeat_end_time,1)-1; // add one day (minus 1 sec) to the end time to make sure the last occurrence is covered
406		}
407
408		if(!empty($rrule->count)) {
409			$recur->occurrences = $rrule->count;
410		}
411		switch ($rrule->freq) {
412			case 'DAILY':
413				$recur->type = 0;
414				break;
415			case 'WEEKLY':
416				$recur->type = 1;
417				$recur->dayofweek = self::weekday2ASync($rrule->byday);
418				break;
419			case 'MONTHLY':
420				if (isset($rrule->byday[0])) {
421					$recur->type = 3;
422					$recur->weekofmonth = $rrule->bysetpos;
423					$recur->dayofweek = self::weekday2ASync($rrule->byday);
424				} else {
425					$recur->dayofmonth = date('j', $model->start_time);
426					$recur->type = 2;
427				}
428				break;
429			case 'YEARLY':
430				$recur->type = 5;
431				$recur->monthofyear = date('n', $model->start_time);
432				$recur->dayofmonth = date('j', $model->start_time);
433				break;
434		}
435
436		date_default_timezone_set($old);
437
438		return $recur;
439	}
440
441}
442