Completed
Branch develop (eb876f)
by
unknown
21:14
created
htdocs/includes/sabre/sabre/vobject/lib/ITip/Message.php 1 patch
Indentation   +105 added lines, -105 removed lines patch added patch discarded remove patch
@@ -16,121 +16,121 @@
 block discarded – undo
16 16
  */
17 17
 class Message
18 18
 {
19
-    /**
20
-     * The object's UID.
21
-     *
22
-     * @var string
23
-     */
24
-    public $uid;
19
+	/**
20
+	 * The object's UID.
21
+	 *
22
+	 * @var string
23
+	 */
24
+	public $uid;
25 25
 
26
-    /**
27
-     * The component type, such as VEVENT.
28
-     *
29
-     * @var string
30
-     */
31
-    public $component;
26
+	/**
27
+	 * The component type, such as VEVENT.
28
+	 *
29
+	 * @var string
30
+	 */
31
+	public $component;
32 32
 
33
-    /**
34
-     * Contains the ITip method, which is something like REQUEST, REPLY or
35
-     * CANCEL.
36
-     *
37
-     * @var string
38
-     */
39
-    public $method;
33
+	/**
34
+	 * Contains the ITip method, which is something like REQUEST, REPLY or
35
+	 * CANCEL.
36
+	 *
37
+	 * @var string
38
+	 */
39
+	public $method;
40 40
 
41
-    /**
42
-     * The current sequence number for the event.
43
-     *
44
-     * @var int
45
-     */
46
-    public $sequence;
41
+	/**
42
+	 * The current sequence number for the event.
43
+	 *
44
+	 * @var int
45
+	 */
46
+	public $sequence;
47 47
 
48
-    /**
49
-     * The senders' email address.
50
-     *
51
-     * Note that this does not imply that this has to be used in a From: field
52
-     * if the message is sent by email. It may also be populated in Reply-To:
53
-     * or not at all.
54
-     *
55
-     * @var string
56
-     */
57
-    public $sender;
48
+	/**
49
+	 * The senders' email address.
50
+	 *
51
+	 * Note that this does not imply that this has to be used in a From: field
52
+	 * if the message is sent by email. It may also be populated in Reply-To:
53
+	 * or not at all.
54
+	 *
55
+	 * @var string
56
+	 */
57
+	public $sender;
58 58
 
59
-    /**
60
-     * The name of the sender. This is often populated from a CN parameter from
61
-     * either the ORGANIZER or ATTENDEE, depending on the message.
62
-     *
63
-     * @var string|null
64
-     */
65
-    public $senderName;
59
+	/**
60
+	 * The name of the sender. This is often populated from a CN parameter from
61
+	 * either the ORGANIZER or ATTENDEE, depending on the message.
62
+	 *
63
+	 * @var string|null
64
+	 */
65
+	public $senderName;
66 66
 
67
-    /**
68
-     * The recipient's email address.
69
-     *
70
-     * @var string
71
-     */
72
-    public $recipient;
67
+	/**
68
+	 * The recipient's email address.
69
+	 *
70
+	 * @var string
71
+	 */
72
+	public $recipient;
73 73
 
74
-    /**
75
-     * The name of the recipient. This is usually populated with the CN
76
-     * parameter from the ATTENDEE or ORGANIZER property, if it's available.
77
-     *
78
-     * @var string|null
79
-     */
80
-    public $recipientName;
74
+	/**
75
+	 * The name of the recipient. This is usually populated with the CN
76
+	 * parameter from the ATTENDEE or ORGANIZER property, if it's available.
77
+	 *
78
+	 * @var string|null
79
+	 */
80
+	public $recipientName;
81 81
 
82
-    /**
83
-     * After the message has been delivered, this should contain a string such
84
-     * as : 1.1;Sent or 1.2;Delivered.
85
-     *
86
-     * In case of a failure, this will hold the error status code.
87
-     *
88
-     * See:
89
-     * http://tools.ietf.org/html/rfc6638#section-7.3
90
-     *
91
-     * @var string
92
-     */
93
-    public $scheduleStatus;
82
+	/**
83
+	 * After the message has been delivered, this should contain a string such
84
+	 * as : 1.1;Sent or 1.2;Delivered.
85
+	 *
86
+	 * In case of a failure, this will hold the error status code.
87
+	 *
88
+	 * See:
89
+	 * http://tools.ietf.org/html/rfc6638#section-7.3
90
+	 *
91
+	 * @var string
92
+	 */
93
+	public $scheduleStatus;
94 94
 
95
-    /**
96
-     * The iCalendar / iTip body.
97
-     *
98
-     * @var \Sabre\VObject\Component\VCalendar
99
-     */
100
-    public $message;
95
+	/**
96
+	 * The iCalendar / iTip body.
97
+	 *
98
+	 * @var \Sabre\VObject\Component\VCalendar
99
+	 */
100
+	public $message;
101 101
 
102
-    /**
103
-     * This will be set to true, if the iTip broker considers the change
104
-     * 'significant'.
105
-     *
106
-     * In practice, this means that we'll only mark it true, if for instance
107
-     * DTSTART changed. This allows systems to only send iTip messages when
108
-     * significant changes happened. This is especially useful for iMip, as
109
-     * normally a ton of messages may be generated for normal calendar use.
110
-     *
111
-     * To see the list of properties that are considered 'significant', check
112
-     * out Sabre\VObject\ITip\Broker::$significantChangeProperties.
113
-     *
114
-     * @var bool
115
-     */
116
-    public $significantChange = true;
102
+	/**
103
+	 * This will be set to true, if the iTip broker considers the change
104
+	 * 'significant'.
105
+	 *
106
+	 * In practice, this means that we'll only mark it true, if for instance
107
+	 * DTSTART changed. This allows systems to only send iTip messages when
108
+	 * significant changes happened. This is especially useful for iMip, as
109
+	 * normally a ton of messages may be generated for normal calendar use.
110
+	 *
111
+	 * To see the list of properties that are considered 'significant', check
112
+	 * out Sabre\VObject\ITip\Broker::$significantChangeProperties.
113
+	 *
114
+	 * @var bool
115
+	 */
116
+	public $significantChange = true;
117 117
 
118
-    /**
119
-     * Returns the schedule status as a string.
120
-     *
121
-     * For example:
122
-     * 1.2
123
-     *
124
-     * @return mixed bool|string
125
-     */
126
-    public function getScheduleStatus()
127
-    {
128
-        if (!$this->scheduleStatus) {
129
-            return false;
130
-        } else {
131
-            list($scheduleStatus) = explode(';', $this->scheduleStatus);
118
+	/**
119
+	 * Returns the schedule status as a string.
120
+	 *
121
+	 * For example:
122
+	 * 1.2
123
+	 *
124
+	 * @return mixed bool|string
125
+	 */
126
+	public function getScheduleStatus()
127
+	{
128
+		if (!$this->scheduleStatus) {
129
+			return false;
130
+		} else {
131
+			list($scheduleStatus) = explode(';', $this->scheduleStatus);
132 132
 
133
-            return $scheduleStatus;
134
-        }
135
-    }
133
+			return $scheduleStatus;
134
+		}
135
+	}
136 136
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Writer.php 1 patch
Indentation   +42 added lines, -42 removed lines patch added patch discarded remove patch
@@ -16,53 +16,53 @@
 block discarded – undo
16 16
  */
17 17
 class Writer
18 18
 {
19
-    /**
20
-     * Serializes a vCard or iCalendar object.
21
-     *
22
-     * @return string
23
-     */
24
-    public static function write(Component $component)
25
-    {
26
-        return $component->serialize();
27
-    }
19
+	/**
20
+	 * Serializes a vCard or iCalendar object.
21
+	 *
22
+	 * @return string
23
+	 */
24
+	public static function write(Component $component)
25
+	{
26
+		return $component->serialize();
27
+	}
28 28
 
29
-    /**
30
-     * Serializes a jCal or jCard object.
31
-     *
32
-     * @param int $options
33
-     *
34
-     * @return string
35
-     */
36
-    public static function writeJson(Component $component, $options = 0)
37
-    {
38
-        return json_encode($component, $options);
39
-    }
29
+	/**
30
+	 * Serializes a jCal or jCard object.
31
+	 *
32
+	 * @param int $options
33
+	 *
34
+	 * @return string
35
+	 */
36
+	public static function writeJson(Component $component, $options = 0)
37
+	{
38
+		return json_encode($component, $options);
39
+	}
40 40
 
41
-    /**
42
-     * Serializes a xCal or xCard object.
43
-     *
44
-     * @return string
45
-     */
46
-    public static function writeXml(Component $component)
47
-    {
48
-        $writer = new Xml\Writer();
49
-        $writer->openMemory();
50
-        $writer->setIndent(true);
41
+	/**
42
+	 * Serializes a xCal or xCard object.
43
+	 *
44
+	 * @return string
45
+	 */
46
+	public static function writeXml(Component $component)
47
+	{
48
+		$writer = new Xml\Writer();
49
+		$writer->openMemory();
50
+		$writer->setIndent(true);
51 51
 
52
-        $writer->startDocument('1.0', 'utf-8');
52
+		$writer->startDocument('1.0', 'utf-8');
53 53
 
54
-        if ($component instanceof Component\VCalendar) {
55
-            $writer->startElement('icalendar');
56
-            $writer->writeAttribute('xmlns', Parser\XML::XCAL_NAMESPACE);
57
-        } else {
58
-            $writer->startElement('vcards');
59
-            $writer->writeAttribute('xmlns', Parser\XML::XCARD_NAMESPACE);
60
-        }
54
+		if ($component instanceof Component\VCalendar) {
55
+			$writer->startElement('icalendar');
56
+			$writer->writeAttribute('xmlns', Parser\XML::XCAL_NAMESPACE);
57
+		} else {
58
+			$writer->startElement('vcards');
59
+			$writer->writeAttribute('xmlns', Parser\XML::XCARD_NAMESPACE);
60
+		}
61 61
 
62
-        $component->xmlSerialize($writer);
62
+		$component->xmlSerialize($writer);
63 63
 
64
-        $writer->endElement();
64
+		$writer->endElement();
65 65
 
66
-        return $writer->outputMemory();
67
-    }
66
+		return $writer->outputMemory();
67
+	}
68 68
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/ElementList.php 1 patch
Indentation   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -17,32 +17,32 @@
 block discarded – undo
17 17
  */
18 18
 class ElementList extends ArrayIterator
19 19
 {
20
-    /* {{{ ArrayAccess Interface */
20
+	/* {{{ ArrayAccess Interface */
21 21
 
22
-    /**
23
-     * Sets an item through ArrayAccess.
24
-     *
25
-     * @param int   $offset
26
-     * @param mixed $value
27
-     */
28
-    #[\ReturnTypeWillChange]
29
-    public function offsetSet($offset, $value)
30
-    {
31
-        throw new LogicException('You can not add new objects to an ElementList');
32
-    }
22
+	/**
23
+	 * Sets an item through ArrayAccess.
24
+	 *
25
+	 * @param int   $offset
26
+	 * @param mixed $value
27
+	 */
28
+	#[\ReturnTypeWillChange]
29
+	public function offsetSet($offset, $value)
30
+	{
31
+		throw new LogicException('You can not add new objects to an ElementList');
32
+	}
33 33
 
34
-    /**
35
-     * Sets an item through ArrayAccess.
36
-     *
37
-     * This method just forwards the request to the inner iterator
38
-     *
39
-     * @param int $offset
40
-     */
41
-    #[\ReturnTypeWillChange]
42
-    public function offsetUnset($offset)
43
-    {
44
-        throw new LogicException('You can not remove objects from an ElementList');
45
-    }
34
+	/**
35
+	 * Sets an item through ArrayAccess.
36
+	 *
37
+	 * This method just forwards the request to the inner iterator
38
+	 *
39
+	 * @param int $offset
40
+	 */
41
+	#[\ReturnTypeWillChange]
42
+	public function offsetUnset($offset)
43
+	{
44
+		throw new LogicException('You can not remove objects from an ElementList');
45
+	}
46 46
 
47
-    /* }}} */
47
+	/* }}} */
48 48
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Settings.php 1 patch
Indentation   +33 added lines, -33 removed lines patch added patch discarded remove patch
@@ -17,39 +17,39 @@
 block discarded – undo
17 17
  */
18 18
 class Settings
19 19
 {
20
-    /**
21
-     * The minimum date we accept for various calculations with dates, such as
22
-     * recurrences.
23
-     *
24
-     * The choice of 1900 is pretty arbitrary, but it covers most common
25
-     * use-cases. In particular, it covers birthdates for virtually everyone
26
-     * alive on earth, which is less than 5 people at the time of writing.
27
-     */
28
-    public static $minDate = '1900-01-01';
20
+	/**
21
+	 * The minimum date we accept for various calculations with dates, such as
22
+	 * recurrences.
23
+	 *
24
+	 * The choice of 1900 is pretty arbitrary, but it covers most common
25
+	 * use-cases. In particular, it covers birthdates for virtually everyone
26
+	 * alive on earth, which is less than 5 people at the time of writing.
27
+	 */
28
+	public static $minDate = '1900-01-01';
29 29
 
30
-    /**
31
-     * The maximum date we accept for various calculations with dates, such as
32
-     * recurrences.
33
-     *
34
-     * The choice of 2100 is pretty arbitrary, but should cover most
35
-     * appointments made for many years to come.
36
-     */
37
-    public static $maxDate = '2100-01-01';
30
+	/**
31
+	 * The maximum date we accept for various calculations with dates, such as
32
+	 * recurrences.
33
+	 *
34
+	 * The choice of 2100 is pretty arbitrary, but should cover most
35
+	 * appointments made for many years to come.
36
+	 */
37
+	public static $maxDate = '2100-01-01';
38 38
 
39
-    /**
40
-     * The maximum number of recurrences that will be generated.
41
-     *
42
-     * This setting limits the maximum of recurring events that this library
43
-     * generates in its recurrence iterators.
44
-     *
45
-     * This is a security measure. Without this, it would be possible to craft
46
-     * specific events that recur many, many times, potentially DDOSing the
47
-     * server.
48
-     *
49
-     * The default (3500) allows creation of a daily event that goes on for 10
50
-     * years, which is hopefully long enough for most.
51
-     *
52
-     * Set this value to -1 to disable this control altogether.
53
-     */
54
-    public static $maxRecurrences = 3500;
39
+	/**
40
+	 * The maximum number of recurrences that will be generated.
41
+	 *
42
+	 * This setting limits the maximum of recurring events that this library
43
+	 * generates in its recurrence iterators.
44
+	 *
45
+	 * This is a security measure. Without this, it would be possible to craft
46
+	 * specific events that recur many, many times, potentially DDOSing the
47
+	 * server.
48
+	 *
49
+	 * The default (3500) allows creation of a daily event that goes on for 10
50
+	 * years, which is hopefully long enough for most.
51
+	 *
52
+	 * Set this value to -1 to disable this control altogether.
53
+	 */
54
+	public static $maxRecurrences = 3500;
55 55
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Reader.php 1 patch
Indentation   +71 added lines, -71 removed lines patch added patch discarded remove patch
@@ -14,82 +14,82 @@
 block discarded – undo
14 14
  */
15 15
 class Reader
16 16
 {
17
-    /**
18
-     * If this option is passed to the reader, it will be less strict about the
19
-     * validity of the lines.
20
-     */
21
-    const OPTION_FORGIVING = 1;
17
+	/**
18
+	 * If this option is passed to the reader, it will be less strict about the
19
+	 * validity of the lines.
20
+	 */
21
+	const OPTION_FORGIVING = 1;
22 22
 
23
-    /**
24
-     * If this option is turned on, any lines we cannot parse will be ignored
25
-     * by the reader.
26
-     */
27
-    const OPTION_IGNORE_INVALID_LINES = 2;
23
+	/**
24
+	 * If this option is turned on, any lines we cannot parse will be ignored
25
+	 * by the reader.
26
+	 */
27
+	const OPTION_IGNORE_INVALID_LINES = 2;
28 28
 
29
-    /**
30
-     * Parses a vCard or iCalendar object, and returns the top component.
31
-     *
32
-     * The options argument is a bitfield. Pass any of the OPTIONS constant to
33
-     * alter the parsers' behaviour.
34
-     *
35
-     * You can either supply a string, or a readable stream for input.
36
-     *
37
-     * @param string|resource $data
38
-     * @param int             $options
39
-     * @param string          $charset
40
-     *
41
-     * @return Document
42
-     */
43
-    public static function read($data, $options = 0, $charset = 'UTF-8')
44
-    {
45
-        $parser = new Parser\MimeDir();
46
-        $parser->setCharset($charset);
47
-        $result = $parser->parse($data, $options);
29
+	/**
30
+	 * Parses a vCard or iCalendar object, and returns the top component.
31
+	 *
32
+	 * The options argument is a bitfield. Pass any of the OPTIONS constant to
33
+	 * alter the parsers' behaviour.
34
+	 *
35
+	 * You can either supply a string, or a readable stream for input.
36
+	 *
37
+	 * @param string|resource $data
38
+	 * @param int             $options
39
+	 * @param string          $charset
40
+	 *
41
+	 * @return Document
42
+	 */
43
+	public static function read($data, $options = 0, $charset = 'UTF-8')
44
+	{
45
+		$parser = new Parser\MimeDir();
46
+		$parser->setCharset($charset);
47
+		$result = $parser->parse($data, $options);
48 48
 
49
-        return $result;
50
-    }
49
+		return $result;
50
+	}
51 51
 
52
-    /**
53
-     * Parses a jCard or jCal object, and returns the top component.
54
-     *
55
-     * The options argument is a bitfield. Pass any of the OPTIONS constant to
56
-     * alter the parsers' behaviour.
57
-     *
58
-     * You can either a string, a readable stream, or an array for its input.
59
-     * Specifying the array is useful if json_decode was already called on the
60
-     * input.
61
-     *
62
-     * @param string|resource|array $data
63
-     * @param int                   $options
64
-     *
65
-     * @return Document
66
-     */
67
-    public static function readJson($data, $options = 0)
68
-    {
69
-        $parser = new Parser\Json();
70
-        $result = $parser->parse($data, $options);
52
+	/**
53
+	 * Parses a jCard or jCal object, and returns the top component.
54
+	 *
55
+	 * The options argument is a bitfield. Pass any of the OPTIONS constant to
56
+	 * alter the parsers' behaviour.
57
+	 *
58
+	 * You can either a string, a readable stream, or an array for its input.
59
+	 * Specifying the array is useful if json_decode was already called on the
60
+	 * input.
61
+	 *
62
+	 * @param string|resource|array $data
63
+	 * @param int                   $options
64
+	 *
65
+	 * @return Document
66
+	 */
67
+	public static function readJson($data, $options = 0)
68
+	{
69
+		$parser = new Parser\Json();
70
+		$result = $parser->parse($data, $options);
71 71
 
72
-        return $result;
73
-    }
72
+		return $result;
73
+	}
74 74
 
75
-    /**
76
-     * Parses a xCard or xCal object, and returns the top component.
77
-     *
78
-     * The options argument is a bitfield. Pass any of the OPTIONS constant to
79
-     * alter the parsers' behaviour.
80
-     *
81
-     * You can either supply a string, or a readable stream for input.
82
-     *
83
-     * @param string|resource $data
84
-     * @param int             $options
85
-     *
86
-     * @return Document
87
-     */
88
-    public static function readXML($data, $options = 0)
89
-    {
90
-        $parser = new Parser\XML();
91
-        $result = $parser->parse($data, $options);
75
+	/**
76
+	 * Parses a xCard or xCal object, and returns the top component.
77
+	 *
78
+	 * The options argument is a bitfield. Pass any of the OPTIONS constant to
79
+	 * alter the parsers' behaviour.
80
+	 *
81
+	 * You can either supply a string, or a readable stream for input.
82
+	 *
83
+	 * @param string|resource $data
84
+	 * @param int             $options
85
+	 *
86
+	 * @return Document
87
+	 */
88
+	public static function readXML($data, $options = 0)
89
+	{
90
+		$parser = new Parser\XML();
91
+		$result = $parser->parse($data, $options);
92 92
 
93
-        return $result;
94
-    }
93
+		return $result;
94
+	}
95 95
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/StringUtil.php 1 patch
Indentation   +33 added lines, -33 removed lines patch added patch discarded remove patch
@@ -11,40 +11,40 @@
 block discarded – undo
11 11
  */
12 12
 class StringUtil
13 13
 {
14
-    /**
15
-     * Returns true or false depending on if a string is valid UTF-8.
16
-     *
17
-     * @param string $str
18
-     *
19
-     * @return bool
20
-     */
21
-    public static function isUTF8($str)
22
-    {
23
-        // Control characters
24
-        if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) {
25
-            return false;
26
-        }
14
+	/**
15
+	 * Returns true or false depending on if a string is valid UTF-8.
16
+	 *
17
+	 * @param string $str
18
+	 *
19
+	 * @return bool
20
+	 */
21
+	public static function isUTF8($str)
22
+	{
23
+		// Control characters
24
+		if (preg_match('%[\x00-\x08\x0B-\x0C\x0E\x0F]%', $str)) {
25
+			return false;
26
+		}
27 27
 
28
-        return (bool) preg_match('%%u', $str);
29
-    }
28
+		return (bool) preg_match('%%u', $str);
29
+	}
30 30
 
31
-    /**
32
-     * This method tries its best to convert the input string to UTF-8.
33
-     *
34
-     * Currently only ISO-5991-1 input and UTF-8 input is supported, but this
35
-     * may be expanded upon if we receive other examples.
36
-     *
37
-     * @param string $str
38
-     *
39
-     * @return string
40
-     */
41
-    public static function convertToUTF8($str)
42
-    {
43
-        if (!mb_check_encoding($str, 'UTF-8') && mb_check_encoding($str, 'ISO-8859-1')) {
44
-            $str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1');
45
-        }
31
+	/**
32
+	 * This method tries its best to convert the input string to UTF-8.
33
+	 *
34
+	 * Currently only ISO-5991-1 input and UTF-8 input is supported, but this
35
+	 * may be expanded upon if we receive other examples.
36
+	 *
37
+	 * @param string $str
38
+	 *
39
+	 * @return string
40
+	 */
41
+	public static function convertToUTF8($str)
42
+	{
43
+		if (!mb_check_encoding($str, 'UTF-8') && mb_check_encoding($str, 'ISO-8859-1')) {
44
+			$str = mb_convert_encoding($str, 'UTF-8', 'ISO-8859-1');
45
+		}
46 46
 
47
-        // Removing any control characters
48
-        return preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $str);
49
-    }
47
+		// Removing any control characters
48
+		return preg_replace('%(?:[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F])%', '', $str);
49
+	}
50 50
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/BirthdayCalendarGenerator.php 1 patch
Indentation   +156 added lines, -156 removed lines patch added patch discarded remove patch
@@ -13,160 +13,160 @@
 block discarded – undo
13 13
  */
14 14
 class BirthdayCalendarGenerator
15 15
 {
16
-    /**
17
-     * Input objects.
18
-     *
19
-     * @var array
20
-     */
21
-    protected $objects = [];
22
-
23
-    /**
24
-     * Default year.
25
-     * Used for dates without a year.
26
-     */
27
-    const DEFAULT_YEAR = 2000;
28
-
29
-    /**
30
-     * Output format for the SUMMARY.
31
-     *
32
-     * @var string
33
-     */
34
-    protected $format = '%1$s\'s Birthday';
35
-
36
-    /**
37
-     * Creates the generator.
38
-     *
39
-     * Check the setTimeRange and setObjects methods for details about the
40
-     * arguments.
41
-     *
42
-     * @param mixed $objects
43
-     */
44
-    public function __construct($objects = null)
45
-    {
46
-        if ($objects) {
47
-            $this->setObjects($objects);
48
-        }
49
-    }
50
-
51
-    /**
52
-     * Sets the input objects.
53
-     *
54
-     * You must either supply a vCard as a string or as a Component/VCard object.
55
-     * It's also possible to supply an array of strings or objects.
56
-     *
57
-     * @param mixed $objects
58
-     */
59
-    public function setObjects($objects)
60
-    {
61
-        if (!is_array($objects)) {
62
-            $objects = [$objects];
63
-        }
64
-
65
-        $this->objects = [];
66
-        foreach ($objects as $object) {
67
-            if (is_string($object)) {
68
-                $vObj = Reader::read($object);
69
-                if (!$vObj instanceof Component\VCard) {
70
-                    throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
71
-                }
72
-
73
-                $this->objects[] = $vObj;
74
-            } elseif ($object instanceof Component\VCard) {
75
-                $this->objects[] = $object;
76
-            } else {
77
-                throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
78
-            }
79
-        }
80
-    }
81
-
82
-    /**
83
-     * Sets the output format for the SUMMARY.
84
-     *
85
-     * @param string $format
86
-     */
87
-    public function setFormat($format)
88
-    {
89
-        $this->format = $format;
90
-    }
91
-
92
-    /**
93
-     * Parses the input data and returns a VCALENDAR.
94
-     *
95
-     * @return Component/VCalendar
96
-     */
97
-    public function getResult()
98
-    {
99
-        $calendar = new VCalendar();
100
-
101
-        foreach ($this->objects as $object) {
102
-            // Skip if there is no BDAY property.
103
-            if (!$object->select('BDAY')) {
104
-                continue;
105
-            }
106
-
107
-            // We've seen clients (ez-vcard) putting "BDAY:" properties
108
-            // without a value into vCards. If we come across those, we'll
109
-            // skip them.
110
-            if (empty($object->BDAY->getValue())) {
111
-                continue;
112
-            }
113
-
114
-            // We're always converting to vCard 4.0 so we can rely on the
115
-            // VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
116
-            $object = $object->convert(Document::VCARD40);
117
-
118
-            // Skip if the card has no FN property.
119
-            if (!isset($object->FN)) {
120
-                continue;
121
-            }
122
-
123
-            // Skip if the BDAY property is not of the right type.
124
-            if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
125
-                continue;
126
-            }
127
-
128
-            // Skip if we can't parse the BDAY value.
129
-            try {
130
-                $dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
131
-            } catch (InvalidDataException $e) {
132
-                continue;
133
-            }
134
-
135
-            // Set a year if it's not set.
136
-            $unknownYear = false;
137
-
138
-            if (!$dateParts['year']) {
139
-                $object->BDAY = self::DEFAULT_YEAR.'-'.$dateParts['month'].'-'.$dateParts['date'];
140
-
141
-                $unknownYear = true;
142
-            }
143
-
144
-            // Create event.
145
-            $event = $calendar->add('VEVENT', [
146
-                'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
147
-                'DTSTART' => new \DateTime($object->BDAY->getValue()),
148
-                'RRULE' => 'FREQ=YEARLY',
149
-                'TRANSP' => 'TRANSPARENT',
150
-            ]);
151
-
152
-            // add VALUE=date
153
-            $event->DTSTART['VALUE'] = 'DATE';
154
-
155
-            // Add X-SABRE-BDAY property.
156
-            if ($unknownYear) {
157
-                $event->add('X-SABRE-BDAY', 'BDAY', [
158
-                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
159
-                    'X-SABRE-VCARD-FN' => $object->FN->getValue(),
160
-                    'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
161
-                ]);
162
-            } else {
163
-                $event->add('X-SABRE-BDAY', 'BDAY', [
164
-                    'X-SABRE-VCARD-UID' => $object->UID->getValue(),
165
-                    'X-SABRE-VCARD-FN' => $object->FN->getValue(),
166
-                ]);
167
-            }
168
-        }
169
-
170
-        return $calendar;
171
-    }
16
+	/**
17
+	 * Input objects.
18
+	 *
19
+	 * @var array
20
+	 */
21
+	protected $objects = [];
22
+
23
+	/**
24
+	 * Default year.
25
+	 * Used for dates without a year.
26
+	 */
27
+	const DEFAULT_YEAR = 2000;
28
+
29
+	/**
30
+	 * Output format for the SUMMARY.
31
+	 *
32
+	 * @var string
33
+	 */
34
+	protected $format = '%1$s\'s Birthday';
35
+
36
+	/**
37
+	 * Creates the generator.
38
+	 *
39
+	 * Check the setTimeRange and setObjects methods for details about the
40
+	 * arguments.
41
+	 *
42
+	 * @param mixed $objects
43
+	 */
44
+	public function __construct($objects = null)
45
+	{
46
+		if ($objects) {
47
+			$this->setObjects($objects);
48
+		}
49
+	}
50
+
51
+	/**
52
+	 * Sets the input objects.
53
+	 *
54
+	 * You must either supply a vCard as a string or as a Component/VCard object.
55
+	 * It's also possible to supply an array of strings or objects.
56
+	 *
57
+	 * @param mixed $objects
58
+	 */
59
+	public function setObjects($objects)
60
+	{
61
+		if (!is_array($objects)) {
62
+			$objects = [$objects];
63
+		}
64
+
65
+		$this->objects = [];
66
+		foreach ($objects as $object) {
67
+			if (is_string($object)) {
68
+				$vObj = Reader::read($object);
69
+				if (!$vObj instanceof Component\VCard) {
70
+					throw new \InvalidArgumentException('String could not be parsed as \\Sabre\\VObject\\Component\\VCard by setObjects');
71
+				}
72
+
73
+				$this->objects[] = $vObj;
74
+			} elseif ($object instanceof Component\VCard) {
75
+				$this->objects[] = $object;
76
+			} else {
77
+				throw new \InvalidArgumentException('You can only pass strings or \\Sabre\\VObject\\Component\\VCard arguments to setObjects');
78
+			}
79
+		}
80
+	}
81
+
82
+	/**
83
+	 * Sets the output format for the SUMMARY.
84
+	 *
85
+	 * @param string $format
86
+	 */
87
+	public function setFormat($format)
88
+	{
89
+		$this->format = $format;
90
+	}
91
+
92
+	/**
93
+	 * Parses the input data and returns a VCALENDAR.
94
+	 *
95
+	 * @return Component/VCalendar
96
+	 */
97
+	public function getResult()
98
+	{
99
+		$calendar = new VCalendar();
100
+
101
+		foreach ($this->objects as $object) {
102
+			// Skip if there is no BDAY property.
103
+			if (!$object->select('BDAY')) {
104
+				continue;
105
+			}
106
+
107
+			// We've seen clients (ez-vcard) putting "BDAY:" properties
108
+			// without a value into vCards. If we come across those, we'll
109
+			// skip them.
110
+			if (empty($object->BDAY->getValue())) {
111
+				continue;
112
+			}
113
+
114
+			// We're always converting to vCard 4.0 so we can rely on the
115
+			// VCardConverter handling the X-APPLE-OMIT-YEAR property for us.
116
+			$object = $object->convert(Document::VCARD40);
117
+
118
+			// Skip if the card has no FN property.
119
+			if (!isset($object->FN)) {
120
+				continue;
121
+			}
122
+
123
+			// Skip if the BDAY property is not of the right type.
124
+			if (!$object->BDAY instanceof Property\VCard\DateAndOrTime) {
125
+				continue;
126
+			}
127
+
128
+			// Skip if we can't parse the BDAY value.
129
+			try {
130
+				$dateParts = DateTimeParser::parseVCardDateTime($object->BDAY->getValue());
131
+			} catch (InvalidDataException $e) {
132
+				continue;
133
+			}
134
+
135
+			// Set a year if it's not set.
136
+			$unknownYear = false;
137
+
138
+			if (!$dateParts['year']) {
139
+				$object->BDAY = self::DEFAULT_YEAR.'-'.$dateParts['month'].'-'.$dateParts['date'];
140
+
141
+				$unknownYear = true;
142
+			}
143
+
144
+			// Create event.
145
+			$event = $calendar->add('VEVENT', [
146
+				'SUMMARY' => sprintf($this->format, $object->FN->getValue()),
147
+				'DTSTART' => new \DateTime($object->BDAY->getValue()),
148
+				'RRULE' => 'FREQ=YEARLY',
149
+				'TRANSP' => 'TRANSPARENT',
150
+			]);
151
+
152
+			// add VALUE=date
153
+			$event->DTSTART['VALUE'] = 'DATE';
154
+
155
+			// Add X-SABRE-BDAY property.
156
+			if ($unknownYear) {
157
+				$event->add('X-SABRE-BDAY', 'BDAY', [
158
+					'X-SABRE-VCARD-UID' => $object->UID->getValue(),
159
+					'X-SABRE-VCARD-FN' => $object->FN->getValue(),
160
+					'X-SABRE-OMIT-YEAR' => self::DEFAULT_YEAR,
161
+				]);
162
+			} else {
163
+				$event->add('X-SABRE-BDAY', 'BDAY', [
164
+					'X-SABRE-VCARD-UID' => $object->UID->getValue(),
165
+					'X-SABRE-VCARD-FN' => $object->FN->getValue(),
166
+				]);
167
+			}
168
+		}
169
+
170
+		return $calendar;
171
+	}
172 172
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Recur/EventIterator.php 1 patch
Indentation   +434 added lines, -434 removed lines patch added patch discarded remove patch
@@ -60,438 +60,438 @@
 block discarded – undo
60 60
  */
61 61
 class EventIterator implements \Iterator
62 62
 {
63
-    /**
64
-     * Reference timeZone for floating dates and times.
65
-     *
66
-     * @var DateTimeZone
67
-     */
68
-    protected $timeZone;
69
-
70
-    /**
71
-     * True if we're iterating an all-day event.
72
-     *
73
-     * @var bool
74
-     */
75
-    protected $allDay = false;
76
-
77
-    /**
78
-     * Creates the iterator.
79
-     *
80
-     * There's three ways to set up the iterator.
81
-     *
82
-     * 1. You can pass a VCALENDAR component and a UID.
83
-     * 2. You can pass an array of VEVENTs (all UIDS should match).
84
-     * 3. You can pass a single VEVENT component.
85
-     *
86
-     * Only the second method is recommended. The other 1 and 3 will be removed
87
-     * at some point in the future.
88
-     *
89
-     * The $uid parameter is only required for the first method.
90
-     *
91
-     * @param Component|array $input
92
-     * @param string|null     $uid
93
-     * @param DateTimeZone    $timeZone reference timezone for floating dates and
94
-     *                                  times
95
-     */
96
-    public function __construct($input, $uid = null, DateTimeZone $timeZone = null)
97
-    {
98
-        if (is_null($timeZone)) {
99
-            $timeZone = new DateTimeZone('UTC');
100
-        }
101
-        $this->timeZone = $timeZone;
102
-
103
-        if (is_array($input)) {
104
-            $events = $input;
105
-        } elseif ($input instanceof VEvent) {
106
-            // Single instance mode.
107
-            $events = [$input];
108
-        } else {
109
-            // Calendar + UID mode.
110
-            $uid = (string) $uid;
111
-            if (!$uid) {
112
-                throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
113
-            }
114
-            if (!isset($input->VEVENT)) {
115
-                throw new InvalidArgumentException('No events found in this calendar');
116
-            }
117
-            $events = $input->getByUID($uid);
118
-        }
119
-
120
-        foreach ($events as $vevent) {
121
-            if (!isset($vevent->{'RECURRENCE-ID'})) {
122
-                $this->masterEvent = $vevent;
123
-            } else {
124
-                $this->exceptions[
125
-                    $vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
126
-                ] = true;
127
-                $this->overriddenEvents[] = $vevent;
128
-            }
129
-        }
130
-
131
-        if (!$this->masterEvent) {
132
-            // No base event was found. CalDAV does allow cases where only
133
-            // overridden instances are stored.
134
-            //
135
-            // In this particular case, we're just going to grab the first
136
-            // event and use that instead. This may not always give the
137
-            // desired result.
138
-            if (!count($this->overriddenEvents)) {
139
-                throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid);
140
-            }
141
-            $this->masterEvent = array_shift($this->overriddenEvents);
142
-        }
143
-
144
-        $this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
145
-        $this->allDay = !$this->masterEvent->DTSTART->hasTime();
146
-
147
-        if (isset($this->masterEvent->EXDATE)) {
148
-            foreach ($this->masterEvent->EXDATE as $exDate) {
149
-                foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
150
-                    $this->exceptions[$dt->getTimeStamp()] = true;
151
-                }
152
-            }
153
-        }
154
-
155
-        if (isset($this->masterEvent->DTEND)) {
156
-            $this->eventDuration =
157
-                $this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
158
-                $this->startDate->getTimeStamp();
159
-        } elseif (isset($this->masterEvent->DURATION)) {
160
-            $duration = $this->masterEvent->DURATION->getDateInterval();
161
-            $end = clone $this->startDate;
162
-            $end = $end->add($duration);
163
-            $this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
164
-        } elseif ($this->allDay) {
165
-            $this->eventDuration = 3600 * 24;
166
-        } else {
167
-            $this->eventDuration = 0;
168
-        }
169
-
170
-        if (isset($this->masterEvent->RDATE)) {
171
-            $this->recurIterator = new RDateIterator(
172
-                $this->masterEvent->RDATE->getParts(),
173
-                $this->startDate
174
-            );
175
-        } elseif (isset($this->masterEvent->RRULE)) {
176
-            $this->recurIterator = new RRuleIterator(
177
-                $this->masterEvent->RRULE->getParts(),
178
-                $this->startDate
179
-            );
180
-        } else {
181
-            $this->recurIterator = new RRuleIterator(
182
-                [
183
-                    'FREQ' => 'DAILY',
184
-                    'COUNT' => 1,
185
-                ],
186
-                $this->startDate
187
-            );
188
-        }
189
-
190
-        $this->rewind();
191
-        if (!$this->valid()) {
192
-            throw new NoInstancesException('This recurrence rule does not generate any valid instances');
193
-        }
194
-    }
195
-
196
-    /**
197
-     * Returns the date for the current position of the iterator.
198
-     *
199
-     * @return DateTimeImmutable
200
-     */
201
-    #[\ReturnTypeWillChange]
202
-    public function current()
203
-    {
204
-        if ($this->currentDate) {
205
-            return clone $this->currentDate;
206
-        }
207
-    }
208
-
209
-    /**
210
-     * This method returns the start date for the current iteration of the
211
-     * event.
212
-     *
213
-     * @return DateTimeImmutable
214
-     */
215
-    public function getDtStart()
216
-    {
217
-        if ($this->currentDate) {
218
-            return clone $this->currentDate;
219
-        }
220
-    }
221
-
222
-    /**
223
-     * This method returns the end date for the current iteration of the
224
-     * event.
225
-     *
226
-     * @return DateTimeImmutable
227
-     */
228
-    public function getDtEnd()
229
-    {
230
-        if (!$this->valid()) {
231
-            return;
232
-        }
233
-        if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
234
-            return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
235
-        } else {
236
-            $end = clone $this->currentDate;
237
-
238
-            return $end->modify('+'.$this->eventDuration.' seconds');
239
-        }
240
-    }
241
-
242
-    /**
243
-     * Returns a VEVENT for the current iterations of the event.
244
-     *
245
-     * This VEVENT will have a recurrence id, and its DTSTART and DTEND
246
-     * altered.
247
-     *
248
-     * @return VEvent
249
-     */
250
-    public function getEventObject()
251
-    {
252
-        if ($this->currentOverriddenEvent) {
253
-            return $this->currentOverriddenEvent;
254
-        }
255
-
256
-        $event = clone $this->masterEvent;
257
-
258
-        // Ignoring the following block, because PHPUnit's code coverage
259
-        // ignores most of these lines, and this messes with our stats.
260
-        //
261
-        // @codeCoverageIgnoreStart
262
-        unset(
263
-            $event->RRULE,
264
-            $event->EXDATE,
265
-            $event->RDATE,
266
-            $event->EXRULE,
267
-            $event->{'RECURRENCE-ID'}
268
-        );
269
-        // @codeCoverageIgnoreEnd
270
-
271
-        $event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
272
-        if (isset($event->DTEND)) {
273
-            $event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
274
-        }
275
-        $recurid = clone $event->DTSTART;
276
-        $recurid->name = 'RECURRENCE-ID';
277
-        $event->add($recurid);
278
-
279
-        return $event;
280
-    }
281
-
282
-    /**
283
-     * Returns the current position of the iterator.
284
-     *
285
-     * This is for us simply a 0-based index.
286
-     *
287
-     * @return int
288
-     */
289
-    #[\ReturnTypeWillChange]
290
-    public function key()
291
-    {
292
-        // The counter is always 1 ahead.
293
-        return $this->counter - 1;
294
-    }
295
-
296
-    /**
297
-     * This is called after next, to see if the iterator is still at a valid
298
-     * position, or if it's at the end.
299
-     *
300
-     * @return bool
301
-     */
302
-    #[\ReturnTypeWillChange]
303
-    public function valid()
304
-    {
305
-        if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) {
306
-            throw new MaxInstancesExceededException('Recurring events are only allowed to generate '.Settings::$maxRecurrences);
307
-        }
308
-
309
-        return (bool) $this->currentDate;
310
-    }
311
-
312
-    /**
313
-     * Sets the iterator back to the starting point.
314
-     *
315
-     * @return void
316
-     */
317
-    #[\ReturnTypeWillChange]
318
-    public function rewind()
319
-    {
320
-        $this->recurIterator->rewind();
321
-        // re-creating overridden event index.
322
-        $index = [];
323
-        foreach ($this->overriddenEvents as $key => $event) {
324
-            $stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
325
-            $index[$stamp][] = $key;
326
-        }
327
-        krsort($index);
328
-        $this->counter = 0;
329
-        $this->overriddenEventsIndex = $index;
330
-        $this->currentOverriddenEvent = null;
331
-
332
-        $this->nextDate = null;
333
-        $this->currentDate = clone $this->startDate;
334
-
335
-        $this->next();
336
-    }
337
-
338
-    /**
339
-     * Advances the iterator with one step.
340
-     *
341
-     * @return void
342
-     */
343
-    #[\ReturnTypeWillChange]
344
-    public function next()
345
-    {
346
-        $this->currentOverriddenEvent = null;
347
-        ++$this->counter;
348
-        if ($this->nextDate) {
349
-            // We had a stored value.
350
-            $nextDate = $this->nextDate;
351
-            $this->nextDate = null;
352
-        } else {
353
-            // We need to ask rruleparser for the next date.
354
-            // We need to do this until we find a date that's not in the
355
-            // exception list.
356
-            do {
357
-                if (!$this->recurIterator->valid()) {
358
-                    $nextDate = null;
359
-                    break;
360
-                }
361
-                $nextDate = $this->recurIterator->current();
362
-                $this->recurIterator->next();
363
-            } while (isset($this->exceptions[$nextDate->getTimeStamp()]));
364
-        }
365
-
366
-        // $nextDate now contains what rrule thinks is the next one, but an
367
-        // overridden event may cut ahead.
368
-        if ($this->overriddenEventsIndex) {
369
-            $offsets = end($this->overriddenEventsIndex);
370
-            $timestamp = key($this->overriddenEventsIndex);
371
-            $offset = end($offsets);
372
-            if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
373
-                // Overridden event comes first.
374
-                $this->currentOverriddenEvent = $this->overriddenEvents[$offset];
375
-
376
-                // Putting the rrule next date aside.
377
-                $this->nextDate = $nextDate;
378
-                $this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
379
-
380
-                // Ensuring that this item will only be used once.
381
-                array_pop($this->overriddenEventsIndex[$timestamp]);
382
-                if (!$this->overriddenEventsIndex[$timestamp]) {
383
-                    array_pop($this->overriddenEventsIndex);
384
-                }
385
-
386
-                // Exit point!
387
-                return;
388
-            }
389
-        }
390
-
391
-        $this->currentDate = $nextDate;
392
-    }
393
-
394
-    /**
395
-     * Quickly jump to a date in the future.
396
-     */
397
-    public function fastForward(DateTimeInterface $dateTime)
398
-    {
399
-        while ($this->valid() && $this->getDtEnd() <= $dateTime) {
400
-            $this->next();
401
-        }
402
-    }
403
-
404
-    /**
405
-     * Returns true if this recurring event never ends.
406
-     *
407
-     * @return bool
408
-     */
409
-    public function isInfinite()
410
-    {
411
-        return $this->recurIterator->isInfinite();
412
-    }
413
-
414
-    /**
415
-     * RRULE parser.
416
-     *
417
-     * @var RRuleIterator
418
-     */
419
-    protected $recurIterator;
420
-
421
-    /**
422
-     * The duration, in seconds, of the master event.
423
-     *
424
-     * We use this to calculate the DTEND for subsequent events.
425
-     */
426
-    protected $eventDuration;
427
-
428
-    /**
429
-     * A reference to the main (master) event.
430
-     *
431
-     * @var VEVENT
432
-     */
433
-    protected $masterEvent;
434
-
435
-    /**
436
-     * List of overridden events.
437
-     *
438
-     * @var array
439
-     */
440
-    protected $overriddenEvents = [];
441
-
442
-    /**
443
-     * Overridden event index.
444
-     *
445
-     * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent
446
-     * property.
447
-     *
448
-     * @var array
449
-     */
450
-    protected $overriddenEventsIndex;
451
-
452
-    /**
453
-     * A list of recurrence-id's that are either part of EXDATE, or are
454
-     * overridden.
455
-     *
456
-     * @var array
457
-     */
458
-    protected $exceptions = [];
459
-
460
-    /**
461
-     * Internal event counter.
462
-     *
463
-     * @var int
464
-     */
465
-    protected $counter;
466
-
467
-    /**
468
-     * The very start of the iteration process.
469
-     *
470
-     * @var DateTimeImmutable
471
-     */
472
-    protected $startDate;
473
-
474
-    /**
475
-     * Where we are currently in the iteration process.
476
-     *
477
-     * @var DateTimeImmutable
478
-     */
479
-    protected $currentDate;
480
-
481
-    /**
482
-     * The next date from the rrule parser.
483
-     *
484
-     * Sometimes we need to temporary store the next date, because an
485
-     * overridden event came before.
486
-     *
487
-     * @var DateTimeImmutable
488
-     */
489
-    protected $nextDate;
490
-
491
-    /**
492
-     * The event that overwrites the current iteration.
493
-     *
494
-     * @var VEVENT
495
-     */
496
-    protected $currentOverriddenEvent;
63
+	/**
64
+	 * Reference timeZone for floating dates and times.
65
+	 *
66
+	 * @var DateTimeZone
67
+	 */
68
+	protected $timeZone;
69
+
70
+	/**
71
+	 * True if we're iterating an all-day event.
72
+	 *
73
+	 * @var bool
74
+	 */
75
+	protected $allDay = false;
76
+
77
+	/**
78
+	 * Creates the iterator.
79
+	 *
80
+	 * There's three ways to set up the iterator.
81
+	 *
82
+	 * 1. You can pass a VCALENDAR component and a UID.
83
+	 * 2. You can pass an array of VEVENTs (all UIDS should match).
84
+	 * 3. You can pass a single VEVENT component.
85
+	 *
86
+	 * Only the second method is recommended. The other 1 and 3 will be removed
87
+	 * at some point in the future.
88
+	 *
89
+	 * The $uid parameter is only required for the first method.
90
+	 *
91
+	 * @param Component|array $input
92
+	 * @param string|null     $uid
93
+	 * @param DateTimeZone    $timeZone reference timezone for floating dates and
94
+	 *                                  times
95
+	 */
96
+	public function __construct($input, $uid = null, DateTimeZone $timeZone = null)
97
+	{
98
+		if (is_null($timeZone)) {
99
+			$timeZone = new DateTimeZone('UTC');
100
+		}
101
+		$this->timeZone = $timeZone;
102
+
103
+		if (is_array($input)) {
104
+			$events = $input;
105
+		} elseif ($input instanceof VEvent) {
106
+			// Single instance mode.
107
+			$events = [$input];
108
+		} else {
109
+			// Calendar + UID mode.
110
+			$uid = (string) $uid;
111
+			if (!$uid) {
112
+				throw new InvalidArgumentException('The UID argument is required when a VCALENDAR is passed to this constructor');
113
+			}
114
+			if (!isset($input->VEVENT)) {
115
+				throw new InvalidArgumentException('No events found in this calendar');
116
+			}
117
+			$events = $input->getByUID($uid);
118
+		}
119
+
120
+		foreach ($events as $vevent) {
121
+			if (!isset($vevent->{'RECURRENCE-ID'})) {
122
+				$this->masterEvent = $vevent;
123
+			} else {
124
+				$this->exceptions[
125
+					$vevent->{'RECURRENCE-ID'}->getDateTime($this->timeZone)->getTimeStamp()
126
+				] = true;
127
+				$this->overriddenEvents[] = $vevent;
128
+			}
129
+		}
130
+
131
+		if (!$this->masterEvent) {
132
+			// No base event was found. CalDAV does allow cases where only
133
+			// overridden instances are stored.
134
+			//
135
+			// In this particular case, we're just going to grab the first
136
+			// event and use that instead. This may not always give the
137
+			// desired result.
138
+			if (!count($this->overriddenEvents)) {
139
+				throw new InvalidArgumentException('This VCALENDAR did not have an event with UID: '.$uid);
140
+			}
141
+			$this->masterEvent = array_shift($this->overriddenEvents);
142
+		}
143
+
144
+		$this->startDate = $this->masterEvent->DTSTART->getDateTime($this->timeZone);
145
+		$this->allDay = !$this->masterEvent->DTSTART->hasTime();
146
+
147
+		if (isset($this->masterEvent->EXDATE)) {
148
+			foreach ($this->masterEvent->EXDATE as $exDate) {
149
+				foreach ($exDate->getDateTimes($this->timeZone) as $dt) {
150
+					$this->exceptions[$dt->getTimeStamp()] = true;
151
+				}
152
+			}
153
+		}
154
+
155
+		if (isset($this->masterEvent->DTEND)) {
156
+			$this->eventDuration =
157
+				$this->masterEvent->DTEND->getDateTime($this->timeZone)->getTimeStamp() -
158
+				$this->startDate->getTimeStamp();
159
+		} elseif (isset($this->masterEvent->DURATION)) {
160
+			$duration = $this->masterEvent->DURATION->getDateInterval();
161
+			$end = clone $this->startDate;
162
+			$end = $end->add($duration);
163
+			$this->eventDuration = $end->getTimeStamp() - $this->startDate->getTimeStamp();
164
+		} elseif ($this->allDay) {
165
+			$this->eventDuration = 3600 * 24;
166
+		} else {
167
+			$this->eventDuration = 0;
168
+		}
169
+
170
+		if (isset($this->masterEvent->RDATE)) {
171
+			$this->recurIterator = new RDateIterator(
172
+				$this->masterEvent->RDATE->getParts(),
173
+				$this->startDate
174
+			);
175
+		} elseif (isset($this->masterEvent->RRULE)) {
176
+			$this->recurIterator = new RRuleIterator(
177
+				$this->masterEvent->RRULE->getParts(),
178
+				$this->startDate
179
+			);
180
+		} else {
181
+			$this->recurIterator = new RRuleIterator(
182
+				[
183
+					'FREQ' => 'DAILY',
184
+					'COUNT' => 1,
185
+				],
186
+				$this->startDate
187
+			);
188
+		}
189
+
190
+		$this->rewind();
191
+		if (!$this->valid()) {
192
+			throw new NoInstancesException('This recurrence rule does not generate any valid instances');
193
+		}
194
+	}
195
+
196
+	/**
197
+	 * Returns the date for the current position of the iterator.
198
+	 *
199
+	 * @return DateTimeImmutable
200
+	 */
201
+	#[\ReturnTypeWillChange]
202
+	public function current()
203
+	{
204
+		if ($this->currentDate) {
205
+			return clone $this->currentDate;
206
+		}
207
+	}
208
+
209
+	/**
210
+	 * This method returns the start date for the current iteration of the
211
+	 * event.
212
+	 *
213
+	 * @return DateTimeImmutable
214
+	 */
215
+	public function getDtStart()
216
+	{
217
+		if ($this->currentDate) {
218
+			return clone $this->currentDate;
219
+		}
220
+	}
221
+
222
+	/**
223
+	 * This method returns the end date for the current iteration of the
224
+	 * event.
225
+	 *
226
+	 * @return DateTimeImmutable
227
+	 */
228
+	public function getDtEnd()
229
+	{
230
+		if (!$this->valid()) {
231
+			return;
232
+		}
233
+		if ($this->currentOverriddenEvent && $this->currentOverriddenEvent->DTEND) {
234
+			return $this->currentOverriddenEvent->DTEND->getDateTime($this->timeZone);
235
+		} else {
236
+			$end = clone $this->currentDate;
237
+
238
+			return $end->modify('+'.$this->eventDuration.' seconds');
239
+		}
240
+	}
241
+
242
+	/**
243
+	 * Returns a VEVENT for the current iterations of the event.
244
+	 *
245
+	 * This VEVENT will have a recurrence id, and its DTSTART and DTEND
246
+	 * altered.
247
+	 *
248
+	 * @return VEvent
249
+	 */
250
+	public function getEventObject()
251
+	{
252
+		if ($this->currentOverriddenEvent) {
253
+			return $this->currentOverriddenEvent;
254
+		}
255
+
256
+		$event = clone $this->masterEvent;
257
+
258
+		// Ignoring the following block, because PHPUnit's code coverage
259
+		// ignores most of these lines, and this messes with our stats.
260
+		//
261
+		// @codeCoverageIgnoreStart
262
+		unset(
263
+			$event->RRULE,
264
+			$event->EXDATE,
265
+			$event->RDATE,
266
+			$event->EXRULE,
267
+			$event->{'RECURRENCE-ID'}
268
+		);
269
+		// @codeCoverageIgnoreEnd
270
+
271
+		$event->DTSTART->setDateTime($this->getDtStart(), $event->DTSTART->isFloating());
272
+		if (isset($event->DTEND)) {
273
+			$event->DTEND->setDateTime($this->getDtEnd(), $event->DTEND->isFloating());
274
+		}
275
+		$recurid = clone $event->DTSTART;
276
+		$recurid->name = 'RECURRENCE-ID';
277
+		$event->add($recurid);
278
+
279
+		return $event;
280
+	}
281
+
282
+	/**
283
+	 * Returns the current position of the iterator.
284
+	 *
285
+	 * This is for us simply a 0-based index.
286
+	 *
287
+	 * @return int
288
+	 */
289
+	#[\ReturnTypeWillChange]
290
+	public function key()
291
+	{
292
+		// The counter is always 1 ahead.
293
+		return $this->counter - 1;
294
+	}
295
+
296
+	/**
297
+	 * This is called after next, to see if the iterator is still at a valid
298
+	 * position, or if it's at the end.
299
+	 *
300
+	 * @return bool
301
+	 */
302
+	#[\ReturnTypeWillChange]
303
+	public function valid()
304
+	{
305
+		if ($this->counter > Settings::$maxRecurrences && -1 !== Settings::$maxRecurrences) {
306
+			throw new MaxInstancesExceededException('Recurring events are only allowed to generate '.Settings::$maxRecurrences);
307
+		}
308
+
309
+		return (bool) $this->currentDate;
310
+	}
311
+
312
+	/**
313
+	 * Sets the iterator back to the starting point.
314
+	 *
315
+	 * @return void
316
+	 */
317
+	#[\ReturnTypeWillChange]
318
+	public function rewind()
319
+	{
320
+		$this->recurIterator->rewind();
321
+		// re-creating overridden event index.
322
+		$index = [];
323
+		foreach ($this->overriddenEvents as $key => $event) {
324
+			$stamp = $event->DTSTART->getDateTime($this->timeZone)->getTimeStamp();
325
+			$index[$stamp][] = $key;
326
+		}
327
+		krsort($index);
328
+		$this->counter = 0;
329
+		$this->overriddenEventsIndex = $index;
330
+		$this->currentOverriddenEvent = null;
331
+
332
+		$this->nextDate = null;
333
+		$this->currentDate = clone $this->startDate;
334
+
335
+		$this->next();
336
+	}
337
+
338
+	/**
339
+	 * Advances the iterator with one step.
340
+	 *
341
+	 * @return void
342
+	 */
343
+	#[\ReturnTypeWillChange]
344
+	public function next()
345
+	{
346
+		$this->currentOverriddenEvent = null;
347
+		++$this->counter;
348
+		if ($this->nextDate) {
349
+			// We had a stored value.
350
+			$nextDate = $this->nextDate;
351
+			$this->nextDate = null;
352
+		} else {
353
+			// We need to ask rruleparser for the next date.
354
+			// We need to do this until we find a date that's not in the
355
+			// exception list.
356
+			do {
357
+				if (!$this->recurIterator->valid()) {
358
+					$nextDate = null;
359
+					break;
360
+				}
361
+				$nextDate = $this->recurIterator->current();
362
+				$this->recurIterator->next();
363
+			} while (isset($this->exceptions[$nextDate->getTimeStamp()]));
364
+		}
365
+
366
+		// $nextDate now contains what rrule thinks is the next one, but an
367
+		// overridden event may cut ahead.
368
+		if ($this->overriddenEventsIndex) {
369
+			$offsets = end($this->overriddenEventsIndex);
370
+			$timestamp = key($this->overriddenEventsIndex);
371
+			$offset = end($offsets);
372
+			if (!$nextDate || $timestamp < $nextDate->getTimeStamp()) {
373
+				// Overridden event comes first.
374
+				$this->currentOverriddenEvent = $this->overriddenEvents[$offset];
375
+
376
+				// Putting the rrule next date aside.
377
+				$this->nextDate = $nextDate;
378
+				$this->currentDate = $this->currentOverriddenEvent->DTSTART->getDateTime($this->timeZone);
379
+
380
+				// Ensuring that this item will only be used once.
381
+				array_pop($this->overriddenEventsIndex[$timestamp]);
382
+				if (!$this->overriddenEventsIndex[$timestamp]) {
383
+					array_pop($this->overriddenEventsIndex);
384
+				}
385
+
386
+				// Exit point!
387
+				return;
388
+			}
389
+		}
390
+
391
+		$this->currentDate = $nextDate;
392
+	}
393
+
394
+	/**
395
+	 * Quickly jump to a date in the future.
396
+	 */
397
+	public function fastForward(DateTimeInterface $dateTime)
398
+	{
399
+		while ($this->valid() && $this->getDtEnd() <= $dateTime) {
400
+			$this->next();
401
+		}
402
+	}
403
+
404
+	/**
405
+	 * Returns true if this recurring event never ends.
406
+	 *
407
+	 * @return bool
408
+	 */
409
+	public function isInfinite()
410
+	{
411
+		return $this->recurIterator->isInfinite();
412
+	}
413
+
414
+	/**
415
+	 * RRULE parser.
416
+	 *
417
+	 * @var RRuleIterator
418
+	 */
419
+	protected $recurIterator;
420
+
421
+	/**
422
+	 * The duration, in seconds, of the master event.
423
+	 *
424
+	 * We use this to calculate the DTEND for subsequent events.
425
+	 */
426
+	protected $eventDuration;
427
+
428
+	/**
429
+	 * A reference to the main (master) event.
430
+	 *
431
+	 * @var VEVENT
432
+	 */
433
+	protected $masterEvent;
434
+
435
+	/**
436
+	 * List of overridden events.
437
+	 *
438
+	 * @var array
439
+	 */
440
+	protected $overriddenEvents = [];
441
+
442
+	/**
443
+	 * Overridden event index.
444
+	 *
445
+	 * Key is timestamp, value is the list of indexes of the item in the $overriddenEvent
446
+	 * property.
447
+	 *
448
+	 * @var array
449
+	 */
450
+	protected $overriddenEventsIndex;
451
+
452
+	/**
453
+	 * A list of recurrence-id's that are either part of EXDATE, or are
454
+	 * overridden.
455
+	 *
456
+	 * @var array
457
+	 */
458
+	protected $exceptions = [];
459
+
460
+	/**
461
+	 * Internal event counter.
462
+	 *
463
+	 * @var int
464
+	 */
465
+	protected $counter;
466
+
467
+	/**
468
+	 * The very start of the iteration process.
469
+	 *
470
+	 * @var DateTimeImmutable
471
+	 */
472
+	protected $startDate;
473
+
474
+	/**
475
+	 * Where we are currently in the iteration process.
476
+	 *
477
+	 * @var DateTimeImmutable
478
+	 */
479
+	protected $currentDate;
480
+
481
+	/**
482
+	 * The next date from the rrule parser.
483
+	 *
484
+	 * Sometimes we need to temporary store the next date, because an
485
+	 * overridden event came before.
486
+	 *
487
+	 * @var DateTimeImmutable
488
+	 */
489
+	protected $nextDate;
490
+
491
+	/**
492
+	 * The event that overwrites the current iteration.
493
+	 *
494
+	 * @var VEVENT
495
+	 */
496
+	protected $currentOverriddenEvent;
497 497
 }
