Completed
Push — develop ( 19c595...0fe9d6 )
by Seth
06:00
created

Calendar::save()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 59
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 59
rs 9.597
cc 2
eloc 22
nc 2
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace smtech\CanvasICSSync\SyncIntoCanvas;
4
5
use DateTime;
6
use vcalendar;
7
use iCalUtilityFunctions;
8
use Battis\DataUtilities;
9
use Michelf\Markdown;
10
11
class Calendar
12
{
13
    /**
14
     * Canvas calendar context
15
     * @var CalendarContext
16
     */
17
    protected $context;
18
19
    /**
20
     * ICS or webcal feed URL
21
     * @var string
22
     */
23
    protected $feedUrl;
24
25
    /**
26
     * Name of this calendar (extracted from feed)
27
     * @var string
28
     */
29
    protected $name;
30
31
    /**
32
     * Filter for events in this calendar
33
     * @var Filter
34
     */
35
    protected $filter;
36
37
    /**
38
     * Construct a Calendar object
39
     *
40
     * @param string $canvasUrl URL of a Canvas calendar context
41
     * @param string $feedUrl URL of a webcal or ICS calendar feed
42
     * @param boolean $enableFilter (Optional, default `false`)
43
     * @param string $include (Optional) Regular expression to select events
44
     *     for inclusion in the calendar sync
45
     * @param string $exclude (Optional) Regular expression to select events
46
     *     for exclusion from the calendar sync
47
     */
48
    public function __construct($canvasUrl, $feedUrl, $enableFilter = false, $include = null, $exclude = null)
49
    {
50
        $this->setContext(new CalendarContext($canvasUrl));
51
        $this->setFeed($feedUrl);
0 ignored issues
show
Bug introduced by
The method setFeed() does not exist on smtech\CanvasICSSync\SyncIntoCanvas\Calendar. Did you maybe mean setFeedUrl()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
52
        $this->setFilter(new Filter(
53
            $enableFilter,
54
            $include,
55
            $exclude
56
        ));
57
    }
58
59
    /**
60
     * Set the Canvas calendar context
61
     *
62
     * @param CalendarContext $context
63
     * @throws Exception If `$context` is null
64
     */
65
    public function setContext(CalendarContext $context)
66
    {
67
        if (!empty($context)) {
68
            $this->context = $context;
69
        } else {
70
            throw new Exception(
71
                'Context cannot be null'
72
            );
73
        }
74
    }
75
76
    /**
77
     * Get the Canvas calendar context
78
     *
79
     * @return CalendarContext
80
     */
81
    public function getContext()
82
    {
83
        return $this->context;
84
    }
85
86
    /**
87
     * Set the webcal or ICS feed URl for this calendar
88
     *
89
     * @param string $feedUrl
90
     * @throws Exception If `$feedUrl` is not a valid URL
91
     */
92
    public function setFeedUrl($feedUrl)
93
    {
94
        if (!empty($feedUrl)) {
95
            /* crude test to see if the feed is a valid URL */
96
            $handle = fopen($feedUrl, 'r');
97
            if ($handle !== false) {
98
                $this->feedUrl = $feedUrl;
99
            }
100
        }
101
        throw new Exception(
102
            'Feed must be a valid URL'
103
        );
104
    }
105
106
    /**
107
     * Get the feed URL for this calendar
108
     *
109
     * @return string
110
     */
111
    public function getFeedUrl()
112
    {
113
        return $this->feedUrl;
114
    }
115
116
    /**
117
     * Set the name of the calendar
118
     *
119
     * @param string $name
120
     */
121
    public function setName($name)
122
    {
123
        $this->name = (string) $name;
124
    }
125
126
    /**
127
     * Get the name of the calendar
128
     *
129
     * @return string
130
     */
131
    public function getName()
132
    {
133
        return $this->name;
134
    }
135
136
    /**
137
     * Set the regular expression filter for this calendar
138
     *
139
     * @param Filter $filter
140
     */
141
    public function setFilter(Filter $filter)
142
    {
143
        $this->filter = $filter;
144
    }
145
146
    /**
147
     * Get the regular expression filter for this calendar
148
     *
149
     * @return Filter
150
     */
151
    public function getFilter()
152
    {
153
        return $this->filter;
154
    }
155
156
    /**
157
     * Generate a unique ID to identify this particular pairing of ICS feed and
158
     * Canvas calendar
159
     **/
160
    protected function getPairingHash()
161
    {
162
        return md5($this->getContext()->getCanonicalUrl() . $this->getFeedUrl());
163
    }
164
165
    public function save()
166
    {
167
        $db = static::getDatabase();
0 ignored issues
show
Bug introduced by
The method getDatabase() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
168
        $find = $db->prepare(
169
            "SELECT * FROM `calendars` WHERE `id` = :id"
170
        );
171
        $update = $db->prepare(
172
            "UPDATE `calendars`
173
                SET
174
                    `name` = :name,
175
                    `canvas_url` = :canvas_url,
176
                    `ics_url` = :ics_url,
177
                    `synced` = :synced,
178
                    `enable_regex_filter` = :enable_regex_filter,
179
                    `include_regexp` = :include_regexp,
180
                    `exclude_regexp` = :exclude_regexp
181
                WHERE
182
                `id` = :id"
183
        );
184
        $insert = $db->prepare(
185
            "INSERT INTO `calendars`
186
                (
187
                    `id`,
188
                    `name`,
189
                    `canvas_url`,
190
                    `ics_url`,
191
                    `synced`,
192
                    `enable_regex_filter`,
193
                    `include_regexp`,
194
                    `exclude_regexp`
195
                ) VALUES (
196
                    :id,
197
                    :name,
198
                    :canvas_url,
199
                    :ics_url,
200
                    :synced
201
                    :enable_regex_filter,
202
                    :include_regexp,
203
                    :exclude_regexp
204
                )"
205
        );
206
        $params = [
207
            'id' => $this->getPairingHash(),
208
            'name' => $this->getName(),
209
            'canvas_url' => $this->getContext()->getCanonicalUrl(),
210
            'ics_url' => $this->getFeedUrl(),
211
            'synced' => static::getSyncTimestamp(),
0 ignored issues
show
Bug introduced by
The method getSyncTimestamp() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
212
            'enable_regex_filter' => $this->getFilter()->isEnabled(),
213
            'include_regexp' => $this->getFilter()->getIncludeExpression(),
214
            'exclude_regexp' => $this->getFilter()->getExcludeExpression()
215
        ];
216
217
        $find->execute($params);
218
        if ($find->fetch() !== false) {
219
            $update->execute($params);
220
        } else {
221
            $insert->execute($params);
222
        }
223
    }
224
225
    /**
226
     * Load a Calendar from the database
227
     *
228
     * @param int $id
229
     * @return Calendar
230
     */
231
    public static function load($id)
232
    {
233
        $find = static::getDatabase()->prepare(
0 ignored issues
show
Bug introduced by
The method getDatabase() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
234
            "SELECT * FROM `calendars` WHERE `id` = :id"
235
        );
236
        $find->execute($id);
237
        if (($calendar = $find->fetch()) !== false) {
238
            return new Calendar(
239
                $calendar['canvas_url'],
240
                $calendar['ics_url'],
241
                $calendar['enable_regex_filter'],
242
                $calendar['include_regexp'],
243
                $calendar['exclude_regexp']
244
            );
245
        }
246
        return null;
247
    }
248
249
    public function import(Log $log)
250
    {
251
        $db = static::getDatabase();
0 ignored issues
show
Bug introduced by
The method getDatabase() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
252
        $api = static::getApi();
0 ignored issues
show
Bug introduced by
The method getApi() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
253
254
        /*
255
         * This will throw an exception if it's not found
256
         */
257
        $canvasObject = $api->get($this->getContext()->getVerificationUrl());
258
259
        if (!DataUtilities::URLexists($this>getFeedUrl())) {
0 ignored issues
show
Documentation introduced by
$this > getFeedUrl() is of type boolean, but the function expects a 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...
260
            throw new Exception(
261
                "Cannot sync calendars with a valid calendar feed"
262
            );
263
        }
264
265
        if ($log) {
266
            $log->log(static::getSyncTimestamp() . ' sync started', PEAR_LOG_INFO);
0 ignored issues
show
Bug introduced by
The method getSyncTimestamp() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
267
        }
268
        $this->save();
269
270
        $ics = new vcalendar([
271
            'unique_id' => __FILE__,
272
            'url' => $this->getFeedUrl()
273
        ]);
274
        $ics->parse();
275
276
        /*
277
         * TODO: would it be worth the performance improvement to just process
278
         *     things from today's date forward? (i.e. ignore old items, even
279
         *     if they've changed...)
280
         */
281
        /*
282
         * TODO:0 the best window for syncing would be the term of the course
283
         *     in question, right? issue:12
284
         */
285
        /*
286
         * TODO:0 Arbitrarily selecting events in for a year on either side of
287
         *     today's date, probably a better system? issue:12
288
         */
289
        foreach ($ics->selectComponents(
0 ignored issues
show
Bug introduced by
The expression $ics->selectComponents(d...nt', false, true, true) of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
290
            date('Y')-1, // startYear
291
            date('m'), // startMonth
292
            date('d'), // startDay
293
            date('Y')+1, // endYEar
294
            date('m'), // endMonth
295
            date('d'), // endDay
296
            'vevent', // cType
297
            false, // flat
298
            true, // any
299
            true // split
300
        ) as $year) {
301
            foreach ($year as $month => $days) {
302
                foreach ($days as $day => $events) {
303
                    foreach ($events as $i => $_event) {
304
                        $event = new Event($_event, $this->getPairingHash());
305
                        if ($this->getFilter()->filterEvent($event)) {
0 ignored issues
show
Bug introduced by
The method filterEvent() does not exist on smtech\CanvasICSSync\SyncIntoCanvas\Filter. Did you maybe mean filter()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
306
                            $eventCache = Event::load($this->getPairingHash(), $eventHash);
0 ignored issues
show
Bug introduced by
The variable $eventHash does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
307
                            if (empty($eventCache)) {
308
                                /* multi-day event instance start times need to be changed to _this_ date */
309
                                $start = new DateTime(
310
                                    iCalUtilityFunctions::_date2strdate(
311
                                        $event->getProperty('DTSTART')
312
                                    )
313
                                );
314
                                $end = new DateTime(
315
                                    iCalUtilityFunctions::_date2strdate(
316
                                        $event->getProperty('DTEND')
317
                                    )
318
                                );
319
                                if ($event->getProperty('X-RECURRENCE')) {
320
                                    $start = new DateTime($event->getProperty('X-CURRENT-DTSTART')[1]);
321
                                    $end = new DateTime($event->getProperty('X-CURRENT-DTEND')[1]);
322
                                }
323
                                $start->setTimeZone(new DateTimeZone(Constants::LOCAL_TIMEZONE));
324
                                $end->setTimeZone(new DateTimeZone(Constants::LOCAL_TIMEZONE));
325
326
                                try {
327
                                    $calendarEvent = $api->post(
0 ignored issues
show
Unused Code introduced by
$calendarEvent 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...
328
                                        "/calendar_events",
329
                                        [
330
                                            'calendar_event' => [
331
                                                'context_code' => $this->getContext() . "_{$canvasObject['id']}",
332
                                                'title' => preg_replace(
333
                                                    '%^([^\]]+)(\s*\[[^\]]+\]\s*)+$%',
334
                                                    '\\1',
335
                                                    strip_tags($event->getProperty('SUMMARY'))
336
                                                ),
337
                                                'description' => Markdown::defaultTransform(str_replace(
338
                                                    '\n',
339
                                                    "\n\n",
340
                                                    $event->getProperty('DESCRIPTION', 1)
341
                                                )),
342
                                                'start_at' => $start->format(Constants::CANVAS_TIMESTAMP_FORMAT),
343
                                                'end_at' => $end->format(Constants::CANVAS_TIMESTAMP_FORMAT),
344
                                                'location_name' => $event->getProperty('LOCATION')
345
                                            ]
346
                                        ]
347
                                    );
348
                                } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class smtech\CanvasICSSync\SyncIntoCanvas\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
349
                                    if ($log) {
350
                                        $log->log($e->getMessage(), PEAR_LOG_ERR);
351
                                    } else {
352
                                        throw $e;
353
                                    }
354
                                }
355
356
                                $eventCache = new Event($this->getPairingHash(), $canvasObject['id'], $eventHash);
357
                            }
358
                            $eventCache->save();
359
                        }
360
                    }
361
                }
362
            }
363
        }
364
365
        $findDeletedEvents = $db->prepare(
366
            "SELECT *
367
                FROM `events`
368
                WHERE
369
                    `calendar` = :calendar AND
370
                    `synced` != :synced
371
            "
372
        );
373
        $deleteCachedEvent = $db->prepare(
374
            "DELETE FROM `events` WHERE `id` = :id"
375
        );
376
        $findDeletedEvents->execute([
377
            'calendar' => $this->getPairingHash(),
378
            'synced' => static::getSyncTimestamp()
0 ignored issues
show
Bug introduced by
The method getSyncTimestamp() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
379
        ]);
380
        if (($deletedEvents = $findDeletedEvents->fetchAll()) !== false) {
381
            foreach ($deletedEvents as $eventCache) {
382
                try {
383
                    $api->delete(
384
                        "/calendar_events/{$eventCache['calendar_event[id]']}",
385
                        [
386
                            'cancel_reason' => getSyncTimestamp(),
387
                            /*
388
                             * TODO: this feels skeevy -- like the empty string
389
                             *     will break
390
                             */
391
                            'as_user_id' => ($this->getContext()->getContext() == 'user' ? $canvasObject['id'] : '')
392
                        ]
393
                    );
394
                    $deleteCachedEvent->execute($eventCache['id']);
395
                } catch (Pest_Unauthorized $e) {
0 ignored issues
show
Bug introduced by
The class smtech\CanvasICSSync\Syn...anvas\Pest_Unauthorized does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
396
                    if ($log) {
397
                        $log->log(
398
                            "calendar_event[{$eventCache['calendar_event[id]']}] no longer exists and will be purged from cache.",
399
                            PEAR_LOG_WARN
400
                        );
401
                    } else {
402
                        throw $e;
403
                    }
404
                } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class smtech\CanvasICSSync\SyncIntoCanvas\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
405
                    if ($log) {
406
                        $log->log($e->getMessage(), PEAR_LOG_ERR);
407
                    } else {
408
                        throw $e;
409
                    }
410
                }
411
            }
412
        }
413
414
        if ($log) {
415
            $log->log(static::getSyncTimestamp() . ' sync finished', PEAR_LOG_INFO);
0 ignored issues
show
Bug introduced by
The method getSyncTimestamp() does not seem to exist on object<smtech\CanvasICSS...yncIntoCanvas\Calendar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
416
        }
417
    }
418
}
419