Completed
Push — develop ( 61ee3a...f2bd12 )
by Seth
08:02
created

Event::delete()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 41
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 41
rs 8.5806
cc 4
eloc 21
nc 5
nop 0
1
<?php
2
3
namespace smtech\CanvasICSSync\SyncIntoCanvas;
4
5
use DateTime;
6
use Exception;
7
use vevent;
8
use iCalUtilityFunctions;
9
use Battis\Educoder\Pest_Unauthorized;
10
11
class Event extends Syncable
12
{
13
    /*
14
     * TODO:0 Can we detect the timezone for the Canvas instance and use it? issue:18
15
     */
16
    const LOCAL_TIMEZONE = 'US/Eastern';
17
    const CANVAS_TIMESTAMP_FORMAT = 'Y-m-d\TH:iP';
18
    const FIELD_MAP = [
19
        'calendar_event[title]' => 'SUMMARY',
20
        'calendar_event[description]' => 'DESCRIPTION',
21
        'calendar_event[start_at]' => [
22
            0 => 'X-CURRENT-DTSTART',
23
            1 => 'DTSTART'
24
        ],
25
        'calendar_event[end_at]' => [
26
            0 => 'X-CURRENT-DTEND',
27
            1 => 'DTEND'
28
        ],
29
        'calendar_event[location_name]' => 'LOCATION'
30
    ];
31
32
    /**
33
     * VEVENT
34
     * @var vevent
35
     */
36
    protected $vevent;
37
38
    /**
39
     * Calendar
40
     * @var Calendar
41
     */
42
    protected $calendar;
43
44
    /**
45
     * Unique hash identifying this version of this event
46
     * @var string
47
     */
48
    protected $hash;
49
50
    /**
51
     * Construct an event
52
     *
53
     * @param vevent|string $veventOrHash
54
     * @param Calendar $calendar
55
     */
56
    public function __construct($veventOrHash, Calendar $calendar)
57
    {
58
        $fail = false;
59
        if (empty($veventOrHash)) {
60
            $fail = true;
61
        } elseif (is_string($veventOrHash)) {
62
            $this->hash = $veventOrHash;
63
        } elseif (is_a($veventOrHash, vevent::class)) {
64
            $this->vevent = $veventOrHash;
65
        } else {
66
            $fail = true;
67
        }
68
        if ($fail) {
69
            throw new Exception(
70
                'Valid VEVENT or hash required'
71
            );
72
        }
73
74
        if (empty($calendar)) {
75
            throw new Exception(
76
                'Valid Calendar required'
77
            );
78
        }
79
        $this->calendar = $calendar;
80
81
        $this->getHash();
82
    }
83
84
    /**
85
     * Get VEVENT component property value/params
86
     *
87
     * @return mixed
88
     */
89
    public function getProperty()
90
    {
91
        return (empty($this->vevent) ? false : call_user_func_array([$this->vevent, 'getProperty'], func_get_args()));
92
    }
93
94
    /**
95
     * Set VEVENT component property value/params
96
     *
97
     * @return mixed
98
     */
99
    public function setProperty()
100
    {
101
        return (empty($this->vevent) ? false : call_user_func_array([$this->vevent, 'setProperty'], func_get_args()));
102
    }
103
104
    /**
105
     * Get the associated calendar
106
     *
107
     * @return Calendar
108
     */
109
    public function getCalendar()
110
    {
111
        return $this->calendar;
112
    }
113
114
    /**
115
     * Generate a hash of this version of an event to cache in the database
116
     **/
117
    public function getHash($algorithm = 'md5')
118
    {
119
        if (empty($this->hash)) {
120
            if (empty($this->vevent)) {
121
                throw new Exception(
122
                    'Neither hash or VEVENT to compute hash available'
123
                );
124
            }
125
            $blob = '';
126
            foreach (static::$FIELD_MAP as $field) {
127
                if (is_array($field)) {
128
                    foreach ($field as $option) {
129
                        if (!empty($property = $this->getProperty($option))) {
130
                            $blob .= serialize($property);
131
                            break;
132
                        }
133
                    }
134
                } else {
135
                    if (!empty($property = $this->getProperty($field))) {
136
                        $blob .= serialize($property);
137
                    }
138
                }
139
            }
140
            $this->hash = hash($algorithm, $blob);
141
        }
142
        return $this->hash;
143
    }
144
145
    /**
146
     * Save this event to the database and Canvas context
147
     *
148
     * @return void
149
     */
150
    public function save()
151
    {
152
        $db = Syncable::getDatabase();
153
        $api = Syncable::getApi();
154
155
        $select = $db->prepare(
156
            "SELECT *
157
                FROM `events`
158
                WHERE
159
                    `event_hash` = :event_hash AND
160
                    `calendar` = :calendar"
161
        );
162
        $update = $db->prepare(
163
            "UPDATE `events`
164
                SET
165
                    `synced` = :synced
166
                WHERE
167
                    `event_hash` = :event_hash AND
168
                    `calendar` = :calendar"
169
        );
170
        $insert = $db->prepare(
171
            "INSERT INTO `events`
172
                (
173
                    `calendar`,
174
                    `calendar_event[id]`,
175
                    `event_hash`,
176
                    `synced`
177
                ) VALUES (
178
                    :calendar,
179
                    :calendar_event_id,
180
                    :event_hash,
181
                    :synced
182
                )"
183
        );
184
185
        $params = [
186
            'calendar' => $this->getCalendar()->getId(),
187
            'event_hash' => $this->getHash(),
188
            'synced' => Syncable::getTimestamp()
189
        ];
190
191
        $select->execute($params);
192
        if ($select->fetch() !== false) {
193
            $update->execute($params);
194
        } else {
195
            /*
196
             * FIXME: how sure are we of this? issue:14
197
             */
198
            /*
199
             * multi-day event instance start times need to be changed to
200
             * _this_ date
201
             */
202
            $start = new DateTime(
203
                iCalUtilityFunctions::_date2strdate(
204
                    $this->getProperty('DTSTART')
205
                )
206
            );
207
            $end = new DateTime(
208
                iCalUtilityFunctions::_date2strdate(
209
                    $this->getProperty('DTEND')
210
                )
211
            );
212
            if ($this->getProperty('X-RECURRENCE')) {
213
                $start = new DateTime($this->getProperty('X-CURRENT-DTSTART')[1]);
214
                $end = new DateTime($this->getProperty('X-CURRENT-DTEND')[1]);
215
            }
216
            $start->setTimeZone(new DateTimeZone(self::LOCAL_TIMEZONE));
217
            $end->setTimeZone(new DateTimeZone(self::LOCAL_TIMEZONE));
218
219
            $calendarEvent = $api->post(
220
                "/calendar_events",
221
                [
0 ignored issues
show
Documentation introduced by
array('calendar_event' =...tProperty('LOCATION'))) is of type array<string,array<strin...ation_name\":\"*\"}>"}>, but the function expects a string|array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
222
                    'calendar_event' => [
223
                        'context_code' => $this->getCalendar()->getContextCode(),
224
                        'title' => $this->getProperty('SUMMARY'),
225
                        'description' => $this->getProperty('DESCRIPTION', 1),
226
                        'start_at' => $start->format(self::CANVAS_TIMESTAMP_FORMAT),
227
                        'end_at' => $end->format(self::CANVAS_TIMESTAMP_FORMAT),
228
                        'location_name' => $this->getProperty('LOCATION')
229
                    ]
230
                ]
231
            );
232
            $params['calendar_event_id'] = $calendarEvent['id'];
233
            $insert->execute($params);
234
        }
