Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

BaseRecurrence::daysInMonth()   A

Complexity

Conditions 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nop 2
dl 0
loc 8
rs 10
1
<?php
2
	/**
3
	 * BaseRecurrence
4
	 * this class is superclass for recurrence for appointments and tasks. This class provides all
5
	 * basic features of recurrence.
6
	 */
7
	class BaseRecurrence
8
	{
9
		/**
10
		 * @var object Mapi Message Store (may be null if readonly)
11
		 */
12
		var $store;
13
14
		/**
15
		 * @var object Mapi Message (may be null if readonly)
16
		 */
17
		var $message;
18
19
		/**
20
		 * @var array Message Properties
21
		 */
22
		var $messageprops;
23
24
		/**
25
		 * @var array list of property tags
26
		 */
27
		var $proptags;
28
29
		/**
30
		 * @var recurrence data of this calendar item
31
		 */
32
		var $recur;
33
34
		/**
35
		 * @var Timezone data of this calendar item
0 ignored issues
show
Bug introduced by
The type Timezone was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
36
		 */
37
		var $tz;
38
39
		/**
40
		 * Constructor
41
		 * @param resource $store MAPI Message Store Object
42
		 * @param resource $message the MAPI (appointment) message
43
		 * @param array $properties the list of MAPI properties the message has.
44
		 */
45
		function __construct($store, $message)
46
		{
47
			$this->store = $store;
0 ignored issues
show
Documentation Bug introduced by
It seems like $store of type resource is incompatible with the declared type object of property $store.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
48
49
			if(is_array($message)) {
0 ignored issues
show
introduced by
The condition is_array($message) is always false.
Loading history...
50
				$this->messageprops = $message;
51
			} else {
52
				$this->message = $message;
0 ignored issues
show
Documentation Bug introduced by
It seems like $message of type resource is incompatible with the declared type object of property $message.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
53
				$this->messageprops = mapi_getprops($this->message, $this->proptags);
54
			}
55
56
			if(isset($this->messageprops[$this->proptags["recurring_data"]])) {
57
				// There is a possibility that recurr blob can be more than 255 bytes so get full blob through stream interface
58
				if (strlen($this->messageprops[$this->proptags["recurring_data"]]) >= 255) {
59
					$this->getFullRecurrenceBlob();
60
				}
61
62
				$this->recur = $this->parseRecurrence($this->messageprops[$this->proptags["recurring_data"]]);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parseRecurrence($...ags['recurring_data']]) of type array is incompatible with the declared type recurrence of property $recur.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
			}
64
			if(isset($this->proptags["timezone_data"]) && isset($this->messageprops[$this->proptags["timezone_data"]])) {
65
				$this->tz = $this->parseTimezone($this->messageprops[$this->proptags["timezone_data"]]);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->parseTimezone($th...tags['timezone_data']]) can also be of type array. However, the property $tz is declared as type Timezone. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
66
			}
67
		}
68
69
		function getRecurrence()
70
		{
71
			return $this->recur;
72
		}
73
74
		function getFullRecurrenceBlob()
75
		{
76
			$message = mapi_msgstore_openentry($this->store, $this->messageprops[PR_ENTRYID]);
77
78
			$recurrBlob = '';
79
			$stream = mapi_openproperty($message, $this->proptags["recurring_data"], IID_IStream, 0, 0);
80
			$stat = mapi_stream_stat($stream);
81
82
			for ($i = 0; $i < $stat['cb']; $i += 1024) {
83
				$recurrBlob .= mapi_stream_read($stream, 1024);
84
			}
85
86
			if (!empty($recurrBlob)) {
87
				$this->messageprops[$this->proptags["recurring_data"]] = $recurrBlob;
88
			}
89
		}
90
91
		/**
92
		* Function for parsing the Recurrence value of a Calendar item.
93
		*
94
		* Retrieve it from Named Property 0x8216 as a PT_BINARY and pass the
95
		* data to this function
96
		*
97
		* Returns a structure containing the data:
98
		*
99
		* type		- type of recurrence: day=10, week=11, month=12, year=13
100
		* subtype	- type of day recurrence: 2=monthday (ie 21st day of month), 3=nday'th weekdays (ie. 2nd Tuesday and Wednesday)
101
		* start	- unix timestamp of first occurrence
102
		* end		- unix timestamp of last occurrence (up to and including), so when start == end -> occurrences = 1
103
		* numoccur     - occurrences (may be very large when there is no end data)
104
		*
105
		* then, for each type:
106
		*
107
		* Daily:
108
		*  everyn	- every [everyn] days in minutes
109
		*  regen	- regenerating event (like tasks)
110
		*
111
		* Weekly:
112
		*  everyn	- every [everyn] weeks in weeks
113
		*  regen	- regenerating event (like tasks)
114
		*  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
115
		*
116
		* Monthly:
117
		*  everyn	- every [everyn] months
118
		*  regen	- regenerating event (like tasks)
119
		*
120
		*  subtype 2:
121
		*	  monthday - on day [monthday] of the month
122
		*
123
		*  subtype 3:
124
		*	  weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
125
		*   nday	- on [nday]'th [weekdays] of the month
126
		*
127
		* Yearly:
128
		*  everyn	- every [everyn] months (12, 24, 36, ...)
129
		*  month	- in month [month] (although the month is encoded in minutes since the startning of the year ........)
130
		*  regen	- regenerating event (like tasks)
131
		*
132
		*  subtype 2:
133
		*   monthday - on day [monthday] of the month
134
		*
135
		*  subtype 3:
136
		*   weekdays - bitmask of week days, where each bit is one weekday (weekdays & 1 = Sunday, weekdays & 2 = Monday, etc)
137
		*	  nday	- on [nday]'th [weekdays] of the month [month]
138
		* @param string $rdata Binary string
139
		* @return array recurrence data.
140
		*/
141
		function parseRecurrence($rdata)
