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( |
||
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 |
||
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 |
||
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) { |
||
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 |
||
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 |
||
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 |
||
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.