@@ -18,121 +18,121 @@ |
||
18 | 18 | */ |
19 | 19 | class VAlarm extends VObject\Component |
20 | 20 | { |
21 | - /** |
|
22 | - * Returns a DateTime object when this alarm is going to trigger. |
|
23 | - * |
|
24 | - * This ignores repeated alarm, only the first trigger is returned. |
|
25 | - * |
|
26 | - * @return DateTimeImmutable |
|
27 | - */ |
|
28 | - public function getEffectiveTriggerTime() |
|
29 | - { |
|
30 | - $trigger = $this->TRIGGER; |
|
31 | - if (!isset($trigger['VALUE']) || 'DURATION' === strtoupper($trigger['VALUE'])) { |
|
32 | - $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); |
|
33 | - $related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START'; |
|
21 | + /** |
|
22 | + * Returns a DateTime object when this alarm is going to trigger. |
|
23 | + * |
|
24 | + * This ignores repeated alarm, only the first trigger is returned. |
|
25 | + * |
|
26 | + * @return DateTimeImmutable |
|
27 | + */ |
|
28 | + public function getEffectiveTriggerTime() |
|
29 | + { |
|
30 | + $trigger = $this->TRIGGER; |
|
31 | + if (!isset($trigger['VALUE']) || 'DURATION' === strtoupper($trigger['VALUE'])) { |
|
32 | + $triggerDuration = VObject\DateTimeParser::parseDuration($this->TRIGGER); |
|
33 | + $related = (isset($trigger['RELATED']) && 'END' == strtoupper($trigger['RELATED'])) ? 'END' : 'START'; |
|
34 | 34 | |
35 | - $parentComponent = $this->parent; |
|
36 | - if ('START' === $related) { |
|
37 | - if ('VTODO' === $parentComponent->name) { |
|
38 | - $propName = 'DUE'; |
|
39 | - } else { |
|
40 | - $propName = 'DTSTART'; |
|
41 | - } |
|
35 | + $parentComponent = $this->parent; |
|
36 | + if ('START' === $related) { |
|
37 | + if ('VTODO' === $parentComponent->name) { |
|
38 | + $propName = 'DUE'; |
|
39 | + } else { |
|
40 | + $propName = 'DTSTART'; |
|
41 | + } |
|
42 | 42 | |
43 | - $effectiveTrigger = $parentComponent->$propName->getDateTime(); |
|
44 | - $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
45 | - } else { |
|
46 | - if ('VTODO' === $parentComponent->name) { |
|
47 | - $endProp = 'DUE'; |
|
48 | - } elseif ('VEVENT' === $parentComponent->name) { |
|
49 | - $endProp = 'DTEND'; |
|
50 | - } else { |
|
51 | - throw new InvalidDataException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); |
|
52 | - } |
|
43 | + $effectiveTrigger = $parentComponent->$propName->getDateTime(); |
|
44 | + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
45 | + } else { |
|
46 | + if ('VTODO' === $parentComponent->name) { |
|
47 | + $endProp = 'DUE'; |
|
48 | + } elseif ('VEVENT' === $parentComponent->name) { |
|
49 | + $endProp = 'DTEND'; |
|
50 | + } else { |
|
51 | + throw new InvalidDataException('time-range filters on VALARM components are only supported when they are a child of VTODO or VEVENT'); |
|
52 | + } |
|
53 | 53 | |
54 | - if (isset($parentComponent->$endProp)) { |
|
55 | - $effectiveTrigger = $parentComponent->$endProp->getDateTime(); |
|
56 | - $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
57 | - } elseif (isset($parentComponent->DURATION)) { |
|
58 | - $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); |
|
59 | - $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION); |
|
60 | - $effectiveTrigger = $effectiveTrigger->add($duration); |
|
61 | - $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
62 | - } else { |
|
63 | - $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); |
|
64 | - $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
65 | - } |
|
66 | - } |
|
67 | - } else { |
|
68 | - $effectiveTrigger = $trigger->getDateTime(); |
|
69 | - } |
|
54 | + if (isset($parentComponent->$endProp)) { |
|
55 | + $effectiveTrigger = $parentComponent->$endProp->getDateTime(); |
|
56 | + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
57 | + } elseif (isset($parentComponent->DURATION)) { |
|
58 | + $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); |
|
59 | + $duration = VObject\DateTimeParser::parseDuration($parentComponent->DURATION); |
|
60 | + $effectiveTrigger = $effectiveTrigger->add($duration); |
|
61 | + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
62 | + } else { |
|
63 | + $effectiveTrigger = $parentComponent->DTSTART->getDateTime(); |
|
64 | + $effectiveTrigger = $effectiveTrigger->add($triggerDuration); |
|
65 | + } |
|
66 | + } |
|
67 | + } else { |
|
68 | + $effectiveTrigger = $trigger->getDateTime(); |
|
69 | + } |
|
70 | 70 | |
71 | - return $effectiveTrigger; |
|
72 | - } |
|
71 | + return $effectiveTrigger; |
|
72 | + } |
|
73 | 73 | |
74 | - /** |
|
75 | - * Returns true or false depending on if the event falls in the specified |
|
76 | - * time-range. This is used for filtering purposes. |
|
77 | - * |
|
78 | - * The rules used to determine if an event falls within the specified |
|
79 | - * time-range is based on the CalDAV specification. |
|
80 | - * |
|
81 | - * @param DateTime $start |
|
82 | - * @param DateTime $end |
|
83 | - * |
|
84 | - * @return bool |
|
85 | - */ |
|
86 | - public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) |
|
87 | - { |
|
88 | - $effectiveTrigger = $this->getEffectiveTriggerTime(); |
|
74 | + /** |
|
75 | + * Returns true or false depending on if the event falls in the specified |
|
76 | + * time-range. This is used for filtering purposes. |
|
77 | + * |
|
78 | + * The rules used to determine if an event falls within the specified |
|
79 | + * time-range is based on the CalDAV specification. |
|
80 | + * |
|
81 | + * @param DateTime $start |
|
82 | + * @param DateTime $end |
|
83 | + * |
|
84 | + * @return bool |
|
85 | + */ |
|
86 | + public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) |
|
87 | + { |
|
88 | + $effectiveTrigger = $this->getEffectiveTriggerTime(); |
|
89 | 89 | |
90 | - if (isset($this->DURATION)) { |
|
91 | - $duration = VObject\DateTimeParser::parseDuration($this->DURATION); |
|
92 | - $repeat = (string) $this->REPEAT; |
|
93 | - if (!$repeat) { |
|
94 | - $repeat = 1; |
|
95 | - } |
|
90 | + if (isset($this->DURATION)) { |
|
91 | + $duration = VObject\DateTimeParser::parseDuration($this->DURATION); |
|
92 | + $repeat = (string) $this->REPEAT; |
|
93 | + if (!$repeat) { |
|
94 | + $repeat = 1; |
|
95 | + } |
|
96 | 96 | |
97 | - $period = new \DatePeriod($effectiveTrigger, $duration, (int) $repeat); |
|
97 | + $period = new \DatePeriod($effectiveTrigger, $duration, (int) $repeat); |
|
98 | 98 | |
99 | - foreach ($period as $occurrence) { |
|
100 | - if ($start <= $occurrence && $end > $occurrence) { |
|
101 | - return true; |
|
102 | - } |
|
103 | - } |
|
99 | + foreach ($period as $occurrence) { |
|
100 | + if ($start <= $occurrence && $end > $occurrence) { |
|
101 | + return true; |
|
102 | + } |
|
103 | + } |
|
104 | 104 | |
105 | - return false; |
|
106 | - } else { |
|
107 | - return $start <= $effectiveTrigger && $end > $effectiveTrigger; |
|
108 | - } |
|
109 | - } |
|
105 | + return false; |
|
106 | + } else { |
|
107 | + return $start <= $effectiveTrigger && $end > $effectiveTrigger; |
|
108 | + } |
|
109 | + } |
|
110 | 110 | |
111 | - /** |
|
112 | - * A simple list of validation rules. |
|
113 | - * |
|
114 | - * This is simply a list of properties, and how many times they either |
|
115 | - * must or must not appear. |
|
116 | - * |
|
117 | - * Possible values per property: |
|
118 | - * * 0 - Must not appear. |
|
119 | - * * 1 - Must appear exactly once. |
|
120 | - * * + - Must appear at least once. |
|
121 | - * * * - Can appear any number of times. |
|
122 | - * * ? - May appear, but not more than once. |
|
123 | - * |
|
124 | - * @var array |
|
125 | - */ |
|
126 | - public function getValidationRules() |
|
127 | - { |
|
128 | - return [ |
|
129 | - 'ACTION' => 1, |
|
130 | - 'TRIGGER' => 1, |
|
111 | + /** |
|
112 | + * A simple list of validation rules. |
|
113 | + * |
|
114 | + * This is simply a list of properties, and how many times they either |
|
115 | + * must or must not appear. |
|
116 | + * |
|
117 | + * Possible values per property: |
|
118 | + * * 0 - Must not appear. |
|
119 | + * * 1 - Must appear exactly once. |
|
120 | + * * + - Must appear at least once. |
|
121 | + * * * - Can appear any number of times. |
|
122 | + * * ? - May appear, but not more than once. |
|
123 | + * |
|
124 | + * @var array |
|
125 | + */ |
|
126 | + public function getValidationRules() |
|
127 | + { |
|
128 | + return [ |
|
129 | + 'ACTION' => 1, |
|
130 | + 'TRIGGER' => 1, |
|
131 | 131 | |
132 | - 'DURATION' => '?', |
|
133 | - 'REPEAT' => '?', |
|
132 | + 'DURATION' => '?', |
|
133 | + 'REPEAT' => '?', |
|
134 | 134 | |
135 | - 'ATTACH' => '?', |
|
136 | - ]; |
|
137 | - } |
|
135 | + 'ATTACH' => '?', |
|
136 | + ]; |
|
137 | + } |
|
138 | 138 | } |
@@ -16,166 +16,166 @@ |
||
16 | 16 | */ |
17 | 17 | class VTodo extends VObject\Component |
18 | 18 | { |
19 | - /** |
|
20 | - * Returns true or false depending on if the event falls in the specified |
|
21 | - * time-range. This is used for filtering purposes. |
|
22 | - * |
|
23 | - * The rules used to determine if an event falls within the specified |
|
24 | - * time-range is based on the CalDAV specification. |
|
25 | - * |
|
26 | - * @return bool |
|
27 | - */ |
|
28 | - public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) |
|
29 | - { |
|
30 | - $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null; |
|
31 | - $duration = isset($this->DURATION) ? VObject\DateTimeParser::parseDuration($this->DURATION) : null; |
|
32 | - $due = isset($this->DUE) ? $this->DUE->getDateTime() : null; |
|
33 | - $completed = isset($this->COMPLETED) ? $this->COMPLETED->getDateTime() : null; |
|
34 | - $created = isset($this->CREATED) ? $this->CREATED->getDateTime() : null; |
|
19 | + /** |
|
20 | + * Returns true or false depending on if the event falls in the specified |
|
21 | + * time-range. This is used for filtering purposes. |
|
22 | + * |
|
23 | + * The rules used to determine if an event falls within the specified |
|
24 | + * time-range is based on the CalDAV specification. |
|
25 | + * |
|
26 | + * @return bool |
|
27 | + */ |
|
28 | + public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) |
|
29 | + { |
|
30 | + $dtstart = isset($this->DTSTART) ? $this->DTSTART->getDateTime() : null; |
|
31 | + $duration = isset($this->DURATION) ? VObject\DateTimeParser::parseDuration($this->DURATION) : null; |
|
32 | + $due = isset($this->DUE) ? $this->DUE->getDateTime() : null; |
|
33 | + $completed = isset($this->COMPLETED) ? $this->COMPLETED->getDateTime() : null; |
|
34 | + $created = isset($this->CREATED) ? $this->CREATED->getDateTime() : null; |
|
35 | 35 | |
36 | - if ($dtstart) { |
|
37 | - if ($duration) { |
|
38 | - $effectiveEnd = $dtstart->add($duration); |
|
36 | + if ($dtstart) { |
|
37 | + if ($duration) { |
|
38 | + $effectiveEnd = $dtstart->add($duration); |
|
39 | 39 | |
40 | - return $start <= $effectiveEnd && $end > $dtstart; |
|
41 | - } elseif ($due) { |
|
42 | - return |
|
43 | - ($start < $due || $start <= $dtstart) && |
|
44 | - ($end > $dtstart || $end >= $due); |
|
45 | - } else { |
|
46 | - return $start <= $dtstart && $end > $dtstart; |
|
47 | - } |
|
48 | - } |
|
49 | - if ($due) { |
|
50 | - return $start < $due && $end >= $due; |
|
51 | - } |
|
52 | - if ($completed && $created) { |
|
53 | - return |
|
54 | - ($start <= $created || $start <= $completed) && |
|
55 | - ($end >= $created || $end >= $completed); |
|
56 | - } |
|
57 | - if ($completed) { |
|
58 | - return $start <= $completed && $end >= $completed; |
|
59 | - } |
|
60 | - if ($created) { |
|
61 | - return $end > $created; |
|
62 | - } |
|
40 | + return $start <= $effectiveEnd && $end > $dtstart; |
|
41 | + } elseif ($due) { |
|
42 | + return |
|
43 | + ($start < $due || $start <= $dtstart) && |
|
44 | + ($end > $dtstart || $end >= $due); |
|
45 | + } else { |
|
46 | + return $start <= $dtstart && $end > $dtstart; |
|
47 | + } |
|
48 | + } |
|
49 | + if ($due) { |
|
50 | + return $start < $due && $end >= $due; |
|
51 | + } |
|
52 | + if ($completed && $created) { |
|
53 | + return |
|
54 | + ($start <= $created || $start <= $completed) && |
|
55 | + ($end >= $created || $end >= $completed); |
|
56 | + } |
|
57 | + if ($completed) { |
|
58 | + return $start <= $completed && $end >= $completed; |
|
59 | + } |
|
60 | + if ($created) { |
|
61 | + return $end > $created; |
|
62 | + } |
|
63 | 63 | |
64 | - return true; |
|
65 | - } |
|
64 | + return true; |
|
65 | + } |
|
66 | 66 | |
67 | - /** |
|
68 | - * A simple list of validation rules. |
|
69 | - * |
|
70 | - * This is simply a list of properties, and how many times they either |
|
71 | - * must or must not appear. |
|
72 | - * |
|
73 | - * Possible values per property: |
|
74 | - * * 0 - Must not appear. |
|
75 | - * * 1 - Must appear exactly once. |
|
76 | - * * + - Must appear at least once. |
|
77 | - * * * - Can appear any number of times. |
|
78 | - * * ? - May appear, but not more than once. |
|
79 | - * |
|
80 | - * @var array |
|
81 | - */ |
|
82 | - public function getValidationRules() |
|
83 | - { |
|
84 | - return [ |
|
85 | - 'UID' => 1, |
|
86 | - 'DTSTAMP' => 1, |
|
67 | + /** |
|
68 | + * A simple list of validation rules. |
|
69 | + * |
|
70 | + * This is simply a list of properties, and how many times they either |
|
71 | + * must or must not appear. |
|
72 | + * |
|
73 | + * Possible values per property: |
|
74 | + * * 0 - Must not appear. |
|
75 | + * * 1 - Must appear exactly once. |
|
76 | + * * + - Must appear at least once. |
|
77 | + * * * - Can appear any number of times. |
|
78 | + * * ? - May appear, but not more than once. |
|
79 | + * |
|
80 | + * @var array |
|
81 | + */ |
|
82 | + public function getValidationRules() |
|
83 | + { |
|
84 | + return [ |
|
85 | + 'UID' => 1, |
|
86 | + 'DTSTAMP' => 1, |
|
87 | 87 | |
88 | - 'CLASS' => '?', |
|
89 | - 'COMPLETED' => '?', |
|
90 | - 'CREATED' => '?', |
|
91 | - 'DESCRIPTION' => '?', |
|
92 | - 'DTSTART' => '?', |
|
93 | - 'GEO' => '?', |
|
94 | - 'LAST-MODIFIED' => '?', |
|
95 | - 'LOCATION' => '?', |
|
96 | - 'ORGANIZER' => '?', |
|
97 | - 'PERCENT' => '?', |
|
98 | - 'PRIORITY' => '?', |
|
99 | - 'RECURRENCE-ID' => '?', |
|
100 | - 'SEQUENCE' => '?', |
|
101 | - 'STATUS' => '?', |
|
102 | - 'SUMMARY' => '?', |
|
103 | - 'URL' => '?', |
|
88 | + 'CLASS' => '?', |
|
89 | + 'COMPLETED' => '?', |
|
90 | + 'CREATED' => '?', |
|
91 | + 'DESCRIPTION' => '?', |
|
92 | + 'DTSTART' => '?', |
|
93 | + 'GEO' => '?', |
|
94 | + 'LAST-MODIFIED' => '?', |
|
95 | + 'LOCATION' => '?', |
|
96 | + 'ORGANIZER' => '?', |
|
97 | + 'PERCENT' => '?', |
|
98 | + 'PRIORITY' => '?', |
|
99 | + 'RECURRENCE-ID' => '?', |
|
100 | + 'SEQUENCE' => '?', |
|
101 | + 'STATUS' => '?', |
|
102 | + 'SUMMARY' => '?', |
|
103 | + 'URL' => '?', |
|
104 | 104 | |
105 | - 'RRULE' => '?', |
|
106 | - 'DUE' => '?', |
|
107 | - 'DURATION' => '?', |
|
105 | + 'RRULE' => '?', |
|
106 | + 'DUE' => '?', |
|
107 | + 'DURATION' => '?', |
|
108 | 108 | |
109 | - 'ATTACH' => '*', |
|
110 | - 'ATTENDEE' => '*', |
|
111 | - 'CATEGORIES' => '*', |
|
112 | - 'COMMENT' => '*', |
|
113 | - 'CONTACT' => '*', |
|
114 | - 'EXDATE' => '*', |
|
115 | - 'REQUEST-STATUS' => '*', |
|
116 | - 'RELATED-TO' => '*', |
|
117 | - 'RESOURCES' => '*', |
|
118 | - 'RDATE' => '*', |
|
119 | - ]; |
|
120 | - } |
|
109 | + 'ATTACH' => '*', |
|
110 | + 'ATTENDEE' => '*', |
|
111 | + 'CATEGORIES' => '*', |
|
112 | + 'COMMENT' => '*', |
|
113 | + 'CONTACT' => '*', |
|
114 | + 'EXDATE' => '*', |
|
115 | + 'REQUEST-STATUS' => '*', |
|
116 | + 'RELATED-TO' => '*', |
|
117 | + 'RESOURCES' => '*', |
|
118 | + 'RDATE' => '*', |
|
119 | + ]; |
|
120 | + } |
|
121 | 121 | |
122 | - /** |
|
123 | - * Validates the node for correctness. |
|
124 | - * |
|
125 | - * The following options are supported: |
|
126 | - * Node::REPAIR - May attempt to automatically repair the problem. |
|
127 | - * |
|
128 | - * This method returns an array with detected problems. |
|
129 | - * Every element has the following properties: |
|
130 | - * |
|
131 | - * * level - problem level. |
|
132 | - * * message - A human-readable string describing the issue. |
|
133 | - * * node - A reference to the problematic node. |
|
134 | - * |
|
135 | - * The level means: |
|
136 | - * 1 - The issue was repaired (only happens if REPAIR was turned on) |
|
137 | - * 2 - An inconsequential issue |
|
138 | - * 3 - A severe issue. |
|
139 | - * |
|
140 | - * @param int $options |
|
141 | - * |
|
142 | - * @return array |
|
143 | - */ |
|
144 | - public function validate($options = 0) |
|
145 | - { |
|
146 | - $result = parent::validate($options); |
|
147 | - if (isset($this->DUE) && isset($this->DTSTART)) { |
|
148 | - $due = $this->DUE; |
|
149 | - $dtStart = $this->DTSTART; |
|
122 | + /** |
|
123 | + * Validates the node for correctness. |
|
124 | + * |
|
125 | + * The following options are supported: |
|
126 | + * Node::REPAIR - May attempt to automatically repair the problem. |
|
127 | + * |
|
128 | + * This method returns an array with detected problems. |
|
129 | + * Every element has the following properties: |
|
130 | + * |
|
131 | + * * level - problem level. |
|
132 | + * * message - A human-readable string describing the issue. |
|
133 | + * * node - A reference to the problematic node. |
|
134 | + * |
|
135 | + * The level means: |
|
136 | + * 1 - The issue was repaired (only happens if REPAIR was turned on) |
|
137 | + * 2 - An inconsequential issue |
|
138 | + * 3 - A severe issue. |
|
139 | + * |
|
140 | + * @param int $options |
|
141 | + * |
|
142 | + * @return array |
|
143 | + */ |
|
144 | + public function validate($options = 0) |
|
145 | + { |
|
146 | + $result = parent::validate($options); |
|
147 | + if (isset($this->DUE) && isset($this->DTSTART)) { |
|
148 | + $due = $this->DUE; |
|
149 | + $dtStart = $this->DTSTART; |
|
150 | 150 | |
151 | - if ($due->getValueType() !== $dtStart->getValueType()) { |
|
152 | - $result[] = [ |
|
153 | - 'level' => 3, |
|
154 | - 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART', |
|
155 | - 'node' => $due, |
|
156 | - ]; |
|
157 | - } elseif ($due->getDateTime() < $dtStart->getDateTime()) { |
|
158 | - $result[] = [ |
|
159 | - 'level' => 3, |
|
160 | - 'message' => 'DUE must occur after DTSTART', |
|
161 | - 'node' => $due, |
|
162 | - ]; |
|
163 | - } |
|
164 | - } |
|
151 | + if ($due->getValueType() !== $dtStart->getValueType()) { |
|
152 | + $result[] = [ |
|
153 | + 'level' => 3, |
|
154 | + 'message' => 'The value type (DATE or DATE-TIME) must be identical for DUE and DTSTART', |
|
155 | + 'node' => $due, |
|
156 | + ]; |
|
157 | + } elseif ($due->getDateTime() < $dtStart->getDateTime()) { |
|
158 | + $result[] = [ |
|
159 | + 'level' => 3, |
|
160 | + 'message' => 'DUE must occur after DTSTART', |
|
161 | + 'node' => $due, |
|
162 | + ]; |
|
163 | + } |
|
164 | + } |
|
165 | 165 | |
166 | - return $result; |
|
167 | - } |
|
166 | + return $result; |
|
167 | + } |
|
168 | 168 | |
169 | - /** |
|
170 | - * This method should return a list of default property values. |
|
171 | - * |
|
172 | - * @return array |
|
173 | - */ |
|
174 | - protected function getDefaults() |
|
175 | - { |
|
176 | - return [ |
|
177 | - 'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), |
|
178 | - 'DTSTAMP' => date('Ymd\\THis\\Z'), |
|
179 | - ]; |
|
180 | - } |
|
169 | + /** |
|
170 | + * This method should return a list of default property values. |
|
171 | + * |
|
172 | + * @return array |
|
173 | + */ |
|
174 | + protected function getDefaults() |
|
175 | + { |
|
176 | + return [ |
|
177 | + 'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), |
|
178 | + 'DTSTAMP' => date('Ymd\\THis\\Z'), |
|
179 | + ]; |
|
180 | + } |
|
181 | 181 | } |
@@ -17,77 +17,77 @@ |
||
17 | 17 | */ |
18 | 18 | class VFreeBusy extends VObject\Component |
19 | 19 | { |
20 | - /** |
|
21 | - * Checks based on the contained FREEBUSY information, if a timeslot is |
|
22 | - * available. |
|
23 | - * |
|
24 | - * @return bool |
|
25 | - */ |
|
26 | - public function isFree(DateTimeInterface $start, DatetimeInterface $end) |
|
27 | - { |
|
28 | - foreach ($this->select('FREEBUSY') as $freebusy) { |
|
29 | - // We are only interested in FBTYPE=BUSY (the default), |
|
30 | - // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE. |
|
31 | - if (isset($freebusy['FBTYPE']) && 'BUSY' !== strtoupper(substr((string) $freebusy['FBTYPE'], 0, 4))) { |
|
32 | - continue; |
|
33 | - } |
|
20 | + /** |
|
21 | + * Checks based on the contained FREEBUSY information, if a timeslot is |
|
22 | + * available. |
|
23 | + * |
|
24 | + * @return bool |
|
25 | + */ |
|
26 | + public function isFree(DateTimeInterface $start, DatetimeInterface $end) |
|
27 | + { |
|
28 | + foreach ($this->select('FREEBUSY') as $freebusy) { |
|
29 | + // We are only interested in FBTYPE=BUSY (the default), |
|
30 | + // FBTYPE=BUSY-TENTATIVE or FBTYPE=BUSY-UNAVAILABLE. |
|
31 | + if (isset($freebusy['FBTYPE']) && 'BUSY' !== strtoupper(substr((string) $freebusy['FBTYPE'], 0, 4))) { |
|
32 | + continue; |
|
33 | + } |
|
34 | 34 | |
35 | - // The freebusy component can hold more than 1 value, separated by |
|
36 | - // commas. |
|
37 | - $periods = explode(',', (string) $freebusy); |
|
35 | + // The freebusy component can hold more than 1 value, separated by |
|
36 | + // commas. |
|
37 | + $periods = explode(',', (string) $freebusy); |
|
38 | 38 | |
39 | - foreach ($periods as $period) { |
|
40 | - // Every period is formatted as [start]/[end]. The start is an |
|
41 | - // absolute UTC time, the end may be an absolute UTC time, or |
|
42 | - // duration (relative) value. |
|
43 | - list($busyStart, $busyEnd) = explode('/', $period); |
|
39 | + foreach ($periods as $period) { |
|
40 | + // Every period is formatted as [start]/[end]. The start is an |
|
41 | + // absolute UTC time, the end may be an absolute UTC time, or |
|
42 | + // duration (relative) value. |
|
43 | + list($busyStart, $busyEnd) = explode('/', $period); |
|
44 | 44 | |
45 | - $busyStart = VObject\DateTimeParser::parse($busyStart); |
|
46 | - $busyEnd = VObject\DateTimeParser::parse($busyEnd); |
|
47 | - if ($busyEnd instanceof \DateInterval) { |
|
48 | - $busyEnd = $busyStart->add($busyEnd); |
|
49 | - } |
|
45 | + $busyStart = VObject\DateTimeParser::parse($busyStart); |
|
46 | + $busyEnd = VObject\DateTimeParser::parse($busyEnd); |
|
47 | + if ($busyEnd instanceof \DateInterval) { |
|
48 | + $busyEnd = $busyStart->add($busyEnd); |
|
49 | + } |
|
50 | 50 | |
51 | - if ($start < $busyEnd && $end > $busyStart) { |
|
52 | - return false; |
|
53 | - } |
|
54 | - } |
|
55 | - } |
|
51 | + if ($start < $busyEnd && $end > $busyStart) { |
|
52 | + return false; |
|
53 | + } |
|
54 | + } |
|
55 | + } |
|
56 | 56 | |
57 | - return true; |
|
58 | - } |
|
57 | + return true; |
|
58 | + } |
|
59 | 59 | |
60 | - /** |
|
61 | - * A simple list of validation rules. |
|
62 | - * |
|
63 | - * This is simply a list of properties, and how many times they either |
|
64 | - * must or must not appear. |
|
65 | - * |
|
66 | - * Possible values per property: |
|
67 | - * * 0 - Must not appear. |
|
68 | - * * 1 - Must appear exactly once. |
|
69 | - * * + - Must appear at least once. |
|
70 | - * * * - Can appear any number of times. |
|
71 | - * * ? - May appear, but not more than once. |
|
72 | - * |
|
73 | - * @var array |
|
74 | - */ |
|
75 | - public function getValidationRules() |
|
76 | - { |
|
77 | - return [ |
|
78 | - 'UID' => 1, |
|
79 | - 'DTSTAMP' => 1, |
|
60 | + /** |
|
61 | + * A simple list of validation rules. |
|
62 | + * |
|
63 | + * This is simply a list of properties, and how many times they either |
|
64 | + * must or must not appear. |
|
65 | + * |
|
66 | + * Possible values per property: |
|
67 | + * * 0 - Must not appear. |
|
68 | + * * 1 - Must appear exactly once. |
|
69 | + * * + - Must appear at least once. |
|
70 | + * * * - Can appear any number of times. |
|
71 | + * * ? - May appear, but not more than once. |
|
72 | + * |
|
73 | + * @var array |
|
74 | + */ |
|
75 | + public function getValidationRules() |
|
76 | + { |
|
77 | + return [ |
|
78 | + 'UID' => 1, |
|
79 | + 'DTSTAMP' => 1, |
|
80 | 80 | |
81 | - 'CONTACT' => '?', |
|
82 | - 'DTSTART' => '?', |
|
83 | - 'DTEND' => '?', |
|
84 | - 'ORGANIZER' => '?', |
|
85 | - 'URL' => '?', |
|
81 | + 'CONTACT' => '?', |
|
82 | + 'DTSTART' => '?', |
|
83 | + 'DTEND' => '?', |
|
84 | + 'ORGANIZER' => '?', |
|
85 | + 'URL' => '?', |
|
86 | 86 | |
87 | - 'ATTENDEE' => '*', |
|
88 | - 'COMMENT' => '*', |
|
89 | - 'FREEBUSY' => '*', |
|
90 | - 'REQUEST-STATUS' => '*', |
|
91 | - ]; |
|
92 | - } |
|
87 | + 'ATTENDEE' => '*', |
|
88 | + 'COMMENT' => '*', |
|
89 | + 'FREEBUSY' => '*', |
|
90 | + 'REQUEST-STATUS' => '*', |
|
91 | + ]; |
|
92 | + } |
|
93 | 93 | } |
@@ -18,123 +18,123 @@ |
||
18 | 18 | */ |
19 | 19 | class VEvent extends VObject\Component |
20 | 20 | { |
21 | - /** |
|
22 | - * Returns true or false depending on if the event falls in the specified |
|
23 | - * time-range. This is used for filtering purposes. |
|
24 | - * |
|
25 | - * The rules used to determine if an event falls within the specified |
|
26 | - * time-range is based on the CalDAV specification. |
|
27 | - * |
|
28 | - * @return bool |
|
29 | - */ |
|
30 | - public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) |
|
31 | - { |
|
32 | - if ($this->RRULE) { |
|
33 | - try { |
|
34 | - $it = new EventIterator($this, null, $start->getTimezone()); |
|
35 | - } catch (NoInstancesException $e) { |
|
36 | - // If we've caught this exception, there are no instances |
|
37 | - // for the event that fall into the specified time-range. |
|
38 | - return false; |
|
39 | - } |
|
21 | + /** |
|
22 | + * Returns true or false depending on if the event falls in the specified |
|
23 | + * time-range. This is used for filtering purposes. |
|
24 | + * |
|
25 | + * The rules used to determine if an event falls within the specified |
|
26 | + * time-range is based on the CalDAV specification. |
|
27 | + * |
|
28 | + * @return bool |
|
29 | + */ |
|
30 | + public function isInTimeRange(DateTimeInterface $start, DateTimeInterface $end) |
|
31 | + { |
|
32 | + if ($this->RRULE) { |
|
33 | + try { |
|
34 | + $it = new EventIterator($this, null, $start->getTimezone()); |
|
35 | + } catch (NoInstancesException $e) { |
|
36 | + // If we've caught this exception, there are no instances |
|
37 | + // for the event that fall into the specified time-range. |
|
38 | + return false; |
|
39 | + } |
|
40 | 40 | |
41 | - $it->fastForward($start); |
|
41 | + $it->fastForward($start); |
|
42 | 42 | |
43 | - // We fast-forwarded to a spot where the end-time of the |
|
44 | - // recurrence instance exceeded the start of the requested |
|
45 | - // time-range. |
|
46 | - // |
|
47 | - // If the starttime of the recurrence did not exceed the |
|
48 | - // end of the time range as well, we have a match. |
|
49 | - return $it->getDTStart() < $end && $it->getDTEnd() > $start; |
|
50 | - } |
|
43 | + // We fast-forwarded to a spot where the end-time of the |
|
44 | + // recurrence instance exceeded the start of the requested |
|
45 | + // time-range. |
|
46 | + // |
|
47 | + // If the starttime of the recurrence did not exceed the |
|
48 | + // end of the time range as well, we have a match. |
|
49 | + return $it->getDTStart() < $end && $it->getDTEnd() > $start; |
|
50 | + } |
|
51 | 51 | |
52 | - $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone()); |
|
53 | - if (isset($this->DTEND)) { |
|
54 | - // The DTEND property is considered non inclusive. So for a 3 day |
|
55 | - // event in july, dtstart and dtend would have to be July 1st and |
|
56 | - // July 4th respectively. |
|
57 | - // |
|
58 | - // See: |
|
59 | - // http://tools.ietf.org/html/rfc5545#page-54 |
|
60 | - $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone()); |
|
61 | - } elseif (isset($this->DURATION)) { |
|
62 | - $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); |
|
63 | - } elseif (!$this->DTSTART->hasTime()) { |
|
64 | - $effectiveEnd = $effectiveStart->modify('+1 day'); |
|
65 | - } else { |
|
66 | - $effectiveEnd = $effectiveStart; |
|
67 | - } |
|
52 | + $effectiveStart = $this->DTSTART->getDateTime($start->getTimezone()); |
|
53 | + if (isset($this->DTEND)) { |
|
54 | + // The DTEND property is considered non inclusive. So for a 3 day |
|
55 | + // event in july, dtstart and dtend would have to be July 1st and |
|
56 | + // July 4th respectively. |
|
57 | + // |
|
58 | + // See: |
|
59 | + // http://tools.ietf.org/html/rfc5545#page-54 |
|
60 | + $effectiveEnd = $this->DTEND->getDateTime($end->getTimezone()); |
|
61 | + } elseif (isset($this->DURATION)) { |
|
62 | + $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); |
|
63 | + } elseif (!$this->DTSTART->hasTime()) { |
|
64 | + $effectiveEnd = $effectiveStart->modify('+1 day'); |
|
65 | + } else { |
|
66 | + $effectiveEnd = $effectiveStart; |
|
67 | + } |
|
68 | 68 | |
69 | - return |
|
70 | - ($start < $effectiveEnd) && ($end > $effectiveStart) |
|
71 | - ; |
|
72 | - } |
|
69 | + return |
|
70 | + ($start < $effectiveEnd) && ($end > $effectiveStart) |
|
71 | + ; |
|
72 | + } |
|
73 | 73 | |
74 | - /** |
|
75 | - * This method should return a list of default property values. |
|
76 | - * |
|
77 | - * @return array |
|
78 | - */ |
|
79 | - protected function getDefaults() |
|
80 | - { |
|
81 | - return [ |
|
82 | - 'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), |
|
83 | - 'DTSTAMP' => gmdate('Ymd\\THis\\Z'), |
|
84 | - ]; |
|
85 | - } |
|
74 | + /** |
|
75 | + * This method should return a list of default property values. |
|
76 | + * |
|
77 | + * @return array |
|
78 | + */ |
|
79 | + protected function getDefaults() |
|
80 | + { |
|
81 | + return [ |
|
82 | + 'UID' => 'sabre-vobject-'.VObject\UUIDUtil::getUUID(), |
|
83 | + 'DTSTAMP' => gmdate('Ymd\\THis\\Z'), |
|
84 | + ]; |
|
85 | + } |
|
86 | 86 | |
87 | - /** |
|
88 | - * A simple list of validation rules. |
|
89 | - * |
|
90 | - * This is simply a list of properties, and how many times they either |
|
91 | - * must or must not appear. |
|
92 | - * |
|
93 | - * Possible values per property: |
|
94 | - * * 0 - Must not appear. |
|
95 | - * * 1 - Must appear exactly once. |
|
96 | - * * + - Must appear at least once. |
|
97 | - * * * - Can appear any number of times. |
|
98 | - * * ? - May appear, but not more than once. |
|
99 | - * |
|
100 | - * @var array |
|
101 | - */ |
|
102 | - public function getValidationRules() |
|
103 | - { |
|
104 | - $hasMethod = isset($this->parent->METHOD); |
|
87 | + /** |
|
88 | + * A simple list of validation rules. |
|
89 | + * |
|
90 | + * This is simply a list of properties, and how many times they either |
|
91 | + * must or must not appear. |
|
92 | + * |
|
93 | + * Possible values per property: |
|
94 | + * * 0 - Must not appear. |
|
95 | + * * 1 - Must appear exactly once. |
|
96 | + * * + - Must appear at least once. |
|
97 | + * * * - Can appear any number of times. |
|
98 | + * * ? - May appear, but not more than once. |
|
99 | + * |
|
100 | + * @var array |
|
101 | + */ |
|
102 | + public function getValidationRules() |
|
103 | + { |
|
104 | + $hasMethod = isset($this->parent->METHOD); |
|
105 | 105 | |
106 | - return [ |
|
107 | - 'UID' => 1, |
|
108 | - 'DTSTAMP' => 1, |
|
109 | - 'DTSTART' => $hasMethod ? '?' : '1', |
|
110 | - 'CLASS' => '?', |
|
111 | - 'CREATED' => '?', |
|
112 | - 'DESCRIPTION' => '?', |
|
113 | - 'GEO' => '?', |
|
114 | - 'LAST-MODIFIED' => '?', |
|
115 | - 'LOCATION' => '?', |
|
116 | - 'ORGANIZER' => '?', |
|
117 | - 'PRIORITY' => '?', |
|
118 | - 'SEQUENCE' => '?', |
|
119 | - 'STATUS' => '?', |
|
120 | - 'SUMMARY' => '?', |
|
121 | - 'TRANSP' => '?', |
|
122 | - 'URL' => '?', |
|
123 | - 'RECURRENCE-ID' => '?', |
|
124 | - 'RRULE' => '?', |
|
125 | - 'DTEND' => '?', |
|
126 | - 'DURATION' => '?', |
|
106 | + return [ |
|
107 | + 'UID' => 1, |
|
108 | + 'DTSTAMP' => 1, |
|
109 | + 'DTSTART' => $hasMethod ? '?' : '1', |
|
110 | + 'CLASS' => '?', |
|
111 | + 'CREATED' => '?', |
|
112 | + 'DESCRIPTION' => '?', |
|
113 | + 'GEO' => '?', |
|
114 | + 'LAST-MODIFIED' => '?', |
|
115 | + 'LOCATION' => '?', |
|
116 | + 'ORGANIZER' => '?', |
|
117 | + 'PRIORITY' => '?', |
|
118 | + 'SEQUENCE' => '?', |
|
119 | + 'STATUS' => '?', |
|
120 | + 'SUMMARY' => '?', |
|
121 | + 'TRANSP' => '?', |
|
122 | + 'URL' => '?', |
|
123 | + 'RECURRENCE-ID' => '?', |
|
124 | + 'RRULE' => '?', |
|
125 | + 'DTEND' => '?', |
|
126 | + 'DURATION' => '?', |
|
127 | 127 | |
128 | - 'ATTACH' => '*', |
|
129 | - 'ATTENDEE' => '*', |
|
130 | - 'CATEGORIES' => '*', |
|
131 | - 'COMMENT' => '*', |
|
132 | - 'CONTACT' => '*', |
|
133 | - 'EXDATE' => '*', |
|
134 | - 'REQUEST-STATUS' => '*', |
|
135 | - 'RELATED-TO' => '*', |
|
136 | - 'RESOURCES' => '*', |
|
137 | - 'RDATE' => '*', |
|
138 | - ]; |
|
139 | - } |
|
128 | + 'ATTACH' => '*', |
|
129 | + 'ATTENDEE' => '*', |
|
130 | + 'CATEGORIES' => '*', |
|
131 | + 'COMMENT' => '*', |
|
132 | + 'CONTACT' => '*', |
|
133 | + 'EXDATE' => '*', |
|
134 | + 'REQUEST-STATUS' => '*', |
|
135 | + 'RELATED-TO' => '*', |
|
136 | + 'RESOURCES' => '*', |
|
137 | + 'RDATE' => '*', |
|
138 | + ]; |
|
139 | + } |
|
140 | 140 | } |
@@ -16,48 +16,48 @@ |
||
16 | 16 | */ |
17 | 17 | class VTimeZone extends VObject\Component |
18 | 18 | { |
19 | - /** |
|
20 | - * Returns the PHP DateTimeZone for this VTIMEZONE component. |
|
21 | - * |
|
22 | - * If we can't accurately determine the timezone, this method will return |
|
23 | - * UTC. |
|
24 | - * |
|
25 | - * @return \DateTimeZone |
|
26 | - */ |
|
27 | - public function getTimeZone() |
|
28 | - { |
|
29 | - return VObject\TimeZoneUtil::getTimeZone((string) $this->TZID, $this->root); |
|
30 | - } |
|
19 | + /** |
|
20 | + * Returns the PHP DateTimeZone for this VTIMEZONE component. |
|
21 | + * |
|
22 | + * If we can't accurately determine the timezone, this method will return |
|
23 | + * UTC. |
|
24 | + * |
|
25 | + * @return \DateTimeZone |
|
26 | + */ |
|
27 | + public function getTimeZone() |
|
28 | + { |
|
29 | + return VObject\TimeZoneUtil::getTimeZone((string) $this->TZID, $this->root); |
|
30 | + } |
|
31 | 31 | |
32 | - /** |
|
33 | - * A simple list of validation rules. |
|
34 | - * |
|
35 | - * This is simply a list of properties, and how many times they either |
|
36 | - * must or must not appear. |
|
37 | - * |
|
38 | - * Possible values per property: |
|
39 | - * * 0 - Must not appear. |
|
40 | - * * 1 - Must appear exactly once. |
|
41 | - * * + - Must appear at least once. |
|
42 | - * * * - Can appear any number of times. |
|
43 | - * * ? - May appear, but not more than once. |
|
44 | - * |
|
45 | - * @var array |
|
46 | - */ |
|
47 | - public function getValidationRules() |
|
48 | - { |
|
49 | - return [ |
|
50 | - 'TZID' => 1, |
|
32 | + /** |
|
33 | + * A simple list of validation rules. |
|
34 | + * |
|
35 | + * This is simply a list of properties, and how many times they either |
|
36 | + * must or must not appear. |
|
37 | + * |
|
38 | + * Possible values per property: |
|
39 | + * * 0 - Must not appear. |
|
40 | + * * 1 - Must appear exactly once. |
|
41 | + * * + - Must appear at least once. |
|
42 | + * * * - Can appear any number of times. |
|
43 | + * * ? - May appear, but not more than once. |
|
44 | + * |
|
45 | + * @var array |
|
46 | + */ |
|
47 | + public function getValidationRules() |
|
48 | + { |
|
49 | + return [ |
|
50 | + 'TZID' => 1, |
|
51 | 51 | |
52 | - 'LAST-MODIFIED' => '?', |
|
53 | - 'TZURL' => '?', |
|
52 | + 'LAST-MODIFIED' => '?', |
|
53 | + 'TZURL' => '?', |
|
54 | 54 | |
55 | - // At least 1 STANDARD or DAYLIGHT must appear. |
|
56 | - // |
|
57 | - // The validator is not specific yet to pick this up, so these |
|
58 | - // rules are too loose. |
|
59 | - 'STANDARD' => '*', |
|
60 | - 'DAYLIGHT' => '*', |
|
61 | - ]; |
|
62 | - } |
|
55 | + // At least 1 STANDARD or DAYLIGHT must appear. |
|
56 | + // |
|
57 | + // The validator is not specific yet to pick this up, so these |
|
58 | + // rules are too loose. |
|
59 | + 'STANDARD' => '*', |
|
60 | + 'DAYLIGHT' => '*', |
|
61 | + ]; |
|
62 | + } |
|
63 | 63 | } |
@@ -16,108 +16,108 @@ |
||
16 | 16 | */ |
17 | 17 | class Available extends VObject\Component |
18 | 18 | { |
19 | - /** |
|
20 | - * Returns the 'effective start' and 'effective end' of this VAVAILABILITY |
|
21 | - * component. |
|
22 | - * |
|
23 | - * We use the DTSTART and DTEND or DURATION to determine this. |
|
24 | - * |
|
25 | - * The returned value is an array containing DateTimeImmutable instances. |
|
26 | - * If either the start or end is 'unbounded' its value will be null |
|
27 | - * instead. |
|
28 | - * |
|
29 | - * @return array |
|
30 | - */ |
|
31 | - public function getEffectiveStartEnd() |
|
32 | - { |
|
33 | - $effectiveStart = $this->DTSTART->getDateTime(); |
|
34 | - if (isset($this->DTEND)) { |
|
35 | - $effectiveEnd = $this->DTEND->getDateTime(); |
|
36 | - } else { |
|
37 | - $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); |
|
38 | - } |
|
19 | + /** |
|
20 | + * Returns the 'effective start' and 'effective end' of this VAVAILABILITY |
|
21 | + * component. |
|
22 | + * |
|
23 | + * We use the DTSTART and DTEND or DURATION to determine this. |
|
24 | + * |
|
25 | + * The returned value is an array containing DateTimeImmutable instances. |
|
26 | + * If either the start or end is 'unbounded' its value will be null |
|
27 | + * instead. |
|
28 | + * |
|
29 | + * @return array |
|
30 | + */ |
|
31 | + public function getEffectiveStartEnd() |
|
32 | + { |
|
33 | + $effectiveStart = $this->DTSTART->getDateTime(); |
|
34 | + if (isset($this->DTEND)) { |
|
35 | + $effectiveEnd = $this->DTEND->getDateTime(); |
|
36 | + } else { |
|
37 | + $effectiveEnd = $effectiveStart->add(VObject\DateTimeParser::parseDuration($this->DURATION)); |
|
38 | + } |
|
39 | 39 | |
40 | - return [$effectiveStart, $effectiveEnd]; |
|
41 | - } |
|
40 | + return [$effectiveStart, $effectiveEnd]; |
|
41 | + } |
|
42 | 42 | |
43 | - /** |
|
44 | - * A simple list of validation rules. |
|
45 | - * |
|
46 | - * This is simply a list of properties, and how many times they either |
|
47 | - * must or must not appear. |
|
48 | - * |
|
49 | - * Possible values per property: |
|
50 | - * * 0 - Must not appear. |
|
51 | - * * 1 - Must appear exactly once. |
|
52 | - * * + - Must appear at least once. |
|
53 | - * * * - Can appear any number of times. |
|
54 | - * * ? - May appear, but not more than once. |
|
55 | - * |
|
56 | - * @var array |
|
57 | - */ |
|
58 | - public function getValidationRules() |
|
59 | - { |
|
60 | - return [ |
|
61 | - 'UID' => 1, |
|
62 | - 'DTSTART' => 1, |
|
63 | - 'DTSTAMP' => 1, |
|
43 | + /** |
|
44 | + * A simple list of validation rules. |
|
45 | + * |
|
46 | + * This is simply a list of properties, and how many times they either |
|
47 | + * must or must not appear. |
|
48 | + * |
|
49 | + * Possible values per property: |
|
50 | + * * 0 - Must not appear. |
|
51 | + * * 1 - Must appear exactly once. |
|
52 | + * * + - Must appear at least once. |
|
53 | + * * * - Can appear any number of times. |
|
54 | + * * ? - May appear, but not more than once. |
|
55 | + * |
|
56 | + * @var array |
|
57 | + */ |
|
58 | + public function getValidationRules() |
|
59 | + { |
|
60 | + return [ |
|
61 | + 'UID' => 1, |
|
62 | + 'DTSTART' => 1, |
|
63 | + 'DTSTAMP' => 1, |
|
64 | 64 | |
65 | - 'DTEND' => '?', |
|
66 | - 'DURATION' => '?', |
|
65 | + 'DTEND' => '?', |
|
66 | + 'DURATION' => '?', |
|
67 | 67 | |
68 | - 'CREATED' => '?', |
|
69 | - 'DESCRIPTION' => '?', |
|
70 | - 'LAST-MODIFIED' => '?', |
|
71 | - 'RECURRENCE-ID' => '?', |
|
72 | - 'RRULE' => '?', |
|
73 | - 'SUMMARY' => '?', |
|
68 | + 'CREATED' => '?', |
|
69 | + 'DESCRIPTION' => '?', |
|
70 | + 'LAST-MODIFIED' => '?', |
|
71 | + 'RECURRENCE-ID' => '?', |
|
72 | + 'RRULE' => '?', |
|
73 | + 'SUMMARY' => '?', |
|
74 | 74 | |
75 | - 'CATEGORIES' => '*', |
|
76 | - 'COMMENT' => '*', |
|
77 | - 'CONTACT' => '*', |
|
78 | - 'EXDATE' => '*', |
|
79 | - 'RDATE' => '*', |
|
75 | + 'CATEGORIES' => '*', |
|
76 | + 'COMMENT' => '*', |
|
77 | + 'CONTACT' => '*', |
|
78 | + 'EXDATE' => '*', |
|
79 | + 'RDATE' => '*', |
|
80 | 80 | |
81 | - 'AVAILABLE' => '*', |
|
82 | - ]; |
|
83 | - } |
|
81 | + 'AVAILABLE' => '*', |
|
82 | + ]; |
|
83 | + } |
|
84 | 84 | |
85 | - /** |
|
86 | - * Validates the node for correctness. |
|
87 | - * |
|
88 | - * The following options are supported: |
|
89 | - * Node::REPAIR - May attempt to automatically repair the problem. |
|
90 | - * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. |
|
91 | - * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. |
|
92 | - * |
|
93 | - * This method returns an array with detected problems. |
|
94 | - * Every element has the following properties: |
|
95 | - * |
|
96 | - * * level - problem level. |
|
97 | - * * message - A human-readable string describing the issue. |
|
98 | - * * node - A reference to the problematic node. |
|
99 | - * |
|
100 | - * The level means: |
|
101 | - * 1 - The issue was repaired (only happens if REPAIR was turned on). |
|
102 | - * 2 - A warning. |
|
103 | - * 3 - An error. |
|
104 | - * |
|
105 | - * @param int $options |
|
106 | - * |
|
107 | - * @return array |
|
108 | - */ |
|
109 | - public function validate($options = 0) |
|
110 | - { |
|
111 | - $result = parent::validate($options); |
|
85 | + /** |
|
86 | + * Validates the node for correctness. |
|
87 | + * |
|
88 | + * The following options are supported: |
|
89 | + * Node::REPAIR - May attempt to automatically repair the problem. |
|
90 | + * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes. |
|
91 | + * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes. |
|
92 | + * |
|
93 | + * This method returns an array with detected problems. |
|
94 | + * Every element has the following properties: |
|
95 | + * |
|
96 | + * * level - problem level. |
|
97 | + * * message - A human-readable string describing the issue. |
|
98 | + * * node - A reference to the problematic node. |
|
99 | + * |
|
100 | + * The level means: |
|
101 | + * 1 - The issue was repaired (only happens if REPAIR was turned on). |
|
102 | + * 2 - A warning. |
|
103 | + * 3 - An error. |
|
104 | + * |
|
105 | + * @param int $options |
|
106 | + * |
|
107 | + * @return array |
|
108 | + */ |
|
109 | + public function validate($options = 0) |
|
110 | + { |
|
111 | + $result = parent::validate($options); |
|
112 | 112 | |
113 | - if (isset($this->DTEND) && isset($this->DURATION)) { |
|
114 | - $result[] = [ |
|
115 | - 'level' => 3, |
|
116 | - 'message' => 'DTEND and DURATION cannot both be present', |
|
117 | - 'node' => $this, |
|
118 | - ]; |
|
119 | - } |
|
113 | + if (isset($this->DTEND) && isset($this->DURATION)) { |
|
114 | + $result[] = [ |
|
115 | + 'level' => 3, |
|
116 | + 'message' => 'DTEND and DURATION cannot both be present', |
|
117 | + 'node' => $this, |
|
118 | + ]; |
|
119 | + } |
|
120 | 120 | |
121 | - return $result; |
|
122 | - } |
|
121 | + return $result; |
|
122 | + } |
|
123 | 123 | } |
@@ -15,52 +15,52 @@ |
||
15 | 15 | */ |
16 | 16 | class UUIDUtil |
17 | 17 | { |
18 | - /** |
|
19 | - * Returns a pseudo-random v4 UUID. |
|
20 | - * |
|
21 | - * This function is based on a comment by Andrew Moore on php.net |
|
22 | - * |
|
23 | - * @see http://www.php.net/manual/en/function.uniqid.php#94959 |
|
24 | - * |
|
25 | - * @return string |
|
26 | - */ |
|
27 | - public static function getUUID() |
|
28 | - { |
|
29 | - return sprintf( |
|
30 | - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
|
18 | + /** |
|
19 | + * Returns a pseudo-random v4 UUID. |
|
20 | + * |
|
21 | + * This function is based on a comment by Andrew Moore on php.net |
|
22 | + * |
|
23 | + * @see http://www.php.net/manual/en/function.uniqid.php#94959 |
|
24 | + * |
|
25 | + * @return string |
|
26 | + */ |
|
27 | + public static function getUUID() |
|
28 | + { |
|
29 | + return sprintf( |
|
30 | + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', |
|
31 | 31 | |
32 | - // 32 bits for "time_low" |
|
33 | - mt_rand(0, 0xffff), mt_rand(0, 0xffff), |
|
32 | + // 32 bits for "time_low" |
|
33 | + mt_rand(0, 0xffff), mt_rand(0, 0xffff), |
|
34 | 34 | |
35 | - // 16 bits for "time_mid" |
|
36 | - mt_rand(0, 0xffff), |
|
35 | + // 16 bits for "time_mid" |
|
36 | + mt_rand(0, 0xffff), |
|
37 | 37 | |
38 | - // 16 bits for "time_hi_and_version", |
|
39 | - // four most significant bits holds version number 4 |
|
40 | - mt_rand(0, 0x0fff) | 0x4000, |
|
38 | + // 16 bits for "time_hi_and_version", |
|
39 | + // four most significant bits holds version number 4 |
|
40 | + mt_rand(0, 0x0fff) | 0x4000, |
|
41 | 41 | |
42 | - // 16 bits, 8 bits for "clk_seq_hi_res", |
|
43 | - // 8 bits for "clk_seq_low", |
|
44 | - // two most significant bits holds zero and one for variant DCE1.1 |
|
45 | - mt_rand(0, 0x3fff) | 0x8000, |
|
42 | + // 16 bits, 8 bits for "clk_seq_hi_res", |
|
43 | + // 8 bits for "clk_seq_low", |
|
44 | + // two most significant bits holds zero and one for variant DCE1.1 |
|
45 | + mt_rand(0, 0x3fff) | 0x8000, |
|
46 | 46 | |
47 | - // 48 bits for "node" |
|
48 | - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) |
|
49 | - ); |
|
50 | - } |
|
47 | + // 48 bits for "node" |
|
48 | + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) |
|
49 | + ); |
|
50 | + } |
|
51 | 51 | |
52 | - /** |
|
53 | - * Checks if a string is a valid UUID. |
|
54 | - * |
|
55 | - * @param string $uuid |
|
56 | - * |
|
57 | - * @return bool |
|
58 | - */ |
|
59 | - public static function validateUUID($uuid) |
|
60 | - { |
|
61 | - return 0 !== preg_match( |
|
62 | - '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', |
|
63 | - $uuid |
|
64 | - ); |
|
65 | - } |
|
52 | + /** |
|
53 | + * Checks if a string is a valid UUID. |
|
54 | + * |
|
55 | + * @param string $uuid |
|
56 | + * |
|
57 | + * @return bool |
|
58 | + */ |
|
59 | + public static function validateUUID($uuid) |
|
60 | + { |
|
61 | + return 0 !== preg_match( |
|
62 | + '/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i', |
|
63 | + $uuid |
|
64 | + ); |
|
65 | + } |
|
66 | 66 | } |
@@ -11,411 +11,411 @@ |
||
11 | 11 | */ |
12 | 12 | class VCardConverter |
13 | 13 | { |
14 | - /** |
|
15 | - * Converts a vCard object to a new version. |
|
16 | - * |
|
17 | - * targetVersion must be one of: |
|
18 | - * Document::VCARD21 |
|
19 | - * Document::VCARD30 |
|
20 | - * Document::VCARD40 |
|
21 | - * |
|
22 | - * Currently only 3.0 and 4.0 as input and output versions. |
|
23 | - * |
|
24 | - * 2.1 has some minor support for the input version, it's incomplete at the |
|
25 | - * moment though. |
|
26 | - * |
|
27 | - * If input and output version are identical, a clone is returned. |
|
28 | - * |
|
29 | - * @param int $targetVersion |
|
30 | - */ |
|
31 | - public function convert(Component\VCard $input, $targetVersion) |
|
32 | - { |
|
33 | - $inputVersion = $input->getDocumentType(); |
|
34 | - if ($inputVersion === $targetVersion) { |
|
35 | - return clone $input; |
|
36 | - } |
|
37 | - |
|
38 | - if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) { |
|
39 | - throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data'); |
|
40 | - } |
|
41 | - if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) { |
|
42 | - throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); |
|
43 | - } |
|
44 | - |
|
45 | - $newVersion = Document::VCARD40 === $targetVersion ? '4.0' : '3.0'; |
|
46 | - |
|
47 | - $output = new Component\VCard([ |
|
48 | - 'VERSION' => $newVersion, |
|
49 | - ]); |
|
50 | - |
|
51 | - // We might have generated a default UID. Remove it! |
|
52 | - unset($output->UID); |
|
53 | - |
|
54 | - foreach ($input->children() as $property) { |
|
55 | - $this->convertProperty($input, $output, $property, $targetVersion); |
|
56 | - } |
|
57 | - |
|
58 | - return $output; |
|
59 | - } |
|
60 | - |
|
61 | - /** |
|
62 | - * Handles conversion of a single property. |
|
63 | - * |
|
64 | - * @param int $targetVersion |
|
65 | - */ |
|
66 | - protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) |
|
67 | - { |
|
68 | - // Skipping these, those are automatically added. |
|
69 | - if (in_array($property->name, ['VERSION', 'PRODID'])) { |
|
70 | - return; |
|
71 | - } |
|
72 | - |
|
73 | - $parameters = $property->parameters(); |
|
74 | - $valueType = null; |
|
75 | - if (isset($parameters['VALUE'])) { |
|
76 | - $valueType = $parameters['VALUE']->getValue(); |
|
77 | - unset($parameters['VALUE']); |
|
78 | - } |
|
79 | - if (!$valueType) { |
|
80 | - $valueType = $property->getValueType(); |
|
81 | - } |
|
82 | - if (Document::VCARD30 !== $targetVersion && 'PHONE-NUMBER' === $valueType) { |
|
83 | - $valueType = null; |
|
84 | - } |
|
85 | - $newProperty = $output->createProperty( |
|
86 | - $property->name, |
|
87 | - $property->getParts(), |
|
88 | - [], // parameters will get added a bit later. |
|
89 | - $valueType |
|
90 | - ); |
|
91 | - |
|
92 | - if (Document::VCARD30 === $targetVersion) { |
|
93 | - if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { |
|
94 | - $newProperty = $this->convertUriToBinary($output, $newProperty); |
|
95 | - } elseif ($property instanceof Property\VCard\DateAndOrTime) { |
|
96 | - // In vCard 4, the birth year may be optional. This is not the |
|
97 | - // case for vCard 3. Apple has a workaround for this that |
|
98 | - // allows applications that support Apple's extension still |
|
99 | - // omit birthyears in vCard 3, but applications that do not |
|
100 | - // support this, will just use a random birthyear. We're |
|
101 | - // choosing 1604 for the birthyear, because that's what apple |
|
102 | - // uses. |
|
103 | - $parts = DateTimeParser::parseVCardDateTime($property->getValue()); |
|
104 | - if (is_null($parts['year'])) { |
|
105 | - $newValue = '1604-'.$parts['month'].'-'.$parts['date']; |
|
106 | - $newProperty->setValue($newValue); |
|
107 | - $newProperty['X-APPLE-OMIT-YEAR'] = '1604'; |
|
108 | - } |
|
109 | - |
|
110 | - if ('ANNIVERSARY' == $newProperty->name) { |
|
111 | - // Microsoft non-standard anniversary |
|
112 | - $newProperty->name = 'X-ANNIVERSARY'; |
|
113 | - |
|
114 | - // We also need to add a new apple property for the same |
|
115 | - // purpose. This apple property needs a 'label' in the same |
|
116 | - // group, so we first need to find a groupname that doesn't |
|
117 | - // exist yet. |
|
118 | - $x = 1; |
|
119 | - while ($output->select('ITEM'.$x.'.')) { |
|
120 | - ++$x; |
|
121 | - } |
|
122 | - $output->add('ITEM'.$x.'.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']); |
|
123 | - $output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_'); |
|
124 | - } |
|
125 | - } elseif ('KIND' === $property->name) { |
|
126 | - switch (strtolower($property->getValue())) { |
|
127 | - case 'org': |
|
128 | - // vCard 3.0 does not have an equivalent to KIND:ORG, |
|
129 | - // but apple has an extension that means the same |
|
130 | - // thing. |
|
131 | - $newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY'); |
|
132 | - break; |
|
133 | - |
|
134 | - case 'individual': |
|
135 | - // Individual is implicit, so we skip it. |
|
136 | - return; |
|
137 | - |
|
138 | - case 'group': |
|
139 | - // OS X addressbook property |
|
140 | - $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP'); |
|
141 | - break; |
|
142 | - } |
|
143 | - } elseif ('MEMBER' === $property->name) { |
|
144 | - $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-MEMBER', $property->getValue()); |
|
145 | - } |
|
146 | - } elseif (Document::VCARD40 === $targetVersion) { |
|
147 | - // These properties were removed in vCard 4.0 |
|
148 | - if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) { |
|
149 | - return; |
|
150 | - } |
|
151 | - |
|
152 | - if ($property instanceof Property\Binary) { |
|
153 | - $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); |
|
154 | - } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { |
|
155 | - // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR', |
|
156 | - // then we're stripping the year from the vcard 4 value. |
|
157 | - $parts = DateTimeParser::parseVCardDateTime($property->getValue()); |
|
158 | - if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) { |
|
159 | - $newValue = '--'.$parts['month'].'-'.$parts['date']; |
|
160 | - $newProperty->setValue($newValue); |
|
161 | - } |
|
162 | - |
|
163 | - // Regardless if the year matched or not, we do need to strip |
|
164 | - // X-APPLE-OMIT-YEAR. |
|
165 | - unset($parameters['X-APPLE-OMIT-YEAR']); |
|
166 | - } |
|
167 | - switch ($property->name) { |
|
168 | - case 'X-ABSHOWAS': |
|
169 | - if ('COMPANY' === strtoupper($property->getValue())) { |
|
170 | - $newProperty = $output->createProperty('KIND', 'ORG'); |
|
171 | - } |
|
172 | - break; |
|
173 | - case 'X-ADDRESSBOOKSERVER-KIND': |
|
174 | - if ('GROUP' === strtoupper($property->getValue())) { |
|
175 | - $newProperty = $output->createProperty('KIND', 'GROUP'); |
|
176 | - } |
|
177 | - break; |
|
178 | - case 'X-ADDRESSBOOKSERVER-MEMBER': |
|
179 | - $newProperty = $output->createProperty('MEMBER', $property->getValue()); |
|
180 | - break; |
|
181 | - case 'X-ANNIVERSARY': |
|
182 | - $newProperty->name = 'ANNIVERSARY'; |
|
183 | - // If we already have an anniversary property with the same |
|
184 | - // value, ignore. |
|
185 | - foreach ($output->select('ANNIVERSARY') as $anniversary) { |
|
186 | - if ($anniversary->getValue() === $newProperty->getValue()) { |
|
187 | - return; |
|
188 | - } |
|
189 | - } |
|
190 | - break; |
|
191 | - case 'X-ABDATE': |
|
192 | - // Find out what the label was, if it exists. |
|
193 | - if (!$property->group) { |
|
194 | - break; |
|
195 | - } |
|
196 | - $label = $input->{$property->group.'.X-ABLABEL'}; |
|
197 | - |
|
198 | - // We only support converting anniversaries. |
|
199 | - if (!$label || '_$!<Anniversary>!$_' !== $label->getValue()) { |
|
200 | - break; |
|
201 | - } |
|
202 | - |
|
203 | - // If we already have an anniversary property with the same |
|
204 | - // value, ignore. |
|
205 | - foreach ($output->select('ANNIVERSARY') as $anniversary) { |
|
206 | - if ($anniversary->getValue() === $newProperty->getValue()) { |
|
207 | - return; |
|
208 | - } |
|
209 | - } |
|
210 | - $newProperty->name = 'ANNIVERSARY'; |
|
211 | - break; |
|
212 | - // Apple's per-property label system. |
|
213 | - case 'X-ABLABEL': |
|
214 | - if ('_$!<Anniversary>!$_' === $newProperty->getValue()) { |
|
215 | - // We can safely remove these, as they are converted to |
|
216 | - // ANNIVERSARY properties. |
|
217 | - return; |
|
218 | - } |
|
219 | - break; |
|
220 | - } |
|
221 | - } |
|
222 | - |
|
223 | - // set property group |
|
224 | - $newProperty->group = $property->group; |
|
225 | - |
|
226 | - if (Document::VCARD40 === $targetVersion) { |
|
227 | - $this->convertParameters40($newProperty, $parameters); |
|
228 | - } else { |
|
229 | - $this->convertParameters30($newProperty, $parameters); |
|
230 | - } |
|
231 | - |
|
232 | - // Lastly, we need to see if there's a need for a VALUE parameter. |
|
233 | - // |
|
234 | - // We can do that by instantiating a empty property with that name, and |
|
235 | - // seeing if the default valueType is identical to the current one. |
|
236 | - $tempProperty = $output->createProperty($newProperty->name); |
|
237 | - if ($tempProperty->getValueType() !== $newProperty->getValueType()) { |
|
238 | - $newProperty['VALUE'] = $newProperty->getValueType(); |
|
239 | - } |
|
240 | - |
|
241 | - $output->add($newProperty); |
|
242 | - } |
|
243 | - |
|
244 | - /** |
|
245 | - * Converts a BINARY property to a URI property. |
|
246 | - * |
|
247 | - * vCard 4.0 no longer supports BINARY properties. |
|
248 | - * |
|
249 | - * @param Property\Uri $property the input property |
|
250 | - * @param $parameters list of parameters that will eventually be added to |
|
251 | - * the new property |
|
252 | - * |
|
253 | - * @return Property\Uri |
|
254 | - */ |
|
255 | - protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) |
|
256 | - { |
|
257 | - $value = $newProperty->getValue(); |
|
258 | - $newProperty = $output->createProperty( |
|
259 | - $newProperty->name, |
|
260 | - null, // no value |
|
261 | - [], // no parameters yet |
|
262 | - 'URI' // Forcing the BINARY type |
|
263 | - ); |
|
264 | - |
|
265 | - $mimeType = 'application/octet-stream'; |
|
266 | - |
|
267 | - // See if we can find a better mimetype. |
|
268 | - if (isset($parameters['TYPE'])) { |
|
269 | - $newTypes = []; |
|
270 | - foreach ($parameters['TYPE']->getParts() as $typePart) { |
|
271 | - if (in_array( |
|
272 | - strtoupper($typePart), |
|
273 | - ['JPEG', 'PNG', 'GIF'] |
|
274 | - )) { |
|
275 | - $mimeType = 'image/'.strtolower($typePart); |
|
276 | - } else { |
|
277 | - $newTypes[] = $typePart; |
|
278 | - } |
|
279 | - } |
|
280 | - |
|
281 | - // If there were any parameters we're not converting to a |
|
282 | - // mime-type, we need to keep them. |
|
283 | - if ($newTypes) { |
|
284 | - $parameters['TYPE']->setParts($newTypes); |
|
285 | - } else { |
|
286 | - unset($parameters['TYPE']); |
|
287 | - } |
|
288 | - } |
|
289 | - |
|
290 | - $newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value)); |
|
291 | - |
|
292 | - return $newProperty; |
|
293 | - } |
|
294 | - |
|
295 | - /** |
|
296 | - * Converts a URI property to a BINARY property. |
|
297 | - * |
|
298 | - * In vCard 4.0 attachments are encoded as data: uri. Even though these may |
|
299 | - * be valid in vCard 3.0 as well, we should convert those to BINARY if |
|
300 | - * possible, to improve compatibility. |
|
301 | - * |
|
302 | - * @param Property\Uri $property the input property |
|
303 | - * |
|
304 | - * @return Property\Binary|null |
|
305 | - */ |
|
306 | - protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) |
|
307 | - { |
|
308 | - $value = $newProperty->getValue(); |
|
309 | - |
|
310 | - // Only converting data: uris |
|
311 | - if ('data:' !== substr($value, 0, 5)) { |
|
312 | - return $newProperty; |
|
313 | - } |
|
314 | - |
|
315 | - $newProperty = $output->createProperty( |
|
316 | - $newProperty->name, |
|
317 | - null, // no value |
|
318 | - [], // no parameters yet |
|
319 | - 'BINARY' |
|
320 | - ); |
|
321 | - |
|
322 | - $mimeType = substr($value, 5, strpos($value, ',') - 5); |
|
323 | - if (strpos($mimeType, ';')) { |
|
324 | - $mimeType = substr($mimeType, 0, strpos($mimeType, ';')); |
|
325 | - $newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1))); |
|
326 | - } else { |
|
327 | - $newProperty->setValue(substr($value, strpos($value, ',') + 1)); |
|
328 | - } |
|
329 | - unset($value); |
|
330 | - |
|
331 | - $newProperty['ENCODING'] = 'b'; |
|
332 | - switch ($mimeType) { |
|
333 | - case 'image/jpeg': |
|
334 | - $newProperty['TYPE'] = 'JPEG'; |
|
335 | - break; |
|
336 | - case 'image/png': |
|
337 | - $newProperty['TYPE'] = 'PNG'; |
|
338 | - break; |
|
339 | - case 'image/gif': |
|
340 | - $newProperty['TYPE'] = 'GIF'; |
|
341 | - break; |
|
342 | - } |
|
343 | - |
|
344 | - return $newProperty; |
|
345 | - } |
|
346 | - |
|
347 | - /** |
|
348 | - * Adds parameters to a new property for vCard 4.0. |
|
349 | - */ |
|
350 | - protected function convertParameters40(Property $newProperty, array $parameters) |
|
351 | - { |
|
352 | - // Adding all parameters. |
|
353 | - foreach ($parameters as $param) { |
|
354 | - // vCard 2.1 allowed parameters with no name |
|
355 | - if ($param->noName) { |
|
356 | - $param->noName = false; |
|
357 | - } |
|
358 | - |
|
359 | - switch ($param->name) { |
|
360 | - // We need to see if there's any TYPE=PREF, because in vCard 4 |
|
361 | - // that's now PREF=1. |
|
362 | - case 'TYPE': |
|
363 | - foreach ($param->getParts() as $paramPart) { |
|
364 | - if ('PREF' === strtoupper($paramPart)) { |
|
365 | - $newProperty->add('PREF', '1'); |
|
366 | - } else { |
|
367 | - $newProperty->add($param->name, $paramPart); |
|
368 | - } |
|
369 | - } |
|
370 | - break; |
|
371 | - // These no longer exist in vCard 4 |
|
372 | - case 'ENCODING': |
|
373 | - case 'CHARSET': |
|
374 | - break; |
|
375 | - |
|
376 | - default: |
|
377 | - $newProperty->add($param->name, $param->getParts()); |
|
378 | - break; |
|
379 | - } |
|
380 | - } |
|
381 | - } |
|
382 | - |
|
383 | - /** |
|
384 | - * Adds parameters to a new property for vCard 3.0. |
|
385 | - */ |
|
386 | - protected function convertParameters30(Property $newProperty, array $parameters) |
|
387 | - { |
|
388 | - // Adding all parameters. |
|
389 | - foreach ($parameters as $param) { |
|
390 | - // vCard 2.1 allowed parameters with no name |
|
391 | - if ($param->noName) { |
|
392 | - $param->noName = false; |
|
393 | - } |
|
394 | - |
|
395 | - switch ($param->name) { |
|
396 | - case 'ENCODING': |
|
397 | - // This value only existed in vCard 2.1, and should be |
|
398 | - // removed for anything else. |
|
399 | - if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) { |
|
400 | - $newProperty->add($param->name, $param->getParts()); |
|
401 | - } |
|
402 | - break; |
|
403 | - |
|
404 | - /* |
|
14 | + /** |
|
15 | + * Converts a vCard object to a new version. |
|
16 | + * |
|
17 | + * targetVersion must be one of: |
|
18 | + * Document::VCARD21 |
|
19 | + * Document::VCARD30 |
|
20 | + * Document::VCARD40 |
|
21 | + * |
|
22 | + * Currently only 3.0 and 4.0 as input and output versions. |
|
23 | + * |
|
24 | + * 2.1 has some minor support for the input version, it's incomplete at the |
|
25 | + * moment though. |
|
26 | + * |
|
27 | + * If input and output version are identical, a clone is returned. |
|
28 | + * |
|
29 | + * @param int $targetVersion |
|
30 | + */ |
|
31 | + public function convert(Component\VCard $input, $targetVersion) |
|
32 | + { |
|
33 | + $inputVersion = $input->getDocumentType(); |
|
34 | + if ($inputVersion === $targetVersion) { |
|
35 | + return clone $input; |
|
36 | + } |
|
37 | + |
|
38 | + if (!in_array($inputVersion, [Document::VCARD21, Document::VCARD30, Document::VCARD40])) { |
|
39 | + throw new \InvalidArgumentException('Only vCard 2.1, 3.0 and 4.0 are supported for the input data'); |
|
40 | + } |
|
41 | + if (!in_array($targetVersion, [Document::VCARD30, Document::VCARD40])) { |
|
42 | + throw new \InvalidArgumentException('You can only use vCard 3.0 or 4.0 for the target version'); |
|
43 | + } |
|
44 | + |
|
45 | + $newVersion = Document::VCARD40 === $targetVersion ? '4.0' : '3.0'; |
|
46 | + |
|
47 | + $output = new Component\VCard([ |
|
48 | + 'VERSION' => $newVersion, |
|
49 | + ]); |
|
50 | + |
|
51 | + // We might have generated a default UID. Remove it! |
|
52 | + unset($output->UID); |
|
53 | + |
|
54 | + foreach ($input->children() as $property) { |
|
55 | + $this->convertProperty($input, $output, $property, $targetVersion); |
|
56 | + } |
|
57 | + |
|
58 | + return $output; |
|
59 | + } |
|
60 | + |
|
61 | + /** |
|
62 | + * Handles conversion of a single property. |
|
63 | + * |
|
64 | + * @param int $targetVersion |
|
65 | + */ |
|
66 | + protected function convertProperty(Component\VCard $input, Component\VCard $output, Property $property, $targetVersion) |
|
67 | + { |
|
68 | + // Skipping these, those are automatically added. |
|
69 | + if (in_array($property->name, ['VERSION', 'PRODID'])) { |
|
70 | + return; |
|
71 | + } |
|
72 | + |
|
73 | + $parameters = $property->parameters(); |
|
74 | + $valueType = null; |
|
75 | + if (isset($parameters['VALUE'])) { |
|
76 | + $valueType = $parameters['VALUE']->getValue(); |
|
77 | + unset($parameters['VALUE']); |
|
78 | + } |
|
79 | + if (!$valueType) { |
|
80 | + $valueType = $property->getValueType(); |
|
81 | + } |
|
82 | + if (Document::VCARD30 !== $targetVersion && 'PHONE-NUMBER' === $valueType) { |
|
83 | + $valueType = null; |
|
84 | + } |
|
85 | + $newProperty = $output->createProperty( |
|
86 | + $property->name, |
|
87 | + $property->getParts(), |
|
88 | + [], // parameters will get added a bit later. |
|
89 | + $valueType |
|
90 | + ); |
|
91 | + |
|
92 | + if (Document::VCARD30 === $targetVersion) { |
|
93 | + if ($property instanceof Property\Uri && in_array($property->name, ['PHOTO', 'LOGO', 'SOUND'])) { |
|
94 | + $newProperty = $this->convertUriToBinary($output, $newProperty); |
|
95 | + } elseif ($property instanceof Property\VCard\DateAndOrTime) { |
|
96 | + // In vCard 4, the birth year may be optional. This is not the |
|
97 | + // case for vCard 3. Apple has a workaround for this that |
|
98 | + // allows applications that support Apple's extension still |
|
99 | + // omit birthyears in vCard 3, but applications that do not |
|
100 | + // support this, will just use a random birthyear. We're |
|
101 | + // choosing 1604 for the birthyear, because that's what apple |
|
102 | + // uses. |
|
103 | + $parts = DateTimeParser::parseVCardDateTime($property->getValue()); |
|
104 | + if (is_null($parts['year'])) { |
|
105 | + $newValue = '1604-'.$parts['month'].'-'.$parts['date']; |
|
106 | + $newProperty->setValue($newValue); |
|
107 | + $newProperty['X-APPLE-OMIT-YEAR'] = '1604'; |
|
108 | + } |
|
109 | + |
|
110 | + if ('ANNIVERSARY' == $newProperty->name) { |
|
111 | + // Microsoft non-standard anniversary |
|
112 | + $newProperty->name = 'X-ANNIVERSARY'; |
|
113 | + |
|
114 | + // We also need to add a new apple property for the same |
|
115 | + // purpose. This apple property needs a 'label' in the same |
|
116 | + // group, so we first need to find a groupname that doesn't |
|
117 | + // exist yet. |
|
118 | + $x = 1; |
|
119 | + while ($output->select('ITEM'.$x.'.')) { |
|
120 | + ++$x; |
|
121 | + } |
|
122 | + $output->add('ITEM'.$x.'.X-ABDATE', $newProperty->getValue(), ['VALUE' => 'DATE-AND-OR-TIME']); |
|
123 | + $output->add('ITEM'.$x.'.X-ABLABEL', '_$!<Anniversary>!$_'); |
|
124 | + } |
|
125 | + } elseif ('KIND' === $property->name) { |
|
126 | + switch (strtolower($property->getValue())) { |
|
127 | + case 'org': |
|
128 | + // vCard 3.0 does not have an equivalent to KIND:ORG, |
|
129 | + // but apple has an extension that means the same |
|
130 | + // thing. |
|
131 | + $newProperty = $output->createProperty('X-ABSHOWAS', 'COMPANY'); |
|
132 | + break; |
|
133 | + |
|
134 | + case 'individual': |
|
135 | + // Individual is implicit, so we skip it. |
|
136 | + return; |
|
137 | + |
|
138 | + case 'group': |
|
139 | + // OS X addressbook property |
|
140 | + $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-KIND', 'GROUP'); |
|
141 | + break; |
|
142 | + } |
|
143 | + } elseif ('MEMBER' === $property->name) { |
|
144 | + $newProperty = $output->createProperty('X-ADDRESSBOOKSERVER-MEMBER', $property->getValue()); |
|
145 | + } |
|
146 | + } elseif (Document::VCARD40 === $targetVersion) { |
|
147 | + // These properties were removed in vCard 4.0 |
|
148 | + if (in_array($property->name, ['NAME', 'MAILER', 'LABEL', 'CLASS'])) { |
|
149 | + return; |
|
150 | + } |
|
151 | + |
|
152 | + if ($property instanceof Property\Binary) { |
|
153 | + $newProperty = $this->convertBinaryToUri($output, $newProperty, $parameters); |
|
154 | + } elseif ($property instanceof Property\VCard\DateAndOrTime && isset($parameters['X-APPLE-OMIT-YEAR'])) { |
|
155 | + // If a property such as BDAY contained 'X-APPLE-OMIT-YEAR', |
|
156 | + // then we're stripping the year from the vcard 4 value. |
|
157 | + $parts = DateTimeParser::parseVCardDateTime($property->getValue()); |
|
158 | + if ($parts['year'] === $property['X-APPLE-OMIT-YEAR']->getValue()) { |
|
159 | + $newValue = '--'.$parts['month'].'-'.$parts['date']; |
|
160 | + $newProperty->setValue($newValue); |
|
161 | + } |
|
162 | + |
|
163 | + // Regardless if the year matched or not, we do need to strip |
|
164 | + // X-APPLE-OMIT-YEAR. |
|
165 | + unset($parameters['X-APPLE-OMIT-YEAR']); |
|
166 | + } |
|
167 | + switch ($property->name) { |
|
168 | + case 'X-ABSHOWAS': |
|
169 | + if ('COMPANY' === strtoupper($property->getValue())) { |
|
170 | + $newProperty = $output->createProperty('KIND', 'ORG'); |
|
171 | + } |
|
172 | + break; |
|
173 | + case 'X-ADDRESSBOOKSERVER-KIND': |
|
174 | + if ('GROUP' === strtoupper($property->getValue())) { |
|
175 | + $newProperty = $output->createProperty('KIND', 'GROUP'); |
|
176 | + } |
|
177 | + break; |
|
178 | + case 'X-ADDRESSBOOKSERVER-MEMBER': |
|
179 | + $newProperty = $output->createProperty('MEMBER', $property->getValue()); |
|
180 | + break; |
|
181 | + case 'X-ANNIVERSARY': |
|
182 | + $newProperty->name = 'ANNIVERSARY'; |
|
183 | + // If we already have an anniversary property with the same |
|
184 | + // value, ignore. |
|
185 | + foreach ($output->select('ANNIVERSARY') as $anniversary) { |
|
186 | + if ($anniversary->getValue() === $newProperty->getValue()) { |
|
187 | + return; |
|
188 | + } |
|
189 | + } |
|
190 | + break; |
|
191 | + case 'X-ABDATE': |
|
192 | + // Find out what the label was, if it exists. |
|
193 | + if (!$property->group) { |
|
194 | + break; |
|
195 | + } |
|
196 | + $label = $input->{$property->group.'.X-ABLABEL'}; |
|
197 | + |
|
198 | + // We only support converting anniversaries. |
|
199 | + if (!$label || '_$!<Anniversary>!$_' !== $label->getValue()) { |
|
200 | + break; |
|
201 | + } |
|
202 | + |
|
203 | + // If we already have an anniversary property with the same |
|
204 | + // value, ignore. |
|
205 | + foreach ($output->select('ANNIVERSARY') as $anniversary) { |
|
206 | + if ($anniversary->getValue() === $newProperty->getValue()) { |
|
207 | + return; |
|
208 | + } |
|
209 | + } |
|
210 | + $newProperty->name = 'ANNIVERSARY'; |
|
211 | + break; |
|
212 | + // Apple's per-property label system. |
|
213 | + case 'X-ABLABEL': |
|
214 | + if ('_$!<Anniversary>!$_' === $newProperty->getValue()) { |
|
215 | + // We can safely remove these, as they are converted to |
|
216 | + // ANNIVERSARY properties. |
|
217 | + return; |
|
218 | + } |
|
219 | + break; |
|
220 | + } |
|
221 | + } |
|
222 | + |
|
223 | + // set property group |
|
224 | + $newProperty->group = $property->group; |
|
225 | + |
|
226 | + if (Document::VCARD40 === $targetVersion) { |
|
227 | + $this->convertParameters40($newProperty, $parameters); |
|
228 | + } else { |
|
229 | + $this->convertParameters30($newProperty, $parameters); |
|
230 | + } |
|
231 | + |
|
232 | + // Lastly, we need to see if there's a need for a VALUE parameter. |
|
233 | + // |
|
234 | + // We can do that by instantiating a empty property with that name, and |
|
235 | + // seeing if the default valueType is identical to the current one. |
|
236 | + $tempProperty = $output->createProperty($newProperty->name); |
|
237 | + if ($tempProperty->getValueType() !== $newProperty->getValueType()) { |
|
238 | + $newProperty['VALUE'] = $newProperty->getValueType(); |
|
239 | + } |
|
240 | + |
|
241 | + $output->add($newProperty); |
|
242 | + } |
|
243 | + |
|
244 | + /** |
|
245 | + * Converts a BINARY property to a URI property. |
|
246 | + * |
|
247 | + * vCard 4.0 no longer supports BINARY properties. |
|
248 | + * |
|
249 | + * @param Property\Uri $property the input property |
|
250 | + * @param $parameters list of parameters that will eventually be added to |
|
251 | + * the new property |
|
252 | + * |
|
253 | + * @return Property\Uri |
|
254 | + */ |
|
255 | + protected function convertBinaryToUri(Component\VCard $output, Property\Binary $newProperty, array &$parameters) |
|
256 | + { |
|
257 | + $value = $newProperty->getValue(); |
|
258 | + $newProperty = $output->createProperty( |
|
259 | + $newProperty->name, |
|
260 | + null, // no value |
|
261 | + [], // no parameters yet |
|
262 | + 'URI' // Forcing the BINARY type |
|
263 | + ); |
|
264 | + |
|
265 | + $mimeType = 'application/octet-stream'; |
|
266 | + |
|
267 | + // See if we can find a better mimetype. |
|
268 | + if (isset($parameters['TYPE'])) { |
|
269 | + $newTypes = []; |
|
270 | + foreach ($parameters['TYPE']->getParts() as $typePart) { |
|
271 | + if (in_array( |
|
272 | + strtoupper($typePart), |
|
273 | + ['JPEG', 'PNG', 'GIF'] |
|
274 | + )) { |
|
275 | + $mimeType = 'image/'.strtolower($typePart); |
|
276 | + } else { |
|
277 | + $newTypes[] = $typePart; |
|
278 | + } |
|
279 | + } |
|
280 | + |
|
281 | + // If there were any parameters we're not converting to a |
|
282 | + // mime-type, we need to keep them. |
|
283 | + if ($newTypes) { |
|
284 | + $parameters['TYPE']->setParts($newTypes); |
|
285 | + } else { |
|
286 | + unset($parameters['TYPE']); |
|
287 | + } |
|
288 | + } |
|
289 | + |
|
290 | + $newProperty->setValue('data:'.$mimeType.';base64,'.base64_encode($value)); |
|
291 | + |
|
292 | + return $newProperty; |
|
293 | + } |
|
294 | + |
|
295 | + /** |
|
296 | + * Converts a URI property to a BINARY property. |
|
297 | + * |
|
298 | + * In vCard 4.0 attachments are encoded as data: uri. Even though these may |
|
299 | + * be valid in vCard 3.0 as well, we should convert those to BINARY if |
|
300 | + * possible, to improve compatibility. |
|
301 | + * |
|
302 | + * @param Property\Uri $property the input property |
|
303 | + * |
|
304 | + * @return Property\Binary|null |
|
305 | + */ |
|
306 | + protected function convertUriToBinary(Component\VCard $output, Property\Uri $newProperty) |
|
307 | + { |
|
308 | + $value = $newProperty->getValue(); |
|
309 | + |
|
310 | + // Only converting data: uris |
|
311 | + if ('data:' !== substr($value, 0, 5)) { |
|
312 | + return $newProperty; |
|
313 | + } |
|
314 | + |
|
315 | + $newProperty = $output->createProperty( |
|
316 | + $newProperty->name, |
|
317 | + null, // no value |
|
318 | + [], // no parameters yet |
|
319 | + 'BINARY' |
|
320 | + ); |
|
321 | + |
|
322 | + $mimeType = substr($value, 5, strpos($value, ',') - 5); |
|
323 | + if (strpos($mimeType, ';')) { |
|
324 | + $mimeType = substr($mimeType, 0, strpos($mimeType, ';')); |
|
325 | + $newProperty->setValue(base64_decode(substr($value, strpos($value, ',') + 1))); |
|
326 | + } else { |
|
327 | + $newProperty->setValue(substr($value, strpos($value, ',') + 1)); |
|
328 | + } |
|
329 | + unset($value); |
|
330 | + |
|
331 | + $newProperty['ENCODING'] = 'b'; |
|
332 | + switch ($mimeType) { |
|
333 | + case 'image/jpeg': |
|
334 | + $newProperty['TYPE'] = 'JPEG'; |
|
335 | + break; |
|
336 | + case 'image/png': |
|
337 | + $newProperty['TYPE'] = 'PNG'; |
|
338 | + break; |
|
339 | + case 'image/gif': |
|
340 | + $newProperty['TYPE'] = 'GIF'; |
|
341 | + break; |
|
342 | + } |
|
343 | + |
|
344 | + return $newProperty; |
|
345 | + } |
|
346 | + |
|
347 | + /** |
|
348 | + * Adds parameters to a new property for vCard 4.0. |
|
349 | + */ |
|
350 | + protected function convertParameters40(Property $newProperty, array $parameters) |
|
351 | + { |
|
352 | + // Adding all parameters. |
|
353 | + foreach ($parameters as $param) { |
|
354 | + // vCard 2.1 allowed parameters with no name |
|
355 | + if ($param->noName) { |
|
356 | + $param->noName = false; |
|
357 | + } |
|
358 | + |
|
359 | + switch ($param->name) { |
|
360 | + // We need to see if there's any TYPE=PREF, because in vCard 4 |
|
361 | + // that's now PREF=1. |
|
362 | + case 'TYPE': |
|
363 | + foreach ($param->getParts() as $paramPart) { |
|
364 | + if ('PREF' === strtoupper($paramPart)) { |
|
365 | + $newProperty->add('PREF', '1'); |
|
366 | + } else { |
|
367 | + $newProperty->add($param->name, $paramPart); |
|
368 | + } |
|
369 | + } |
|
370 | + break; |
|
371 | + // These no longer exist in vCard 4 |
|
372 | + case 'ENCODING': |
|
373 | + case 'CHARSET': |
|
374 | + break; |
|
375 | + |
|
376 | + default: |
|
377 | + $newProperty->add($param->name, $param->getParts()); |
|
378 | + break; |
|
379 | + } |
|
380 | + } |
|
381 | + } |
|
382 | + |
|
383 | + /** |
|
384 | + * Adds parameters to a new property for vCard 3.0. |
|
385 | + */ |
|
386 | + protected function convertParameters30(Property $newProperty, array $parameters) |
|
387 | + { |
|
388 | + // Adding all parameters. |
|
389 | + foreach ($parameters as $param) { |
|
390 | + // vCard 2.1 allowed parameters with no name |
|
391 | + if ($param->noName) { |
|
392 | + $param->noName = false; |
|
393 | + } |
|
394 | + |
|
395 | + switch ($param->name) { |
|
396 | + case 'ENCODING': |
|
397 | + // This value only existed in vCard 2.1, and should be |
|
398 | + // removed for anything else. |
|
399 | + if ('QUOTED-PRINTABLE' !== strtoupper($param->getValue())) { |
|
400 | + $newProperty->add($param->name, $param->getParts()); |
|
401 | + } |
|
402 | + break; |
|
403 | + |
|
404 | + /* |
|
405 | 405 | * Converting PREF=1 to TYPE=PREF. |
406 | 406 | * |
407 | 407 | * Any other PREF numbers we'll drop. |
408 | 408 | */ |
409 | - case 'PREF': |
|
410 | - if ('1' == $param->getValue()) { |
|
411 | - $newProperty->add('TYPE', 'PREF'); |
|
412 | - } |
|
413 | - break; |
|
414 | - |
|
415 | - default: |
|
416 | - $newProperty->add($param->name, $param->getParts()); |
|
417 | - break; |
|
418 | - } |
|
419 | - } |
|
420 | - } |
|
409 | + case 'PREF': |
|
410 | + if ('1' == $param->getValue()) { |
|
411 | + $newProperty->add('TYPE', 'PREF'); |
|
412 | + } |
|
413 | + break; |
|
414 | + |
|
415 | + default: |
|
416 | + $newProperty->add($param->name, $param->getParts()); |
|
417 | + break; |
|
418 | + } |
|
419 | + } |
|
420 | + } |
|
421 | 421 | } |
@@ -31,7 +31,7 @@ discard block |
||
31 | 31 | } |
32 | 32 | |
33 | 33 | $base = parse($basePath); |
34 | - $pick = function ($part) use ($base, $delta) { |
|
34 | + $pick = function($part) use ($base, $delta) { |
|
35 | 35 | if ($delta[$part]) { |
36 | 36 | return $delta[$part]; |
37 | 37 | } elseif ($base[$part]) { |
@@ -187,7 +187,7 @@ discard block |
||
187 | 187 | // uriencode them first. |
188 | 188 | $uri = preg_replace_callback( |
189 | 189 | '/[^[:ascii:]]/u', |
190 | - function ($matches) { |
|
190 | + function($matches) { |
|
191 | 191 | return rawurlencode($matches[0]); |
192 | 192 | }, |
193 | 193 | $uri |
@@ -316,7 +316,7 @@ discard block |
||
316 | 316 | // uriencode them first. |
317 | 317 | $uri = preg_replace_callback( |
318 | 318 | '/[^[:ascii:]]/u', |
319 | - function ($matches) { |
|
319 | + function($matches) { |
|
320 | 320 | return rawurlencode($matches[0]); |
321 | 321 | }, |
322 | 322 | $uri |
@@ -22,80 +22,80 @@ discard block |
||
22 | 22 | */ |
23 | 23 | function resolve(string $basePath, string $newPath): string |
24 | 24 | { |
25 | - $delta = parse($newPath); |
|
26 | - |
|
27 | - // If the new path defines a scheme, it's absolute and we can just return |
|
28 | - // that. |
|
29 | - if ($delta['scheme']) { |
|
30 | - return build($delta); |
|
31 | - } |
|
32 | - |
|
33 | - $base = parse($basePath); |
|
34 | - $pick = function ($part) use ($base, $delta) { |
|
35 | - if ($delta[$part]) { |
|
36 | - return $delta[$part]; |
|
37 | - } elseif ($base[$part]) { |
|
38 | - return $base[$part]; |
|
39 | - } |
|
40 | - |
|
41 | - return null; |
|
42 | - }; |
|
43 | - |
|
44 | - $newParts = []; |
|
45 | - |
|
46 | - $newParts['scheme'] = $pick('scheme'); |
|
47 | - $newParts['host'] = $pick('host'); |
|
48 | - $newParts['port'] = $pick('port'); |
|
49 | - |
|
50 | - $path = ''; |
|
51 | - if (is_string($delta['path']) and strlen($delta['path']) > 0) { |
|
52 | - // If the path starts with a slash |
|
53 | - if ('/' === $delta['path'][0]) { |
|
54 | - $path = $delta['path']; |
|
55 | - } else { |
|
56 | - // Removing last component from base path. |
|
57 | - $path = $base['path']; |
|
58 | - $length = strrpos((string) $path, '/'); |
|
59 | - if (false !== $length) { |
|
60 | - $path = substr($path, 0, $length); |
|
61 | - } |
|
62 | - $path .= '/'.$delta['path']; |
|
63 | - } |
|
64 | - } else { |
|
65 | - $path = $base['path'] ?: '/'; |
|
66 | - } |
|
67 | - // Removing .. and . |
|
68 | - $pathParts = explode('/', $path); |
|
69 | - $newPathParts = []; |
|
70 | - foreach ($pathParts as $pathPart) { |
|
71 | - switch ($pathPart) { |
|
72 | - //case '' : |
|
73 | - case '.': |
|
74 | - break; |
|
75 | - case '..': |
|
76 | - array_pop($newPathParts); |
|
77 | - break; |
|
78 | - default: |
|
79 | - $newPathParts[] = $pathPart; |
|
80 | - break; |
|
81 | - } |
|
82 | - } |
|
83 | - |
|
84 | - $path = implode('/', $newPathParts); |
|
85 | - |
|
86 | - // If the source url ended with a /, we want to preserve that. |
|
87 | - $newParts['path'] = 0 === strpos($path, '/') ? $path : '/'.$path; |
|
88 | - if ($delta['query']) { |
|
89 | - $newParts['query'] = $delta['query']; |
|
90 | - } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) { |
|
91 | - // Keep the old query if host and path didn't change |
|
92 | - $newParts['query'] = $base['query']; |
|
93 | - } |
|
94 | - if ($delta['fragment']) { |
|
95 | - $newParts['fragment'] = $delta['fragment']; |
|
96 | - } |
|
97 | - |
|
98 | - return build($newParts); |
|
25 | + $delta = parse($newPath); |
|
26 | + |
|
27 | + // If the new path defines a scheme, it's absolute and we can just return |
|
28 | + // that. |
|
29 | + if ($delta['scheme']) { |
|
30 | + return build($delta); |
|
31 | + } |
|
32 | + |
|
33 | + $base = parse($basePath); |
|
34 | + $pick = function ($part) use ($base, $delta) { |
|
35 | + if ($delta[$part]) { |
|
36 | + return $delta[$part]; |
|
37 | + } elseif ($base[$part]) { |
|
38 | + return $base[$part]; |
|
39 | + } |
|
40 | + |
|
41 | + return null; |
|
42 | + }; |
|
43 | + |
|
44 | + $newParts = []; |
|
45 | + |
|
46 | + $newParts['scheme'] = $pick('scheme'); |
|
47 | + $newParts['host'] = $pick('host'); |
|
48 | + $newParts['port'] = $pick('port'); |
|
49 | + |
|
50 | + $path = ''; |
|
51 | + if (is_string($delta['path']) and strlen($delta['path']) > 0) { |
|
52 | + // If the path starts with a slash |
|
53 | + if ('/' === $delta['path'][0]) { |
|
54 | + $path = $delta['path']; |
|
55 | + } else { |
|
56 | + // Removing last component from base path. |
|
57 | + $path = $base['path']; |
|
58 | + $length = strrpos((string) $path, '/'); |
|
59 | + if (false !== $length) { |
|
60 | + $path = substr($path, 0, $length); |
|
61 | + } |
|
62 | + $path .= '/'.$delta['path']; |
|
63 | + } |
|
64 | + } else { |
|
65 | + $path = $base['path'] ?: '/'; |
|
66 | + } |
|
67 | + // Removing .. and . |
|
68 | + $pathParts = explode('/', $path); |
|
69 | + $newPathParts = []; |
|
70 | + foreach ($pathParts as $pathPart) { |
|
71 | + switch ($pathPart) { |
|
72 | + //case '' : |
|
73 | + case '.': |
|
74 | + break; |
|
75 | + case '..': |
|
76 | + array_pop($newPathParts); |
|
77 | + break; |
|
78 | + default: |
|
79 | + $newPathParts[] = $pathPart; |
|
80 | + break; |
|
81 | + } |
|
82 | + } |
|
83 | + |
|
84 | + $path = implode('/', $newPathParts); |
|
85 | + |
|
86 | + // If the source url ended with a /, we want to preserve that. |
|
87 | + $newParts['path'] = 0 === strpos($path, '/') ? $path : '/'.$path; |
|
88 | + if ($delta['query']) { |
|
89 | + $newParts['query'] = $delta['query']; |
|
90 | + } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) { |
|
91 | + // Keep the old query if host and path didn't change |
|
92 | + $newParts['query'] = $base['query']; |
|
93 | + } |
|
94 | + if ($delta['fragment']) { |
|
95 | + $newParts['fragment'] = $delta['fragment']; |
|
96 | + } |
|
97 | + |
|
98 | + return build($newParts); |
|
99 | 99 | } |
100 | 100 | |
101 | 101 | /** |
@@ -111,57 +111,57 @@ discard block |
||
111 | 111 | */ |
112 | 112 | function normalize(string $uri): string |
113 | 113 | { |
114 | - $parts = parse($uri); |
|
115 | - |
|
116 | - if (!empty($parts['path'])) { |
|
117 | - $pathParts = explode('/', ltrim($parts['path'], '/')); |
|
118 | - $newPathParts = []; |
|
119 | - foreach ($pathParts as $pathPart) { |
|
120 | - switch ($pathPart) { |
|
121 | - case '.': |
|
122 | - // skip |
|
123 | - break; |
|
124 | - case '..': |
|
125 | - // One level up in the hierarchy |
|
126 | - array_pop($newPathParts); |
|
127 | - break; |
|
128 | - default: |
|
129 | - // Ensuring that everything is correctly percent-encoded. |
|
130 | - $newPathParts[] = rawurlencode(rawurldecode($pathPart)); |
|
131 | - break; |
|
132 | - } |
|
133 | - } |
|
134 | - $parts['path'] = '/'.implode('/', $newPathParts); |
|
135 | - } |
|
136 | - |
|
137 | - if ($parts['scheme']) { |
|
138 | - $parts['scheme'] = strtolower($parts['scheme']); |
|
139 | - $defaultPorts = [ |
|
140 | - 'http' => '80', |
|
141 | - 'https' => '443', |
|
142 | - ]; |
|
143 | - |
|
144 | - if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) { |
|
145 | - // Removing default ports. |
|
146 | - unset($parts['port']); |
|
147 | - } |
|
148 | - // A few HTTP specific rules. |
|
149 | - switch ($parts['scheme']) { |
|
150 | - case 'http': |
|
151 | - case 'https': |
|
152 | - if (empty($parts['path'])) { |
|
153 | - // An empty path is equivalent to / in http. |
|
154 | - $parts['path'] = '/'; |
|
155 | - } |
|
156 | - break; |
|
157 | - } |
|
158 | - } |
|
159 | - |
|
160 | - if ($parts['host']) { |
|
161 | - $parts['host'] = strtolower($parts['host']); |
|
162 | - } |
|
163 | - |
|
164 | - return build($parts); |
|
114 | + $parts = parse($uri); |
|
115 | + |
|
116 | + if (!empty($parts['path'])) { |
|
117 | + $pathParts = explode('/', ltrim($parts['path'], '/')); |
|
118 | + $newPathParts = []; |
|
119 | + foreach ($pathParts as $pathPart) { |
|
120 | + switch ($pathPart) { |
|
121 | + case '.': |
|
122 | + // skip |
|
123 | + break; |
|
124 | + case '..': |
|
125 | + // One level up in the hierarchy |
|
126 | + array_pop($newPathParts); |
|
127 | + break; |
|
128 | + default: |
|
129 | + // Ensuring that everything is correctly percent-encoded. |
|
130 | + $newPathParts[] = rawurlencode(rawurldecode($pathPart)); |
|
131 | + break; |
|
132 | + } |
|
133 | + } |
|
134 | + $parts['path'] = '/'.implode('/', $newPathParts); |
|
135 | + } |
|
136 | + |
|
137 | + if ($parts['scheme']) { |
|
138 | + $parts['scheme'] = strtolower($parts['scheme']); |
|
139 | + $defaultPorts = [ |
|
140 | + 'http' => '80', |
|
141 | + 'https' => '443', |
|
142 | + ]; |
|
143 | + |
|
144 | + if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) { |
|
145 | + // Removing default ports. |
|
146 | + unset($parts['port']); |
|
147 | + } |
|
148 | + // A few HTTP specific rules. |
|
149 | + switch ($parts['scheme']) { |
|
150 | + case 'http': |
|
151 | + case 'https': |
|
152 | + if (empty($parts['path'])) { |
|
153 | + // An empty path is equivalent to / in http. |
|
154 | + $parts['path'] = '/'; |
|
155 | + } |
|
156 | + break; |
|
157 | + } |
|
158 | + } |
|
159 | + |
|
160 | + if ($parts['host']) { |
|
161 | + $parts['host'] = strtolower($parts['host']); |
|
162 | + } |
|
163 | + |
|
164 | + return build($parts); |
|
165 | 165 | } |
166 | 166 | |
167 | 167 | /** |
@@ -180,34 +180,34 @@ discard block |
||
180 | 180 | */ |
181 | 181 | function parse(string $uri): array |
182 | 182 | { |
183 | - // Normally a URI must be ASCII, however. However, often it's not and |
|
184 | - // parse_url might corrupt these strings. |
|
185 | - // |
|
186 | - // For that reason we take any non-ascii characters from the uri and |
|
187 | - // uriencode them first. |
|
188 | - $uri = preg_replace_callback( |
|
189 | - '/[^[:ascii:]]/u', |
|
190 | - function ($matches) { |
|
191 | - return rawurlencode($matches[0]); |
|
192 | - }, |
|
193 | - $uri |
|
194 | - ); |
|
195 | - |
|
196 | - $result = parse_url($uri); |
|
197 | - if (!$result) { |
|
198 | - $result = _parse_fallback($uri); |
|
199 | - } |
|
200 | - |
|
201 | - return |
|
202 | - $result + [ |
|
203 | - 'scheme' => null, |
|
204 | - 'host' => null, |
|
205 | - 'path' => null, |
|
206 | - 'port' => null, |
|
207 | - 'user' => null, |
|
208 | - 'query' => null, |
|
209 | - 'fragment' => null, |
|
210 | - ]; |
|
183 | + // Normally a URI must be ASCII, however. However, often it's not and |
|
184 | + // parse_url might corrupt these strings. |
|
185 | + // |
|
186 | + // For that reason we take any non-ascii characters from the uri and |
|
187 | + // uriencode them first. |
|
188 | + $uri = preg_replace_callback( |
|
189 | + '/[^[:ascii:]]/u', |
|
190 | + function ($matches) { |
|
191 | + return rawurlencode($matches[0]); |
|
192 | + }, |
|
193 | + $uri |
|
194 | + ); |
|
195 | + |
|
196 | + $result = parse_url($uri); |
|
197 | + if (!$result) { |
|
198 | + $result = _parse_fallback($uri); |
|
199 | + } |
|
200 | + |
|
201 | + return |
|
202 | + $result + [ |
|
203 | + 'scheme' => null, |
|
204 | + 'host' => null, |
|
205 | + 'path' => null, |
|
206 | + 'port' => null, |
|
207 | + 'user' => null, |
|
208 | + 'query' => null, |
|
209 | + 'fragment' => null, |
|
210 | + ]; |
|
211 | 211 | } |
212 | 212 | |
213 | 213 | /** |
@@ -218,39 +218,39 @@ discard block |
||
218 | 218 | */ |
219 | 219 | function build(array $parts): string |
220 | 220 | { |
221 | - $uri = ''; |
|
222 | - |
|
223 | - $authority = ''; |
|
224 | - if (!empty($parts['host'])) { |
|
225 | - $authority = $parts['host']; |
|
226 | - if (!empty($parts['user'])) { |
|
227 | - $authority = $parts['user'].'@'.$authority; |
|
228 | - } |
|
229 | - if (!empty($parts['port'])) { |
|
230 | - $authority = $authority.':'.$parts['port']; |
|
231 | - } |
|
232 | - } |
|
233 | - |
|
234 | - if (!empty($parts['scheme'])) { |
|
235 | - // If there's a scheme, there's also a host. |
|
236 | - $uri = $parts['scheme'].':'; |
|
237 | - } |
|
238 | - if ($authority || (!empty($parts['scheme']) && 'file' === $parts['scheme'])) { |
|
239 | - // No scheme, but there is a host. |
|
240 | - $uri .= '//'.$authority; |
|
241 | - } |
|
242 | - |
|
243 | - if (!empty($parts['path'])) { |
|
244 | - $uri .= $parts['path']; |
|
245 | - } |
|
246 | - if (!empty($parts['query'])) { |
|
247 | - $uri .= '?'.$parts['query']; |
|
248 | - } |
|
249 | - if (!empty($parts['fragment'])) { |
|
250 | - $uri .= '#'.$parts['fragment']; |
|
251 | - } |
|
252 | - |
|
253 | - return $uri; |
|
221 | + $uri = ''; |
|
222 | + |
|
223 | + $authority = ''; |
|
224 | + if (!empty($parts['host'])) { |
|
225 | + $authority = $parts['host']; |
|
226 | + if (!empty($parts['user'])) { |
|
227 | + $authority = $parts['user'].'@'.$authority; |
|
228 | + } |
|
229 | + if (!empty($parts['port'])) { |
|
230 | + $authority = $authority.':'.$parts['port']; |
|
231 | + } |
|
232 | + } |
|
233 | + |
|
234 | + if (!empty($parts['scheme'])) { |
|
235 | + // If there's a scheme, there's also a host. |
|
236 | + $uri = $parts['scheme'].':'; |
|
237 | + } |
|
238 | + if ($authority || (!empty($parts['scheme']) && 'file' === $parts['scheme'])) { |
|
239 | + // No scheme, but there is a host. |
|
240 | + $uri .= '//'.$authority; |
|
241 | + } |
|
242 | + |
|
243 | + if (!empty($parts['path'])) { |
|
244 | + $uri .= $parts['path']; |
|
245 | + } |
|
246 | + if (!empty($parts['query'])) { |
|
247 | + $uri .= '?'.$parts['query']; |
|
248 | + } |
|
249 | + if (!empty($parts['fragment'])) { |
|
250 | + $uri .= '#'.$parts['fragment']; |
|
251 | + } |
|
252 | + |
|
253 | + return $uri; |
|
254 | 254 | } |
255 | 255 | |
256 | 256 | /** |
@@ -272,12 +272,12 @@ discard block |
||
272 | 272 | */ |
273 | 273 | function split(string $path): array |
274 | 274 | { |
275 | - $matches = []; |
|
276 | - if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) { |
|
277 | - return [$matches[1], $matches[2]]; |
|
278 | - } |
|
275 | + $matches = []; |
|
276 | + if (preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) { |
|
277 | + return [$matches[1], $matches[2]]; |
|
278 | + } |
|
279 | 279 | |
280 | - return [null, null]; |
|
280 | + return [null, null]; |
|
281 | 281 | } |
282 | 282 | |
283 | 283 | /** |
@@ -296,79 +296,79 @@ discard block |
||
296 | 296 | */ |
297 | 297 | function _parse_fallback(string $uri): array |
298 | 298 | { |
299 | - // Normally a URI must be ASCII, however. However, often it's not and |
|
300 | - // parse_url might corrupt these strings. |
|
301 | - // |
|
302 | - // For that reason we take any non-ascii characters from the uri and |
|
303 | - // uriencode them first. |
|
304 | - $uri = preg_replace_callback( |
|
305 | - '/[^[:ascii:]]/u', |
|
306 | - function ($matches) { |
|
307 | - return rawurlencode($matches[0]); |
|
308 | - }, |
|
309 | - $uri |
|
310 | - ); |
|
311 | - |
|
312 | - $result = [ |
|
313 | - 'scheme' => null, |
|
314 | - 'host' => null, |
|
315 | - 'port' => null, |
|
316 | - 'user' => null, |
|
317 | - 'path' => null, |
|
318 | - 'fragment' => null, |
|
319 | - 'query' => null, |
|
320 | - ]; |
|
321 | - |
|
322 | - if (preg_match('% ^([A-Za-z][A-Za-z0-9+-\.]+): %x', $uri, $matches)) { |
|
323 | - $result['scheme'] = $matches[1]; |
|
324 | - // Take what's left. |
|
325 | - $uri = substr($uri, strlen($result['scheme']) + 1); |
|
326 | - } |
|
327 | - |
|
328 | - // Taking off a fragment part |
|
329 | - if (false !== strpos($uri, '#')) { |
|
330 | - list($uri, $result['fragment']) = explode('#', $uri, 2); |
|
331 | - } |
|
332 | - // Taking off the query part |
|
333 | - if (false !== strpos($uri, '?')) { |
|
334 | - list($uri, $result['query']) = explode('?', $uri, 2); |
|
335 | - } |
|
336 | - |
|
337 | - if ('///' === substr($uri, 0, 3)) { |
|
338 | - // The triple slash uris are a bit unusual, but we have special handling |
|
339 | - // for them. |
|
340 | - $result['path'] = substr($uri, 2); |
|
341 | - $result['host'] = ''; |
|
342 | - } elseif ('//' === substr($uri, 0, 2)) { |
|
343 | - // Uris that have an authority part. |
|
344 | - $regex = '%^ |
|
299 | + // Normally a URI must be ASCII, however. However, often it's not and |
|
300 | + // parse_url might corrupt these strings. |
|
301 | + // |
|
302 | + // For that reason we take any non-ascii characters from the uri and |
|
303 | + // uriencode them first. |
|
304 | + $uri = preg_replace_callback( |
|
305 | + '/[^[:ascii:]]/u', |
|
306 | + function ($matches) { |
|
307 | + return rawurlencode($matches[0]); |
|
308 | + }, |
|
309 | + $uri |
|
310 | + ); |
|
311 | + |
|
312 | + $result = [ |
|
313 | + 'scheme' => null, |
|
314 | + 'host' => null, |
|
315 | + 'port' => null, |
|
316 | + 'user' => null, |
|
317 | + 'path' => null, |
|
318 | + 'fragment' => null, |
|
319 | + 'query' => null, |
|
320 | + ]; |
|
321 | + |
|
322 | + if (preg_match('% ^([A-Za-z][A-Za-z0-9+-\.]+): %x', $uri, $matches)) { |
|
323 | + $result['scheme'] = $matches[1]; |
|
324 | + // Take what's left. |
|
325 | + $uri = substr($uri, strlen($result['scheme']) + 1); |
|
326 | + } |
|
327 | + |
|
328 | + // Taking off a fragment part |
|
329 | + if (false !== strpos($uri, '#')) { |
|
330 | + list($uri, $result['fragment']) = explode('#', $uri, 2); |
|
331 | + } |
|
332 | + // Taking off the query part |
|
333 | + if (false !== strpos($uri, '?')) { |
|
334 | + list($uri, $result['query']) = explode('?', $uri, 2); |
|
335 | + } |
|
336 | + |
|
337 | + if ('///' === substr($uri, 0, 3)) { |
|
338 | + // The triple slash uris are a bit unusual, but we have special handling |
|
339 | + // for them. |
|
340 | + $result['path'] = substr($uri, 2); |
|
341 | + $result['host'] = ''; |
|
342 | + } elseif ('//' === substr($uri, 0, 2)) { |
|
343 | + // Uris that have an authority part. |
|
344 | + $regex = '%^ |
|
345 | 345 | // |
346 | 346 | (?: (?<user> [^:@]+) (: (?<pass> [^@]+)) @)? |
347 | 347 | (?<host> ( [^:/]* | \[ [^\]]+ \] )) |
348 | 348 | (?: : (?<port> [0-9]+))? |
349 | 349 | (?<path> / .*)? |
350 | 350 | $%x'; |
351 | - if (!preg_match($regex, $uri, $matches)) { |
|
352 | - throw new InvalidUriException('Invalid, or could not parse URI'); |
|
353 | - } |
|
354 | - if ($matches['host']) { |
|
355 | - $result['host'] = $matches['host']; |
|
356 | - } |
|
357 | - if (isset($matches['port'])) { |
|
358 | - $result['port'] = (int) $matches['port']; |
|
359 | - } |
|
360 | - if (isset($matches['path'])) { |
|
361 | - $result['path'] = $matches['path']; |
|
362 | - } |
|
363 | - if ($matches['user']) { |
|
364 | - $result['user'] = $matches['user']; |
|
365 | - } |
|
366 | - if ($matches['pass']) { |
|
367 | - $result['pass'] = $matches['pass']; |
|
368 | - } |
|
369 | - } else { |
|
370 | - $result['path'] = $uri; |
|
371 | - } |
|
372 | - |
|
373 | - return $result; |
|
351 | + if (!preg_match($regex, $uri, $matches)) { |
|
352 | + throw new InvalidUriException('Invalid, or could not parse URI'); |
|
353 | + } |
|
354 | + if ($matches['host']) { |
|
355 | + $result['host'] = $matches['host']; |
|
356 | + } |
|
357 | + if (isset($matches['port'])) { |
|
358 | + $result['port'] = (int) $matches['port']; |
|
359 | + } |
|
360 | + if (isset($matches['path'])) { |
|
361 | + $result['path'] = $matches['path']; |
|
362 | + } |
|
363 | + if ($matches['user']) { |
|
364 | + $result['user'] = $matches['user']; |
|
365 | + } |
|
366 | + if ($matches['pass']) { |
|
367 | + $result['pass'] = $matches['pass']; |
|
368 | + } |
|
369 | + } else { |
|
370 | + $result['path'] = $uri; |
|
371 | + } |
|
372 | + |
|
373 | + return $result; |
|
374 | 374 | } |