142
		{
143
			if (strlen($rdata) < 10) {
144
				return;
145
			}
146
147
			$ret["changed_occurrences"] = array();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$ret was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ret = array(); before regardless.
Loading history...
148
			$ret["deleted_occurrences"] = array();
149
150
			$data = unpack("Vconst1/Crtype/Cconst2/Vrtype2", $rdata);
151
152
			$ret["type"] = $data["rtype"];
153
			$ret["subtype"] = $data["rtype2"];
154
			$rdata = substr($rdata, 10);
155
156
			switch ($data["rtype"])
157
			{
158
				case 0x0a:
159
					// Daily
160
					if (strlen($rdata) < 12) {
161
						return $ret;
162
					}
163
164
					$data = unpack("Vunknown/Veveryn/Vregen", $rdata);
165
					$ret["everyn"] = $data["everyn"];
166
					$ret["regen"] = $data["regen"];
167
168
					switch($ret["subtype"])
169
					{
170
						case 0:
171
							$rdata = substr($rdata, 12);
172
							break;
173
						case 1:
174
							$rdata = substr($rdata, 16);
175
							break;
176
					}
177
178
					break;
179
180
				case 0x0b:
181
					// Weekly
182
					if (strlen($rdata) < 16) {
183
						return $ret;
184
					}
185
186
					$data = unpack("Vconst1/Veveryn/Vregen", $rdata);
187
					$rdata = substr($rdata, 12);
188
189
					$ret["everyn"] = $data["everyn"];
190
					$ret["regen"] = $data["regen"];
191
					$ret["weekdays"] = 0;
192
193
					if ($data["regen"] == 0) {
194
						$data = unpack("Vweekdays", $rdata);
195
						$rdata = substr($rdata, 4);
196
197
						$ret["weekdays"] = $data["weekdays"];
198
					}
199
					break;
200
201
				case 0x0c:
202
					// Monthly
203
					if (strlen($rdata) < 16) {
204
						return $ret;
205
					}
206
207
					$data = unpack("Vconst1/Veveryn/Vregen/Vmonthday", $rdata);
208
209
					$ret["everyn"] = $data["everyn"];
210
					$ret["regen"] = $data["regen"];
211
212
					if ($ret["subtype"] == 3) {
213
						$ret["weekdays"] = $data["monthday"];
214
					} else {
215
						$ret["monthday"] = $data["monthday"];
216
					}
217
218
					$rdata = substr($rdata, 16);
219
220
					if ($ret["subtype"] == 3) {
221
						$data = unpack("Vnday", $rdata);
222
						$ret["nday"] = $data["nday"];
223
						$rdata = substr($rdata, 4);
224
					}
225
					break;
226
227
				case 0x0d:
228
					// Yearly
229
					if (strlen($rdata) < 16)
230
						return $ret;
231
232
					$data = unpack("Vmonth/Veveryn/Vregen/Vmonthday", $rdata);
233
234
					$ret["month"] = $data["month"];
235
					$ret["everyn"] = $data["everyn"];
236
					$ret["regen"] = $data["regen"];
237
238
					if ($ret["subtype"] == 3) {
239
						$ret["weekdays"] = $data["monthday"];
240
					} else {
241
						$ret["monthday"] = $data["monthday"];
242
					}
243
244
					$rdata = substr($rdata, 16);
245
246
					if ($ret["subtype"] == 3) {
247
						$data = unpack("Vnday", $rdata);
248
						$ret["nday"] = $data["nday"];
249
						$rdata = substr($rdata, 4);
250
					}
251
					break;
252
			}
253
254
			if (strlen($rdata) < 16) {
255
				return $ret;
256
			}
257
258
			$data = unpack("Cterm/C3const1/Vnumoccur/Vconst2/Vnumexcept", $rdata);
259
260
			$rdata = substr($rdata, 16);
261
262
			$ret["term"] = $data["term"];
263
			$ret["numoccur"] = $data["numoccur"];
264
			$ret["numexcept"] = $data["numexcept"];
265
266
			// exc_base_dates are *all* the base dates that have been either deleted or modified
267
			$exc_base_dates = array();
268
			for($i = 0; $i < $ret["numexcept"]; $i++)
269
			{
270
                if (strlen($rdata) < 4) {
271
					// We shouldn't arrive here, because that implies
272
					// numexcept does not match the amount of data
273
					// which is available for the exceptions.
274
					return $ret;
275
				}
276
				$data = unpack("Vbasedate", $rdata);
277
				$rdata = substr($rdata, 4);
278
				$exc_base_dates[] = $this->recurDataToUnixData($data["basedate"]);
279
			}
280
281
			if (strlen($rdata) < 4) {
282
				return $ret;
283
			}
284
285
			$data = unpack("Vnumexceptmod", $rdata);
286
			$rdata = substr($rdata, 4);
287
288
			$ret["numexceptmod"] = $data["numexceptmod"];
289
290
			// exc_changed are the base dates of *modified* occurrences. exactly what is modified
291
			// is in the attachments *and* in the data further down this function.
292
			$exc_changed = array();
293
			for($i = 0; $i < $ret["numexceptmod"]; $i++)
294
			{
295
                if (strlen($rdata) < 4) {
296
					// We shouldn't arrive here, because that implies
297
					// numexceptmod does not match the amount of data
298
					// which is available for the exceptions.
299
					return $ret;
300
				}
301
				$data = unpack("Vstartdate", $rdata);
302
				$rdata = substr($rdata, 4);
303
				$exc_changed[] = $this->recurDataToUnixData($data["startdate"]);
304
			}
305
306
			if (strlen($rdata) < 8) {
307
				return $ret;
308
			}
309
310
			$data = unpack("Vstart/Vend", $rdata);
311
			$rdata = substr($rdata, 8);
312
313
			$ret["start"] = $this->recurDataToUnixData($data["start"]);
314
			$ret["end"] = $this->recurDataToUnixData($data["end"]);
315
316
			// this is where task recurrence stop
317
			if (strlen($rdata) < 16) {
318
				return $ret;
319
			}
320
321
			$data = unpack("Vreaderversion/Vwriterversion/Vstartmin/Vendmin", $rdata);
322
			$rdata = substr($rdata, 16);
323
324
			$ret["startocc"] = $data["startmin"];
325
			$ret["endocc"] = $data["endmin"];
326
			$writerversion = $data["writerversion"];
327
328
			$data = unpack("vnumber", $rdata);
329
			$rdata = substr($rdata, 2);
330
331
			$nexceptions = $data["number"];
332
			$exc_changed_details = array();
333
334
			// Parse n modified exceptions
335
			for($i=0;$i<$nexceptions;$i++)
336
			{
337
				$item = array();
338
339
				// Get exception startdate, enddate and basedate (the date at which the occurrence would have started)
340
				$data = unpack("Vstartdate/Venddate/Vbasedate", $rdata);
341
				$rdata = substr($rdata, 12);
342
343
				// Convert recurtimestamp to unix timestamp
344
				$startdate = $this->recurDataToUnixData($data["startdate"]);
345
				$enddate = $this->recurDataToUnixData($data["enddate"]);
346
				$basedate = $this->recurDataToUnixData($data["basedate"]);
347
348
				// Set the right properties
349
				$item["basedate"] = $this->dayStartOf($basedate);
0 ignored issues
show
Bug introduced by
$basedate of type integer is incompatible with the type date expected by parameter $date of BaseRecurrence::dayStartOf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

349
				$item["basedate"] = $this->dayStartOf(/** @scrutinizer ignore-type */ $basedate);
Loading history...
350
				$item["start"] = $startdate;
351
				$item["end"] = $enddate;
352
353
				$data = unpack("vbitmask", $rdata);
354
				$rdata = substr($rdata, 2);
355
				$item["bitmask"] = $data["bitmask"]; // save bitmask for extended exceptions
356
357
				// Bitmask to verify what properties are changed
358
				$bitmask = $data["bitmask"];
359
360
				// ARO_SUBJECT: 0x0001
361
				// Look for field: SubjectLength (2b), SubjectLength2 (2b) and Subject
362
				if(($bitmask &(1 << 0))) {
363
					$data = unpack("vnull_length/vlength", $rdata);
364
					$rdata = substr($rdata, 4);
365
366
					$length = $data["length"];
367
					$item["subject"] = ""; // Normalized subject
368
					for($j = 0; $j < $length && strlen($rdata); $j++)
369
					{
370
						$data = unpack("Cchar", $rdata);
371
						$rdata = substr($rdata, 1);
372
373
						$item["subject"] .= chr($data["char"]);
374
					}
375
				}
376
377
				// ARO_MEETINGTYPE: 0x0002
378
				if(($bitmask &(1 << 1))) {
379
					$rdata = substr($rdata, 4);
380
					// Attendees modified: no data here (only in attachment)
381
				}
382
383
				// ARO_REMINDERDELTA: 0x0004
384
				// Look for field: ReminderDelta (4b)
385
				if(($bitmask &(1 << 2))) {
386
					$data = unpack("Vremind_before", $rdata);
387
					$rdata = substr($rdata, 4);
388
389
					$item["remind_before"] = $data["remind_before"];
390
				}
391
392
				// ARO_REMINDER: 0x0008
393
				// Look field: ReminderSet (4b)
394
				if(($bitmask &(1 << 3))) {
395
					$data = unpack("Vreminder_set", $rdata);
396
					$rdata = substr($rdata, 4);
397
398
					$item["reminder_set"] = $data["reminder_set"];
399
				}
400
401
				// ARO_LOCATION: 0x0010
402
				// Look for fields: LocationLength (2b), LocationLength2 (2b) and Location
403
				// Similar to ARO_SUBJECT above.
404
				if(($bitmask &(1 << 4))) {
405
					$data = unpack("vnull_length/vlength", $rdata);
406
					$rdata = substr($rdata, 4);
407
408
					$item["location"] = "";
409
410
					$length = $data["length"];
411
					$data = substr($rdata, 0, $length);
412
					$rdata = substr($rdata, $length);
413
414
					$item["location"] .= $data;
415
				}
416
417
				// ARO_BUSYSTATUS: 0x0020
418
				// Look for field: BusyStatus (4b)
419
				if(($bitmask &(1 << 5))) {
420
					$data = unpack("Vbusystatus", $rdata);
421
					$rdata = substr($rdata, 4);
422
423
					$item["busystatus"] = $data["busystatus"];
424
				}
425
426
				// ARO_ATTACHMENT: 0x0040
427
				if(($bitmask &(1 << 6))) {
428
					// no data: RESERVED
429
					$rdata = substr($rdata, 4);
430
				}
431
432
				// ARO_SUBTYPE: 0x0080
433
				// Look for field: SubType (4b). Determines whether it is an allday event.
434
				if(($bitmask &(1 << 7))) {
435
					$data = unpack("Vallday", $rdata);
436
					$rdata = substr($rdata, 4);
437
438
					$item["alldayevent"] = $data["allday"];
439
				}
440
441
				// ARO_APPTCOLOR: 0x0100
442
				// Look for field: AppointmentColor (4b)
443
				if(($bitmask &(1 << 8))) {
444
					$data = unpack("Vlabel", $rdata);
445
					$rdata = substr($rdata, 4);
446
447
					$item["label"] = $data["label"];
448
				}
449
450
				// ARO_EXCEPTIONAL_BODY: 0x0200
451
				if(($bitmask &(1 << 9))) {
452
					// Notes or Attachments modified: no data here (only in attachment)
453
				}
454
455
				array_push($exc_changed_details, $item);
456
			}
457
458
			/**
459
			 * We now have $exc_changed, $exc_base_dates and $exc_changed_details
460
			 * We will ignore $exc_changed, as this information is available in $exc_changed_details
461
			 * also. If an item is in $exc_base_dates and NOT in $exc_changed_details, then the item
462
			 * has been deleted.
463
			 */
464
465
			// Find deleted occurrences
466
			$deleted_occurrences = array();
467
468
			foreach($exc_base_dates as $base_date) {
469
				$found = false;
470
471
				foreach($exc_changed_details as $details) {
472
					if($details["basedate"] == $base_date) {
473
						$found = true;
474
						break;
475
					}
476
				}
477
				if(! $found) {
478
					// item was not in exc_changed_details, so it must be deleted
479
					$deleted_occurrences[] = $base_date;
480
				}
481
			}
482
483
			$ret["deleted_occurrences"] = $deleted_occurrences;
484
			$ret["changed_occurrences"] = $exc_changed_details;
485
486
			// enough data for normal exception (no extended data)
487
			if (strlen($rdata) < 16) {
488
				return $ret;
489
			}
490
491
			$data = unpack("Vreservedsize", $rdata);
492
			$rdata = substr($rdata, 4 + $data["reservedsize"]);
493
494
			for($i=0;$i<$nexceptions;$i++)
495
			{
496
				// subject and location in ucs-2 to utf-8
497
				if ($writerversion >= 0x3009) {
498
					$data = unpack("Vsize/Vvalue", $rdata); // size includes sizeof(value)==4
499
					$rdata = substr($rdata, 4 + $data["size"]);
500
				}
501
502
				$data = unpack("Vreservedsize", $rdata);
503
				$rdata = substr($rdata, 4 + $data["reservedsize"]);
504
505
				// ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
506
				if ($exc_changed_details[$i]["bitmask"] & 0x11) {
507
					$data = unpack("Vstart/Vend/Vorig", $rdata);
508
					$rdata = substr($rdata, 4 * 3);
509
510
					$exc_changed_details[$i]["ex_start_datetime"] = $data["start"];
511
					$exc_changed_details[$i]["ex_end_datetime"] = $data["end"];
512
					$exc_changed_details[$i]["ex_orig_date"] = $data["orig"];
513
				}
514
515
				// ARO_SUBJECT
516
				if ($exc_changed_details[$i]["bitmask"] & 0x01) {
517
					// decode ucs2 string to utf-8
518
					$data = unpack("vlength", $rdata);
519
					$rdata = substr($rdata, 2);
520
					$length = $data["length"];
521
					$data = substr($rdata, 0, $length * 2);
522
					$rdata = substr($rdata, $length * 2);
523
					$subject = iconv("UCS-2LE", "UTF-8", $data);
524
					// replace subject with unicode subject
525
					$exc_changed_details[$i]["subject"] = $subject;
526
				}
527
528
				// ARO_LOCATION
529
				if ($exc_changed_details[$i]["bitmask"] & 0x10) {
530
					// decode ucs2 string to utf-8
531
					$data = unpack("vlength", $rdata);
532
					$rdata = substr($rdata, 2);
533
					$length = $data["length"];
534
					$data = substr($rdata, 0, $length * 2);
535
					$rdata = substr($rdata, $length * 2);
536
					$location = iconv("UCS-2LE", "UTF-8", $data);
537
					// replace subject with unicode subject
538
					$exc_changed_details[$i]["location"] = $location;
539
				}
540
541
				// ARO_SUBJECT(0x01) | ARO_LOCATION(0x10)
542
				if ($exc_changed_details[$i]["bitmask"] & 0x11) {
543
					$data = unpack("Vreservedsize", $rdata);
544
					$rdata = substr($rdata, 4 + $data["reservedsize"]);
545
				}
546
			}
547
548
			// update with extended data
549
			$ret["changed_occurrences"] = $exc_changed_details;
550
551
			return $ret;
552
		}
