Completed
Pull Request — master (#75)
by
unknown
01:30
created

Ics::addVTimezoneComponents()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 4
1
<?php
2
3
namespace Spatie\CalendarLinks\Generators;
4
5
use Sabre\VObject\Component;
6
use Spatie\CalendarLinks\Link;
7
use Sabre\VObject\TimeZoneUtil;
8
use Spatie\CalendarLinks\Generator;
9
use Sabre\VObject\Component\VCalendar;
10
11
/**
12
 * @see https://icalendar.org/RFC-Specifications/iCalendar-RFC-5545/
13
 */
14
class Ics implements Generator
15
{
16
    public function generate(Link $link): string
17
    {
18
        $timeZones = [];
19
20
        $vcalendar = new VCalendar();
21
        $vevent = $vcalendar->createComponent('VEVENT', [
22
            'UID' => $this->generateEventUid($link),
23
            'SUMMARY' => $link->title,
24
        ]);
25
        $vcalendar->add($vevent);
26
27
        if ($link->allDay) {
28
            $vevent->add('DTSTART', $link->from);
29
            $vevent->add('DURATION:P1D');
30
            $timeZones[$link->from->getTimezone()->getName()] = $link->from->getTimezone();
31
        } else {
32
            $vevent->add('DTSTART', $link->from);
33
            $timeZones[$link->from->getTimezone()->getName()] = $link->from->getTimezone();
34
35
            $vevent->add('DTEND', $link->to);
36
            $timeZones[$link->to->getTimezone()->getName()] = $link->to->getTimezone();
37
        }
38
39
        if ($link->description) {
40
            $vevent->add('DESCRIPTION', $link->description);
41
        }
42
43
        if ($link->address) {
44
            $vevent->add('LOCATION', $link->address);
45
        }
46
47
        $this->addVTimezoneComponents($vcalendar, $timeZones, $link->from, $link->to);
48
49
        // Remove non-wanted component
50
        $vcalendar->remove('PRODID');
51
        $vevent->remove('DTSTAMP');
52
53
        $redirectLink = str_replace("\r\n", '%0d%0a', $vcalendar->serialize());
54
55
        return 'data:text/calendar;charset=utf8,'.$redirectLink;
56
    }
57
58
    /** @see https://tools.ietf.org/html/rfc5545.html#section-3.3.11 */
59
    protected function escapeString(string $field): string
60
    {
61
        return addcslashes($field, "\r\n,;");
62
    }
63
64
    /** @see https://tools.ietf.org/html/rfc5545#section-3.8.4.7 */
65
    protected function generateEventUid(Link $link): string
66
    {
67
        return md5($link->from->format(\DateTime::ATOM).$link->to->format(\DateTime::ATOM).$link->title.$link->address);
68
    }
69
70
    protected function addVTimezoneComponents(VCalendar $vcalendar, array $timeZones, \DateTime $from, \DateTime $to)
71
    {
72
        foreach ($timeZones as $timeZone) {
73
            /* @var \DateTimeZone $timeZone */
74
            if ($timeZone->getName() !== 'UTC') {
75
                $vcalendar->add(
76
                    $this->generateVTimeZoneComponent(
77
                        $vcalendar,
78
                        $timeZone,
79
                        $from->getTimestamp(),
80
                        $to->getTimestamp()
81
                    )
82
                );
83
            }
84
        }
85
    }
86
87
    /**
88
     * Returns a VTIMEZONE component for a Olson timezone identifier
89
     * with daylight transitions covering the given date range.
90
     *
91
     * Kinldy inspired from https://gist.github.com/thomascube/47ff7d530244c669825736b10877a200
92
     * and https://stackoverflow.com/a/25971680
93
     *
94
     * @param VCalendar $vcalendar
95
     * @param \DateTimeZone $timeZone Timezone
96
     * @param int $from Unix timestamp with first date/time in this timezone
97
     * @param int $to Unix timestap with last date/time in this timezone
98
     *
99
     * @return Component A Sabre\VObject\Component object representing a VTIMEZONE definition
100
     */
101
    protected function generateVTimeZoneComponent(VCalendar $vcalendar, \DateTimeZone $timeZone, int $from = 0, int $to = 0)
102
    {
103
        if (! $from) {
104
            $from = time();
105
        }
106
        if (! $to) {
107
            $to = $from;
108
        }
109
110
        // get all transitions for one year back/ahead
111
        $year = 86400 * 360;
112
        $transitions = $timeZone->getTransitions($from - $year, $to + $year);
113
114
        $vTimeZone = $vcalendar->createComponent('VTIMEZONE');
115
        $vTimeZone->TZID = $timeZone->getName();
116
117
        $std = null;
118
        $dst = null;
119
        foreach ($transitions as $i => $trans) {
120
            $component = null;
0 ignored issues
show
Unused Code introduced by
$component is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
121
122
            if ($i === 0) {
123
                // remember the offset for the next TZOFFSETFROM value
124
                $tzfrom = $trans['offset'] / 3600;
125
            }
126
127
            // daylight saving time definition
128
            if ($trans['isdst']) {
129
                $t_dst = $trans['ts'];
130
                $dst = $vcalendar->createComponent('DAYLIGHT');
131
                $component = $dst;
132
            } // standard time definition
133
            else {
134
                $t_std = $trans['ts'];
135
                $std = $vcalendar->createComponent('STANDARD');
136
                $component = $std;
137
            }
138
139
            if ($component) {
140
                $dt = new \DateTime($trans['time']);
141
                $offset = $trans['offset'] / 3600;
142
143
                $component->DTSTART = $dt->format('Ymd\THis');
144
                $component->TZOFFSETFROM = sprintf('%s%02d%02d', $tzfrom >= 0 ? '+' : '', floor($tzfrom),
0 ignored issues
show
Bug introduced by
The variable $tzfrom does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
145
                    ($tzfrom - floor($tzfrom)) * 60);
146
                $component->TZOFFSETTO = sprintf('%s%02d%02d', $offset >= 0 ? '+' : '', floor($offset),
147
                    ($offset - floor($offset)) * 60);
148
149
                // add abbreviated timezone name if available
150
                if (! empty($trans['abbr'])) {
151
                    $component->TZNAME = $trans['abbr'];
152
                }
153
154
                $tzfrom = $offset;
155
                $vTimeZone->add($component);
156
            }
157
158
            // we covered the entire date range
159
            if ($std && $dst && min($t_std, $t_dst) < $from && max($t_std, $t_dst) > $to) {
0 ignored issues
show
Bug introduced by
The variable $t_std does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $t_dst does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
160
                break;
161
            }
162
        }
163
164
        // add X-MICROSOFT-CDO-TZID if available
165
        $microsoftExchangeMap = array_flip(TimeZoneUtil::$microsoftExchangeMap);
166
        if (array_key_exists($timeZone->getName(), $microsoftExchangeMap)) {
167
            $vTimeZone->add('X-MICROSOFT-CDO-TZID', $microsoftExchangeMap[$timeZone->getName()]);
168
        }
169
170
        return $vTimeZone;
171
    }
172
}
173