1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
if (php_sapi_name() == 'cli') { |
4
|
|
|
// TODO there is a more streamlined way of doing this that escapes me just this second |
5
|
|
|
$_REQUEST['cal'] = $argv[1]; |
6
|
|
|
$_REQUEST['canvas_url'] = $argv[2]; |
7
|
|
|
$_REQUEST['schedule'] = $argv[3]; |
8
|
|
|
|
9
|
|
|
define ('IGNORE_LTI', true); |
10
|
|
|
} |
11
|
|
|
|
12
|
|
|
require_once 'common.inc.php'; |
13
|
|
|
|
14
|
|
|
use smtech\CanvasPest\CanvasPest; |
15
|
|
|
use Battis\BootstrapSmarty\NotificationMessage; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* Check to see if a URL exists |
19
|
|
|
**/ |
20
|
|
|
function urlExists($url) { |
21
|
|
|
$handle = fopen($url, 'r'); |
22
|
|
|
return $handle !== false; |
23
|
|
|
} |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* compute the calendar context for the canvas object based on its URL |
27
|
|
|
**/ |
28
|
|
|
function getCanvasContext($canvasUrl) { |
|
|
|
|
29
|
|
|
global $metadata; |
|
|
|
|
30
|
|
|
|
31
|
|
|
// TODO: accept calendar2?contexts links too (they would be an intuitively obvious link to use, after all) |
32
|
|
|
// FIXME: users aren't working |
33
|
|
|
// TODO: it would probably be better to look up users by email address than URL |
34
|
|
|
/* get the context (user, course or group) for the canvas URL */ |
35
|
|
|
$canvasContext = array(); |
36
|
|
|
if (preg_match('%(https?://)?(' . parse_url($metadata['CANVAS_INSTANCE_URL'], PHP_URL_HOST) . '/((about/(\d+))|(courses/(\d+)(/groups/(\d+))?)|(accounts/\d+/groups/(\d+))))%', $_REQUEST['canvas_url'], $matches)) { |
37
|
|
|
$canvasContext['canonical_url'] = "https://{$matches[2]}"; // https://stmarksschool.instructure.com/courses/953 |
38
|
|
|
|
39
|
|
|
// course or account groups |
40
|
|
|
if (isset($matches[9]) || isset($matches[11])) { |
41
|
|
|
$canvasContext['context'] = 'group'; // used to for context_code in events |
42
|
|
|
$canvasContext['id'] = ($matches[9] > $matches[11] ? $matches[9] : $matches[11]); |
43
|
|
|
$canvasContext['verification_url'] = "groups/{$canvasContext['id']}"; // used once to look up the object to be sure it really exists |
44
|
|
|
|
45
|
|
|
// courses |
46
|
|
|
} elseif (isset($matches[7])) { |
47
|
|
|
$canvasContext['context'] = 'course'; |
48
|
|
|
$canvasContext['id'] = $matches[7]; |
49
|
|
|
$canvasContext['verification_url'] = "courses/{$canvasContext['id']}"; |
50
|
|
|
|
51
|
|
|
// users |
52
|
|
|
} elseif (isset($matches[5])) { |
53
|
|
|
$canvasContext['context'] = 'user'; |
54
|
|
|
$canvasContext['id'] = $matches[5]; |
55
|
|
|
$canvasContext['verification_url'] = "users/{$canvasContext['id']}/profile"; |
56
|
|
|
|
57
|
|
|
// we're somewhere where we don't know where we are |
58
|
|
|
} else { |
59
|
|
|
return false; |
60
|
|
|
} |
61
|
|
|
return $canvasContext; |
62
|
|
|
} |
63
|
|
|
return false; |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Filter and clean event data before posting to Canvas |
68
|
|
|
* |
69
|
|
|
* This must happen AFTER the event hash has been calculated! |
70
|
|
|
**/ |
71
|
|
|
function filterEvent($event, $calendarCache) { |
72
|
|
|
|
73
|
|
|
return ( |
74
|
|
|
// include this event if filtering is off... |
75
|
|
|
$calendarCache['enable_regexp_filter'] == false || |
76
|
|
|
( |
77
|
|
|
( |
78
|
|
|
( // if filtering is on, and there's an include pattern test that pattern... |
79
|
|
|
!empty($calendarCache['include_regexp']) && |
80
|
|
|
preg_match("%{$calendarCache['include_regexp']}%", $event->getProperty('SUMMARY')) |
81
|
|
|
) |
82
|
|
|
) && |
83
|
|
|
!( // if there is an exclude pattern, make sure that this event is NOT excluded |
84
|
|
|
!empty($calendarCache['exclude_regexp']) && |
85
|
|
|
preg_match("%{$calendarCache['exclude_regexp']}%", $event->getProperty('SUMMARY')) |
86
|
|
|
) |
87
|
|
|
) |
88
|
|
|
); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
// TODO: it would be nice to be able to cleanly remove a synched calendar |
92
|
|
|
// TODO: it would be nice to be able unschedule a scheduled sync without removing the calendar |
93
|
|
|
// TODO: how about something to extirpate non-synced data (could be done right now by brute force -- once overwrite is implemented -- by marking all of the cached events as invalid and then importing the calendar and overwriting, but that's a little icky) |
94
|
|
|
// TODO: right now, if a user changes a synced event in Canvas, it will never get "corrected" back to the ICS feed... we could cache the Canvas events as well as the ICS feed and do a periodic (much less frequent, given the speed of looking everything up in the API) check and re-sync modified events too |
95
|
|
|
|
96
|
|
|
/* do we have the vital information (an ICS feed and a URL to a canvas |
97
|
|
|
object)? */ |
98
|
|
|
if (isset($_REQUEST['cal']) && isset($_REQUEST['canvas_url'])) { |
99
|
|
|
|
100
|
|
|
if ($canvasContext = getCanvasContext($_REQUEST['canvas_url'])) { |
101
|
|
|
/* check ICS feed to be sure it exists */ |
102
|
|
|
if(urlExists($_REQUEST['cal'])) { |
103
|
|
|
/* look up the canvas object -- mostly to make sure that it exists! */ |
104
|
|
|
if ($canvasObject = $api->get($canvasContext['verification_url'])) { |
105
|
|
|
|
106
|
|
|
/* calculate the unique pairing ID of this ICS feed and canvas object */ |
107
|
|
|
$pairingHash = getPairingHash($_REQUEST['cal'], $canvasContext['canonical_url']); |
108
|
|
|
$log = Log::singleton('file', __DIR__ . "/logs/$pairingHash.log"); |
109
|
|
|
postMessage('Sync started', getSyncTimestamp(), NotificationMessage::INFO); |
110
|
|
|
|
111
|
|
|
/* tell users that it's started and to cool their jets */ |
112
|
|
|
if (php_sapi_name() != 'cli') { |
113
|
|
|
$smarty->assign('content', ' |
114
|
|
|
<h3>Calendar Import Started</h3> |
115
|
|
|
<p>The calendar import that you requested has begun. You may leave this page at anytime. You can see the progress of the import by visiting <a target="_blank" href="https://' . parse_url($metadata['CANVAS_INSTANCE_URL'], PHP_URL_HOST) . "/calendar?include_contexts={$canvasContext['context']}_{$canvasObject['id']}\">this calendar</a> in Canvas.</p>" |
116
|
|
|
); |
117
|
|
|
$smarty->display('page.tpl'); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/* parse the ICS feed */ |
121
|
|
|
$ics = new vcalendar( |
122
|
|
|
array( |
123
|
|
|
'unique_id' => $metadata['APP_ID'], |
124
|
|
|
'url' => $_REQUEST['cal'] |
125
|
|
|
) |
126
|
|
|
); |
127
|
|
|
$ics->parse(); |
128
|
|
|
|
129
|
|
|
/* log this pairing in the database cache, if it doesn't already exist */ |
130
|
|
|
$calendarCacheResponse = $sql->query(" |
131
|
|
|
SELECT * |
132
|
|
|
FROM `calendars` |
133
|
|
|
WHERE |
134
|
|
|
`id` = '$pairingHash' |
135
|
|
|
"); |
136
|
|
|
$calendarCache = $calendarCacheResponse->fetch_assoc(); |
137
|
|
|
|
138
|
|
|
/* if the calendar is already cached, just update the sync timestamp */ |
139
|
|
|
if ($calendarCache) { |
140
|
|
|
$sql->query(" |
141
|
|
|
UPDATE `calendars` |
142
|
|
|
SET |
143
|
|
|
`synced` = '" . getSyncTimestamp() . "' |
144
|
|
|
WHERE |
145
|
|
|
`id` = '$pairingHash' |
146
|
|
|
"); |
147
|
|
|
} else { |
148
|
|
|
$sql->query(" |
149
|
|
|
INSERT INTO `calendars` |
150
|
|
|
( |
151
|
|
|
`id`, |
152
|
|
|
`name`, |
153
|
|
|
`ics_url`, |
154
|
|
|
`canvas_url`, |
155
|
|
|
`synced`, |
156
|
|
|
`enable_regexp_filter`, |
157
|
|
|
`include_regexp`, |
158
|
|
|
`exclude_regexp` |
159
|
|
|
) |
160
|
|
|
VALUES ( |
161
|
|
|
'$pairingHash', |
162
|
|
|
'" . $sql->real_escape_string($ics->getProperty('X-WR-CALNAME')) . "', |
163
|
|
|
'{$_REQUEST['cal']}', |
164
|
|
|
'{$canvasContext['canonical_url']}', |
165
|
|
|
'" . getSyncTimestamp() . "', |
166
|
|
|
'" . ($_REQUEST['enable_regexp_filter'] == VALUE_ENABLE_REGEXP_FILTER) . "', |
167
|
|
|
" . ($_REQUEST['enable_regexp_filter'] == VALUE_ENABLE_REGEXP_FILTER ? "'" . $sql->real_escape_string($_REQUEST['include_regexp']) . "'" : 'NULL') . ", |
168
|
|
|
" . ($_REQUEST['enable_regexp_filter'] == VALUE_ENABLE_REGEXP_FILTER ? "'" . $sql->real_escape_string($_REQUEST['exclude_regexp']) . "'" : 'NULL') . " |
169
|
|
|
) |
170
|
|
|
"); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/* refresh calendar information from cache database */ |
174
|
|
|
$calendarCacheResponse = $sql->query(" |
175
|
|
|
SELECT * |
176
|
|
|
FROM `calendars` |
177
|
|
|
WHERE |
178
|
|
|
`id` = '$pairingHash' |
179
|
|
|
"); |
180
|
|
|
$calendarCache = $calendarCacheResponse->fetch_assoc(); |
181
|
|
|
|
182
|
|
|
/* walk through $master_array and update the Canvas calendar to match the |
183
|
|
|
ICS feed, caching changes in the database */ |
184
|
|
|
// TODO: would it be worth the performance improvement to just process things from today's date forward? (i.e. ignore old items, even if they've changed...) |
185
|
|
|
// TODO:0 the best window for syncing would be the term of the course in question, right? issue:12 |
186
|
|
|
// TODO:0 Arbitrarily selecting events in for a year on either side of today's date, probably a better system? issue:12 |
187
|
|
|
foreach ($ics->selectComponents( |
|
|
|
|
188
|
|
|
date('Y')-1, // startYear |
189
|
|
|
date('m'), // startMonth |
190
|
|
|
date('d'), // startDay |
191
|
|
|
date('Y')+1, // endYEar |
192
|
|
|
date('m'), // endMonth |
193
|
|
|
date('d'), // endDay |
194
|
|
|
'vevent', // cType |
195
|
|
|
false, // flat |
196
|
|
|
true, // any |
197
|
|
|
true // split |
198
|
|
|
) as $year) { |
199
|
|
|
foreach ($year as $month => $days) { |
200
|
|
|
foreach ($days as $day => $events) { |
201
|
|
|
foreach ($events as $i => $event) { |
202
|
|
|
|
203
|
|
|
/* does this event already exist in Canvas? */ |
204
|
|
|
$eventHash = getEventHash($event); |
205
|
|
|
|
206
|
|
|
/* if the event should be included... */ |
207
|
|
|
if (filterEvent($event, $calendarCache)) { |
208
|
|
|
|
209
|
|
|
/* have we cached this event already? */ |
210
|
|
|
$eventCacheResponse = $sql->query(" |
211
|
|
|
SELECT * |
212
|
|
|
FROM `events` |
213
|
|
|
WHERE |
214
|
|
|
`calendar` = '{$calendarCache['id']}' AND |
215
|
|
|
`event_hash` = '$eventHash' |
216
|
|
|
"); |
217
|
|
|
|
218
|
|
|
|
219
|
|
|
/* if we already have the event cached in its current form, just update |
220
|
|
|
the timestamp */ |
221
|
|
|
$eventCache = $eventCacheResponse->fetch_assoc(); |
222
|
|
|
if ($eventCache) { |
223
|
|
|
$sql->query(" |
224
|
|
|
UPDATE `events` |
225
|
|
|
SET |
226
|
|
|
`synced` = '" . getSyncTimestamp() . "' |
227
|
|
|
WHERE |
228
|
|
|
`id` = '{$eventCache['id']}' |
229
|
|
|
"); |
230
|
|
|
|
231
|
|
|
/* otherwise, add this new event and cache it */ |
232
|
|
|
} else { |
233
|
|
|
/* multi-day event instance start times need to be changed to _this_ date */ |
234
|
|
|
$start = new DateTime(iCalUtilityFunctions::_date2strdate($event->getProperty('DTSTART'))); |
235
|
|
|
$end = new DateTime(iCalUtilityFunctions::_date2strdate($event->getProperty('DTEND'))); |
236
|
|
|
if ($event->getProperty('X-RECURRENCE')) { |
237
|
|
|
$start = new DateTime($event->getProperty('X-CURRENT-DTSTART')[1]); |
238
|
|
|
$end = new DateTime($event->getProperty('X-CURRENT-DTEND')[1]); |
239
|
|
|
} |
240
|
|
|
$start->setTimeZone(new DateTimeZone(LOCAL_TIMEZONE)); |
241
|
|
|
$end->setTimeZone(new DateTimeZone(LOCAL_TIMEZONE)); |
242
|
|
|
|
243
|
|
|
$calendarEvent = $api->post("/calendar_events", |
244
|
|
|
array( |
245
|
|
|
'calendar_event[context_code]' => "{$canvasContext['context']}_{$canvasObject['id']}", |
246
|
|
|
'calendar_event[title]' => preg_replace('%^([^\]]+)(\s*\[[^\]]+\]\s*)+$%', '\\1', strip_tags($event->getProperty('SUMMARY'))), |
247
|
|
|
'calendar_event[description]' => \Michelf\Markdown::defaultTransform(str_replace('\n', "\n\n", $event->getProperty('DESCRIPTION', 1))), |
248
|
|
|
'calendar_event[start_at]' => $start->format(CANVAS_TIMESTAMP_FORMAT), |
249
|
|
|
'calendar_event[end_at]' => $end->format(CANVAS_TIMESTAMP_FORMAT), |
250
|
|
|
'calendar_event[location_name]' => $event->getProperty('LOCATION') |
251
|
|
|
) |
252
|
|
|
); |
253
|
|
|
|
254
|
|
|
$sql->query(" |
255
|
|
|
INSERT INTO `events` |
256
|
|
|
( |
257
|
|
|
`calendar`, |
258
|
|
|
`calendar_event[id]`, |
259
|
|
|
`event_hash`, |
260
|
|
|
`synced` |
261
|
|
|
) |
262
|
|
|
VALUES ( |
263
|
|
|
'{$calendarCache['id']}', |
264
|
|
|
'{$calendarEvent['id']}', |
265
|
|
|
'$eventHash', |
266
|
|
|
'" . getSyncTimestamp() . "' |
267
|
|
|
) |
268
|
|
|
"); |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/* clean out previously synced events that are no longer correct */ |
277
|
|
|
$deletedEventsResponse = $sql->query(" |
278
|
|
|
SELECT * FROM `events` |
279
|
|
|
WHERE |
280
|
|
|
`calendar` = '{$calendarCache['id']}' AND |
281
|
|
|
`synced` != '" . getSyncTimestamp() . "' |
282
|
|
|
"); |
283
|
|
|
while ($deletedEventCache = $deletedEventsResponse->fetch_assoc()) { |
284
|
|
|
try { |
285
|
|
|
$deletedEvent = $api->delete("/calendar_events/{$deletedEventCache['calendar_event[id]']}", |
286
|
|
|
array( |
287
|
|
|
'cancel_reason' => getSyncTimestamp(), |
288
|
|
|
'as_user_id' => ($canvasContext['context'] == 'user' ? $canvasObject['id'] : '') // TODO: this feels skeevy -- like the empty string will break |
289
|
|
|
) |
290
|
|
|
); |
291
|
|
|
} catch (Pest_Unauthorized $e) { |
|
|
|
|
292
|
|
|
/* if the event has been deleted in Canvas, we'll get an error when |
293
|
|
|
we try to delete it a second time. We still need to delete it from |
294
|
|
|
our cache database, however */ |
295
|
|
|
postMessage('Cache out-of-sync', "calendar_event[{$deletedEventCache['calendar_event[id]']}] no longer exists and will be purged from cache.", NotificationMessage::INFO); |
296
|
|
|
} catch (Pest_ClientError $e) { |
|
|
|
|
297
|
|
|
postMessage( |
298
|
|
|
'API Client Error', |
299
|
|
|
'<pre>' . print_r(array( |
300
|
|
|
'Status' => $PEST->lastStatus(), |
301
|
|
|
'Error' => $PEST->lastBody(), |
302
|
|
|
'Verb' => $verb, |
303
|
|
|
'URL' => $url, |
304
|
|
|
'Data' => $data |
305
|
|
|
), false) . '</pre>', |
306
|
|
|
NotificationMessage::ERROR |
|
|
|
|
307
|
|
|
); |
308
|
|
|
if (php_sapi_name() != 'cli') $smarty->display('page.tpl'); |
309
|
|
|
exit; |
310
|
|
|
} |
311
|
|
|
$sql->query(" |
312
|
|
|
DELETE FROM `events` |
313
|
|
|
WHERE |
314
|
|
|
`id` = '{$deletedEventCache['id']}' |
315
|
|
|
"); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/* if this was a scheduled import (i.e. a sync), update that schedule */ |
319
|
|
|
if (isset($_REQUEST['schedule'])) { |
320
|
|
|
$sql->query(" |
321
|
|
|
UPDATE `schedules` |
322
|
|
|
SET |
323
|
|
|
`synced` = '" . getSyncTimestamp() . "' |
324
|
|
|
WHERE |
325
|
|
|
`id` = '{$_REQUEST['schedule']}' |
326
|
|
|
"); |
327
|
|
|
} |
328
|
|
|
/* are we setting up a regular synchronization? */ |
329
|
|
|
if (isset($_REQUEST['sync']) && $_REQUEST['sync'] != SCHEDULE_ONCE) { |
330
|
|
|
|
331
|
|
|
// FIXME:0 CRON SYNC SETUP GOES HERE issue:15 issue:13 |
332
|
|
|
|
333
|
|
|
/* add to the cache database schedule, replacing any schedules for this |
334
|
|
|
calendar that are already there */ |
335
|
|
|
$schedulesResponse = $sql->query(" |
336
|
|
|
SELECT * |
337
|
|
|
FROM `schedules` |
338
|
|
|
WHERE |
339
|
|
|
`calendar` = '{$calendarCache['id']}' |
340
|
|
|
"); |
341
|
|
|
|
342
|
|
|
if ($schedule = $schedulesResponse->fetch_assoc()) { |
343
|
|
|
|
344
|
|
|
/* only need to worry if the cached schedule is different from the |
345
|
|
|
new one we just set */ |
346
|
|
|
if ($shellArguments[INDEX_SCHEDULE] != $schedule['schedule']) { |
347
|
|
|
/* was this the last schedule to require this trigger? */ |
348
|
|
|
$schedulesResponse = $sql->query(" |
349
|
|
|
SELECT * |
350
|
|
|
FROM `schedules` |
351
|
|
|
WHERE |
352
|
|
|
`calendar` != '{$calendarCache['id']}' AND |
353
|
|
|
`schedule` == '{$schedule['schedule']}' |
354
|
|
|
"); |
355
|
|
|
/* we're the last one, delete it from crontab */ |
356
|
|
|
if ($schedulesResponse->num_rows == 0) { |
357
|
|
|
$crontabs = preg_replace("%^.*{$schedule['schedule']}.*" . PHP_EOL . '%', '', shell_exec('crontab -l')); |
358
|
|
|
$filename = md5(getSyncTimestamp()) . '.txt'; |
359
|
|
|
file_put_contents("/tmp/$filename", $crontabs); |
360
|
|
|
shell_exec("crontab /tmp/$filename"); |
361
|
|
|
postMessage('Unused schedule', "removed schedule '{$schedule['schedule']}' from crontab", NotificationMessage::INFO); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
$sql->query(" |
365
|
|
|
UPDATE `schedules` |
366
|
|
|
SET |
367
|
|
|
`schedule` = '" . $shellArguments[INDEX_SCHEDULE] . "', |
368
|
|
|
`synced` = '" . getSyncTimestamp() . "' |
369
|
|
|
WHERE |
370
|
|
|
`calendar` = '{$calendarCache['id']}' |
371
|
|
|
"); |
372
|
|
|
} |
373
|
|
|
} else { |
374
|
|
|
$sql->query(" |
375
|
|
|
INSERT INTO `schedules` |
376
|
|
|
( |
377
|
|
|
`calendar`, |
378
|
|
|
`schedule`, |
379
|
|
|
`synced` |
380
|
|
|
) |
381
|
|
|
VALUES ( |
382
|
|
|
'{$calendarCache['id']}', |
383
|
|
|
'" . $shellArguments[INDEX_SCHEDULE] . "', |
384
|
|
|
'" . getSyncTimestamp() . "' |
385
|
|
|
) |
386
|
|
|
"); |
387
|
|
|
} |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/* if we're ovewriting data (for example, if this is a recurring sync, we |
391
|
|
|
need to remove the events that were _not_ synced this in this round */ |
392
|
|
|
if (isset($_REQUEST['overwrite']) && $_REQUEST['overwrite'] == VALUE_OVERWRITE_CANVAS_CALENDAR) { |
|
|
|
|
393
|
|
|
// TODO: actually deal with this |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
// TODO: deal with messaging based on context |
397
|
|
|
|
398
|
|
|
postMessage('Finished sync', getSyncTimestamp(), NotificationMessage::INFO); |
399
|
|
|
exit; |
400
|
|
|
} else { |
401
|
|
|
postMessage( |
402
|
|
|
'Canvas Object Not Found', |
403
|
|
|
'The object whose URL you submitted could not be found.<pre>' . print_r(array( |
404
|
|
|
'Canvas URL' => $_REQUEST['canvas_url'], |
405
|
|
|
'Canvas Context' => $canvasContext, |
406
|
|
|
'Canvas Object' => $canvasObject |
407
|
|
|
), false) . '</pre>', |
408
|
|
|
NotificationMessage::ERROR |
|
|
|
|
409
|
|
|
); |
410
|
|
|
} |
411
|
|
|
} else { |
412
|
|
|
postMessage( |
413
|
|
|
'ICS feed Not Found', |
414
|
|
|
'The calendar whose URL you submitted could not be found.<pre>' . $_REQUEST['cal'] . '</pre>', |
415
|
|
|
NotificationMessage::ERROR |
|
|
|
|
416
|
|
|
); |
417
|
|
|
} |
418
|
|
|
} else { |
419
|
|
|
postMessage( |
420
|
|
|
'Invalid Canvas URL', |
421
|
|
|
'The Canvas URL you submitted could not be parsed.<pre>' . $_REQUEST['canvas_url'] . '</pre>', |
422
|
|
|
NotificationMessage::ERROR |
|
|
|
|
423
|
|
|
); |
424
|
|
|
if (php_sapi_name() != 'cli') $smarty->display('page.tpl'); |
425
|
|
|
exit; |
426
|
|
|
} |
427
|
|
|
} |
428
|
|
|
|
429
|
|
|
?> |
|
|
|
|
430
|
|
|
|
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.