553
554
		/**
555
		 * Saves the recurrence data to the recurrence property
556
		 * @param array $properties the recurrence data.
557
		 * @return string binary string
558
		 */
559
		function saveRecurrence()
560
		{
561
			// Only save if a message was passed
562
			if(!isset($this->message))
563
				return;
564
565
			// Abort if no recurrence was set
566
			if(!isset($this->recur["type"]) && !isset($this->recur["subtype"])) {
567
				return;
568
			}
569
570
			if(!isset($this->recur["start"]) && !isset($this->recur["end"])) {
571
				return;
572
			}
573
574
			if(!isset($this->recur["startocc"]) && !isset($this->recur["endocc"])) {
575
				return;
576
			}
577
578
			$rdata = pack("CCCCCCV", 0x04, 0x30, 0x04, 0x30, (int) $this->recur["type"], 0x20, (int) $this->recur["subtype"]);
579
580
			$weekstart = 1; //monday
581
			$forwardcount = 0;
582
			$restocc = 0;
583
			$dayofweek = (int) gmdate("w", (int) $this->recur["start"]); //0 (for Sunday) through 6 (for Saturday)
584
585
			$term = (int) $this->recur["type"];
586
			switch($term)
587
			{
588
				case 0x0A:
589
					// Daily
590
					if(!isset($this->recur["everyn"])) {
591
						return;
592
					}
593
594
					if($this->recur["subtype"] == 1) {
595
596
						// Daily every workday
597
						$rdata .= pack("VVVV", (6 * 24 * 60), 1, 0, 0x3E);
598
					} else {
599
						// Daily every N days (everyN in minutes)
600
601
						$everyn =  ((int) $this->recur["everyn"]) / 1440;
0 ignored issues
show
Unused Code introduced by
The assignment to $everyn is dead and can be removed.
Loading history...
602
603
						// Calc first occ
604
						$firstocc = $this->unixDataToRecurData($this->recur["start"]) % ((int) $this->recur["everyn"]);
605
606
						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], $this->recur["regen"] ? 1 : 0);
607
					}
608
					break;
609
				case 0x0B:
610
					// Weekly
611
					if(!isset($this->recur["everyn"])) {
612
						return;
613
					}
614
615
					if (!$this->recur["regen"] && !isset($this->recur["weekdays"])) {
616
						return;
617
					}
618
619
					// No need to calculate startdate if sliding flag was set.
620
					if (!$this->recur['regen']) {
621
						// Calculate start date of recurrence
622
623
						// Find the first day that matches one of the weekdays selected
624
						$daycount = 0;
625
						$dayskip = -1;
626
						for($j = 0; $j < 7; $j++) {
627
							if(((int) $this->recur["weekdays"]) & (1<<( ($dayofweek+$j)%7)) ) {
628
								if($dayskip == -1)
629
									$dayskip = $j;
630
631
								$daycount++;
632
							}
633
						}
634
635
						// $dayskip is the number of days to skip from the startdate until the first occurrence
636
						// $daycount is the number of days per week that an occurrence occurs
637
638
						$weekskip = 0;
639
						if(($dayofweek < $weekstart && $dayskip > 0) || ($dayofweek+$dayskip) > 6)
640
							$weekskip = 1;
641
642
						// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
643
						// remaining occurrences based on the start of the recurrence.
644
						if (((int) $this->recur["term"]) == 0x22) {
645
							// $weekskip is the amount of weeks to skip from the startdate before the first occurrence
646
							// $forwardcount is the maximum number of week occurrences we can go ahead after the first occurrence that
647
							// is still inside the recurrence. We subtract one to make sure that the last week is never forwarded over
648
							// (eg when numoccur = 2, and daycount = 1)
649
							$forwardcount = floor( (int) ($this->recur["numoccur"] -1 ) / $daycount);
650
651
							// $restocc is the number of occurrences left after $forwardcount whole weeks of occurrences, minus one
652
							// for the occurrence on the first day
653
							$restocc = ((int) $this->recur["numoccur"]) - ($forwardcount*$daycount) - 1;
654
655
							// $forwardcount is now the number of weeks we can go forward and still be inside the recurrence
656
							$forwardcount *= (int) $this->recur["everyn"];
657
						}
658
659
						// The real start is start + dayskip + weekskip-1 (since dayskip will already bring us into the next week)
660
						$this->recur["start"] = ((int) $this->recur["start"]) + ($dayskip * 24*60*60)+ ($weekskip *(((int) $this->recur["everyn"]) - 1) * 7 * 24*60*60);
661
					}
662
663
					// Calc first occ
664
					$firstocc = ($this->unixDataToRecurData($this->recur["start"]) ) % ( ((int) $this->recur["everyn"]) * 7 * 24 * 60);
665
666
					$firstocc -= (((int) gmdate("w", (int) $this->recur["start"])) - 1) * 24 * 60;
667
668
					if ($this->recur["regen"])
669
						$rdata .= pack("VVV", $firstocc, (int) $this->recur["everyn"], 1);
670
					else
671
						$rdata .= pack("VVVV", $firstocc, (int) $this->recur["everyn"], 0, (int) $this->recur["weekdays"]);
672
					break;
673
				case 0x0C:
674
					// Monthly
675
				case 0x0D:
676
					// Yearly
677
					if(!isset($this->recur["everyn"])) {
678
						return;
679
					}
680
					if($term == 0x0D /*yearly*/ && !isset($this->recur["month"])) {
681
						return;
682
					}
683
684
					if($term == 0x0C /*monthly*/) {
685
						$everyn = (int) $this->recur["everyn"];
686
					} else {
687
						$everyn = $this->recur["regen"] ? ((int) $this->recur["everyn"]) * 12 : 12;
688
					}
689
690
					// Get montday/month/year of original start
691
					$curmonthday = gmdate("j", (int) $this->recur["start"] );
692
					$curyear = gmdate("Y", (int) $this->recur["start"] );
693
					$curmonth = gmdate("n", (int) $this->recur["start"] );
694
695
					// Check if the recurrence ends after a number of occurrences, in that case we must calculate the
696
					// remaining occurrences based on the start of the recurrence.
697
					if (((int) $this->recur["term"]) == 0x22) {
698
						// $forwardcount is the number of occurrences we can skip and still be inside the recurrence range (minus
699
						// one to make sure there are always at least one occurrence left)
700
						$forwardcount = ((((int) $this->recur["numoccur"])-1) * $everyn );
701
					}
702
703
					// Get month for yearly on D'th day of month M
704
					if($term == 0x0D /*yearly*/) {
705
						$selmonth = floor(((int) $this->recur["month"]) / (24 * 60 *29)) + 1; // 1=jan, 2=feb, eg
706
					}
707
708
					switch((int) $this->recur["subtype"])
709
					{
710
						// on D day of every M month
711
						case 2:
712
							if(!isset($this->recur["monthday"])) {
713
								return;
714
							}
715
							// Recalc startdate
716
717
							// Set on the right begin day
718
719
							// Go the beginning of the month
720
							$this->recur["start"] -= ($curmonthday-1) * 24*60*60;
721
							// Go the the correct month day
722
							$this->recur["start"] += (((int) $this->recur["monthday"])-1) * 24*60*60;
723
724
							// If the previous calculation gave us a start date different than the original start date, then we need to skip to the first occurrence
725
							if ( ($term == 0x0C /*monthly*/ && ((int) $this->recur["monthday"]) < $curmonthday) ||
726
								($term == 0x0D /*yearly*/ && ( $selmonth != $curmonth || ($selmonth == $curmonth && ((int) $this->recur["monthday"]) < $curmonthday)) ))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $selmonth does not seem to be defined for all execution paths leading up to this point.
Loading history...
727
							{
728
								if($term == 0x0D /*yearly*/) {
729
									if ($curmonth > $selmonth) {//go to next occurrence in 'everyn' months minus difference in first occurrence and original date
730
										$count = $everyn - ($curmonth - $selmonth);
731
									} else if($curmonth < $selmonth) {//go to next occurrence upto difference in first occurrence and original date
732
										$count = $selmonth - $curmonth;
733
									} else {
734
										// Go to next occurrence while recurrence start date is greater than occurrence date but within same month
735
										if (((int) $this->recur["monthday"]) < $curmonthday) {
736
											$count = $everyn;
737
										}
738
									}
739
								} else {
740
									$count = $everyn; // Monthly, go to next occurrence in 'everyn' months
741
								}
742
743
								// Forward by $count months. This is done by getting the number of days in that month and forwarding that many days
744
								for($i=0; $i < $count; $i++) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $count does not seem to be defined for all execution paths leading up to this point.
Loading history...
745
									$this->recur["start"] += $this->getMonthInSeconds($curyear, $curmonth);
746
747
									if($curmonth == 12) {
748
										$curyear++;
749
										$curmonth = 0;
750
									}
751
									$curmonth++;
752
								}
753
							}
754
755
							// "start" is now pointing to the first occurrence, except that it will overshoot if the
756
							// month in which it occurs has less days than specified as the day of the month. So 31st
757
							// of each month will overshoot in february (29 days). We compensate for that by checking
758
							// if the day of the month we got is wrong, and then back up to the last day of the previous
759
							// month.
760
							if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 &&
761
								gmdate("j", ((int) $this->recur["start"])) < ((int) $this->recur["monthday"]))
762
							{
763
								$this->recur["start"] -= gmdate("j", ((int) $this->recur["start"])) * 24 * 60 *60;
764
							}
765
766
							// "start" is now the first occurrence