Please login to merge, or discard this patch.
htdocs/includes/sabre/sabre/vobject/lib/Recur/RRuleIterator.php 1 patch
Indentation   +983 added lines, -983 removed lines patch added patch discarded remove patch
@@ -24,987 +24,987 @@
 block discarded – undo
24 24
  */
25 25
 class RRuleIterator implements Iterator
26 26
 {
27
-    /**
28
-     * Constant denoting the upper limit on how long into the future
29
-     * we want to iterate. The value is a unix timestamp and currently
30
-     * corresponds to the datetime 9999-12-31 11:59:59 UTC.
31
-     */
32
-    const dateUpperLimit = 253402300799;
33
-
34
-    /**
35
-     * Creates the Iterator.
36
-     *
37
-     * @param string|array $rrule
38
-     */
39
-    public function __construct($rrule, DateTimeInterface $start)
40
-    {
41
-        $this->startDate = $start;
42
-        $this->parseRRule($rrule);
43
-        $this->currentDate = clone $this->startDate;
44
-    }
45
-
46
-    /* Implementation of the Iterator interface {{{ */
47
-
48
-    #[\ReturnTypeWillChange]
49
-    public function current()
50
-    {
51
-        if (!$this->valid()) {
52
-            return;
53
-        }
54
-
55
-        return clone $this->currentDate;
56
-    }
57
-
58
-    /**
59
-     * Returns the current item number.
60
-     *
61
-     * @return int
62
-     */
63
-    #[\ReturnTypeWillChange]
64
-    public function key()
65
-    {
66
-        return $this->counter;
67
-    }
68
-
69
-    /**
70
-     * Returns whether the current item is a valid item for the recurrence
71
-     * iterator. This will return false if we've gone beyond the UNTIL or COUNT
72
-     * statements.
73
-     *
74
-     * @return bool
75
-     */
76
-    #[\ReturnTypeWillChange]
77
-    public function valid()
78
-    {
79
-        if (null === $this->currentDate) {
80
-            return false;
81
-        }
82
-        if (!is_null($this->count)) {
83
-            return $this->counter < $this->count;
84
-        }
85
-
86
-        return is_null($this->until) || $this->currentDate <= $this->until;
87
-    }
88
-
89
-    /**
90
-     * Resets the iterator.
91
-     *
92
-     * @return void
93
-     */
94
-    #[\ReturnTypeWillChange]
95
-    public function rewind()
96
-    {
97
-        $this->currentDate = clone $this->startDate;
98
-        $this->counter = 0;
99
-    }
100
-
101
-    /**
102
-     * Goes on to the next iteration.
103
-     *
104
-     * @return void
105
-     */
106
-    #[\ReturnTypeWillChange]
107
-    public function next()
108
-    {
109
-        // Otherwise, we find the next event in the normal RRULE
110
-        // sequence.
111
-        switch ($this->frequency) {
112
-            case 'hourly':
113
-                $this->nextHourly();
114
-                break;
115
-
116
-            case 'daily':
117
-                $this->nextDaily();
118
-                break;
119
-
120
-            case 'weekly':
121
-                $this->nextWeekly();
122
-                break;
123
-
124
-            case 'monthly':
125
-                $this->nextMonthly();
126
-                break;
127
-
128
-            case 'yearly':
129
-                $this->nextYearly();
130
-                break;
131
-        }
132
-        ++$this->counter;
133
-    }
134
-
135
-    /* End of Iterator implementation }}} */
136
-
137
-    /**
138
-     * Returns true if this recurring event never ends.
139
-     *
140
-     * @return bool
141
-     */
142
-    public function isInfinite()
143
-    {
144
-        return !$this->count && !$this->until;
145
-    }
146
-
147
-    /**
148
-     * This method allows you to quickly go to the next occurrence after the
149
-     * specified date.
150
-     */
151
-    public function fastForward(DateTimeInterface $dt)
152
-    {
153
-        while ($this->valid() && $this->currentDate < $dt) {
154
-            $this->next();
155
-        }
156
-    }
157
-
158
-    /**
159
-     * The reference start date/time for the rrule.
160
-     *
161
-     * All calculations are based on this initial date.
162
-     *
163
-     * @var DateTimeInterface
164
-     */
165
-    protected $startDate;
166
-
167
-    /**
168
-     * The date of the current iteration. You can get this by calling
169
-     * ->current().
170
-     *
171
-     * @var DateTimeInterface
172
-     */
173
-    protected $currentDate;
174
-
175
-    /**
176
-     * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
177
-     * yearly.
178
-     *
179
-     * @var string
180
-     */
181
-    protected $frequency;
182
-
183
-    /**
184
-     * The number of recurrences, or 'null' if infinitely recurring.
185
-     *
186
-     * @var int
187
-     */
188
-    protected $count;
189
-
190
-    /**
191
-     * The interval.
192
-     *
193
-     * If for example frequency is set to daily, interval = 2 would mean every
194
-     * 2 days.
195
-     *
196
-     * @var int
197
-     */
198
-    protected $interval = 1;
199
-
200
-    /**
201
-     * The last instance of this recurrence, inclusively.
202
-     *
203
-     * @var DateTimeInterface|null
204
-     */
205
-    protected $until;
206
-
207
-    /**
208
-     * Which seconds to recur.
209
-     *
210
-     * This is an array of integers (between 0 and 60)
211
-     *
212
-     * @var array
213
-     */
214
-    protected $bySecond;
215
-
216
-    /**
217
-     * Which minutes to recur.
218
-     *
219
-     * This is an array of integers (between 0 and 59)
220
-     *
221
-     * @var array
222
-     */
223
-    protected $byMinute;
224
-
225
-    /**
226
-     * Which hours to recur.
227
-     *
228
-     * This is an array of integers (between 0 and 23)
229
-     *
230
-     * @var array
231
-     */
232
-    protected $byHour;
233
-
234
-    /**
235
-     * The current item in the list.
236
-     *
237
-     * You can get this number with the key() method.
238
-     *
239
-     * @var int
240
-     */
241
-    protected $counter = 0;
242
-
243
-    /**
244
-     * Which weekdays to recur.
245
-     *
246
-     * This is an array of weekdays
247
-     *
248
-     * This may also be preceded by a positive or negative integer. If present,
249
-     * this indicates the nth occurrence of a specific day within the monthly or
250
-     * yearly rrule. For instance, -2TU indicates the second-last tuesday of
251
-     * the month, or year.
252
-     *
253
-     * @var array
254
-     */
255
-    protected $byDay;
256
-
257
-    /**
258
-     * Which days of the month to recur.
259
-     *
260
-     * This is an array of days of the months (1-31). The value can also be
261
-     * negative. -5 for instance means the 5th last day of the month.
262
-     *
263
-     * @var array
264
-     */
265
-    protected $byMonthDay;
266
-
267
-    /**
268
-     * Which days of the year to recur.
269
-     *
270
-     * This is an array with days of the year (1 to 366). The values can also
271
-     * be negative. For instance, -1 will always represent the last day of the
272
-     * year. (December 31st).
273
-     *
274
-     * @var array
275
-     */
276
-    protected $byYearDay;
277
-
278
-    /**
279
-     * Which week numbers to recur.
280
-     *
281
-     * This is an array of integers from 1 to 53. The values can also be
282
-     * negative. -1 will always refer to the last week of the year.
283
-     *
284
-     * @var array
285
-     */
286
-    protected $byWeekNo;
287
-
288
-    /**
289
-     * Which months to recur.
290
-     *
291
-     * This is an array of integers from 1 to 12.
292
-     *
293
-     * @var array
294
-     */
295
-    protected $byMonth;
296
-
297
-    /**
298
-     * Which items in an existing st to recur.
299
-     *
300
-     * These numbers work together with an existing by* rule. It specifies
301
-     * exactly which items of the existing by-rule to filter.
302
-     *
303
-     * Valid values are 1 to 366 and -1 to -366. As an example, this can be
304
-     * used to recur the last workday of the month.
305
-     *
306
-     * This would be done by setting frequency to 'monthly', byDay to
307
-     * 'MO,TU,WE,TH,FR' and bySetPos to -1.
308
-     *
309
-     * @var array
310
-     */
311
-    protected $bySetPos;
312
-
313
-    /**
314
-     * When the week starts.
315
-     *
316
-     * @var string
317
-     */
318
-    protected $weekStart = 'MO';
319
-
320
-    /* Functions that advance the iterator {{{ */
321
-
322
-    /**
323
-     * Does the processing for advancing the iterator for hourly frequency.
324
-     */
325
-    protected function nextHourly()
326
-    {
327
-        $this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours');
328
-    }
329
-
330
-    /**
331
-     * Does the processing for advancing the iterator for daily frequency.
332
-     */
333
-    protected function nextDaily()
334
-    {
335
-        if (!$this->byHour && !$this->byDay) {
336
-            $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
337
-
338
-            return;
339
-        }
340
-
341
-        $recurrenceHours = [];
342
-        if (!empty($this->byHour)) {
343
-            $recurrenceHours = $this->getHours();
344
-        }
345
-
346
-        $recurrenceDays = [];
347
-        if (!empty($this->byDay)) {
348
-            $recurrenceDays = $this->getDays();
349
-        }
350
-
351
-        $recurrenceMonths = [];
352
-        if (!empty($this->byMonth)) {
353
-            $recurrenceMonths = $this->getMonths();
354
-        }
355
-
356
-        do {
357
-            if ($this->byHour) {
358
-                if ('23' == $this->currentDate->format('G')) {
359
-                    // to obey the interval rule
360
-                    $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
361
-                }
362
-
363
-                $this->currentDate = $this->currentDate->modify('+1 hours');
364
-            } else {
365
-                $this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
366
-            }
367
-
368
-            // Current month of the year
369
-            $currentMonth = $this->currentDate->format('n');
370
-
371
-            // Current day of the week
372
-            $currentDay = $this->currentDate->format('w');
373
-
374
-            // Current hour of the day
375
-            $currentHour = $this->currentDate->format('G');
376
-
377
-            if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
378
-                $this->currentDate = null;
379
-
380
-                return;
381
-            }
382
-        } while (
383
-            ($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
384
-            ($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
385
-            ($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
386
-        );
387
-    }
388
-
389
-    /**
390
-     * Does the processing for advancing the iterator for weekly frequency.
391
-     */
392
-    protected function nextWeekly()
393
-    {
394
-        if (!$this->byHour && !$this->byDay) {
395
-            $this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
396
-
397
-            return;
398
-        }
399
-
400
-        $recurrenceHours = [];
401
-        if ($this->byHour) {
402
-            $recurrenceHours = $this->getHours();
403
-        }
404
-
405
-        $recurrenceDays = [];
406
-        if ($this->byDay) {
407
-            $recurrenceDays = $this->getDays();
408
-        }
409
-
410
-        // First day of the week:
411
-        $firstDay = $this->dayMap[$this->weekStart];
412
-
413
-        do {
414
-            if ($this->byHour) {
415
-                $this->currentDate = $this->currentDate->modify('+1 hours');
416
-            } else {
417
-                $this->currentDate = $this->currentDate->modify('+1 days');
418
-            }
419
-
420
-            // Current day of the week
421
-            $currentDay = (int) $this->currentDate->format('w');
422
-
423
-            // Current hour of the day
424
-            $currentHour = (int) $this->currentDate->format('G');
425
-
426
-            // We need to roll over to the next week
427
-            if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
428
-                $this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
429
-
430
-                // We need to go to the first day of this week, but only if we
431
-                // are not already on this first day of this week.
432
-                if ($this->currentDate->format('w') != $firstDay) {
433
-                    $this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
434
-                }
435
-            }
436
-
437
-            // We have a match
438
-        } while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
439
-    }
440
-
441
-    /**
442
-     * Does the processing for advancing the iterator for monthly frequency.
443
-     */
444
-    protected function nextMonthly()
445
-    {
446
-        $currentDayOfMonth = $this->currentDate->format('j');
447
-        if (!$this->byMonthDay && !$this->byDay) {
448
-            // If the current day is higher than the 28th, rollover can
449
-            // occur to the next month. We Must skip these invalid
450
-            // entries.
451
-            if ($currentDayOfMonth < 29) {
452
-                $this->currentDate = $this->currentDate->modify('+'.$this->interval.' months');
453
-            } else {
454
-                $increase = 0;
455
-                do {
456
-                    ++$increase;
457
-                    $tempDate = clone $this->currentDate;
458
-                    $tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months');
459
-                } while ($tempDate->format('j') != $currentDayOfMonth);
460
-                $this->currentDate = $tempDate;
461
-            }
462
-
463
-            return;
464
-        }
465
-
466
-        $occurrence = -1;
467
-        while (true) {
468
-            $occurrences = $this->getMonthlyOccurrences();
469
-
470
-            foreach ($occurrences as $occurrence) {
471
-                // The first occurrence thats higher than the current
472
-                // day of the month wins.
473
-                if ($occurrence > $currentDayOfMonth) {
474
-                    break 2;
475
-                }
476
-            }
477
-
478
-            // If we made it all the way here, it means there were no
479
-            // valid occurrences, and we need to advance to the next
480
-            // month.
481
-            //
482
-            // This line does not currently work in hhvm. Temporary workaround
483
-            // follows:
484
-            // $this->currentDate->modify('first day of this month');
485
-            $this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
486
-            // end of workaround
487
-            $this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months');
488
-
489
-            // This goes to 0 because we need to start counting at the
490
-            // beginning.
491
-            $currentDayOfMonth = 0;
492
-
493
-            // For some reason the "until" parameter was not being used here,
494
-            // that's why the workaround of the 10000 year bug was needed at all
495
-            // let's stop it before the "until" parameter date
496
-            if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) {
497
-                return;
498
-            }
499
-
500
-            // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
501
-            // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
502
-            if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
503
-                $this->currentDate = null;
504
-
505
-                return;
506
-            }
507
-        }
508
-
509
-        $this->currentDate = $this->currentDate->setDate(
510
-            (int) $this->currentDate->format('Y'),
511
-            (int) $this->currentDate->format('n'),
512
-            (int) $occurrence
513
-        );
514
-    }
515
-
516
-    /**
517
-     * Does the processing for advancing the iterator for yearly frequency.
518
-     */
519
-    protected function nextYearly()
520
-    {
521
-        $currentMonth = $this->currentDate->format('n');
522
-        $currentYear = $this->currentDate->format('Y');
523
-        $currentDayOfMonth = $this->currentDate->format('j');
524
-
525
-        // No sub-rules, so we just advance by year
526
-        if (empty($this->byMonth)) {
527
-            // Unless it was a leap day!
528
-            if (2 == $currentMonth && 29 == $currentDayOfMonth) {
529
-                $counter = 0;
530
-                do {
531
-                    ++$counter;
532
-                    // Here we increase the year count by the interval, until
533
-                    // we hit a date that's also in a leap year.
534
-                    //
535
-                    // We could just find the next interval that's dividable by
536
-                    // 4, but that would ignore the rule that there's no leap
537
-                    // year every year that's dividable by a 100, but not by
538
-                    // 400. (1800, 1900, 2100). So we just rely on the datetime
539
-                    // functions instead.
540
-                    $nextDate = clone $this->currentDate;
541
-                    $nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
542
-                } while (2 != $nextDate->format('n'));
543
-
544
-                $this->currentDate = $nextDate;
545
-
546
-                return;
547
-            }
548
-
549
-            if (null !== $this->byWeekNo) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
550
-                $dayOffsets = [];
551
-                if ($this->byDay) {
552
-                    foreach ($this->byDay as $byDay) {
553
-                        $dayOffsets[] = $this->dayMap[$byDay];
554
-                    }
555
-                } else {   // default is Monday
556
-                    $dayOffsets[] = 1;
557
-                }
558
-
559
-                $currentYear = $this->currentDate->format('Y');
560
-
561
-                while (true) {
562
-                    $checkDates = [];
563
-
564
-                    // loop through all WeekNo and Days to check all the combinations
565
-                    foreach ($this->byWeekNo as $byWeekNo) {
566
-                        foreach ($dayOffsets as $dayOffset) {
567
-                            $date = clone $this->currentDate;
568
-                            $date = $date->setISODate($currentYear, $byWeekNo, $dayOffset);
569
-
570
-                            if ($date > $this->currentDate) {
571
-                                $checkDates[] = $date;
572
-                            }
573
-                        }
574
-                    }
575
-
576
-                    if (count($checkDates) > 0) {
577
-                        $this->currentDate = min($checkDates);
578
-
579
-                        return;
580
-                    }
581
-
582
-                    // if there is no date found, check the next year
583
-                    $currentYear += $this->interval;
584
-                }
585
-            }
586
-
587
-            if (null !== $this->byYearDay) { // byYearDay is an array with values from -366 to -1, or 1 to 366
588
-                $dayOffsets = [];
589
-                if ($this->byDay) {
590
-                    foreach ($this->byDay as $byDay) {
591
-                        $dayOffsets[] = $this->dayMap[$byDay];
592
-                    }
593
-                } else {   // default is Monday-Sunday
594
-                    $dayOffsets = [1, 2, 3, 4, 5, 6, 7];
595
-                }
596
-
597
-                $currentYear = $this->currentDate->format('Y');
598
-
599
-                while (true) {
600
-                    $checkDates = [];
601
-
602
-                    // loop through all YearDay and Days to check all the combinations
603
-                    foreach ($this->byYearDay as $byYearDay) {
604
-                        $date = clone $this->currentDate;
605
-                        if ($byYearDay > 0) {
606
-                            $date = $date->setDate($currentYear, 1, 1);
607
-                            $date = $date->add(new \DateInterval('P'.($byYearDay - 1).'D'));
608
-                        } else {
609
-                            $date = $date->setDate($currentYear, 12, 31);
610
-                            $date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
611
-                        }
612
-
613
-                        if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
614
-                            $checkDates[] = $date;
615
-                        }
616
-                    }
617
-
618
-                    if (count($checkDates) > 0) {
619
-                        $this->currentDate = min($checkDates);
620
-
621
-                        return;
622
-                    }
623
-
624
-                    // if there is no date found, check the next year
625
-                    $currentYear += $this->interval;
626
-                }
627
-            }
628
-
629
-            // The easiest form
630
-            $this->currentDate = $this->currentDate->modify('+'.$this->interval.' years');
631
-
632
-            return;
633
-        }
634
-
635
-        $currentMonth = $this->currentDate->format('n');
636
-        $currentYear = $this->currentDate->format('Y');
637
-        $currentDayOfMonth = $this->currentDate->format('j');
638
-
639
-        $advancedToNewMonth = false;
640
-
641
-        // If we got a byDay or getMonthDay filter, we must first expand
642
-        // further.
643
-        if ($this->byDay || $this->byMonthDay) {
644
-            $occurrence = -1;
645
-            while (true) {
646
-                $occurrences = $this->getMonthlyOccurrences();
647
-
648
-                foreach ($occurrences as $occurrence) {
649
-                    // The first occurrence that's higher than the current
650
-                    // day of the month wins.
651
-                    // If we advanced to the next month or year, the first
652
-                    // occurrence is always correct.
653
-                    if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
654
-                        break 2;
655
-                    }
656
-                }
657
-
658
-                // If we made it here, it means we need to advance to
659
-                // the next month or year.
660
-                $currentDayOfMonth = 1;
661
-                $advancedToNewMonth = true;
662
-                do {
663
-                    ++$currentMonth;
664
-                    if ($currentMonth > 12) {
665
-                        $currentYear += $this->interval;
666
-                        $currentMonth = 1;
667
-                    }
668
-                } while (!in_array($currentMonth, $this->byMonth));
669
-
670
-                $this->currentDate = $this->currentDate->setDate(
671
-                    (int) $currentYear,
672
-                    (int) $currentMonth,
673
-                    (int) $currentDayOfMonth
674
-                );
675
-
676
-                // To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
677
-                // stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
678
-                if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
679
-                    $this->currentDate = null;
680
-
681
-                    return;
682
-                }
683
-            }
684
-
685
-            // If we made it here, it means we got a valid occurrence
686
-            $this->currentDate = $this->currentDate->setDate(
687
-                (int) $currentYear,
688
-                (int) $currentMonth,
689
-                (int) $occurrence
690
-            );
691
-
692
-            return;
693
-        } else {
694
-            // These are the 'byMonth' rules, if there are no byDay or
695
-            // byMonthDay sub-rules.
696
-            do {
697
-                ++$currentMonth;
698
-                if ($currentMonth > 12) {
699
-                    $currentYear += $this->interval;
700
-                    $currentMonth = 1;
701
-                }
702
-            } while (!in_array($currentMonth, $this->byMonth));
703
-            $this->currentDate = $this->currentDate->setDate(
704
-                (int) $currentYear,
705
-                (int) $currentMonth,
706
-                (int) $currentDayOfMonth
707
-            );
708
-
709
-            return;
710
-        }
711
-    }
712
-
713
-    /* }}} */
714
-
715
-    /**
716
-     * This method receives a string from an RRULE property, and populates this
717
-     * class with all the values.
718
-     *
719
-     * @param string|array $rrule
720
-     */
721
-    protected function parseRRule($rrule)
722
-    {
723
-        if (is_string($rrule)) {
724
-            $rrule = Property\ICalendar\Recur::stringToArray($rrule);
725
-        }
726
-
727
-        foreach ($rrule as $key => $value) {
728
-            $key = strtoupper($key);
729
-            switch ($key) {
730
-                case 'FREQ':
731
-                    $value = strtolower($value);
732
-                    if (!in_array(
733
-                        $value,
734
-                        ['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
735
-                    )) {
736
-                        throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
737
-                    }
738
-                    $this->frequency = $value;
739
-                    break;
740
-
741
-                case 'UNTIL':
742
-                    $this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
743
-
744
-                    // In some cases events are generated with an UNTIL=
745
-                    // parameter before the actual start of the event.
746
-                    //
747
-                    // Not sure why this is happening. We assume that the
748
-                    // intention was that the event only recurs once.
749
-                    //
750
-                    // So we are modifying the parameter so our code doesn't
751
-                    // break.
752
-                    if ($this->until < $this->startDate) {
753
-                        $this->until = $this->startDate;
754
-                    }
755
-                    break;
756
-
757
-                case 'INTERVAL':
758
-                case 'COUNT':
759
-                    $val = (int) $value;
760
-                    if ($val < 1) {
761
-                        throw new InvalidDataException(strtoupper($key).' in RRULE must be a positive integer!');
762
-                    }
763
-                    $key = strtolower($key);
764
-                    $this->$key = $val;
765
-                    break;
766
-
767
-                case 'BYSECOND':
768
-                    $this->bySecond = (array) $value;
769
-                    break;
770
-
771
-                case 'BYMINUTE':
772
-                    $this->byMinute = (array) $value;
773
-                    break;
774
-
775
-                case 'BYHOUR':
776
-                    $this->byHour = (array) $value;
777
-                    break;
778
-
779
-                case 'BYDAY':
780
-                    $value = (array) $value;
781
-                    foreach ($value as $part) {
782
-                        if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
783
-                            throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
784
-                        }
785
-                    }
786
-                    $this->byDay = $value;
787
-                    break;
788
-
789
-                case 'BYMONTHDAY':
790
-                    $this->byMonthDay = (array) $value;
791
-                    break;
792
-
793
-                case 'BYYEARDAY':
794
-                    $this->byYearDay = (array) $value;
795
-                    foreach ($this->byYearDay as $byYearDay) {
796
-                        if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
797
-                            throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
798
-                        }
799
-                    }
800
-                    break;
801
-
802
-                case 'BYWEEKNO':
803
-                    $this->byWeekNo = (array) $value;
804
-                    foreach ($this->byWeekNo as $byWeekNo) {
805
-                        if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
806
-                            throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
807
-                        }
808
-                    }
809
-                    break;
810
-
811
-                case 'BYMONTH':
812
-                    $this->byMonth = (array) $value;
813
-                    foreach ($this->byMonth as $byMonth) {
814
-                        if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) {
815
-                            throw new InvalidDataException('BYMONTH in RRULE must have value(s) between 1 and 12!');
816
-                        }
817
-                    }
818
-                    break;
819
-
820
-                case 'BYSETPOS':
821
-                    $this->bySetPos = (array) $value;
822
-                    break;
823
-
824
-                case 'WKST':
825
-                    $this->weekStart = strtoupper($value);
826
-                    break;
827
-
828
-                default:
829
-                    throw new InvalidDataException('Not supported: '.strtoupper($key));
830
-            }
831
-        }
832
-    }
833
-
834
-    /**
835
-     * Mappings between the day number and english day name.
836
-     *
837
-     * @var array
838
-     */
839
-    protected $dayNames = [
840
-        0 => 'Sunday',
841
-        1 => 'Monday',
842
-        2 => 'Tuesday',
843
-        3 => 'Wednesday',
844
-        4 => 'Thursday',
845
-        5 => 'Friday',
846
-        6 => 'Saturday',
847
-    ];
848
-
849
-    /**
850
-     * Returns all the occurrences for a monthly frequency with a 'byDay' or
851
-     * 'byMonthDay' expansion for the current month.
852
-     *
853
-     * The returned list is an array of integers with the day of month (1-31).
854
-     *
855
-     * @return array
856
-     */
857
-    protected function getMonthlyOccurrences()
858
-    {
859
-        $startDate = clone $this->currentDate;
860
-
861
-        $byDayResults = [];
862
-
863
-        // Our strategy is to simply go through the byDays, advance the date to
864
-        // that point and add it to the results.
865
-        if ($this->byDay) {
866
-            foreach ($this->byDay as $day) {
867
-                $dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
868
-
869
-                // Dayname will be something like 'wednesday'. Now we need to find
870
-                // all wednesdays in this month.
871
-                $dayHits = [];
872
-
873
-                // workaround for missing 'first day of the month' support in hhvm
874
-                $checkDate = new \DateTime($startDate->format('Y-m-1'));
875
-                // workaround modify always advancing the date even if the current day is a $dayName in hhvm
876
-                if ($checkDate->format('l') !== $dayName) {
877
-                    $checkDate = $checkDate->modify($dayName);
878
-                }
879
-
880
-                do {
881
-                    $dayHits[] = $checkDate->format('j');
882
-                    $checkDate = $checkDate->modify('next '.$dayName);
883
-                } while ($checkDate->format('n') === $startDate->format('n'));
884
-
885
-                // So now we have 'all wednesdays' for month. It is however
886
-                // possible that the user only really wanted the 1st, 2nd or last
887
-                // wednesday.
888
-                if (strlen($day) > 2) {
889
-                    $offset = (int) substr($day, 0, -2);
890
-
891
-                    if ($offset > 0) {
892
-                        // It is possible that the day does not exist, such as a
893
-                        // 5th or 6th wednesday of the month.
894
-                        if (isset($dayHits[$offset - 1])) {
895
-                            $byDayResults[] = $dayHits[$offset - 1];
896
-                        }
897
-                    } else {
898
-                        // if it was negative we count from the end of the array
899
-                        // might not exist, fx. -5th tuesday
900
-                        if (isset($dayHits[count($dayHits) + $offset])) {
901
-                            $byDayResults[] = $dayHits[count($dayHits) + $offset];
902
-                        }
903
-                    }
904
-                } else {
905
-                    // There was no counter (first, second, last wednesdays), so we
906
-                    // just need to add the all to the list).
907
-                    $byDayResults = array_merge($byDayResults, $dayHits);
908
-                }
909
-            }
910
-        }
911
-
912
-        $byMonthDayResults = [];
913
-        if ($this->byMonthDay) {
914
-            foreach ($this->byMonthDay as $monthDay) {
915
-                // Removing values that are out of range for this month
916
-                if ($monthDay > $startDate->format('t') ||
917
-                    $monthDay < 0 - $startDate->format('t')) {
918
-                    continue;
919
-                }
920
-                if ($monthDay > 0) {
921
-                    $byMonthDayResults[] = $monthDay;
922
-                } else {
923
-                    // Negative values
924
-                    $byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
925
-                }
926
-            }
927
-        }
928
-
929
-        // If there was just byDay or just byMonthDay, they just specify our
930
-        // (almost) final list. If both were provided, then byDay limits the
931
-        // list.
932
-        if ($this->byMonthDay && $this->byDay) {
933
-            $result = array_intersect($byMonthDayResults, $byDayResults);
934
-        } elseif ($this->byMonthDay) {
935
-            $result = $byMonthDayResults;
936
-        } else {
937
-            $result = $byDayResults;
938
-        }
939
-        $result = array_unique($result);
940
-        sort($result, SORT_NUMERIC);
941
-
942
-        // The last thing that needs checking is the BYSETPOS. If it's set, it
943
-        // means only certain items in the set survive the filter.
944
-        if (!$this->bySetPos) {
945
-            return $result;
946
-        }
947
-
948
-        $filteredResult = [];
949
-        foreach ($this->bySetPos as $setPos) {
950
-            if ($setPos < 0) {
951
-                $setPos = count($result) + ($setPos + 1);
952
-            }
953
-            if (isset($result[$setPos - 1])) {
954
-                $filteredResult[] = $result[$setPos - 1];
955
-            }
956
-        }
957
-
958
-        sort($filteredResult, SORT_NUMERIC);
959
-
960
-        return $filteredResult;
961
-    }
962
-
963
-    /**
964
-     * Simple mapping from iCalendar day names to day numbers.
965
-     *
966
-     * @var array
967
-     */
968
-    protected $dayMap = [
969
-        'SU' => 0,
970
-        'MO' => 1,
971
-        'TU' => 2,
972
-        'WE' => 3,
973
-        'TH' => 4,
974
-        'FR' => 5,
975
-        'SA' => 6,
976
-    ];
977
-
978
-    protected function getHours()
979
-    {
980
-        $recurrenceHours = [];
981
-        foreach ($this->byHour as $byHour) {
982
-            $recurrenceHours[] = $byHour;
983
-        }
984
-
985
-        return $recurrenceHours;
986
-    }
987
-
988
-    protected function getDays()
989
-    {
990
-        $recurrenceDays = [];
991
-        foreach ($this->byDay as $byDay) {
992
-            // The day may be preceded with a positive (+n) or
993
-            // negative (-n) integer. However, this does not make
994
-            // sense in 'weekly' so we ignore it here.
995
-            $recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
996
-        }
997
-
998
-        return $recurrenceDays;
999
-    }
1000
-
1001
-    protected function getMonths()
1002
-    {
1003
-        $recurrenceMonths = [];
1004
-        foreach ($this->byMonth as $byMonth) {
1005
-            $recurrenceMonths[] = $byMonth;
1006
-        }
1007
-
1008
-        return $recurrenceMonths;
1009
-    }
27
+	/**
28
+	 * Constant denoting the upper limit on how long into the future
29
+	 * we want to iterate. The value is a unix timestamp and currently
30
+	 * corresponds to the datetime 9999-12-31 11:59:59 UTC.
31
+	 */
32
+	const dateUpperLimit = 253402300799;
33
+
34
+	/**
35
+	 * Creates the Iterator.
36
+	 *
37
+	 * @param string|array $rrule
38
+	 */
39
+	public function __construct($rrule, DateTimeInterface $start)
40
+	{
41
+		$this->startDate = $start;
42
+		$this->parseRRule($rrule);
43
+		$this->currentDate = clone $this->startDate;
44
+	}
45
+
46
+	/* Implementation of the Iterator interface {{{ */
47
+
48
+	#[\ReturnTypeWillChange]
49
+	public function current()
50
+	{
51
+		if (!$this->valid()) {
52
+			return;
53
+		}
54
+
55
+		return clone $this->currentDate;
56
+	}
57
+
58
+	/**
59
+	 * Returns the current item number.
60
+	 *
61
+	 * @return int
62
+	 */
63
+	#[\ReturnTypeWillChange]
64
+	public function key()
65
+	{
66
+		return $this->counter;
67
+	}
68
+
69
+	/**
70
+	 * Returns whether the current item is a valid item for the recurrence
71
+	 * iterator. This will return false if we've gone beyond the UNTIL or COUNT
72
+	 * statements.
73
+	 *
74
+	 * @return bool
75
+	 */
76
+	#[\ReturnTypeWillChange]
77
+	public function valid()
78
+	{
79
+		if (null === $this->currentDate) {
80
+			return false;
81
+		}
82
+		if (!is_null($this->count)) {
83
+			return $this->counter < $this->count;
84
+		}
85
+
86
+		return is_null($this->until) || $this->currentDate <= $this->until;
87
+	}
88
+
89
+	/**
90
+	 * Resets the iterator.
91
+	 *
92
+	 * @return void
93
+	 */
94
+	#[\ReturnTypeWillChange]
95
+	public function rewind()
96
+	{
97
+		$this->currentDate = clone $this->startDate;
98
+		$this->counter = 0;
99
+	}
100
+
101
+	/**
102
+	 * Goes on to the next iteration.
103
+	 *
104
+	 * @return void
105
+	 */
106
+	#[\ReturnTypeWillChange]
107
+	public function next()
108
+	{
109
+		// Otherwise, we find the next event in the normal RRULE
110
+		// sequence.
111
+		switch ($this->frequency) {
112
+			case 'hourly':
113
+				$this->nextHourly();
114
+				break;
115
+
116
+			case 'daily':
117
+				$this->nextDaily();
118
+				break;
119
+
120
+			case 'weekly':
121
+				$this->nextWeekly();
122
+				break;
123
+
124
+			case 'monthly':
125
+				$this->nextMonthly();
126
+				break;
127
+
128
+			case 'yearly':
129
+				$this->nextYearly();
130
+				break;
131
+		}
132
+		++$this->counter;
133
+	}
134
+
135
+	/* End of Iterator implementation }}} */
136
+
137
+	/**
138
+	 * Returns true if this recurring event never ends.
139
+	 *
140
+	 * @return bool
141
+	 */
142
+	public function isInfinite()
143
+	{
144
+		return !$this->count && !$this->until;
145
+	}
146
+
147
+	/**
148
+	 * This method allows you to quickly go to the next occurrence after the
149
+	 * specified date.
150
+	 */
151
+	public function fastForward(DateTimeInterface $dt)
152
+	{
153
+		while ($this->valid() && $this->currentDate < $dt) {
154
+			$this->next();
155
+		}
156
+	}
157
+
158
+	/**
159
+	 * The reference start date/time for the rrule.
160
+	 *
161
+	 * All calculations are based on this initial date.
162
+	 *
163
+	 * @var DateTimeInterface
164
+	 */
165
+	protected $startDate;
166
+
167
+	/**
168
+	 * The date of the current iteration. You can get this by calling
169
+	 * ->current().
170
+	 *
171
+	 * @var DateTimeInterface
172
+	 */
173
+	protected $currentDate;
174
+
175
+	/**
176
+	 * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
177
+	 * yearly.
178
+	 *
179
+	 * @var string
180
+	 */
181
+	protected $frequency;
182
+
183
+	/**
184
+	 * The number of recurrences, or 'null' if infinitely recurring.
185
+	 *
186
+	 * @var int
187
+	 */
188
+	protected $count;
189
+
190
+	/**
191
+	 * The interval.
192
+	 *
193
+	 * If for example frequency is set to daily, interval = 2 would mean every
194
+	 * 2 days.
195
+	 *
196
+	 * @var int
197
+	 */
198
+	protected $interval = 1;
199
+
200
+	/**
201
+	 * The last instance of this recurrence, inclusively.
202
+	 *
203
+	 * @var DateTimeInterface|null
204
+	 */
205
+	protected $until;
206
+
207
+	/**
208
+	 * Which seconds to recur.
209
+	 *
210
+	 * This is an array of integers (between 0 and 60)
211
+	 *
212
+	 * @var array
213
+	 */
214
+	protected $bySecond;
215
+
216
+	/**
217
+	 * Which minutes to recur.
218
+	 *
219
+	 * This is an array of integers (between 0 and 59)
220
+	 *
221
+	 * @var array
222
+	 */
223
+	protected $byMinute;
224
+
225
+	/**
226
+	 * Which hours to recur.
227
+	 *
228
+	 * This is an array of integers (between 0 and 23)
229
+	 *
230
+	 * @var array
231
+	 */
232
+	protected $byHour;
233
+
234
+	/**
235
+	 * The current item in the list.
236
+	 *
237
+	 * You can get this number with the key() method.
238
+	 *
239
+	 * @var int
240
+	 */
241
+	protected $counter = 0;
242
+
243
+	/**
244
+	 * Which weekdays to recur.
245
+	 *
246
+	 * This is an array of weekdays
247
+	 *
248
+	 * This may also be preceded by a positive or negative integer. If present,
249
+	 * this indicates the nth occurrence of a specific day within the monthly or
250
+	 * yearly rrule. For instance, -2TU indicates the second-last tuesday of
251
+	 * the month, or year.
252
+	 *
253
+	 * @var array
254
+	 */
255
+	protected $byDay;
256
+
257
+	/**
258
+	 * Which days of the month to recur.
259
+	 *
260
+	 * This is an array of days of the months (1-31). The value can also be
261
+	 * negative. -5 for instance means the 5th last day of the month.
262
+	 *
263
+	 * @var array
264
+	 */
265
+	protected $byMonthDay;
266
+
267
+	/**
268
+	 * Which days of the year to recur.
269
+	 *
270
+	 * This is an array with days of the year (1 to 366). The values can also
271
+	 * be negative. For instance, -1 will always represent the last day of the
272
+	 * year. (December 31st).
273
+	 *
274
+	 * @var array
275
+	 */
276
+	protected $byYearDay;
277
+
278
+	/**
279
+	 * Which week numbers to recur.
280
+	 *
281
+	 * This is an array of integers from 1 to 53. The values can also be
282
+	 * negative. -1 will always refer to the last week of the year.
283
+	 *
284
+	 * @var array
285
+	 */
286
+	protected $byWeekNo;
287
+
288
+	/**
289
+	 * Which months to recur.
290
+	 *
291
+	 * This is an array of integers from 1 to 12.
292
+	 *
293
+	 * @var array
294
+	 */
295
+	protected $byMonth;
296
+
297
+	/**
298
+	 * Which items in an existing st to recur.
299
+	 *
300
+	 * These numbers work together with an existing by* rule. It specifies
301
+	 * exactly which items of the existing by-rule to filter.
302
+	 *
303
+	 * Valid values are 1 to 366 and -1 to -366. As an example, this can be
304
+	 * used to recur the last workday of the month.
305
+	 *
306
+	 * This would be done by setting frequency to 'monthly', byDay to
307
+	 * 'MO,TU,WE,TH,FR' and bySetPos to -1.
308
+	 *
309
+	 * @var array
310
+	 */
311
+	protected $bySetPos;
312
+
313
+	/**
314
+	 * When the week starts.
315
+	 *
316
+	 * @var string
317
+	 */
318
+	protected $weekStart = 'MO';
319
+
320
+	/* Functions that advance the iterator {{{ */
321
+
322
+	/**
323
+	 * Does the processing for advancing the iterator for hourly frequency.
324
+	 */
325
+	protected function nextHourly()
326
+	{
327
+		$this->currentDate = $this->currentDate->modify('+'.$this->interval.' hours');
328
+	}
329
+
330
+	/**
331
+	 * Does the processing for advancing the iterator for daily frequency.
332
+	 */
333
+	protected function nextDaily()
334
+	{
335
+		if (!$this->byHour && !$this->byDay) {
336
+			$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
337
+
338
+			return;
339
+		}
340
+
341
+		$recurrenceHours = [];
342
+		if (!empty($this->byHour)) {
343
+			$recurrenceHours = $this->getHours();
344
+		}
345
+
346
+		$recurrenceDays = [];
347
+		if (!empty($this->byDay)) {
348
+			$recurrenceDays = $this->getDays();
349
+		}
350
+
351
+		$recurrenceMonths = [];
352
+		if (!empty($this->byMonth)) {
353
+			$recurrenceMonths = $this->getMonths();
354
+		}
355
+
356
+		do {
357
+			if ($this->byHour) {
358
+				if ('23' == $this->currentDate->format('G')) {
359
+					// to obey the interval rule
360
+					$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' days');
361
+				}
362
+
363
+				$this->currentDate = $this->currentDate->modify('+1 hours');
364
+			} else {
365
+				$this->currentDate = $this->currentDate->modify('+'.$this->interval.' days');
366
+			}
367
+
368
+			// Current month of the year
369
+			$currentMonth = $this->currentDate->format('n');
370
+
371
+			// Current day of the week
372
+			$currentDay = $this->currentDate->format('w');
373
+
374
+			// Current hour of the day
375
+			$currentHour = $this->currentDate->format('G');
376
+
377
+			if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
378
+				$this->currentDate = null;
379
+
380
+				return;
381
+			}
382
+		} while (
383
+			($this->byDay && !in_array($currentDay, $recurrenceDays)) ||
384
+			($this->byHour && !in_array($currentHour, $recurrenceHours)) ||
385
+			($this->byMonth && !in_array($currentMonth, $recurrenceMonths))
386
+		);
387
+	}
388
+
389
+	/**
390
+	 * Does the processing for advancing the iterator for weekly frequency.
391
+	 */
392
+	protected function nextWeekly()
393
+	{
394
+		if (!$this->byHour && !$this->byDay) {
395
+			$this->currentDate = $this->currentDate->modify('+'.$this->interval.' weeks');
396
+
397
+			return;
398
+		}
399
+
400
+		$recurrenceHours = [];
401
+		if ($this->byHour) {
402
+			$recurrenceHours = $this->getHours();
403
+		}
404
+
405
+		$recurrenceDays = [];
406
+		if ($this->byDay) {
407
+			$recurrenceDays = $this->getDays();
408
+		}
409
+
410
+		// First day of the week:
411
+		$firstDay = $this->dayMap[$this->weekStart];
412
+
413
+		do {
414
+			if ($this->byHour) {
415
+				$this->currentDate = $this->currentDate->modify('+1 hours');
416
+			} else {
417
+				$this->currentDate = $this->currentDate->modify('+1 days');
418
+			}
419
+
420
+			// Current day of the week
421
+			$currentDay = (int) $this->currentDate->format('w');
422
+
423
+			// Current hour of the day
424
+			$currentHour = (int) $this->currentDate->format('G');
425
+
426
+			// We need to roll over to the next week
427
+			if ($currentDay === $firstDay && (!$this->byHour || '0' == $currentHour)) {
428
+				$this->currentDate = $this->currentDate->modify('+'.($this->interval - 1).' weeks');
429
+
430
+				// We need to go to the first day of this week, but only if we
431
+				// are not already on this first day of this week.
432
+				if ($this->currentDate->format('w') != $firstDay) {
433
+					$this->currentDate = $this->currentDate->modify('last '.$this->dayNames[$this->dayMap[$this->weekStart]]);
434
+				}
435
+			}
436
+
437
+			// We have a match
438
+		} while (($this->byDay && !in_array($currentDay, $recurrenceDays)) || ($this->byHour && !in_array($currentHour, $recurrenceHours)));
439
+	}
440
+
441
+	/**
442
+	 * Does the processing for advancing the iterator for monthly frequency.
443
+	 */
444
+	protected function nextMonthly()
445
+	{
446
+		$currentDayOfMonth = $this->currentDate->format('j');
447
+		if (!$this->byMonthDay && !$this->byDay) {
448
+			// If the current day is higher than the 28th, rollover can
449
+			// occur to the next month. We Must skip these invalid
450
+			// entries.
451
+			if ($currentDayOfMonth < 29) {
452
+				$this->currentDate = $this->currentDate->modify('+'.$this->interval.' months');
453
+			} else {
454
+				$increase = 0;
455
+				do {
456
+					++$increase;
457
+					$tempDate = clone $this->currentDate;
458
+					$tempDate = $tempDate->modify('+ '.($this->interval * $increase).' months');
459
+				} while ($tempDate->format('j') != $currentDayOfMonth);
460
+				$this->currentDate = $tempDate;
461
+			}
462
+
463
+			return;
464
+		}
465
+
466
+		$occurrence = -1;
467
+		while (true) {
468
+			$occurrences = $this->getMonthlyOccurrences();
469
+
470
+			foreach ($occurrences as $occurrence) {
471
+				// The first occurrence thats higher than the current
472
+				// day of the month wins.
473
+				if ($occurrence > $currentDayOfMonth) {
474
+					break 2;
475
+				}
476
+			}
477
+
478
+			// If we made it all the way here, it means there were no
479
+			// valid occurrences, and we need to advance to the next
480
+			// month.
481
+			//
482
+			// This line does not currently work in hhvm. Temporary workaround
483
+			// follows:
484
+			// $this->currentDate->modify('first day of this month');
485
+			$this->currentDate = new DateTimeImmutable($this->currentDate->format('Y-m-1 H:i:s'), $this->currentDate->getTimezone());
486
+			// end of workaround
487
+			$this->currentDate = $this->currentDate->modify('+ '.$this->interval.' months');
488
+
489
+			// This goes to 0 because we need to start counting at the
490
+			// beginning.
491
+			$currentDayOfMonth = 0;
492
+
493
+			// For some reason the "until" parameter was not being used here,
494
+			// that's why the workaround of the 10000 year bug was needed at all
495
+			// let's stop it before the "until" parameter date
496
+			if ($this->until && $this->currentDate->getTimestamp() >= $this->until->getTimestamp()) {
497
+				return;
498
+			}
499
+
500
+			// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
501
+			// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
502
+			if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
503
+				$this->currentDate = null;
504
+
505
+				return;
506
+			}
507
+		}
508
+
509
+		$this->currentDate = $this->currentDate->setDate(
510
+			(int) $this->currentDate->format('Y'),
511
+			(int) $this->currentDate->format('n'),
512
+			(int) $occurrence
513
+		);
514
+	}
515
+
516
+	/**
517
+	 * Does the processing for advancing the iterator for yearly frequency.
518
+	 */
519
+	protected function nextYearly()
520
+	{
521
+		$currentMonth = $this->currentDate->format('n');
522
+		$currentYear = $this->currentDate->format('Y');
523
+		$currentDayOfMonth = $this->currentDate->format('j');
524
+
525
+		// No sub-rules, so we just advance by year
526
+		if (empty($this->byMonth)) {
527
+			// Unless it was a leap day!
528
+			if (2 == $currentMonth && 29 == $currentDayOfMonth) {
529
+				$counter = 0;
530
+				do {
531
+					++$counter;
532
+					// Here we increase the year count by the interval, until
533
+					// we hit a date that's also in a leap year.
534
+					//
535
+					// We could just find the next interval that's dividable by
536
+					// 4, but that would ignore the rule that there's no leap
537
+					// year every year that's dividable by a 100, but not by
538
+					// 400. (1800, 1900, 2100). So we just rely on the datetime
539
+					// functions instead.
540
+					$nextDate = clone $this->currentDate;
541
+					$nextDate = $nextDate->modify('+ '.($this->interval * $counter).' years');
542
+				} while (2 != $nextDate->format('n'));
543
+
544
+				$this->currentDate = $nextDate;
545
+
546
+				return;
547
+			}
548
+
549
+			if (null !== $this->byWeekNo) { // byWeekNo is an array with values from -53 to -1, or 1 to 53
550
+				$dayOffsets = [];
551
+				if ($this->byDay) {
552
+					foreach ($this->byDay as $byDay) {
553
+						$dayOffsets[] = $this->dayMap[$byDay];
554
+					}
555
+				} else {   // default is Monday
556
+					$dayOffsets[] = 1;
557
+				}
558
+
559
+				$currentYear = $this->currentDate->format('Y');
560
+
561
+				while (true) {
562
+					$checkDates = [];
563
+
564
+					// loop through all WeekNo and Days to check all the combinations
565
+					foreach ($this->byWeekNo as $byWeekNo) {
566
+						foreach ($dayOffsets as $dayOffset) {
567
+							$date = clone $this->currentDate;
568
+							$date = $date->setISODate($currentYear, $byWeekNo, $dayOffset);
569
+
570
+							if ($date > $this->currentDate) {
571
+								$checkDates[] = $date;
572
+							}
573
+						}
574
+					}
575
+
576
+					if (count($checkDates) > 0) {
577
+						$this->currentDate = min($checkDates);
578
+
579
+						return;
580
+					}
581
+
582
+					// if there is no date found, check the next year
583
+					$currentYear += $this->interval;
584
+				}
585
+			}
586
+
587
+			if (null !== $this->byYearDay) { // byYearDay is an array with values from -366 to -1, or 1 to 366
588
+				$dayOffsets = [];
589
+				if ($this->byDay) {
590
+					foreach ($this->byDay as $byDay) {
591
+						$dayOffsets[] = $this->dayMap[$byDay];
592
+					}
593
+				} else {   // default is Monday-Sunday
594
+					$dayOffsets = [1, 2, 3, 4, 5, 6, 7];
595
+				}
596
+
597
+				$currentYear = $this->currentDate->format('Y');
598
+
599
+				while (true) {
600
+					$checkDates = [];
601
+
602
+					// loop through all YearDay and Days to check all the combinations
603
+					foreach ($this->byYearDay as $byYearDay) {
604
+						$date = clone $this->currentDate;
605
+						if ($byYearDay > 0) {
606
+							$date = $date->setDate($currentYear, 1, 1);
607
+							$date = $date->add(new \DateInterval('P'.($byYearDay - 1).'D'));
608
+						} else {
609
+							$date = $date->setDate($currentYear, 12, 31);
610
+							$date = $date->sub(new \DateInterval('P'.abs($byYearDay + 1).'D'));
611
+						}
612
+
613
+						if ($date > $this->currentDate && in_array($date->format('N'), $dayOffsets)) {
614
+							$checkDates[] = $date;
615
+						}
616
+					}
617
+
618
+					if (count($checkDates) > 0) {
619
+						$this->currentDate = min($checkDates);
620
+
621
+						return;
622
+					}
623
+
624
+					// if there is no date found, check the next year
625
+					$currentYear += $this->interval;
626
+				}
627
+			}
628
+
629
+			// The easiest form
630
+			$this->currentDate = $this->currentDate->modify('+'.$this->interval.' years');
631
+
632
+			return;
633
+		}
634
+
635
+		$currentMonth = $this->currentDate->format('n');
636
+		$currentYear = $this->currentDate->format('Y');
637
+		$currentDayOfMonth = $this->currentDate->format('j');
638
+
639
+		$advancedToNewMonth = false;
640
+
641
+		// If we got a byDay or getMonthDay filter, we must first expand
642
+		// further.
643
+		if ($this->byDay || $this->byMonthDay) {
644
+			$occurrence = -1;
645
+			while (true) {
646
+				$occurrences = $this->getMonthlyOccurrences();
647
+
648
+				foreach ($occurrences as $occurrence) {
649
+					// The first occurrence that's higher than the current
650
+					// day of the month wins.
651
+					// If we advanced to the next month or year, the first
652
+					// occurrence is always correct.
653
+					if ($occurrence > $currentDayOfMonth || $advancedToNewMonth) {
654
+						break 2;
655
+					}
656
+				}
657
+
658
+				// If we made it here, it means we need to advance to
659
+				// the next month or year.
660
+				$currentDayOfMonth = 1;
661
+				$advancedToNewMonth = true;
662
+				do {
663
+					++$currentMonth;
664
+					if ($currentMonth > 12) {
665
+						$currentYear += $this->interval;
666
+						$currentMonth = 1;
667
+					}
668
+				} while (!in_array($currentMonth, $this->byMonth));
669
+
670
+				$this->currentDate = $this->currentDate->setDate(
671
+					(int) $currentYear,
672
+					(int) $currentMonth,
673
+					(int) $currentDayOfMonth
674
+				);
675
+
676
+				// To prevent running this forever (better: until we hit the max date of DateTimeImmutable) we simply
677
+				// stop at 9999-12-31. Looks like the year 10000 problem is not solved in php ....
678
+				if ($this->currentDate->getTimestamp() > self::dateUpperLimit) {
679
+					$this->currentDate = null;
680
+
681
+					return;
682
+				}
683
+			}
684
+
685
+			// If we made it here, it means we got a valid occurrence
686
+			$this->currentDate = $this->currentDate->setDate(
687
+				(int) $currentYear,
688
+				(int) $currentMonth,
689
+				(int) $occurrence
690
+			);
691
+
692
+			return;
693
+		} else {
694
+			// These are the 'byMonth' rules, if there are no byDay or
695
+			// byMonthDay sub-rules.
696
+			do {
697
+				++$currentMonth;
698
+				if ($currentMonth > 12) {
699
+					$currentYear += $this->interval;
700
+					$currentMonth = 1;
701
+				}
702
+			} while (!in_array($currentMonth, $this->byMonth));
703
+			$this->currentDate = $this->currentDate->setDate(
704
+				(int) $currentYear,
705
+				(int) $currentMonth,
706
+				(int) $currentDayOfMonth
707
+			);
708
+
709
+			return;
710
+		}
711
+	}
712
+
713
+	/* }}} */
714
+
715
+	/**
716
+	 * This method receives a string from an RRULE property, and populates this
717
+	 * class with all the values.
718
+	 *
719
+	 * @param string|array $rrule
720
+	 */
721
+	protected function parseRRule($rrule)
722
+	{
723
+		if (is_string($rrule)) {
724
+			$rrule = Property\ICalendar\Recur::stringToArray($rrule);
725
+		}
726
+
727
+		foreach ($rrule as $key => $value) {
728
+			$key = strtoupper($key);
729
+			switch ($key) {
730
+				case 'FREQ':
731
+					$value = strtolower($value);
732
+					if (!in_array(
733
+						$value,
734
+						['secondly', 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']
735
+					)) {
736
+						throw new InvalidDataException('Unknown value for FREQ='.strtoupper($value));
737
+					}
738
+					$this->frequency = $value;
739
+					break;
740
+
741
+				case 'UNTIL':
742
+					$this->until = DateTimeParser::parse($value, $this->startDate->getTimezone());
743
+
744
+					// In some cases events are generated with an UNTIL=
745
+					// parameter before the actual start of the event.
746
+					//
747
+					// Not sure why this is happening. We assume that the
748
+					// intention was that the event only recurs once.
749
+					//
750
+					// So we are modifying the parameter so our code doesn't
751
+					// break.
752
+					if ($this->until < $this->startDate) {
753
+						$this->until = $this->startDate;
754
+					}
755
+					break;
756
+
757
+				case 'INTERVAL':
758
+				case 'COUNT':
759
+					$val = (int) $value;
760
+					if ($val < 1) {
761
+						throw new InvalidDataException(strtoupper($key).' in RRULE must be a positive integer!');
762
+					}
763
+					$key = strtolower($key);
764
+					$this->$key = $val;
765
+					break;
766
+
767
+				case 'BYSECOND':
768
+					$this->bySecond = (array) $value;
769
+					break;
770
+
771
+				case 'BYMINUTE':
772
+					$this->byMinute = (array) $value;
773
+					break;
774
+
775
+				case 'BYHOUR':
776
+					$this->byHour = (array) $value;
777
+					break;
778
+
779
+				case 'BYDAY':
780
+					$value = (array) $value;
781
+					foreach ($value as $part) {
782
+						if (!preg_match('#^  (-|\+)? ([1-5])? (MO|TU|WE|TH|FR|SA|SU) $# xi', $part)) {
783
+							throw new InvalidDataException('Invalid part in BYDAY clause: '.$part);
784
+						}
785
+					}
786
+					$this->byDay = $value;
787
+					break;
788
+
789
+				case 'BYMONTHDAY':
790
+					$this->byMonthDay = (array) $value;
791
+					break;
792
+
793
+				case 'BYYEARDAY':
794
+					$this->byYearDay = (array) $value;
795
+					foreach ($this->byYearDay as $byYearDay) {
796
+						if (!is_numeric($byYearDay) || (int) $byYearDay < -366 || 0 == (int) $byYearDay || (int) $byYearDay > 366) {
797
+							throw new InvalidDataException('BYYEARDAY in RRULE must have value(s) from 1 to 366, or -366 to -1!');
798
+						}
799
+					}
800
+					break;
801
+
802
+				case 'BYWEEKNO':
803
+					$this->byWeekNo = (array) $value;
804
+					foreach ($this->byWeekNo as $byWeekNo) {
805
+						if (!is_numeric($byWeekNo) || (int) $byWeekNo < -53 || 0 == (int) $byWeekNo || (int) $byWeekNo > 53) {
806
+							throw new InvalidDataException('BYWEEKNO in RRULE must have value(s) from 1 to 53, or -53 to -1!');
807
+						}
808
+					}
809
+					break;
810
+
811
+				case 'BYMONTH':
812
+					$this->byMonth = (array) $value;
813
+					foreach ($this->byMonth as $byMonth) {
814
+						if (!is_numeric($byMonth) || (int) $byMonth < 1 || (int) $byMonth > 12) {
815
+							throw new InvalidDataException('BYMONTH in RRULE must have value(s) between 1 and 12!');
816
+						}
817
+					}
818
+					break;
819
+
820
+				case 'BYSETPOS':
821
+					$this->bySetPos = (array) $value;
822
+					break;
823
+
824
+				case 'WKST':
825
+					$this->weekStart = strtoupper($value);
826
+					break;
827
+
828
+				default:
829
+					throw new InvalidDataException('Not supported: '.strtoupper($key));
830
+			}
831
+		}
832
+	}
833
+
834
+	/**
835
+	 * Mappings between the day number and english day name.
836
+	 *
837
+	 * @var array
838
+	 */
839
+	protected $dayNames = [
840
+		0 => 'Sunday',
841
+		1 => 'Monday',
842
+		2 => 'Tuesday',
843
+		3 => 'Wednesday',
844
+		4 => 'Thursday',
845
+		5 => 'Friday',
846
+		6 => 'Saturday',
847
+	];
848
+
849
+	/**
850
+	 * Returns all the occurrences for a monthly frequency with a 'byDay' or
851
+	 * 'byMonthDay' expansion for the current month.
852
+	 *
853
+	 * The returned list is an array of integers with the day of month (1-31).
854
+	 *
855
+	 * @return array
856
+	 */
857
+	protected function getMonthlyOccurrences()
858
+	{
859
+		$startDate = clone $this->currentDate;
860
+
861
+		$byDayResults = [];
862
+
863
+		// Our strategy is to simply go through the byDays, advance the date to
864
+		// that point and add it to the results.
865
+		if ($this->byDay) {
866
+			foreach ($this->byDay as $day) {
867
+				$dayName = $this->dayNames[$this->dayMap[substr($day, -2)]];
868
+
869
+				// Dayname will be something like 'wednesday'. Now we need to find
870
+				// all wednesdays in this month.
871
+				$dayHits = [];
872
+
873
+				// workaround for missing 'first day of the month' support in hhvm
874
+				$checkDate = new \DateTime($startDate->format('Y-m-1'));
875
+				// workaround modify always advancing the date even if the current day is a $dayName in hhvm
876
+				if ($checkDate->format('l') !== $dayName) {
877
+					$checkDate = $checkDate->modify($dayName);
878
+				}
879
+
880
+				do {
881
+					$dayHits[] = $checkDate->format('j');
882
+					$checkDate = $checkDate->modify('next '.$dayName);
883
+				} while ($checkDate->format('n') === $startDate->format('n'));
884
+
885
+				// So now we have 'all wednesdays' for month. It is however
886
+				// possible that the user only really wanted the 1st, 2nd or last
887
+				// wednesday.
888
+				if (strlen($day) > 2) {
889
+					$offset = (int) substr($day, 0, -2);
890
+
891
+					if ($offset > 0) {
892
+						// It is possible that the day does not exist, such as a
893
+						// 5th or 6th wednesday of the month.
894
+						if (isset($dayHits[$offset - 1])) {
895
+							$byDayResults[] = $dayHits[$offset - 1];
896
+						}
897
+					} else {
898
+						// if it was negative we count from the end of the array
899
+						// might not exist, fx. -5th tuesday
900
+						if (isset($dayHits[count($dayHits) + $offset])) {
901
+							$byDayResults[] = $dayHits[count($dayHits) + $offset];
902
+						}
903
+					}
904
+				} else {
905
+					// There was no counter (first, second, last wednesdays), so we
906
+					// just need to add the all to the list).
907
+					$byDayResults = array_merge($byDayResults, $dayHits);
908
+				}
909
+			}
910
+		}
911
+
912
+		$byMonthDayResults = [];
913
+		if ($this->byMonthDay) {
914
+			foreach ($this->byMonthDay as $monthDay) {
915
+				// Removing values that are out of range for this month
916
+				if ($monthDay > $startDate->format('t') ||
917
+					$monthDay < 0 - $startDate->format('t')) {
918
+					continue;
919
+				}
920
+				if ($monthDay > 0) {
921
+					$byMonthDayResults[] = $monthDay;
922
+				} else {
923
+					// Negative values
924
+					$byMonthDayResults[] = $startDate->format('t') + 1 + $monthDay;
925
+				}
926
+			}
927
+		}
928
+
929
+		// If there was just byDay or just byMonthDay, they just specify our
930
+		// (almost) final list. If both were provided, then byDay limits the
931
+		// list.
932
+		if ($this->byMonthDay && $this->byDay) {
933
+			$result = array_intersect($byMonthDayResults, $byDayResults);
934
+		} elseif ($this->byMonthDay) {
935
+			$result = $byMonthDayResults;
936
+		} else {
937
+			$result = $byDayResults;
938
+		}
939
+		$result = array_unique($result);
940
+		sort($result, SORT_NUMERIC);
941
+
942
+		// The last thing that needs checking is the BYSETPOS. If it's set, it
943
+		// means only certain items in the set survive the filter.
944
+		if (!$this->bySetPos) {
945
+			return $result;
946
+		}
947
+
948
+		$filteredResult = [];
949
+		foreach ($this->bySetPos as $setPos) {
950
+			if ($setPos < 0) {
951
+				$setPos = count($result) + ($setPos + 1);
952
+			}
953
+			if (isset($result[$setPos - 1])) {
954
+				$filteredResult[] = $result[$setPos - 1];
955
+			}
956
+		}
957
+
958
+		sort($filteredResult, SORT_NUMERIC);
959
+
960
+		return $filteredResult;
961
+	}
962
+
963
+	/**
964
+	 * Simple mapping from iCalendar day names to day numbers.
965
+	 *
966
+	 * @var array
967
+	 */
968
+	protected $dayMap = [
969
+		'SU' => 0,
970
+		'MO' => 1,
971
+		'TU' => 2,
972
+		'WE' => 3,
973
+		'TH' => 4,
974
+		'FR' => 5,
975
+		'SA' => 6,
976
+	];
977
+
978
+	protected function getHours()
979
+	{
980
+		$recurrenceHours = [];
981
+		foreach ($this->byHour as $byHour) {
982
+			$recurrenceHours[] = $byHour;
983
+		}
984
+
985
+		return $recurrenceHours;
986
+	}
987
+
988
+	protected function getDays()
989
+	{
990
+		$recurrenceDays = [];
991
+		foreach ($this->byDay as $byDay) {
992
+			// The day may be preceded with a positive (+n) or
993
+			// negative (-n) integer. However, this does not make
994
+			// sense in 'weekly' so we ignore it here.
995
+			$recurrenceDays[] = $this->dayMap[substr($byDay, -2)];
996
+		}
997
+
998
+		return $recurrenceDays;
999
+	}
1000
+
1001
+	protected function getMonths()
1002
+	{
1003
+		$recurrenceMonths = [];
1004
+		foreach ($this->byMonth as $byMonth) {
1005
+			$recurrenceMonths[] = $byMonth;
1006
+		}
1007
+
1008
+		return $recurrenceMonths;
1009
+	}
1010 1010
 }
Please login to merge, or discard this patch.