This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
0 ignored issues
–
show
|
|||
2 | |||
3 | if (php_sapi_name() == 'cli') { |
||
4 | $pairingHash = $argv[1]; |
||
5 | define('IGNORE_LTI', true); |
||
6 | } |
||
7 | |||
8 | require_once 'common.inc.php'; |
||
9 | |||
10 | use smtech\ReflexiveCanvasLTI\LTI\ToolProvider; |
||
11 | use Battis\Educoder\Pest_Unauthorized; |
||
12 | use Battis\Educoder\Pest_ClientError; |
||
13 | use Battis\BootstrapSmarty\NotificationMessage; |
||
14 | |||
15 | /* |
||
16 | * TODO: it would be nice to be able to cleanly remove a synched calendar |
||
17 | */ |
||
18 | /* |
||
19 | * TODO: it would be nice to be able unschedule a scheduled sync without |
||
20 | * removing the calendar |
||
21 | */ |
||
22 | /* |
||
23 | * TODO: how about something to extirpate non-synced data (could be done right |
||
24 | * now by brute force -- once overwrite is implemented -- by marking all of the |
||
25 | * cached events as invalid and then importing the calendar and overwriting, |
||
26 | * but that's a little icky) |
||
27 | */ |
||
28 | /* |
||
29 | * TODO: right now, if a user changes a synced event in Canvas, it will never |
||
30 | * get "corrected" back to the ICS feed... we could cache the Canvas events as |
||
31 | * well as the ICS feed and do a periodic (much less frequent, given the speed |
||
32 | * of looking everything up in the API) check and re-sync modified events too |
||
33 | */ |
||
34 | |||
35 | /* |
||
36 | * pretend we have the interactive parameters if we were passed a pairing hash |
||
37 | * by sync.php |
||
38 | */ |
||
39 | if (!empty($pairingHash)) { |
||
40 | $calendars = $toolbox->mysql_query("SELECT * FROM `calendars` WHERE `id` = '$pairingHash'"); |
||
41 | $calendar = $calendars->fetch_assoc(); |
||
42 | $_REQUEST['cal'] = $calendar['ics_url']; |
||
43 | $_REQUEST['canvas_url'] = $calendar['canvas_url']; |
||
44 | } |
||
45 | |||
46 | /* do we have the vital information (an ICS feed and a URL to a canvas object)? */ |
||
47 | if (empty($_REQUEST['canvas_url'])) { |
||
48 | $_REQUEST['canvas_url'] = |
||
49 | $toolbox->config('TOOL_CANVAS_API')['url'] . |
||
50 | '/courses/' . $_SESSION[ToolProvider::class]['canvas']['course_id']; |
||
51 | } |
||
52 | if (isset($_REQUEST['cal']) && isset($_REQUEST['canvas_url'])) { |
||
53 | if ($canvasContext = $toolbox->getCanvasContext($_REQUEST['canvas_url'])) { |
||
54 | /* check ICS feed to be sure it exists */ |
||
55 | if ($toolbox->urlExists($_REQUEST['cal'])) { |
||
56 | /* look up the canvas object -- mostly to make sure that it exists! */ |
||
57 | $canvasObject = false; |
||
58 | try { |
||
59 | $canvasObject = $toolbox->api_get($canvasContext['verification_url']); |
||
60 | } catch (Exception $e) { |
||
61 | $toolbox->postMessage( |
||
62 | "Error accessing Canvas object", |
||
63 | $canvasContext['verification_url'], |
||
64 | NotificationMessage::DANGER |
||
65 | ); |
||
66 | } |
||
67 | if ($canvasObject) { |
||
68 | /* |
||
69 | * calculate the unique pairing ID of this ICS feed and canvas |
||
70 | * object, if we don't already have one |
||
71 | */ |
||
72 | if (empty($pairingHash)) { |
||
73 | $pairingHash = $toolbox->getPairingHash($_REQUEST['cal'], $canvasContext['canonical_url']); |
||
74 | } |
||
75 | |||
76 | /* |
||
77 | * lock this calendar from other processes |
||
78 | */ |
||
79 | if ($toolbox->lock($pairingHash)) { |
||
80 | $log = Log::singleton('file', __DIR__ . "/logs/$pairingHash.log"); |
||
81 | $toolbox->postMessage('Sync started', $toolbox->getSyncTimestamp(), NotificationMessage::INFO); |
||
82 | |||
83 | /* tell users that it's started and to cool their jets */ |
||
84 | if (php_sapi_name() != 'cli') { |
||
85 | $toolbox->smarty_assign( |
||
86 | 'calendarPreviewUrl', |
||
87 | $toolbox->config('TOOL_CANVAS_API')['url'] . |
||
88 | "/calendar?include_contexts={$canvasContext['context']}_{$canvasObject['id']}" |
||
89 | ); |
||
90 | $toolbox->smarty_display('import-started.tpl'); |
||
91 | } |
||
92 | |||
93 | /* parse the ICS feed */ |
||
94 | $ics = new vcalendar( |
||
95 | array( |
||
96 | 'unique_id' => $toolbox->config('TOOL_ID'), |
||
97 | 'url' => $_REQUEST['cal'] |
||
98 | ) |
||
99 | ); |
||
100 | $ics->parse(); |
||
101 | |||
102 | /* log this pairing in the database cache, if it doesn't already exist */ |
||
103 | $calendarCacheResponse = $toolbox->mysql_query(" |
||
104 | SELECT * |
||
105 | FROM `calendars` |
||
106 | WHERE |
||
107 | `id` = '$pairingHash' |
||
108 | "); |
||
109 | $calendarCache = $calendarCacheResponse->fetch_assoc(); |
||
110 | |||
111 | /* if the calendar is already cached, just update the sync timestamp */ |
||
112 | if ($calendarCache) { |
||
113 | $toolbox->mysql_query(" |
||
114 | UPDATE `calendars` |
||
115 | SET |
||
116 | `synced` = '" . $toolbox->getSyncTimestamp() . "' |
||
117 | WHERE |
||
118 | `id` = '$pairingHash' |
||
119 | "); |
||
120 | } else { |
||
121 | $toolbox->mysql_query(" |
||
122 | INSERT INTO `calendars` |
||
123 | ( |
||
124 | `id`, |
||
125 | `name`, |
||
126 | `ics_url`, |
||
127 | `canvas_url`, |
||
128 | `synced`, |
||
129 | `enable_regexp_filter`, |
||
130 | `include_regexp`, |
||
131 | `exclude_regexp` |
||
132 | ) |
||
133 | VALUES ( |
||
134 | '$pairingHash', |
||
135 | '" . $toolbox->getMySQL()->escape_string($ics->getProperty('X-WR-CALNAME')[1]) . "', |
||
136 | '{$_REQUEST['cal']}', |
||
137 | '{$canvasContext['canonical_url']}', |
||
138 | '" . $toolbox->getSyncTimestamp() . "', |
||
139 | '" . ($_REQUEST['enable_regexp_filter'] == VALUE_ENABLE_REGEXP_FILTER) . "', |
||
140 | " . ($_REQUEST['enable_regexp_filter'] == VALUE_ENABLE_REGEXP_FILTER ? |
||
141 | "'" . $toolbox->getMySQL()->escape_string($_REQUEST['include_regexp']) . "'" : 'NULL' |
||
142 | ) . ", |
||
143 | " . ($_REQUEST['enable_regexp_filter'] == VALUE_ENABLE_REGEXP_FILTER ? |
||
144 | "'" . $toolbox->getMySQL()->escape_string($_REQUEST['exclude_regexp']) . "'" : 'NULL' |
||
145 | ) . " |
||
146 | ) |
||
147 | "); |
||
148 | } |
||
149 | |||
150 | /* refresh calendar information from cache database */ |
||
151 | $calendarCacheResponse = $toolbox->mysql_query(" |
||
152 | SELECT * |
||
153 | FROM `calendars` |
||
154 | WHERE |
||
155 | `id` = '$pairingHash' |
||
156 | "); |
||
157 | $calendarCache = $calendarCacheResponse->fetch_assoc(); |
||
158 | |||
159 | /* |
||
160 | * walk through $master_array and update the Canvas calendar to |
||
161 | * match the ICS feed, caching changes in the database */ |
||
162 | /* |
||
163 | * TODO: would it be worth the performance improvement to just |
||
164 | * process things from today's date forward? (i.e. ignore old |
||
165 | * items, even if they've changed...) |
||
166 | */ |
||
167 | /* |
||
168 | * TODO: the best window for syncing would be the term of the |
||
169 | * course in question, right? |
||
170 | */ |
||
171 | /* |
||
172 | * TODO: Arbitrarily selecting events in for a year on either |
||
173 | * side of today's date, probably a better system? |
||
174 | */ |
||
175 | foreach ($ics->selectComponents( |
||
0 ignored issues
–
show
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.
![]() |
|||
176 | date('Y') - 1, // startYear |
||
177 | date('m'), // startMonth |
||
178 | date('d'), // startDay |
||
179 | date('Y') + 1, // endYEar |
||
180 | date('m'), // endMonth |
||
181 | date('d'), // endDay |
||
182 | 'vevent', // cType |
||
183 | false, // flat |
||
184 | true, // any |
||
185 | true // split |
||
186 | ) as $year) { |
||
187 | foreach ($year as $month => $days) { |
||
188 | foreach ($days as $day => $events) { |
||
189 | foreach ($events as $i => $event) { |
||
190 | /* does this event already exist in Canvas? */ |
||
191 | $eventHash = $toolbox->getEventHash($event); |
||
192 | |||
193 | /* if the event should be included... */ |
||
194 | if ($toolbox->filterEvent($event, $calendarCache)) { |
||
195 | /* have we cached this event already? */ |
||
196 | $eventCacheResponse = $toolbox->mysql_query(" |
||
197 | SELECT * |
||
198 | FROM `events` |
||
199 | WHERE |
||
200 | `calendar` = '{$calendarCache['id']}' AND |
||
201 | `event_hash` = '$eventHash' |
||
202 | "); |
||
203 | |||
204 | |||
205 | /* if we already have the event cached in its current form, just update |
||
206 | the timestamp */ |
||
207 | $eventCache = $eventCacheResponse->fetch_assoc(); |
||
208 | if ($eventCache) { |
||
209 | $toolbox->mysql_query(" |
||
210 | UPDATE `events` |
||
211 | SET |
||
212 | `synced` = '" . $toolbox->getSyncTimestamp() . "' |
||
213 | WHERE |
||
214 | `id` = '{$eventCache['id']}' |
||
215 | "); |
||
216 | |||
217 | /* otherwise, add this new event and cache it */ |
||
218 | } else { |
||
219 | /* multi-day event instance start times need to be changed to _this_ date */ |
||
220 | $start = new DateTime( |
||
221 | iCalUtilityFunctions::_date2strdate($event->getProperty('DTSTART')) |
||
222 | ); |
||
223 | $end = new DateTime( |
||
224 | iCalUtilityFunctions::_date2strdate($event->getProperty('DTEND')) |
||
225 | ); |
||
226 | if ($event->getProperty('X-RECURRENCE')) { |
||
227 | $start = new DateTime($event->getProperty('X-CURRENT-DTSTART')[1]); |
||
228 | $end = new DateTime($event->getProperty('X-CURRENT-DTEND')[1]); |
||
229 | } |
||
230 | $start->setTimeZone(new DateTimeZone(LOCAL_TIMEZONE)); |
||
231 | $end->setTimeZone(new DateTimeZone(LOCAL_TIMEZONE)); |
||
232 | |||
233 | try { |
||
234 | $calendarEvent = $toolbox->api_post( |
||
235 | "/calendar_events", |
||
236 | array( |
||
237 | 'calendar_event[context_code]' => "{$canvasContext['context']}_{$canvasObject['id']}", |
||
238 | 'calendar_event[title]' => preg_replace('%^([^\]]+)(\s*\[[^\]]+\]\s*)+$%', '\\1', strip_tags($event->getProperty('SUMMARY'))), |
||
239 | 'calendar_event[description]' => \Michelf\Markdown::defaultTransform(str_replace('\n', "\n\n", $event->getProperty('DESCRIPTION', 1))), |
||
240 | 'calendar_event[start_at]' => $start->format(CANVAS_TIMESTAMP_FORMAT), |
||
241 | 'calendar_event[end_at]' => $end->format(CANVAS_TIMESTAMP_FORMAT), |
||
242 | 'calendar_event[location_name]' => $event->getProperty('LOCATION') |
||
243 | ) |
||
244 | ); |
||
245 | $toolbox->mysql_query(" |
||
246 | INSERT INTO `events` |
||
247 | ( |
||
248 | `calendar`, |
||
249 | `calendar_event[id]`, |
||
250 | `event_hash`, |
||
251 | `synced` |
||
252 | ) |
||
253 | VALUES ( |
||
254 | '{$calendarCache['id']}', |
||
255 | '{$calendarEvent['id']}', |
||
256 | '$eventHash', |
||
257 | '" . $toolbox->getSyncTimestamp() . "' |
||
258 | ) |
||
259 | "); |
||
260 | } catch (Exception $e) { |
||
261 | $toolbox->postMessage( |
||
262 | 'Error creating calendar event', |
||
263 | $eventHash, |
||
264 | NotificationMessage::ERROR |
||
0 ignored issues
–
show
The constant
Battis\BootstrapSmarty\NotificationMessage::ERROR has been deprecated with message: Use `DANGER` instead for consistency with Bootstrap
This class constant has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead. ![]() |
|||
265 | ); |
||
266 | } |
||
267 | } |
||
268 | } |
||
269 | } |
||
270 | } |
||
271 | } |
||
272 | } |
||
273 | |||
274 | /* clean out previously synced events that are no longer correct */ |
||
275 | $deletedEventsResponse = $toolbox->mysql_query(" |
||
276 | SELECT * FROM `events` |
||
277 | WHERE |
||
278 | `calendar` = '{$calendarCache['id']}' AND |
||
279 | `synced` != '" . $toolbox->getSyncTimestamp() . "' |
||
280 | "); |
||
281 | while ($deletedEventCache = $deletedEventsResponse->fetch_assoc()) { |
||
282 | try { |
||
283 | $deletedEvent = $toolbox->api_delete( |
||
284 | "calendar_events/{$deletedEventCache['calendar_event[id]']}", |
||
285 | array( |
||
286 | 'cancel_reason' => $toolbox->getSyncTimestamp(), |
||
287 | 'as_user_id' => ($canvasContext['context'] == 'user' ? $canvasObject['id'] : '') // TODO: this feels skeevy -- like the empty string will break |
||
288 | ) |
||
289 | ); |
||
290 | } catch (Pest_Unauthorized $e) { |
||
291 | /* if the event has been deleted in Canvas, we'll get an error when |
||
292 | we try to delete it a second time. We still need to delete it from |
||
293 | our cache database, however */ |
||
294 | $toolbox->postMessage( |
||
295 | 'Cache out-of-sync', |
||
296 | "calendar_event[{$deletedEventCache['calendar_event[id]']}] no longer exists and will be purged from cache.", |
||
297 | NotificationMessage::INFO |
||
298 | ); |
||
299 | } catch (Pest_ClientError $e) { |
||
300 | $toolbox->postMessage( |
||
301 | 'API Client Error', |
||
302 | '<pre>' . print_r(array( |
||
303 | 'Status' => $PEST->lastStatus(), |
||
304 | 'Error' => $PEST->lastBody(), |
||
305 | 'Verb' => $verb, |
||
306 | 'URL' => $url, |
||
307 | 'Data' => $data |
||
308 | ), false) . '</pre>', |
||
309 | NotificationMessage::ERROR |
||
0 ignored issues
–
show
The constant
Battis\BootstrapSmarty\NotificationMessage::ERROR has been deprecated with message: Use `DANGER` instead for consistency with Bootstrap
This class constant has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead. ![]() |
|||
310 | ); |
||
311 | if (php_sapi_name() != 'cli') { |
||
312 | $toolbox->smarty_display('page.tpl'); |
||
313 | } |
||
314 | exit; |
||
315 | } |
||
316 | $toolbox->mysql_query(" |
||
317 | DELETE FROM `events` |
||
318 | WHERE |
||
319 | `id` = '{$deletedEventCache['id']}' |
||
320 | "); |
||
321 | } |
||
322 | |||
323 | /* if this was a scheduled import (i.e. a sync), update that schedule */ |
||
324 | if (isset($_REQUEST['schedule'])) { |
||
325 | $toolbox->mysql_query(" |
||
326 | UPDATE `schedules` |
||
327 | SET |
||
328 | `synced` = '" . $toolbox->getSyncTimestamp() . "' |
||
329 | WHERE |
||
330 | `id` = '{$_REQUEST['schedule']}' |
||
331 | "); |
||
332 | } |
||
333 | /* are we setting up a regular synchronization? */ |
||
334 | if (isset($_REQUEST['sync']) && $_REQUEST['sync'] != SCHEDULE_ONCE) { |
||
335 | // FIXME CRON SYNC SETUP GOES HERE |
||
336 | |||
337 | /* add to the cache database schedule, replacing any schedules for this |
||
338 | calendar that are already there */ |
||
339 | $schedulesResponse = $toolbox->mysql_query(" |
||
340 | SELECT * |
||
341 | FROM `schedules` |
||
342 | WHERE |
||
343 | `calendar` = '{$calendarCache['id']}' |
||
344 | "); |
||
345 | |||
346 | if ($schedule = $schedulesResponse->fetch_assoc()) { |
||
347 | /* only need to worry if the cached schedule is different from the |
||
348 | new one we just set */ |
||
349 | if ($shellArguments[INDEX_SCHEDULE] != $schedule['schedule']) { |
||
350 | /* was this the last schedule to require this trigger? */ |
||
351 | $schedulesResponse = $toolbox->mysql_query(" |
||
352 | SELECT * |
||
353 | FROM `schedules` |
||
354 | WHERE |
||
355 | `calendar` != '{$calendarCache['id']}' AND |
||
356 | `schedule` == '{$schedule['schedule']}' |
||
357 | "); |
||
358 | /* we're the last one, delete it from crontab */ |
||
359 | if ($schedulesResponse->num_rows == 0) { |
||
360 | $crontabs = preg_replace("%^.*{$schedule['schedule']}.*" . PHP_EOL . '%', '', shell_exec('crontab -l')); |
||
361 | $filename = md5($toolbox->getSyncTimestamp()) . '.txt'; |
||
362 | file_put_contents("/tmp/$filename", $crontabs); |
||
363 | shell_exec("crontab /tmp/$filename"); |
||
364 | $toolbox->postMessage('Unused schedule', "removed schedule '{$schedule['schedule']}' from crontab", NotificationMessage::INFO); |
||
365 | } |
||
366 | |||
367 | $toolbox->mysql_query(" |
||
368 | UPDATE `schedules` |
||
369 | SET |
||
370 | `schedule` = '" . $shellArguments[INDEX_SCHEDULE] . "', |
||
371 | `synced` = '" . $toolbox->getSyncTimestamp() . "' |
||
372 | WHERE |
||
373 | `calendar` = '{$calendarCache['id']}' |
||
374 | "); |
||
375 | } |
||
376 | } else { |
||
377 | $toolbox->mysql_query(" |
||
378 | INSERT INTO `schedules` |
||
379 | ( |
||
380 | `calendar`, |
||
381 | `schedule`, |
||
382 | `synced` |
||
383 | ) |
||
384 | VALUES ( |
||
385 | '{$calendarCache['id']}', |
||
386 | '" . $_REQUEST['sync'] . "', |
||
387 | '" . $toolbox->getSyncTimestamp() . "' |
||
388 | ) |
||
389 | "); |
||
390 | } |
||
391 | } |
||
392 | |||
393 | /* if we're ovewriting data (for example, if this is a recurring sync, we |
||
394 | need to remove the events that were _not_ synced this in this round */ |
||
395 | if (isset($_REQUEST['overwrite']) && $_REQUEST['overwrite'] == VALUE_OVERWRITE_CANVAS_CALENDAR) { |
||
0 ignored issues
–
show
This
if statement is empty and can be removed.
This check looks for the bodies of These if (rand(1, 6) > 3) {
//print "Check failed";
} else {
print "Check succeeded";
}
could be turned into if (rand(1, 6) <= 3) {
print "Check succeeded";
}
This is much more concise to read. ![]() |
|||
396 | // TODO: actually deal with this |
||
397 | } |
||
398 | |||
399 | // TODO: deal with messaging based on context |
||
400 | |||
401 | $toolbox->postMessage('Finished sync', $toolbox->getSyncTimestamp(), NotificationMessage::INFO); |
||
402 | $toolbox->unlock($pairingHash); |
||
403 | exit; |
||
404 | } |
||
405 | } else { |
||
406 | $toolbox->postMessage( |
||
407 | 'Canvas Object Not Found', |
||
408 | 'The object whose URL you submitted could not be found.<pre>' . print_r(array( |
||
409 | 'Canvas URL' => $_REQUEST['canvas_url'], |
||
410 | 'Canvas Context' => $canvasContext, |
||
411 | 'Canvas Object' => $canvasObject |
||
412 | ), false) . '</pre>', |
||
413 | NotificationMessage::ERROR |
||
0 ignored issues
–
show
The constant
Battis\BootstrapSmarty\NotificationMessage::ERROR has been deprecated with message: Use `DANGER` instead for consistency with Bootstrap
This class constant has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead. ![]() |
|||
414 | ); |
||
415 | } |
||
416 | } else { |
||
417 | $toolbox->postMessage( |
||
418 | 'ICS feed Not Found', |
||
419 | 'The calendar whose URL you submitted could not be found.<pre>' . $_REQUEST['cal'] . '</pre>', |
||
420 | NotificationMessage::ERROR |
||
0 ignored issues
–
show
The constant
Battis\BootstrapSmarty\NotificationMessage::ERROR has been deprecated with message: Use `DANGER` instead for consistency with Bootstrap
This class constant has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead. ![]() |
|||
421 | ); |
||
422 | } |
||
423 | } else { |
||
424 | $toolbox->postMessage( |
||
425 | 'Invalid Canvas URL', |
||
426 | 'The Canvas URL you submitted could not be parsed.<pre>' . $_REQUEST['canvas_url'] . '</pre>', |
||
427 | NotificationMessage::ERROR |
||
0 ignored issues
–
show
The constant
Battis\BootstrapSmarty\NotificationMessage::ERROR has been deprecated with message: Use `DANGER` instead for consistency with Bootstrap
This class constant has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead. ![]() |
|||
428 | ); |
||
429 | if (php_sapi_name() != 'cli') { |
||
430 | $toolbox->smarty_display('page.tpl'); |
||
431 | } |
||
432 | exit; |
||
433 | } |
||
434 | } else { |
||
435 | if (php_sapi_name() != 'cli') { |
||
436 | $toolbox->smarty_display('import.tpl'); |
||
437 | } |
||
438 | } |
||
439 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.