767
768
							if($term == 0x0C /*monthly*/) {
769
								// Calc first occ
770
								$monthIndex = ((((12%$everyn) * ((((int) gmdate("Y", $this->recur["start"])) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn;
771
772
								$firstocc = 0;
773
								for($i=0; $i < $monthIndex; $i++) {
774
									$firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60;
775
								}
776
777
								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
778
							} else {
779
								// Calc first occ
780
								$firstocc = 0;
781
								$monthIndex = (int) gmdate("n", $this->recur["start"]);
782
								for($i=1; $i < $monthIndex; $i++) {
783
									$firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60;
784
								}
785
786
								$rdata .= pack("VVVV", $firstocc, $everyn, $this->recur["regen"], (int) $this->recur["monthday"]);
787
							}
788
							break;
789
790
						case 3:
791
							// monthly: on Nth weekday of every M month
792
							// yearly: on Nth weekday of M month
793
							if(!isset($this->recur["weekdays"]) && !isset($this->recur["nday"])) {
794
								return;
795
							}
796
797
							$weekdays = (int) $this->recur["weekdays"];
798
							$nday = (int) $this->recur["nday"];
799
800
							// Calc startdate
801
							$monthbegindow = (int) $this->recur["start"];
802
803
							if($nday == 5) {
804
								// Set date on the last day of the last month
805
								$monthbegindow += (gmdate("t", $monthbegindow ) - gmdate("j", $monthbegindow )) * 24 * 60 * 60;
806
							} else {
807
								// Set on the first day of the month
808
								$monthbegindow -= ((gmdate("j", $monthbegindow )-1) * 24 * 60 * 60);
809
							}
810
811
							if($term == 0x0D /*yearly*/) {
812
								// Set on right month
813
								if($selmonth < $curmonth)
814
									$tmp = 12 - $curmonth + $selmonth;
815
								else
816
									$tmp = ($selmonth - $curmonth);
817
818
								for($i=0; $i < $tmp; $i++) {
819
									$monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
820
821
									if($curmonth == 12) {
822
										$curyear++;
823
										$curmonth = 0;
824
									}
825
									$curmonth++;
826
								}
827
828
							} else {
829
								// Check or you exist in the right month
830
831
								$dayofweek = gmdate("w", $monthbegindow);
832
								for($i = 0; $i < 7; $i++) {
833
									if($nday == 5 && (($dayofweek-$i)%7 >= 0) && (1<<( ($dayofweek-$i)%7) ) & $weekdays) {
834
										$day = gmdate("j", $monthbegindow) - $i;
835
										break;
836
									} else if($nday != 5 && (1<<( ($dayofweek+$i)%7) ) & $weekdays) {
837
										$day = (($nday-1)*7) + ($i+1);
838
										break;
839
									}
840
								}
841
842
								// Goto the next X month
843
								if(isset($day) && ($day < gmdate("j", (int) $this->recur["start"])) ) {
844
									if($nday == 5) {
845
										$monthbegindow += 24 * 60 * 60;
846
										if($curmonth == 12) {
847
											$curyear++;
848
											$curmonth = 0;
849
										}
850
										$curmonth++;
851
									}
852
853
									for($i=0; $i < $everyn; $i++) {
854
										$monthbegindow += $this->getMonthInSeconds($curyear, $curmonth);
855
856
										if($curmonth == 12) {
857
											$curyear++;
858
											$curmonth = 0;
859
										}
860
										$curmonth++;
861
									}
862
863
									if($nday == 5) {
864
										$monthbegindow -= 24 * 60 * 60;
865
									}
866
								}
867
							}
868
869
							//FIXME: weekstart?
870
871
							$day = 0;
872
							// Set start on the right day
873
							$dayofweek = gmdate("w", $monthbegindow);
874
							for($i = 0; $i < 7; $i++) {
875
								if($nday == 5 && (($dayofweek-$i)%7) >= 0&& (1<<(($dayofweek-$i)%7) ) & $weekdays) {
876
									$day = $i;
877
									break;
878
								} else if($nday != 5 && (1<<( ($dayofweek+$i)%7) ) & $weekdays) {
879
									$day = ($nday - 1) * 7 + ($i+1);
880
									break;
881
								}
882
							}
883
							if($nday == 5)
884
								$monthbegindow -= $day * 24 * 60 *60;
885
							else
886
								$monthbegindow += ($day-1) * 24 * 60 *60;
887
888
							$firstocc = 0;
889
890
							if($term == 0x0C /*monthly*/) {
891
								// Calc first occ
892
								$monthIndex = ((((12%$everyn) * (((int) gmdate("Y", $this->recur["start"]) - 1601)%$everyn)) % $everyn) + (((int) gmdate("n", $this->recur["start"])) - 1))%$everyn;
893
894
								for($i=0; $i < $monthIndex; $i++) {
895
									$firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), ($i%12)+1) / 60;
896
								}
897
898
								$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
899
							} else {
900
								// Calc first occ
901
								$monthIndex = (int) gmdate("n", $this->recur["start"]);
902
903
								for($i=1; $i < $monthIndex; $i++) {
904
									$firstocc+= $this->getMonthInSeconds(1601 + floor($i/12), $i) / 60;
905
								}
906
907
								$rdata .= pack("VVVVV", $firstocc, $everyn, 0, $weekdays, $nday);
908
							}
909
							break;
910
					}
911
					break;
912
913
914
915
			}
916
917
			if(!isset($this->recur["term"])) {
918
				return;
919
			}
920
921
			// Terminate
922
			$term = (int) $this->recur["term"];
923
			$rdata .= pack("CCCC", $term, 0x20, 0x00, 0x00);
924
925
			switch($term)
926
			{
927
				// After the given enddate
928
				case 0x21:
929
					$rdata .= pack("V", 10);
930
					break;
931
				// After a number of times
932
				case 0x22:
933
					if(!isset($this->recur["numoccur"])) {
934
						return;
935
					}
936
937
					$rdata .= pack("V", (int) $this->recur["numoccur"]);
938
					break;
939
				// Never ends
940
				case 0x23:
941
					$rdata .= pack("V", 0);
942
					break;
943
			}
944
945
			// Strange little thing for the recurrence type "every workday"
946
			if(((int) $this->recur["type"]) == 0x0B && ((int) $this->recur["subtype"]) == 1) {
947
				$rdata .= pack("V", 1);
948
			} else { // Other recurrences
949
				$rdata .= pack("V", 0);
950
			}
951
952
			// Exception data
953
954
			// Get all exceptions
955
			$deleted_items = $this->recur["deleted_occurrences"];
956
			$changed_items = $this->recur["changed_occurrences"];
957
958
			// Merge deleted and changed items into one list
959
			$items = $deleted_items;
960
961
			foreach($changed_items as $changed_item)
962
				array_push($items, $changed_item["basedate"]);
963
964
			sort($items);
965
966
			// Add the merged list in to the rdata
967
			$rdata .= pack("V", count($items));
968
			foreach($items as $item)
969
				$rdata .= pack("V", $this->unixDataToRecurData($item));
970
971
			// Loop through the changed exceptions (not deleted)
972
			$rdata .= pack("V", count($changed_items));
973
			$items = array();
974
975
			foreach($changed_items as $changed_item)
976
			{
977
				$items[] = $this->dayStartOf($changed_item["start"]);
978
			}
979
980
			sort($items);
981
982
			// Add the changed items list int the rdata
983
			foreach($items as $item)
984
				$rdata .= pack("V", $this->unixDataToRecurData($item));
985
986
			// Set start date
987
			$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["start"]));
0 ignored issues
show
Bug introduced by
(int)$this->recur['start'] of type integer is incompatible with the type Date expected by parameter $date of BaseRecurrence::unixDataToRecurData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

987
			$rdata .= pack("V", $this->unixDataToRecurData(/** @scrutinizer ignore-type */ (int) $this->recur["start"]));
Loading history...
988
989
			// Set enddate
990
			switch($term)
991
			{
992
				// After the given enddate
993
				case 0x21:
994
					$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]));
995
					break;
996
				// After a number of times
997
				case 0x22:
998
					// @todo: calculate enddate with intval($this->recur["startocc"]) + intval($this->recur["duration"]) > 24 hour
999
					$occenddate = (int) $this->recur["start"];
