-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexercise.php
More file actions
169 lines (157 loc) · 5.4 KB
/
Copy pathexercise.php
File metadata and controls
169 lines (157 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<?php
/**
* Exercise defaults, built-in catalog resolution, and first-launch preload.
*
* Priority when loading a placement:
* 1. Built-in key via Settings::linkDefaultConfigurationFromLaunch (Settings / LTI custom / ?exercise=)
* (then catalog file / existing link JSON for that built-in)
* 2. Valid exercise already in lti_link.json (instructor Edit / Save)
* 3. Full exercise via Autograder::loadCustomConfig (custom_config / ?inherit= / ?exercise= as rlid)
* 4. Empty stub (instructor must pick Settings → Exercise or author one)
*
* Built-in selection (same as cdc6504):
*
* "custom": [ { "key": "exercise", "value": "PantryExercise" } ]
*
* On first launch, Settings::linkDefaultConfigurationFromLaunch('exercise', catalog keys) seeds the
* link settings row from LTI custom or ?exercise= when the value is valid.
*/
require_once __DIR__ . '/assignments.php';
use \Tsugi\Core\Settings;
use \Tsugi\Util\U;
use \Tsugi\Util\Autograder;
/**
* Content-hash cache-bust token for the Web Worker and its importScripts.
* Main-thread CSS/JS can rely on a normal shift-reload.
*/
function dbgrader_asset_bust() {
static $bust = null;
if ($bust !== null) {
return $bust;
}
$files = array(
__DIR__ . '/js/dbgrader-worker.js',
__DIR__ . '/js/dbgrader-meta.js',
__DIR__ . '/js/dbgrader-compare.js',
);
$parts = array();
foreach ($files as $path) {
$parts[] = is_readable($path) ? md5_file($path) : '';
}
$bust = substr(md5(implode('|', $parts)), 0, 12);
return $bust;
}
/**
* Empty exercise when nothing is configured yet.
*/
function dbgrader_empty_exercise() {
return array(
'version' => 1,
'type' => 'sqlite',
'mode' => 'query',
'title' => '',
'prompt' => 'No assignment configured yet. Instructors: open Settings and choose an assignment, or use Edit to author one.',
'setup_sql' => '',
'solution_sql' => '',
'starter_sql' => '',
'verification_sql' => array(),
'hints' => array(),
'comparison' => array(
'column_names' => true,
'column_order' => true,
'row_order' => true,
'numeric_tolerance' => 0,
),
'dialect' => 'sqlite',
'compatibility' => array('dbgrader'),
);
}
/**
* True if decoded array looks like a DBGrader exercise.
*/
function dbgrader_is_valid_exercise($decoded) {
return is_array($decoded)
&& isset($decoded['setup_sql'], $decoded['solution_sql'], $decoded['prompt']);
}
/**
* Decode a JSON string into an exercise array, or null.
*/
function dbgrader_decode_exercise_json($raw) {
if (!$raw || !is_string($raw) || U::isEmpty($raw)) {
return null;
}
$decoded = json_decode($raw, true);
if (dbgrader_is_valid_exercise($decoded)) {
return $decoded;
}
return null;
}
/**
* Load exercise from link JSON; else custom config; else built-in; else empty.
*
* When Settings has a built-in exercise key, reload from the PHP catalog when:
* - link JSON is empty / missing,
* - JSON builtin key does not match, or
* - JSON builtin_rev is stale (catalog file changed) and not marked custom.
*
* @return array{exercise: array, assignmentKey: ?string}
*/
function dbgrader_load_exercise($LINK) {
$assignmentKey = Settings::linkDefaultConfigurationFromLaunch(
'exercise',
array_keys(dbgrader_assignment_catalog())
);
$raw = null;
if ($LINK && method_exists($LINK, 'getJson')) {
$raw = $LINK->getJson();
}
$existing = dbgrader_decode_exercise_json($raw);
if ($assignmentKey) {
$builtin = dbgrader_builtin_exercise($assignmentKey);
if ($builtin) {
$jsonBuiltin = (is_array($existing) && isset($existing['builtin']))
? $existing['builtin']
: null;
$jsonRev = (is_array($existing) && isset($existing['builtin_rev']))
? $existing['builtin_rev']
: null;
$fileRev = isset($builtin['builtin_rev']) ? $builtin['builtin_rev'] : null;
$isCustom = ($jsonRev === 'custom');
$stale = !$isCustom && $fileRev && $jsonRev !== $fileRev;
if (!$existing || $jsonBuiltin !== $assignmentKey || $stale) {
if ($LINK && method_exists($LINK, 'setJson') && !empty($LINK->id)) {
$LINK->setJson(json_encode($builtin));
}
return array(
'exercise' => $builtin,
'assignmentKey' => $assignmentKey,
);
}
// Same built-in (or instructor-customized copy) — keep link JSON.
return array(
'exercise' => $existing,
'assignmentKey' => $assignmentKey,
);
}
}
if ($existing) {
return array(
'exercise' => $existing,
'assignmentKey' => $assignmentKey,
);
}
$fromCustom = Autograder::loadCustomConfig('dbgrader_is_valid_exercise');
if ($fromCustom) {
if ($LINK && method_exists($LINK, 'setJson') && !empty($LINK->id)) {
$LINK->setJson(json_encode($fromCustom));
}
return array(
'exercise' => $fromCustom,
'assignmentKey' => $assignmentKey,
);
}
return array(
'exercise' => dbgrader_empty_exercise(),
'assignmentKey' => $assignmentKey,
);
}