@@ -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 | } |