1000
1001
					switch((int) $this->recur["type"]) {
1002
						case 0x0A: //daily
1003
1004
							if($this->recur["subtype"] == 1) {
1005
								// Daily every workday
1006
								$restocc = (int) $this->recur["numoccur"];
1007
1008
								// Get starting weekday
1009
								$nowtime = $this->gmtime($occenddate);
0 ignored issues
show
Bug introduced by
$occenddate of type integer is incompatible with the type Date expected by parameter $time of BaseRecurrence::gmtime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1009
								$nowtime = $this->gmtime(/** @scrutinizer ignore-type */ $occenddate);
Loading history...
1010
								$j = $nowtime["tm_wday"];
1011
1012
								while(1)
1013
								{
1014
									if(($j%7) > 0 && ($j%7)<6 ) {
1015
										$restocc--;
1016
									}
1017
1018
									$j++;
1019
1020
									if($restocc <= 0)
1021
										break;
1022
1023
									$occenddate += 24*60*60;
1024
								}
1025
1026
							} else {
1027
								// -1 because the first day already counts (from 1-1-1980 to 1-1-1980 is 1 occurrence)
1028
								$occenddate += (((int) $this->recur["everyn"]) * 60 * (((int) $this->recur["numoccur"]-1)));
1029
							}
1030
							break;
1031
						case 0x0B: //weekly
1032
							// Needed values
1033
							// $forwardcount - number of weeks we can skip forward
1034
							// $restocc - number of remaining occurrences after the week skip
1035
1036
							// Add the weeks till the last item
1037
							$occenddate+=($forwardcount*7*24*60*60);
1038
1039
							$dayofweek = gmdate("w", $occenddate);
0 ignored issues
show
Bug introduced by
$occenddate of type double is incompatible with the type integer|null expected by parameter $timestamp of gmdate(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1039
							$dayofweek = gmdate("w", /** @scrutinizer ignore-type */ $occenddate);
Loading history...
1040
1041
							// Loop through the last occurrences until we have had them all
1042
							for($j = 1; $restocc>0; $j++)
1043
							{
1044
								// Jump to the next week (which may be N weeks away) when going over the week boundary
1045
								if((($dayofweek+$j)%7) == $weekstart)
1046
									$occenddate += (((int) $this->recur["everyn"])-1) * 7 * 24*60*60;
1047
1048
								// If this is a matching day, once less occurrence to process
1049
								if(((int) $this->recur["weekdays"]) & (1<<(($dayofweek+$j)%7)) ) {
1050
									$restocc--;
1051
								}
1052
1053
								// Next day
1054
								$occenddate += 24*60*60;
1055
							}
1056
1057
							break;
1058
						case 0x0C: //monthly
1059
						case 0x0D: //yearly
1060
1061
							$curyear = gmdate("Y", (int) $this->recur["start"] );
1062
							$curmonth = gmdate("n", (int) $this->recur["start"] );
1063
							// $forwardcount = months
1064
1065
							switch((int) $this->recur["subtype"])
1066
							{
1067
								case 2: // on D day of every M month
1068
									while($forwardcount > 0)
1069
									{
1070
										$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1071
1072
										if($curmonth >=12) {
1073
											$curmonth = 1;
1074
											$curyear++;
1075
										} else {
1076
											$curmonth++;
1077
										}
1078
										$forwardcount--;
1079
									}
1080
1081
									// compensation between 28 and 31
1082
									if(((int) $this->recur["monthday"]) >=28 && ((int) $this->recur["monthday"]) <= 31 &&
1083
										gmdate("j", $occenddate) < ((int) $this->recur["monthday"]))
1084
									{
1085
										if(gmdate("j", $occenddate) < 28)
1086
											$occenddate -= gmdate("j", $occenddate) * 24 * 60 *60;
1087
										else
1088
											$occenddate += (gmdate("t", $occenddate) - gmdate("j", $occenddate)) * 24 * 60 *60;
1089
									}
1090
1091
1092
									break;
1093
								case 3: // on Nth weekday of every M month
1094
									$nday = (int) $this->recur["nday"]; //1 tot 5
1095
									$weekdays = (int) $this->recur["weekdays"];
1096
1097
1098
									while($forwardcount > 0)
1099
									{
1100
										$occenddate += $this->getMonthInSeconds($curyear, $curmonth);
1101
										if($curmonth >=12) {
1102
											$curmonth = 1;
1103
											$curyear++;
1104
										} else {
1105
											$curmonth++;
1106
										}
1107
1108
										$forwardcount--;
1109
									}
1110
1111
									if($nday == 5) {
1112
										// Set date on the last day of the last month
1113
										$occenddate += (gmdate("t", $occenddate ) - gmdate("j", $occenddate )) * 24 * 60 * 60;
1114
									} else {
1115
										// Set date on the first day of the last month
1116
										$occenddate -= (gmdate("j", $occenddate )-1) * 24 * 60 * 60;
1117
									}
1118
1119
									for($i = 0; $i < 7; $i++) {
1120
										if( $nday == 5 && (1<<( (gmdate("w", $occenddate)-$i)%7) ) & $weekdays) {
1121
											$occenddate -= $i * 24 * 60 * 60;
1122
											break;
1123
										} else if($nday != 5 && (1<<( (gmdate("w", $occenddate)+$i)%7) ) & $weekdays) {
1124
											$occenddate +=  ($i + (($nday-1) *7)) * 24 * 60 * 60;
1125
											break;
1126
										}
1127
									}
1128
1129
								break; //case 3:
1130
								}
1131
1132
							break;
1133
1134
					}
1135
1136
					if (defined("PHP_INT_MAX") && $occenddate > PHP_INT_MAX)
1137
						$occenddate = PHP_INT_MAX;
1138
1139
					$this->recur["end"] = $occenddate;
1140
1141
					$rdata .= pack("V", $this->unixDataToRecurData((int) $this->recur["end"]) );
1142
					break;
1143
				// Never ends
1144
				case 0x23:
1145
				default:
1146
					$this->recur["end"] = 0x7fffffff; // max date -> 2038
1147
					$rdata .= pack("V", 0x5AE980DF);
1148
					break;
1149
			}
1150
1151
			// UTC date
1152
			$utcstart = $this->toGMT($this->tz, (int) $this->recur["start"]);
1153
			$utcend = $this->toGMT($this->tz, (int) $this->recur["end"]);
1154
1155
			//utc date+time
1156
			$utcfirstoccstartdatetime = (isset($this->recur["startocc"])) ? $utcstart + (((int) $this->recur["startocc"])*60) : $utcstart;
1157
			$utcfirstoccenddatetime = (isset($this->recur["endocc"])) ? $utcstart + (((int) $this->recur["endocc"]) * 60) : $utcstart;
1158
1159
			// update reminder time
1160
			mapi_setprops($this->message, Array($this->proptags["reminder_time"] => $utcfirstoccstartdatetime ));
1161
1162
			// update first occurrence date
1163
			mapi_setprops($this->message, Array($this->proptags["startdate"] => $utcfirstoccstartdatetime ));
1164
			mapi_setprops($this->message, Array($this->proptags["duedate"] => $utcfirstoccenddatetime ));
1165
			mapi_setprops($this->message, Array($this->proptags["commonstart"] => $utcfirstoccstartdatetime ));
1166
			mapi_setprops($this->message, Array($this->proptags["commonend"] => $utcfirstoccenddatetime ));
1167
1168
			// Set Outlook properties, if it is an appointment
1169
			if (isset($this->messageprops[$this->proptags["message_class"]]) && $this->messageprops[$this->proptags["message_class"]] == "IPM.Appointment") {
1170
				// update real begin and real end date
1171
				mapi_setprops($this->message, Array($this->proptags["startdate_recurring"] => $utcstart));
1172
				mapi_setprops($this->message, Array($this->proptags["enddate_recurring"] => $utcend));
1173
1174
				// recurrencetype
1175
				// Strange enough is the property recurrencetype, (type-0x9) and not the CDO recurrencetype
1176
				mapi_setprops($this->message, Array($this->proptags["recurrencetype"] => ((int) $this->recur["type"]) - 0x9));
1177
1178
				// set named prop 'side_effects' to 369, needed for Outlook to ask for single or total recurrence when deleting
1179
				mapi_setprops($this->message, Array($this->proptags["side_effects"] => 369));
1180
			} else {
1181
				mapi_setprops($this->message, Array($this->proptags["side_effects"] => 3441));
1182
			}
1183
1184
			// FlagDueBy is datetime of the first reminder occurrence. Outlook gives on this time a reminder popup dialog
1185
			// Any change of the recurrence (including changing and deleting exceptions) causes the flagdueby to be reset
1186
			// to the 'next' occurrence; this makes sure that deleting the next occurrence will correctly set the reminder to
1187
			// the occurrence after that. The 'next' occurrence is defined as being the first occurrence that starts at moment X (server time)
1188
			// with the reminder flag set.
1189
			$reminderprops = mapi_getprops($this->message, array($this->proptags["reminder_minutes"], $this->proptags["flagdueby"]) );
1190
			if(isset($reminderprops[$this->proptags["reminder_minutes"]]) ) {
1191
			    $occ = false;
1192
			    $occurrences = $this->getItems(time(), 0x7ff00000, 3, true);
0 ignored issues
show
Bug introduced by
time() of type integer is incompatible with the type date expected by parameter $start of BaseRecurrence::getItems(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1192
			    $occurrences = $this->getItems(/** @scrutinizer ignore-type */ time(), 0x7ff00000, 3, true);
Loading history...
Bug introduced by
2146435072 of type integer is incompatible with the type date expected by parameter $end of BaseRecurrence::getItems(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1192
			    $occurrences = $this->getItems(time(), /** @scrutinizer ignore-type */ 0x7ff00000, 3, true);
Loading history...
1193
1194
                for($i = 0, $len = count($occurrences) ; $i < $len; $i++) {
1195
                    // This will actually also give us appointments that have already started, but not yet ended. Since we want the next
1196
                    // reminder that occurs after time(), we may have to skip the first few entries. We get 3 entries since that is the maximum
1197
                    // number that would be needed (assuming reminder for item X cannot be before the previous occurrence starts). Worst case:
1198
                    // time() is currently after start but before end of item, but reminder of next item has already passed (reminder for next item
1199
                    // can be DURING the previous item, eg daily allday events). In that case, the first and second items must be skipped.
1200
1201
			        if(($occurrences[$i][$this->proptags["startdate"]] - $reminderprops[$this->proptags["reminder_minutes"]] * 60) > time()) {
1202
			            $occ = $occurrences[$i];
1203
			            break;
1204
                    }
1205
			    }
1206
1207
			    if($occ) {
1208
					if (isset($reminderprops[$this->proptags["flagdueby"]])) {
1209
						mapi_setprops($this->message, Array($this->proptags["flagdueby"] => $reminderprops[$this->proptags["flagdueby"]]));
1210
					} else {
1211
						mapi_setprops($this->message, Array($this->proptags["flagdueby"] => $occ[$this->proptags["startdate"]] - ($reminderprops[$this->proptags["reminder_minutes"]] * 60) ));
1212
					}
1213
                } else {
1214
                    // Last reminder passed, no reminders any more.
1215
                    mapi_setprops($this->message, Array($this->proptags["reminder"] => false, $this->proptags["flagdueby"] => 0x7ff00000));
1216
                }
1217
			}
1218
1219
			// Default data
1220
			// Second item (0x08) indicates the Outlook version (see documentation at the bottom of this file for more information)
1221
			$rdata .= pack("VCCCC", 0x00003006, 0x08, 0x30, 0x00, 0x00);
1222
1223
			if(isset($this->recur["startocc"]) && isset($this->recur["endocc"])) {
1224
				// Set start and endtime in minutes
1225
				$rdata .= pack("VV", (int) $this->recur["startocc"], (int) $this->recur["endocc"]);
1226
			}
1227
1228
			// Detailed exception data
1229
1230
			$changed_items = $this->recur["changed_occurrences"];
1231
1232
			$rdata .= pack("v", count($changed_items));
1233
1234
			foreach($changed_items as $changed_item)
1235
			{
1236
				// Set start and end time of exception
1237
				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1238
				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1239
				$rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1240
1241
				//Bitmask
1242
				$bitmask = 0;
1243
1244
				// Check for changed strings
1245
				if(isset($changed_item["subject"]))	{
1246
					$bitmask |= 1 << 0;
1247
				}
1248
1249
				if(isset($changed_item["remind_before"])) {
1250
					$bitmask |= 1 << 2;
1251
				}
1252
1253
				if(isset($changed_item["reminder_set"])) {
1254
					$bitmask |= 1 << 3;
1255
				}
1256
1257
				if(isset($changed_item["location"])) {
1258
					$bitmask |= 1 << 4;
1259
				}
1260
1261
				if(isset($changed_item["busystatus"])) {
1262
					$bitmask |= 1 << 5;
1263
				}
1264
1265
				if(isset($changed_item["alldayevent"]))	{
1266
					$bitmask |= 1 << 7;
1267
				}
1268
1269
				if(isset($changed_item["label"])) {
1270
					$bitmask |= 1 << 8;
1271
				}
1272
1273
				$rdata .= pack("v", $bitmask);
1274
1275
				// Set "subject"
1276
				if(isset($changed_item["subject"]))	{
1277
					// convert utf-8 to non-unicode blob string (us-ascii?)
1278
					$subject = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["subject"]);
1279
					$length = strlen($subject);
1280
					$rdata .= pack("vv", $length + 1, $length);
1281
					$rdata .= pack("a".$length, $subject);
1282
				}
1283
1284
				if(isset($changed_item["remind_before"])) {
1285
					$rdata .= pack("V", $changed_item["remind_before"]);
1286
				}
1287
1288
				if(isset($changed_item["reminder_set"])) {
1289
					$rdata .= pack("V", $changed_item["reminder_set"]);
1290
				}
1291
1292
				if(isset($changed_item["location"])) {
1293
					$location = iconv("UTF-8", "windows-1252//TRANSLIT", $changed_item["location"]);
1294
					$length = strlen($location);
1295
					$rdata .= pack("vv", $length + 1, $length);
1296
					$rdata .= pack("a".$length, $location);
1297
				}
1298
1299
				if(isset($changed_item["busystatus"])) {
1300
					$rdata .= pack("V", $changed_item["busystatus"]);
1301
				}
1302
1303
				if(isset($changed_item["alldayevent"]))	{
1304
					$rdata .= pack("V", $changed_item["alldayevent"]);
1305
				}
1306
1307
				if(isset($changed_item["label"])) {
1308
					$rdata .= pack("V", $changed_item["label"]);
1309
				}
1310
			}
1311
1312
			$rdata .= pack("V", 0);
1313
1314
			// write extended data
1315
			foreach($changed_items as $changed_item)
1316
			{
1317
				$rdata .= pack("V", 0);
1318
				if(isset($changed_item["subject"]) || isset($changed_item["location"])) {
1319
					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["start"]));
1320
					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["end"]));
