aca318e1 |
1 | <?php // $Id$ |
2 | |
3 | //////////////////////////////////////////////////////////////////// |
4 | /// format.php - Default format class for file imports/exports. // |
5 | /// // |
6 | /// Doesn't do everything on it's own -- it needs to be extended. // |
7 | //////////////////////////////////////////////////////////////////// |
8 | |
9 | // Included by import.php and export.php |
10 | |
f5565b69 |
11 | class qformat_default { |
aca318e1 |
12 | |
13 | var $displayerrors = true; |
14 | var $category = NULL; |
15 | var $course = NULL; |
16 | var $questionids = array(); |
17 | |
18 | // functions to indicate import/export functionality |
19 | // override to return true if implemented |
20 | |
21 | function provide_import() { |
22 | return false; |
23 | } |
24 | |
25 | function provide_export() { |
26 | return false; |
27 | } |
28 | |
29 | /// Importing functions |
30 | |
31 | function importpreprocess($category, $course=NULL ) { |
32 | /// Does any pre-processing that may be desired |
33 | |
34 | $this->category = $category; // Important |
35 | $this->course = $course; |
36 | |
37 | return true; |
38 | } |
39 | |
76f0a334 |
40 | /** |
41 | * |
42 | * @PARAM $matchgrades string 'error' or 'nearest', mismatched grades handling |
43 | */ |
44 | function importprocess($filename, $matchgrades='error') { |
aca318e1 |
45 | /// Processes a given file. There's probably little need to change this |
46 | |
47 | if (! $lines = $this->readdata($filename)) { |
1e3d6fd8 |
48 | notify( get_string('cannotread','quiz') ); |
aca318e1 |
49 | return false; |
50 | } |
51 | |
52 | if (! $questions = $this->readquestions($lines)) { // Extract all the questions |
1e3d6fd8 |
53 | notify( get_string('noquestionsinfile','quiz') ); |
aca318e1 |
54 | return false; |
55 | } |
56 | |
1e3d6fd8 |
57 | notify( get_string('importingquestions','quiz',count($questions)) ); |
aca318e1 |
58 | |
76f0a334 |
59 | // get list of valid answer grades |
60 | $grades = get_grade_options(); |
61 | $gradeoptionsfull = $grades->gradeoptionsfull; |
62 | |
aca318e1 |
63 | $count = 0; |
64 | |
65 | foreach ($questions as $question) { // Process and store each question |
66 | $count++; |
67 | |
68 | echo "<hr /><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>"; |
69 | |
76f0a334 |
70 | // check for answer grades validity (must match fixed list of grades) |
71 | $fractions = $question->fraction; |
2d52056f |
72 | if (!empty($fractions)) { |
73 | $answersvalid = true; // in case they are! |
74 | foreach ($fractions as $key => $fraction) { |
75 | $newfraction = match_grade_options($gradeoptionsfull, $fraction, $matchgrades); |
76 | if ($newfraction===false) { |
77 | $answersvalid = false; |
78 | } |
79 | else { |
80 | $fractions[$key] = $newfraction; |
81 | } |
82 | } |
83 | if (!$answersvalid) { |
84 | notify( get_string('matcherror','quiz') ); |
85 | continue; |
76f0a334 |
86 | } |
87 | else { |
2d52056f |
88 | $question->fraction = $fractions; |
76f0a334 |
89 | } |
90 | } |
76f0a334 |
91 | |
aca318e1 |
92 | $question->category = $this->category->id; |
93 | $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed) |
aca318e1 |
94 | |
062f1125 |
95 | if (!$question->id = insert_record("question", $question)) { |
1e3d6fd8 |
96 | error( get_string('cannotinsert','quiz') ); |
aca318e1 |
97 | } |
98 | |
99 | $this->questionids[] = $question->id; |
100 | |
101 | // Now to save all the answers and type-specific options |
102 | |
103 | global $QTYPES; |
104 | $result = $QTYPES[$question->qtype] |
105 | ->save_question_options($question); |
106 | |
107 | if (!empty($result->error)) { |
108 | notify($result->error); |
109 | return false; |
110 | } |
111 | |
112 | if (!empty($result->notice)) { |
113 | notify($result->notice); |
114 | return true; |
115 | } |
cbe20043 |
116 | |
117 | // Give the question a unique version stamp determined by question_hash() |
118 | set_field('question', 'version', question_hash($question), 'id', $question->id); |
aca318e1 |
119 | } |
120 | return true; |
121 | } |
122 | |
123 | |
124 | function readdata($filename) { |
125 | /// Returns complete file with an array, one item per line |
126 | |
127 | if (is_readable($filename)) { |
128 | $filearray = file($filename); |
129 | |
130 | /// Check for Macintosh OS line returns (ie file on one line), and fix |
131 | if (ereg("\r", $filearray[0]) AND !ereg("\n", $filearray[0])) { |
132 | return explode("\r", $filearray[0]); |
133 | } else { |
134 | return $filearray; |
135 | } |
136 | } |
137 | return false; |
138 | } |
139 | |
140 | function readquestions($lines) { |
141 | /// Parses an array of lines into an array of questions, |
142 | /// where each item is a question object as defined by |
143 | /// readquestion(). Questions are defined as anything |
144 | /// between blank lines. |
145 | |
146 | $questions = array(); |
147 | $currentquestion = array(); |
148 | |
149 | foreach ($lines as $line) { |
150 | $line = trim($line); |
151 | if (empty($line)) { |
152 | if (!empty($currentquestion)) { |
153 | if ($question = $this->readquestion($currentquestion)) { |
154 | $questions[] = $question; |
155 | } |
156 | $currentquestion = array(); |
157 | } |
158 | } else { |
159 | $currentquestion[] = $line; |
160 | } |
161 | } |
162 | |
163 | if (!empty($currentquestion)) { // There may be a final question |
164 | if ($question = $this->readquestion($currentquestion)) { |
165 | $questions[] = $question; |
166 | } |
167 | } |
168 | |
169 | return $questions; |
170 | } |
171 | |
172 | |
173 | function defaultquestion() { |
174 | // returns an "empty" question |
175 | // Somewhere to specify question parameters that are not handled |
176 | // by import but are required db fields. |
177 | // This should not be overridden. |
178 | $question = new stdClass(); |
179 | $question->shuffleanswers = 0; |
180 | $question->defaultgrade = 1; |
181 | $question->image = ""; |
182 | $question->usecase = 0; |
183 | $question->multiplier = array(); |
184 | |
185 | return $question; |
186 | } |
187 | |
188 | function readquestion($lines) { |
189 | /// Given an array of lines known to define a question in |
190 | /// this format, this function converts it into a question |
191 | /// object suitable for processing and insertion into Moodle. |
192 | |
1e3d6fd8 |
193 | $formatnotimplemented = get_string( 'formatnotimplemented','quiz' ); |
194 | echo "<p>$formatnotimplemented</p>"; |
aca318e1 |
195 | |
196 | return NULL; |
197 | } |
198 | |
199 | |
200 | function importpostprocess() { |
201 | /// Does any post-processing that may be desired |
202 | /// Argument is a simple array of question ids that |
203 | /// have just been added. |
204 | |
205 | return true; |
206 | } |
207 | |
d08e16b2 |
208 | function importimagefile( $path, $base64 ) { |
209 | /// imports an image file encoded in base64 format |
210 | /// This should not be overridden. |
211 | global $CFG; |
212 | |
213 | // all this to get the destination directory |
214 | // and filename! |
215 | $fullpath = "{$CFG->dataroot}/{$this->course->id}/$path"; |
216 | $path_parts = pathinfo( $fullpath ); |
217 | $destination = $path_parts['dirname']; |
218 | $file = clean_filename( $path_parts['basename'] ); |
219 | |
220 | // detect and fix any filename collision - get unique filename |
221 | $newfiles = resolve_filename_collisions( $destination, array($file) ); |
222 | $newfile = $newfiles[0]; |
223 | |
224 | // convert and save file contents |
225 | if (!$content = base64_decode( $base64 )) { |
226 | return false; |
227 | } |
228 | $newfullpath = "$destination/$newfile"; |
229 | if (!$fh = fopen( $newfullpath, 'w' )) { |
230 | return false; |
231 | } |
232 | if (!fwrite( $fh, $content )) { |
233 | return false; |
234 | } |
235 | fclose( $fh ); |
236 | |
237 | // return the (possibly) new filename |
238 | return $newfile; |
239 | } |
240 | |
aca318e1 |
241 | // Export functions |
242 | |
243 | |
244 | function export_file_extension() { |
245 | /// return the files extension appropriate for this type |
246 | /// override if you don't want .txt |
247 | |
248 | return ".txt"; |
249 | } |
250 | |
251 | function exportpreprocess($category, $course) { |
252 | /// Does any pre-processing that may be desired |
253 | |
254 | $this->category = $category; // Important |
255 | $this->course = $course; // As is this! |
256 | |
257 | return true; |
258 | } |
259 | |
260 | function presave_process( $content ) { |
261 | /// enables any processing to be done on the content |
262 | /// just prior to the file being saved |
263 | /// default is to do nothing |
264 | |
265 | return $content; |
266 | } |
267 | |
268 | function exportprocess($filename) { |
269 | /// Exports a given category. There's probably little need to change this |
270 | |
271 | global $CFG; |
272 | |
273 | // create a directory for the exports (if not already existing) |
274 | $dirname = get_string("exportfilename","quiz"); |
275 | $courseid = $this->course->id; |
276 | $path = $CFG->dataroot.'/'.$courseid.'/'.$dirname; |
277 | if (!is_dir($path)) { |
278 | if (!mkdir($path, $CFG->directorypermissions)) { |
1e3d6fd8 |
279 | error( get_string('cannotcreatepath','quiz',$path) ); |
aca318e1 |
280 | } |
281 | } |
282 | |
283 | // get the questions (from database) in this category |
284 | // only get q's with no parents (no cloze subquestions specifically) |
285 | $questions = get_questions_category( $this->category, true ); |
286 | |
1e3d6fd8 |
287 | notify( get_string('exportingquestions','quiz') ); |
aca318e1 |
288 | if (!count($questions)) { |
1e3d6fd8 |
289 | notify( get_string('noquestions','quiz') ); |
290 | return false; |
aca318e1 |
291 | } |
292 | $count = 0; |
293 | |
294 | // results are first written into string (and then to a file) |
295 | // so create/initialize the string here |
296 | $expout = ""; |
297 | |
298 | // iterate through questions |
299 | foreach($questions as $question) { |
300 | $count++; |
301 | $qtype = $question->qtype; |
302 | // ignore random questiond |
303 | if ($qtype!=RANDOM) { |
304 | echo "<hr /><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>"; |
305 | $expout .= $this->writequestion( $question ) . "\n"; |
306 | } |
307 | } |
308 | |
309 | // final pre-process on exported data |
310 | $expout = $this->presave_process( $expout ); |
311 | |
312 | // write file |
313 | $filepath = $path."/".$filename . $this->export_file_extension(); |
314 | if (!$fh=fopen($filepath,"w")) { |
1e3d6fd8 |
315 | error( get_string('cannotopen','quiz',$filepath) ); |
aca318e1 |
316 | } |
317 | if (!fwrite($fh, $expout)) { |
1e3d6fd8 |
318 | error( get_string('cannotwrite','quiz',$filepath) ); |
aca318e1 |
319 | } |
320 | fclose($fh); |
321 | |
322 | return true; |
323 | } |
324 | |
325 | function exportpostprocess() { |
326 | /// Does any post-processing that may be desired |
327 | |
328 | return true; |
329 | } |
330 | |
331 | function writequestion($question) { |
332 | /// Turns a question object into textual output in the given format |
333 | /// must be overidden |
334 | |
1e3d6fd8 |
335 | // if not overidden, then this is an error. |
336 | $formatnotimplemented = get_string( 'formatnotimplemented','quiz' ); |
337 | echo "<p>$formatnotimplemented</p>"; |
aca318e1 |
338 | |
339 | return NULL; |
340 | } |
341 | |
342 | } |
343 | |
344 | ?> |