235
    }
236
237
    /**
238
     * Purge all events in this calendar not matching a particular timestamp
239
     *
240
     * @param string $timestamp
241
     * @param Calendar $calendar
242
     * @return void
243
     */
244
    public static function purgeUnmatched($timestamp, Calendar $calendar)
245
    {
246
        $findDeletedEvents = Syncable::getDatabase()->prepare(
247
            "SELECT *
248
                FROM `events`
249
                WHERE
250
                    `calendar` = :calendar AND
251
                    `synced` != :synced"
252
        );
253
254
        $findDeletedEvents->execute([
255
            'calendar' => $calendar->getId(),
256
            'synced' => $timestamp
257
        ]);
258
        if (($deletedEvents = $findDeletedEvents->fetchAll()) !== false) {
259
            foreach ($deletedEvents as $_event) {
260
                if (($event = Event::load($_event['id'], $calendar)) !== null) {
261
                    $event->delete();
262
                }
263
            }
264
        }
265
    }
266
267
    /**
268
     * Delete this event from the database and Canvas context
269
     *
270
     * @return void
271
     */
272
    public function delete()
273
    {
274
        $db = Syncable::getDatabase();
275
        $api = Syncable::getApi();
276
277
        $select = $db->prepare(
278
            "SELECT * FROM `events`
279
                WHERE
280
                    `event_hash` = :event_hash AND
281
                    `calendar` = :calendar"
282
        );
283
        $delete = $db->prepare(
284
            "DELETE FROM `events`
285
                WHERE
286
                    `id` = :id"
287
        );
288
        $select->execute([
289
            'event_hash' => $this->getHash(),
290
            'calendar' => $this->getCalendar()->getId()
291
        ]);
292
        if (($event = $select->fetch()) !== false) {
293
            $params = [];
294
            $params['cancel_reason'] = Syncable::getTimestamp();
295
            if ($this->getCalendar()->getContext()->getContext() == 'user') {
296
                $params['as_user_id'] = $this->getCalendar()->getContext()->getId();
297
            }
298
            try {
299
                $api->delete(
300
                    "/calendar_events/{$event['calendar_event[id]']}",
301
                    $params
302
                );
303
            } catch (Pest_Unauthorized $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
304
                /*
305
                 * Do nothing: an event was cached that had been deleted
306
                 * from Canvas -- deleting the cached event is
307
                 * inconsequential
308
                 */
309
            }
310
            $delete->execute($event['id']);
311
        }
312
    }
313
314
    /**
315
     * Load the identified event from the database
316
     *
317
     * @param  string|int $id
318
     * @param  Calendar $calendar
319
     * @return Event|null
320
     */
321
    public static function load($id, Calendar $calendar)
322
    {
323
        $select = Syncable::getDatabase()->prepare(
324
            "SELECT * FROM `events` WHERE `id` = :id AND `calendar` = :calendar"
325
        );
326
        $select->execute([
327
            'id' => $id,
328
            'calendar' => $calendar->getId()
329
        ]);
330
        if (($event = $select->fetch()) !== false) {
331
            return new Event($event['event_hash'], $calendar);
332
        }
333
        return null;
334
    }
335
}
336