1321
					$rdata .= pack("V", $this->unixDataToRecurData($changed_item["basedate"]));
1322
				}
1323
1324
				if(isset($changed_item["subject"])) {
1325
					$subject = iconv("UTF-8", "UCS-2LE", $changed_item["subject"]);
1326
					$length = iconv_strlen($subject, "UCS-2LE");
1327
					$rdata .= pack("v", $length);
1328
					$rdata .= pack("a".$length*2, $subject);
1329
				}
1330
1331
				if(isset($changed_item["location"])) {
1332
					$location = iconv("UTF-8", "UCS-2LE", $changed_item["location"]);
1333
					$length = iconv_strlen($location, "UCS-2LE");
1334
					$rdata .= pack("v", $length);
1335
					$rdata .= pack("a".$length*2, $location);
1336
				}
1337
1338
				if(isset($changed_item["subject"]) || isset($changed_item["location"])) {
1339
					$rdata .= pack("V", 0);
1340
				}
1341
			}
1342
1343
			$rdata .= pack("V", 0);
1344
1345
			// Set props
1346
			mapi_setprops($this->message, Array($this->proptags["recurring_data"] => $rdata, $this->proptags["recurring"] => true));
1347
			if(isset($this->tz) && $this->tz){
1348
				$timezone = "GMT";
1349
				if ($this->tz["timezone"]!=0){
1350
					// Create user readable timezone information
1351
					$timezone = sprintf("(GMT %s%02d:%02d)",	(-$this->tz["timezone"]>0 ? "+" : "-"),
1352
															abs($this->tz["timezone"]/60),
1353
															abs($this->tz["timezone"]%60));
1354
				}
1355
				mapi_setprops($this->message, Array($this->proptags["timezone_data"] => $this->getTimezoneData($this->tz),
1356
													$this->proptags["timezone"] => $timezone));
1357
			}
1358
		}
1359
1360
		/**
1361
		* Function which converts a recurrence date timestamp to an unix date timestamp.
1362
		* @author Steve Hardy
1363
		* @param Int $rdate the date which will be converted
1364
		* @return Int the converted date
1365
		*/
1366
		function recurDataToUnixData($rdate)
1367
		{
1368
			return ($rdate - 194074560) * 60 ;
1369
		}
1370
1371
		/**
1372
		* Function which converts an unix date timestamp to recurrence date timestamp.
1373
		* @author Johnny Biemans
1374
		* @param Date $date the date which will be converted
1375
		* @return Int the converted date in minutes
1376
		*/
1377
		function unixDataToRecurData($date)
1378
		{
1379
			return ($date / 60) + 194074560;
1380
		}
1381
1382
		/**
1383
		* gmtime() doesn't exist in standard PHP, so we have to implement it ourselves
1384
		* @author Steve Hardy
1385
		*/
1386
		function GetTZOffset($ts)
1387
		{
1388
			$Offset = date("O", $ts);
1389
1390
			$Parity = $Offset < 0 ? -1 : 1;
1391
			$Offset = $Parity * $Offset;
1392
			$Offset = ($Offset - ($Offset % 100)) / 100 * 60 + $Offset % 100;
1393
1394
			return $Parity * $Offset;
1395
		}
1396
1397
		/**
1398
		* gmtime() doesn't exist in standard PHP, so we have to implement it ourselves
1399
		* @author Steve Hardy
1400
		* @param Date $time
1401
		* @return Date GMT Time
1402
		*/
1403
		function gmtime($time)
1404
		{
1405
			$TZOffset = $this->GetTZOffset($time);
1406
1407
			$t_time = $time - $TZOffset * 60; #Counter adjust for localtime()
1408
			$t_arr = localtime($t_time, 1);
1409
1410
			return $t_arr;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $t_arr returns the type array which is incompatible with the documented return type Date.
Loading history...
1411
		}
1412
1413
		function isLeapYear($year) {
1414
			return ( $year % 4 == 0 && ($year % 100 != 0 || $year % 400 == 0) );
1415
		}
1416
1417
		function getMonthInSeconds($year, $month)
1418
		{
1419
			if( in_array($month, array(1,3,5,7,8,10,12) ) ) {
1420
				$day = 31;
1421
			} else if( in_array($month, array(4,6,9,11) ) ) {
1422
				$day = 30;
1423
			} else {
1424
				$day = 28;
1425
				if( $this->isLeapYear($year) == 1 )
1426
					$day++;
1427
			}
1428
			return $day * 24 * 60 * 60;
1429
		}
1430
1431
		/**
1432
		 * Function to get a date by Year Nr, Month Nr, Week Nr, Day Nr, and hour
1433
		 * @param int $year
1434
		 * @param int $month
1435
		 * @param int $week
1436
		 * @param int $day
1437
		 * @param int $hour
1438
		 * @return returns the timestamp of the given date, timezone-independent
0 ignored issues
show
Bug introduced by
The type returns was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1439
		 */
1440
		function getDateByYearMonthWeekDayHour($year, $month, $week, $day, $hour)
1441
		{
1442
			// get first day of month
1443
			$date = gmmktime(0,0,0,$month,0,$year + 1900);
1444
1445
			// get wday info
1446
			$gmdate = $this->gmtime($date);
0 ignored issues
show
Bug introduced by
$date of type integer is incompatible with the type Date expected by parameter $time of BaseRecurrence::gmtime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1446
			$gmdate = $this->gmtime(/** @scrutinizer ignore-type */ $date);
Loading history...
1447
1448
			$date -= $gmdate["tm_wday"] * 24 * 60 * 60; // back up to start of week
1449
1450
			$date += $week * 7 * 24 * 60 * 60; // go to correct week nr
1451
			$date += $day * 24 * 60 * 60;
1452
			$date += $hour * 60 * 60;
1453
1454
			$gmdate = $this->gmtime($date);
1455
1456
			// if we are in the next month, then back up a week, because week '5' means
1457
			// 'last week of month'
1458
1459
			if($gmdate["tm_mon"]+1 != $month)
1460
				$date -= 7 * 24 * 60 * 60;
1461
1462
			return $date;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $date returns the type integer which is incompatible with the documented return type returns.
Loading history...
1463
		}
1464
1465
		/**
1466
		 * getTimezone gives the timezone offset (in minutes) of the given
1467
		 * local date/time according to the given TZ info
1468
		 */
1469
		function getTimezone($tz, $date)
1470
		{
1471
			// No timezone -> GMT (+0)
1472
			if(!isset($tz["timezone"]))
1473
				return 0;
1474
1475
			$dst = false;
1476
			$gmdate = $this->gmtime($date);
1477
1478
			$dststart = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dststartmonth"], $tz["dststartweek"], 0, $tz["dststarthour"]);
1479
			$dstend = $this->getDateByYearMonthWeekDayHour($gmdate["tm_year"], $tz["dstendmonth"], $tz["dstendweek"], 0, $tz["dstendhour"]);
1480
1481
			if($dststart <= $dstend) {
1482
				// Northern hemisphere, eg DST is during Mar-Oct
1483
				if($date > $dststart && $date < $dstend) {
1484
					$dst = true;
1485
				}
1486
			} else {
1487
				// Southern hemisphere, eg DST is during Oct-Mar
1488
				if($date < $dstend || $date > $dststart) {
1489
					$dst = true;
1490
				}
1491
			}
1492
1493
			if($dst) {
1494
				return $tz["timezone"] + $tz["timezonedst"];
1495
			} else {
1496
				return $tz["timezone"];
1497
			}
1498
		}
1499
1500
		/**
1501
		 * getWeekNr() returns the week nr of the month (ie first week of february is 1)
1502
		 */
1503
		function getWeekNr($date)
1504
		{
1505
			$gmdate = gmtime($date);
1506
			$gmdate["tm_mday"] = 0;
1507
			return strftime("%W", $date) - strftime("%W", gmmktime($gmdate)) + 1;
1508
		}
1509
1510
		/**
1511
		 * parseTimezone parses the timezone as specified in named property 0x8233
1512
		 * in Outlook calendar messages. Returns the timezone in minutes negative
1513
		 * offset (GMT +2:00 -> -120)
1514
		 */
