Completed
Pull Request — master (#64)
by
unknown
07:34
created

RecurrenceRule::getFreq()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
nc 1
cc 1
eloc 2
nop 0
crap 2
1
<?php
2
3
namespace Eluceo\iCal\Property\Event;
4
5
use Eluceo\iCal\Property\ValueInterface;
6
use Eluceo\iCal\Property\DateTimeProperty;
7
use Eluceo\iCal\ParameterBag;
8
use InvalidArgumentException;
9
10
/**
11
 * Implementation of Recurrence Rule.
12
 *
13
 * @see http://www.ietf.org/rfc/rfc2445.txt 3.3.10.  Recurrence Rule
14
 */
15
class RecurrenceRule implements ValueInterface
16
{
17
    const FREQ_YEARLY = 'YEARLY';
18
    const FREQ_MONTHLY = 'MONTHLY';
19
    const FREQ_WEEKLY = 'WEEKLY';
20
    const FREQ_DAILY = 'DAILY';
21
22
    const WEEKDAY_SUNDAY = "SU";
23
    const WEEKDAY_MONDAY = "MO";
24
    const WEEKDAY_TUESDAY = "TU";
25
    const WEEKDAY_WEDNESDAY = "WE";
26
    const WEEKDAY_THURSDAY = "TH";
27
    const WEEKDAY_FRIDAY = "FR";
28
    const WEEKDAY_SATURDAY = "SA";
29
30
    /**
31
     * The frequency of an Event.
32
     *
33
     * @var string
34
     */
35
    protected $freq = self::FREQ_YEARLY;
36
37
    /**
38
     * @var null|int
39
     */
40
    protected $interval = 1;
41
42
    /**
43
     * @var null|int
44
     */
45
    protected $count = null;
46
47
    /**
48
     * @var null|\DateTime
49
     */
50
    protected $until = null;
51
52
    /**
53
     * @var null|string
54
     */
55
    protected $wkst;
56
57
    /**
58
     * @var array
59
     */
60
    protected $byMonth = array();
61
62
    /**
63
     * @var array
64
     */
65
    protected $byWeekNo = array();
66
67
    /**
68
     * @var array
69
     */
70
    protected $byYearDay = array();
71
72
    /**
73
     * @var array
74
     */
75
    protected $byMonthDay = array();
76
77
    /**
78
     * @var array
79
     */
80
    protected $byDay = array();
81
82
    /**
83
     * @var array
84
     */
85
    protected $byHour = array();
86
87
    /**
88
     * @var array
89
     */
90
    protected $byMinute = array();
91
92
    /**
93
     * @var array
94
     */
95
    protected $bySecond = array();
96
97
    /**
98
     * Return the value of the Property as an escaped string.
99
     *
100
     * Escape values as per RFC 2445. See http://www.kanzaki.com/docs/ical/text.html
101
     *
102
     * @return string
103
     */
104 1
    public function getEscapedValue()
105
    {
106 1
        return $this->buildParameterBag()->toString();
107
    }
108
109
    /**
110
     * @return ParameterBag
111
     */
112 1
    protected function buildParameterBag()
113
    {
114 1
        $parameterBag = new ParameterBag();
115
116 1
        $parameterBag->setParam('FREQ', $this->freq);
117
118 1
        if (null !== $this->interval) {
119
            $parameterBag->setParam('INTERVAL', $this->interval);
120
        }
121
122 1
        if (null !== $this->count) {
123
            $parameterBag->setParam('COUNT', $this->count);
124
        }
125
126 1
        if (null != $this->until) {
127 1
            $parameterBag->setParam('UNTIL', $this->until->format('Ymd\THis\Z'));
128 1
        }
129
130 1
        if (null !== $this->wkst) {
131
            $parameterBag->setParam('WKST', $this->wkst);
132
        }
133
134 1
        if (!empty($this->byMonth)) {
135
            $parameterBag->setParam('BYMONTH', $this->byMonth);
136
        }
137
138 1
        if (!empty($this->byWeekNo)) {
139
            $parameterBag->setParam('BYWEEKNO', $this->byWeekNo);
140
        }
141
142 1
        if (!empty($this->byYearDay)) {
143
            $parameterBag->setParam('BYYEARDAY', $this->byYearDay);
144
        }
145
146 1
        if (!empty($this->byMonthDay)) {
147
            $parameterBag->setParam('BYMONTHDAY', $this->byMonthDay);
148
        }
149
150 1
        if (!empty($this->byDay)) {
151
            $parameterBag->setParam('BYDAY', $this->byDay);
152
        }
153
154 1
        if (!empty($this->byHour)) {
155
            $parameterBag->setParam('BYHOUR', $this->byHour);
156
        }
157
158 1
        if (!empty($this->byMinute)) {
159
            $parameterBag->setParam('BYMINUTE', $this->byMinute);
160
        }
161
162 1
        if (!empty($this->bySecond)) {
163
            $parameterBag->setParam('BYSECOND', $this->bySecond);
164
        }
165
166 1
        return $parameterBag;
167
    }
168
169
    /**
170
     * @param int|null $count
171
     *
172
     * @return $this
173
     */
174
    public function setCount($count)
175
    {
176
        $this->count = $count;
177
178
        return $this;
179
    }
180
181
    /**
182
     * @return int|null
183
     */
184
    public function getCount()
185
    {
186
        return $this->count;
187
    }
188
189
    /**
190
     * @param \DateTime|null $until
191
     *
192
     * @return $this
193
     */
194 1
    public function setUntil(\DateTime $until = null)
195
    {
196 1
        $this->until = $until;
197
198 1
        return $this;
199
    }
200
201
    /**
202
     * @return \DateTime|null
203
     */
204
    public function getUntil()
205
    {
206
        return $this->until;
207
    }
208
209
    /**
210
     * The FREQ rule part identifies the type of recurrence rule.  This
211
     * rule part MUST be specified in the recurrence rule.  Valid values
212
     * include.
213
     *
214
     * SECONDLY, to specify repeating events based on an interval of a second or more;
215
     * MINUTELY, to specify repeating events based on an interval of a minute or more;
216
     * HOURLY, to specify repeating events based on an interval of an hour or more;
217
     * DAILY, to specify repeating events based on an interval of a day or more;
218
     * WEEKLY, to specify repeating events based on an interval of a week or more;
219
     * MONTHLY, to specify repeating events based on an interval of a month or more;
220
     * YEARLY, to specify repeating events based on an interval of a year or more.
221
     *
222
     * @param string $freq
223
     *
224
     * @return $this
225
     *
226
     * @throws \InvalidArgumentException
227
     */
228 1
    public function setFreq($freq)
229
    {
230 1
        if (self::FREQ_YEARLY === $freq || self::FREQ_MONTHLY === $freq
231 1
            || self::FREQ_WEEKLY === $freq
232 1
            || self::FREQ_DAILY === $freq
233 1
        ) {
234 1
            $this->freq = $freq;
235 1
        } else {
236
            throw new \InvalidArgumentException("The Frequency {$freq} is not supported.");
237
        }
238
239 1
        return $this;
240
    }
241
242
    /**
243
     * @return string
244
     */
245
    public function getFreq()
246
    {
247
        return $this->freq;
248
    }
249
250
    /**
251
     * The INTERVAL rule part contains a positive integer representing at
252
     * which intervals the recurrence rule repeats.
253
     *
254
     * @param int|null $interval
255
     *
256
     * @return $this
257
     */
258 1
    public function setInterval($interval)
259
    {
260 1
        $this->interval = $interval;
261
262 1
        return $this;
263
    }
264
265
    /**
266
     * @return int|null
267
     */
268
    public function getInterval()
269
    {
270
        return $this->interval;
271
    }
272
273
    /**
274
     * The WKST rule part specifies the day on which the workweek starts.
275
     * Valid values are MO, TU, WE, TH, FR, SA, and SU.
276
     *
277
     * @param string $value
278
     *
279
     * @return $this
280
     */
281
    public function setWkst($value)
282
    {
283
        $this->wkst = $value;
284
285
        return $this;
286
    }
287
288
    /**
289
     * The BYMONTH rule part specifies a COMMA-separated list of months of the year.
290
     * Valid value is an array with values of 1 to 12.
291
     *
292
     * @param array $value
293
     *
294
     * @throws InvalidArgumentException
295
     *
296
     * @return $this
297
     */
298
    public function setByMonth($value)
299
    {
300
        if (!$this->piecesAreInRange($value, 12)) {
301
            throw new InvalidArgumentException('Invalid value for BYMONTH');
302
        }
303
304
        $this->byMonth = $value;
305
306
        return $this;
307
    }
308
309
    /**
310
     * The BYWEEKNO rule part specifies a COMMA-separated list of ordinals specifying weeks of the year.
311
     * Valid value is an array with values of 1 to 53 or -53 to -1.
312
     *
313
     * @param array $value
314
     *
315
     * @return $this
316
     */
317
    public function setByWeekNo($value)
318
    {
319
        if (!$this->piecesAreInPositiveOrNegativeRange($value, 53)) {
320
            throw new InvalidArgumentException('Invalid value for BYWEEKNO');
321
        }
322
323
        $this->byWeekNo = $value;
324
325
        return $this;
326
    }
327
328
    /**
329
     * The BYYEARDAY rule part specifies a COMMA-separated list of days of the year.
330
     * Valid value is an array with values of 1 to 366 or -366 to -1.
331
     *
332
     * @param array $value
333
     *
334
     * @return $this
335
     */
336
    public function setByYearDay($value)
337
    {
338
        if (!$this->piecesAreInPositiveOrNegativeRange($value, 366)) {
339
            throw new InvalidArgumentException('Invalid value for BYYEARDAY');
340
        }
341
342
        $this->byYearDay = $value;
343
344
        return $this;
345
    }
346
347
    /**
348
     * The BYMONTHDAY rule part specifies a COMMA-separated list of days of the month.
349
     * Valid value is an array with values of 1 to 31 or -31 to -1.
350
     *
351
     * @param array $value
352
     *
353
     * @return $this
354
     */
355
    public function setByMonthDay($value)
356
    {
357
        if (!$this->piecesAreInPositiveOrNegativeRange($value, 31)) {
358
            throw new InvalidArgumentException('Invalid value for BYMONTHDAY');
359
        }
360
361
        $this->byMonthDay = $value;
362
363
        return $this;
364
    }
365
366
367
    /**
368
     * The BYDAY rule part specifies a COMMA-separated list of days of the week;.
369
     *
370
     * SU indicates Sunday; MO indicates Monday; TU indicates Tuesday;
371
     * WE indicates Wednesday; TH indicates Thursday; FR indicates Friday; and SA indicates Saturday.
372
     *
373
     * Each BYDAY value can also be preceded by a positive (+n) or negative (-n) integer.
374
     * If present, this indicates the nth occurrence of a specific day within the MONTHLY or YEARLY "RRULE".
375
     *
376
     * Valid value is an array with values of MO-SU
377
     *
378
     * @param array $value
379
     *
380
     * @return $this
381
     */
382
    public function setByDay($value)
383
    {
384
        //todo: refactor weekday array handling
385
        $weekdays = array(self::WEEKDAY_MONDAY, self::WEEKDAY_TUESDAY, self::WEEKDAY_WEDNESDAY, self::WEEKDAY_THURSDAY, self::WEEKDAY_FRIDAY, self::WEEKDAY_SATURDAY, self::WEEKDAY_SUNDAY);
386
        $weekdaysNeg = array();
387
        foreach ($weekdays as $weekday) {
388
            $weekdaysNeg[] = '.-' . $weekday;
389
        }
390
        $arrayDiff = array_diff($value, array_merge($weekdays, $weekdaysNeg));
391
        if (!empty($arrayDiff)) {
392
            throw new InvalidArgumentException('Invalid value for BYDAY');
393
        }
394
        $this->byDay = $value;
395
396
        return $this;
397
    }
398
399
    /**
400
     * The BYHOUR rule part specifies a COMMA-separated list of hours of the day.
401
     * Valid value is an array with values of 0 to 23.
402
     *
403
     * @param array $value
404
     *
405
     * @return $this
406
     *
407
     * @throws \InvalidArgumentException
408
     */
409
    public function setByHour($value)
410
    {
411
        if (!$this->piecesAreInRange($value, 59)) {
412
            throw new InvalidArgumentException('Invalid value for BYHOUR');
413
        }
414
415
        $this->byHour = $value;
416
417
        return $this;
418
    }
419
420
    /**
421
     * The BYMINUTE rule part specifies a COMMA-separated list of minutes within an hour.
422
     * Valid value is an array with values of 0 to 59.
423
     *
424
     * @param array $value
425
     *
426
     * @return $this
427
     *
428
     * @throws \InvalidArgumentException
429
     */
430
    public function setByMinute($value)
431
    {
432
        if (!$this->piecesAreInRange($value, 59)) {
433
            throw new InvalidArgumentException('Invalid value for BYMINUTE');
434
        }
435
436
        $this->byMinute = $value;
437
438
        return $this;
439
    }
440
441
    /**
442
     * The BYSECOND rule part specifies a COMMA-separated list of seconds within a minute.
443
     * Valid value is an array with values of 0 to 59.
444
     *
445
     * @param array $value
446
     *
447
     * @return $this
448
     *
449
     * @throws \InvalidArgumentException
450
     */
451
    public function setBySecond($value)
452
    {
453
        if (!$this->piecesAreInRange($value, 59)) {
454
            throw new InvalidArgumentException('Invalid value for BYSECOND');
455
        }
456
457
        $this->bySecond = $value;
458
459
        return $this;
460
    }
461
462
    /**
463
     * Check if pieces of an array are in a given range.
464
     *
465
     * @param int[] $value
466
     * @param int $max
467
     * @param int $min
468
     *
469
     * @return bool
470
     */
471
    private function piecesAreInRange($value, $max, $min = 0)
472
    {
473
        $arrayDiff = array_diff($value, $this->getRange($min, $max));
474
475
        return empty($arrayDiff);
476
    }
477
478
    /**
479
     * Check if pieces of an array are in a given range, as positives or negatives only.
480
     *
481
     * @param int[] $value
482
     * @param int $max
483
     * @param int $min
484
     *
485
     * @return bool
486
     */
487
    private function piecesAreInPositiveOrNegativeRange($value, $max, $min = 1)
488
    {
489
        $arrayDiffPositive = array_diff($value, $this->getRange($min, $max));
490
        $arrayDiffNegative = array_diff($value, $this->getRange(-$min, -$max));
491
        $countDiffPositive = count($arrayDiffPositive);
492
        $countDiffNegative = count($arrayDiffNegative);
493
494
        //return true if all pieces are in the positive range OR all pieces are in the negative range
495
        $isInPositiveRange = (0 == $countDiffPositive && count($value) == $countDiffNegative);
496
        $isInNegativeRange = (count($value) == $countDiffPositive && 0 == $countDiffNegative);
497
498
        return $isInPositiveRange || $isInNegativeRange;
499
    }
500
501
    /**
502
     * Get an array of integers in a given range of integer values.
503
     *
504
     * @param int $max
505
     * @param int $min
506
     *
507
     * @return array
508
     */
509
    private function getRange($min, $max)
510
    {
511
        $range = array();
512
        for ($i = $min; $i <= $max; $i++) {
513
            $range[] = $i;
514
        }
515
516
        return $range;
517
    }
518
519
}
520