1515
		function parseTimezone($data)
1516
		{
1517
			if(strlen($data) < 48)
1518
				return;
1519
1520
			$tz = unpack("ltimezone/lunk/ltimezonedst/lunk/ldstendmonth/vdstendweek/vdstendhour/lunk/lunk/vunk/ldststartmonth/vdststartweek/vdststarthour/lunk/vunk", $data);
1521
			return $tz;
1522
		}
1523
1524
		function getTimezoneData($tz)
1525
		{
1526
			$data = pack("lllllvvllvlvvlv", $tz["timezone"], 0, $tz["timezonedst"], 0, $tz["dstendmonth"], $tz["dstendweek"], $tz["dstendhour"], 0, 0, 0, $tz["dststartmonth"], $tz["dststartweek"], $tz["dststarthour"], 0 ,0);
1527
1528
			return $data;
1529
		}
1530
1531
		/**
1532
		 * createTimezone creates the timezone as specified in the named property 0x8233
1533
		 * see also parseTimezone()
1534
		 * $tz is an array with the timezone data
1535
		 */
1536
		function createTimezone($tz)
1537
		{
1538
			$data = pack("lxxxxlxxxxlvvxxxxxxxxxxlvvxxxxxx",
1539
						$tz["timezone"],
1540
						array_key_exists("timezonedst",$tz)?$tz["timezonedst"]:0,
1541
						array_key_exists("dstendmonth",$tz)?$tz["dstendmonth"]:0,
1542
						array_key_exists("dstendweek",$tz)?$tz["dstendweek"]:0,
1543
						array_key_exists("dstendhour",$tz)?$tz["dstendhour"]:0,
1544
						array_key_exists("dststartmonth",$tz)?$tz["dststartmonth"]:0,
1545
						array_key_exists("dststartweek",$tz)?$tz["dststartweek"]:0,
1546
						array_key_exists("dststarthour",$tz)?$tz["dststarthour"]:0
1547
					);
1548
1549
			return $data;
1550
		}
1551
1552
		/**
1553
		 * toGMT returns a timestamp in GMT time for the time and timezone given
1554
		 */
1555
		function toGMT($tz, $date) {
1556
			if(!isset($tz['timezone']))
1557
				return $date;
1558
			$offset = $this->getTimezone($tz, $date);
1559
1560
			return $date + $offset * 60;
1561
		}
1562
1563
		/**
1564
		 * fromGMT returns a timestamp in the local timezone given from the GMT time given
1565
		 */
1566
		function fromGMT($tz, $date) {
1567
			$offset = $this->getTimezone($tz, $date);
1568
1569
			return $date - $offset * 60;
1570
		}
1571
1572
		/**
1573
		 * Function to get timestamp of the beginning of the day of the timestamp given
1574
		 * @param date $date
1575
		 * @return date timestamp referring to same day but at 00:00:00
1576
		 */
1577
		function dayStartOf($date)
1578
		{
1579
			$time1 = $this->gmtime($date);
1580
1581
			return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, $time1["tm_mday"], $time1["tm_year"] + 1900);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmmktime(0, 0, 0,...ime1['tm_year'] + 1900) returns the type integer which is incompatible with the documented return type date.
Loading history...
1582
		}
1583
1584
		/**
1585
		 * Function to get timestamp of the beginning of the month of the timestamp given
1586
		 * @param date $date
1587
		 * @return date Timestamp referring to same month but on the first day, and at 00:00:00
1588
		 */
1589
		function monthStartOf($date)
1590
		{
1591
			$time1 = $this->gmtime($date);
1592
1593
			return gmmktime(0, 0, 0, $time1["tm_mon"] + 1, 1, $time1["tm_year"] + 1900);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmmktime(0, 0, 0,...ime1['tm_year'] + 1900) returns the type integer which is incompatible with the documented return type date.
Loading history...
1594
		}
1595
1596
		/**
1597
		 * Function to get timestamp of the beginning of the year of the timestamp given
1598
		 * @param date $date
1599
		 * @return date Timestamp referring to the same year but on Jan 01, at 00:00:00
1600
		 */
1601
		function yearStartOf($date)
1602
		{
1603
			$time1 = $this->gmtime($date);
1604
1605
			return gmmktime(0, 0, 0, 1, 1, $time1["tm_year"] + 1900);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmmktime(0, 0, 0,...ime1['tm_year'] + 1900) returns the type integer which is incompatible with the documented return type date.
Loading history...
1606
		}
1607
1608
1609
		/**
1610
		 * Function which returns the items in a given interval. This included expansion of the recurrence and
1611
		 * processing of exceptions (modified and deleted).
1612
		 *
1613
		 * @param string $entryid the entryid of the message
1614
		 * @param array $props the properties of the message
1615
		 * @param date $start start time of the interval (GMT)
1616
		 * @param date $end end time of the interval (GMT)
1617
		 */
1618
		function getItems($start, $end, $limit = 0, $remindersonly = false)
1619
		{
1620
			$items = array();
1621
1622
			if(isset($this->recur)) {
1623
1624
				// Optimization: remindersonly and default reminder is off; since only exceptions with reminder set will match, just look which
1625
				// exceptions are in range and have a reminder set
1626
				if($remindersonly && (!isset($this->messageprops[$this->proptags["reminder"]]) || $this->messageprops[$this->proptags["reminder"]] == false)) {
1627
					// Sort exceptions by start time
1628
					uasort($this->recur["changed_occurrences"], array($this, "sortExceptionStart"));
1629
1630
					// Loop through all changed exceptions
1631
					foreach($this->recur["changed_occurrences"] as $exception) {
1632
						// Check reminder set
1633
						if(!isset($exception["reminder"]) || $exception["reminder"] == false)
1634
							continue;
1635
1636
						// Convert to GMT
1637
						$occstart = $this->toGMT($tz, $exception["start"]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tz seems to be never defined.
Loading history...
1638
						$occend = $this->toGMT($tz, $exception["end"]);
1639
1640
						// Check range criterium
1641
						if($occstart > $end || $occend < $start)
1642
							continue;
1643
1644
						// OK, add to items.
1645
						array_push($items, $this->getExceptionProperties($exception));
0 ignored issues
show
Bug introduced by
The method getExceptionProperties() does not exist on BaseRecurrence. It seems like you code against a sub-type of BaseRecurrence such as Recurrence. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1645
						array_push($items, $this->/** @scrutinizer ignore-call */ getExceptionProperties($exception));
Loading history...
1646
						if($limit && (count($items) == $limit))
1647
							break;
1648
					}
1649
1650
					uasort($items, array($this, "sortStarttime"));
1651
1652
					return $items;
1653
				}
1654
1655
				// From here on, the dates of the occurrences are calculated in local time, so the days we're looking
1656
				// at are calculated from the local time dates of $start and $end
1657
1658
				if (isset($this->recur['regen']) && $this->recur['regen'] && isset($this->action['datecompleted'])) {
1659
					$daystart = $this->dayStartOf($this->action['datecompleted']);
1660
				} else {
1661
					$daystart = $this->dayStartOf($this->recur["start"]); // start on first day of occurrence
1662
				}
1663
1664
				// Calculate the last day on which we want to be looking at a recurrence; this is either the end of the view
1665
				// or the end of the recurrence, whichever comes first
1666
				if($end > $this->toGMT($this->tz, $this->recur["end"])) {
1667
					$rangeend = $this->toGMT($this->tz, $this->recur["end"]);
1668
				} else {
1669
					$rangeend = $end;
1670
				}
1671
1672
				$dayend = $this->dayStartOf($this->fromGMT($this->tz, $rangeend));
0 ignored issues
show
Bug introduced by
$this->fromGMT($this->tz, $rangeend) of type integer is incompatible with the type date expected by parameter $date of BaseRecurrence::dayStartOf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1672
				$dayend = $this->dayStartOf(/** @scrutinizer ignore-type */ $this->fromGMT($this->tz, $rangeend));
Loading history...
1673
1674
				// Loop through the entire recurrence range of dates, and check for each occurrence whether it is in the view range.
1675
1676
				switch($this->recur["type"])
1677
				{
1678
				case 10:
1679
					// Daily
1680
					if($this->recur["everyn"] <= 0)
1681
						$this->recur["everyn"] = 1440;
1682
1683
					if($this->recur["subtype"] == 0) {
1684
						// Every Nth day
1685
						for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * $this->recur["everyn"]) {
1686
							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
0 ignored issues
show
introduced by
The method processOccurrenceItem() does not exist on BaseRecurrence. Maybe you want to declare this class abstract? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1686
							$this->/** @scrutinizer ignore-call */ 
1687
              processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
Loading history...
1687
						}
1688
					} else {
1689
						// Every workday
1690
						for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += 60 * 1440)
1691
						{
1692
							$nowtime = $this->gmtime($now);
0 ignored issues
show
Bug introduced by
It seems like $now can also be of type integer; however, parameter $time of BaseRecurrence::gmtime() does only seem to accept Date, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1692
							$nowtime = $this->gmtime(/** @scrutinizer ignore-type */ $now);
Loading history...
1693
							if ($nowtime["tm_wday"] > 0 && $nowtime["tm_wday"] < 6) { // only add items in the given timespace
1694
								$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1695
							}
1696
						}
1697
					}
1698
					break;
1699
				case 11:
1700
					// Weekly
1701
					if($this->recur["everyn"] <= 0)
1702
						$this->recur["everyn"] = 1;
1703
1704
					// If sliding flag is set then move to 'n' weeks
1705
					if ($this->recur['regen']) $daystart += (60 * 60 * 24 * 7 * $this->recur["everyn"]);
1706
1707
					for($now = $daystart; $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += (60 * 60 * 24 * 7 * $this->recur["everyn"]))
1708
					{
1709
						if ($this->recur['regen']) {
1710
							$this->processOccurrenceItem($items, $start, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1711
						} else {
1712
							// Loop through the whole following week to the first occurrence of the week, add each day that is specified
1713
							for($wday = 0; $wday < 7; $wday++)
1714
							{
1715
								$daynow = $now + $wday * 60 * 60 * 24;
1716
								//checks weather the next coming day in recurring pattern is less than or equal to end day of the recurring item
1717
								if ($daynow <= $dayend){
1718
									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1719
									if(($this->recur["weekdays"] &(1 << $nowtime["tm_wday"]))) { // Selected ?
1720
										$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1721
									}
1722
								}
1723
							}
1724
						}
1725
					}
1726
					break;
1727
				case 12:
1728
					// Monthly
1729
					if($this->recur["everyn"] <= 0)
1730
						$this->recur["everyn"] = 1;
1731
1732
					// Loop through all months from start to end of occurrence, starting at beginning of first month
1733
					for($now = $this->monthStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 )
1734
					{
1735
						if(isset($this->recur["monthday"]) &&($this->recur['monthday'] != "undefined") && !$this->recur['regen']) { // Day M of every N months
1736
							$difference = 1;
1737
							if ($this->daysInMonth($now, $this->recur["everyn"]) < $this->recur["monthday"]) {
0 ignored issues
show
Bug introduced by
$now of type date is incompatible with the type integer expected by parameter $date of BaseRecurrence::daysInMonth(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1737
							if ($this->daysInMonth(/** @scrutinizer ignore-type */ $now, $this->recur["everyn"]) < $this->recur["monthday"]) {
Loading history...
1738
								$difference = $this->recur["monthday"] - $this->daysInMonth($now, $this->recur["everyn"]) + 1;
1739
							}
1740
							$daynow = $now + (($this->recur["monthday"] - $difference) * 24 * 60 * 60);
1741
							//checks weather the next coming day in recurrence pattern is less than or equal to end day of the recurring item
1742
							if ($daynow <= $dayend){
1743
								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1744
							}
1745
						}
1746
						else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] of every N months
1747
						    // Sanitize input
1748
						    if($this->recur["weekdays"] == 0)
1749
						        $this->recur["weekdays"] = 1;
1750
1751
							// If nday is not set to the last day in the month
1752
							if ($this->recur["nday"] < 5) {
1753
								// keep the track of no. of time correct selection pattern(like 2nd weekday, 4th fiday, etc.)is matched
1754
								$ndaycounter = 0;
1755
								// Find matching weekday in this month
1756
								for($day = 0, $total = $this->daysInMonth($now, 1); $day < $total; $day++)
1757
								{
1758
									$daynow = $now + $day * 60 * 60 * 24;
1759
									$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1760
1761
									if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1762
										$ndaycounter ++;
1763
									}
1764
									// check the selected pattern is same as asked Nth weekday,If so set the firstday
1765
									if($this->recur["nday"] == $ndaycounter){
1766
										$firstday = $day;
1767
										break;
1768
									}
1769
								}
1770
								// $firstday is the day of the month on which the asked pattern of nth weekday matches
1771
								$daynow = $now + $firstday * 60 * 60 * 24;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $firstday does not seem to be defined for all execution paths leading up to this point.
Loading history...
1772
							} else {
1773
								// Find last day in the month ($now is the firstday of the month)
1774
								$NumDaysInMonth =  $this->daysInMonth($now, 1);
1775
								$daynow = $now + (($NumDaysInMonth-1) * 24*60*60);
1776
1777
								$nowtime = $this->gmtime($daynow);
1778
								while (($this->recur["weekdays"] & (1 << $nowtime["tm_wday"]))==0){
1779
									$daynow -= 86400;
1780
									$nowtime = $this->gmtime($daynow);
1781
								}
1782
							}
1783
1784
							/**
1785
							 * checks weather the next coming day in recurrence pattern is less than or equal to end day of the			* recurring item.Also check weather the coming day in recurrence pattern is greater than or equal to start * of recurring pattern, so that appointment that fall under the recurrence range are only displayed.
1786
							 */
1787
							if ($daynow <= $dayend && $daynow >= $daystart){
1788
								$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz , $remindersonly);
1789
							}
1790
						} else if ($this->recur['regen']) {
1791
							$next_month_start = $now + ($this->daysInMonth($now, 1) * 24 * 60 * 60);
1792
							$now = $daystart +($this->daysInMonth($next_month_start, $this->recur['everyn']) * 24 * 60 * 60);
1793
1794
							if ($now <= $dayend) {
1795
								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1796
							}
1797
						}
1798
					}
1799
					break;
1800
				case 13:
1801
					// Yearly
1802
					if($this->recur["everyn"] <= 0)
1803
						$this->recur["everyn"] = 12;
1804
1805
					for($now = $this->yearStartOf($daystart); $now <= $dayend && ($limit == 0 || count($items) < $limit); $now += $this->daysInMonth($now, $this->recur["everyn"]) * 24 * 60 * 60 )
1806
					{
1807
						if(isset($this->recur["monthday"]) && !$this->recur['regen']) { // same as monthly, but in a specific month
1808
							// recur["month"] is in minutes since the beginning of the year
1809
							$month = $this->monthOfYear($this->recur["month"]); // $month is now month of year [0..11]
1810
							$monthday = $this->recur["monthday"]; // $monthday is day of the month [1..31]
1811
							$monthstart = $now + $this->daysInMonth($now, $month) * 24 * 60 * 60; // $monthstart is the timestamp of the beginning of the month
1812
							if($monthday > $this->daysInMonth($monthstart, 1))
1813
								$monthday = $this->daysInMonth($monthstart, 1);	// Cap $monthday on month length (eg 28 feb instead of 29 feb)
1814
							$daynow = $monthstart + ($monthday-1) * 24 * 60 * 60;
1815
							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1816
						}
1817
						else if(isset($this->recur["nday"]) && isset($this->recur["weekdays"])) { // Nth [weekday] in month X of every N years
1818
1819
							// Go the correct month
1820
							$monthnow = $now + $this->daysInMonth($now, $this->monthOfYear($this->recur["month"])) * 24 * 60 * 60;
1821
1822
							// Find first matching weekday in this month
1823
							for($wday = 0; $wday < 7; $wday++)
1824
							{
1825
								$daynow = $monthnow + $wday * 60 * 60 * 24;
1826
								$nowtime = $this->gmtime($daynow); // Get the weekday of the current day
1827
1828
								if($this->recur["weekdays"] & (1 << $nowtime["tm_wday"])) { // Selected ?
1829
									$firstday = $wday;
1830
									break;
1831
								}
1832
							}
1833
1834
							// Same as above (monthly)
1835
							$daynow = $monthnow + ($firstday + ($this->recur["nday"]-1)*7) * 60 * 60 * 24;
1836
1837
							while($this->monthStartOf($daynow) != $this->monthStartOf($monthnow)) {
0 ignored issues
show
Bug introduced by
$daynow of type integer is incompatible with the type date expected by parameter $date of BaseRecurrence::monthStartOf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1837
							while($this->monthStartOf(/** @scrutinizer ignore-type */ $daynow) != $this->monthStartOf($monthnow)) {
Loading history...
1838
								$daynow -= 7 * 60 * 60 * 24;
1839
							}
1840
1841
							$this->processOccurrenceItem($items, $start, $end, $daynow, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1842
						} else if ($this->recur['regen']) {
1843
							$year_starttime = $this->gmtime($now);
1844
							$is_next_leapyear = $this->isLeapYear($year_starttime['tm_year'] + 1900 + 1);	// +1 next year
1845
							$now = $daystart + ($is_next_leapyear ? 31622400 /* Leap year in seconds */ : 31536000 /*year in seconds*/);
1846
1847
							if ($now <= $dayend) {
1848
								$this->processOccurrenceItem($items, $daystart, $end, $now, $this->recur["startocc"], $this->recur["endocc"], $this->tz, $remindersonly);
1849
							}
1850
						}
1851
					}
1852
				}
1853
				//to get all exception items
1854
				if (!empty($this->recur['changed_occurrences']))
1855
					$this->processExceptionItems($items, $start, $end);
0 ignored issues
show
Bug introduced by
The method processExceptionItems() does not exist on BaseRecurrence. It seems like you code against a sub-type of BaseRecurrence such as Recurrence. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1855
					$this->/** @scrutinizer ignore-call */ 
1856
            processExceptionItems($items, $start, $end);
Loading history...
1856
			}
1857
1858
			// sort items on starttime
1859
			usort($items, array($this, "sortStarttime"));
1860
1861
			// Return the MAPI-compatible list of items for this object
1862
			return $items;
1863
		}
1864
1865
		function sortStarttime($a, $b)
1866
		{
1867
			$aTime = $a[$this->proptags["startdate"]];
1868
			$bTime = $b[$this->proptags["startdate"]];
1869
1870
			return $aTime==$bTime?0:($aTime>$bTime?1:-1);
1871
		}
1872
1873
		/**
1874
		 * daysInMonth
1875
		 *
1876
		 * Returns the number of days in the upcoming number of months. If you specify 1 month as
1877
		 * $months it will give you the number of days in the month of $date. If you specify more it
1878
		 * will also count the days in the upcoming months and add that to the number of days. So
1879
		 * if you have a date in march and you specify $months as 2 it will return 61.
1880
		 * @param Integer $date Specified date as timestamp from which you want to know the number
1881
		 * of days in the month.
1882
		 * @param Integer $months Number of months you want to know the number of days in.
1883
		 * @returns Integer Number of days in the specified amount of months.
1884
		 */
1885
		function daysInMonth($date, $months) {
1886
		    $days = 0;
1887
1888
		    for($i=0;$i<$months;$i++) {
1889
		        $days += date("t", $date + $days * 24 * 60 * 60);
1890
		    }
1891
1892
		    return $days;
1893
		}
1894
1895
		// Converts MAPI-style 'minutes' into the month of the year [0..11]
1896
		function monthOfYear($minutes) {
1897
		    $d = gmmktime(0,0,0,1,1,2001); // The year 2001 was a non-leap year, and the minutes provided are always in non-leap-year-minutes
1898
1899
		    $d += $minutes*60;
1900
1901
		    $dtime = $this->gmtime($d);
0 ignored issues
show
Bug introduced by
$d of type integer is incompatible with the type Date expected by parameter $time of BaseRecurrence::gmtime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1901
		    $dtime = $this->gmtime(/** @scrutinizer ignore-type */ $d);
Loading history...
1902
1903
		    return $dtime["tm_mon"];
1904
		}
1905
1906
		function sortExceptionStart($a, $b)
1907
		{
1908
		    return $a["start"] == $b["start"] ? 0 : ($a["start"]  > $b["start"] ? 1 : -1 );
1909
		}
1910
	}
1911
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
1912