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 | * |
2d30f53d |
42 | * @param $matchgrades string 'error' or 'nearest', mismatched grades handling |
76f0a334 |
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) |
656ee8c6 |
71 | if (!empty($question->fraction)) { |
72 | $fractions = $question->fraction; |
2d52056f |
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(); |
172f6d95 |
184 | $question->generalfeedback = ''; |
aca318e1 |
185 | |
186 | return $question; |
187 | } |
188 | |
189 | function readquestion($lines) { |
190 | /// Given an array of lines known to define a question in |
191 | /// this format, this function converts it into a question |
192 | /// object suitable for processing and insertion into Moodle. |
193 | |
1e3d6fd8 |
194 | $formatnotimplemented = get_string( 'formatnotimplemented','quiz' ); |
195 | echo "<p>$formatnotimplemented</p>"; |
aca318e1 |
196 | |
197 | return NULL; |
198 | } |
199 | |
200 | |
201 | function importpostprocess() { |
202 | /// Does any post-processing that may be desired |
203 | /// Argument is a simple array of question ids that |
204 | /// have just been added. |
205 | |
206 | return true; |
207 | } |
208 | |
d08e16b2 |
209 | function importimagefile( $path, $base64 ) { |
210 | /// imports an image file encoded in base64 format |
211 | /// This should not be overridden. |
212 | global $CFG; |
213 | |
214 | // all this to get the destination directory |
215 | // and filename! |
216 | $fullpath = "{$CFG->dataroot}/{$this->course->id}/$path"; |
217 | $path_parts = pathinfo( $fullpath ); |
218 | $destination = $path_parts['dirname']; |
219 | $file = clean_filename( $path_parts['basename'] ); |
220 | |
221 | // detect and fix any filename collision - get unique filename |
222 | $newfiles = resolve_filename_collisions( $destination, array($file) ); |
223 | $newfile = $newfiles[0]; |
224 | |
225 | // convert and save file contents |
226 | if (!$content = base64_decode( $base64 )) { |
227 | return false; |
228 | } |
229 | $newfullpath = "$destination/$newfile"; |
230 | if (!$fh = fopen( $newfullpath, 'w' )) { |
231 | return false; |
232 | } |
233 | if (!fwrite( $fh, $content )) { |
234 | return false; |
235 | } |
236 | fclose( $fh ); |
237 | |
238 | // return the (possibly) new filename |
239 | return $newfile; |
240 | } |
241 | |
36e2232e |
242 | //================= |
aca318e1 |
243 | // Export functions |
36e2232e |
244 | //================= |
aca318e1 |
245 | |
246 | function export_file_extension() { |
247 | /// return the files extension appropriate for this type |
248 | /// override if you don't want .txt |
249 | |
250 | return ".txt"; |
251 | } |
252 | |
253 | function exportpreprocess($category, $course) { |
254 | /// Does any pre-processing that may be desired |
255 | |
256 | $this->category = $category; // Important |
257 | $this->course = $course; // As is this! |
258 | |
259 | return true; |
260 | } |
261 | |
262 | function presave_process( $content ) { |
263 | /// enables any processing to be done on the content |
264 | /// just prior to the file being saved |
265 | /// default is to do nothing |
266 | |
267 | return $content; |
268 | } |
269 | |
270 | function exportprocess($filename) { |
271 | /// Exports a given category. There's probably little need to change this |
272 | |
273 | global $CFG; |
274 | |
275 | // create a directory for the exports (if not already existing) |
1367cb8d |
276 | if (! $export_dir = make_upload_directory($this->question_get_export_dir())) { |
277 | error( get_string('cannotcreatepath','quiz',$export_dir) ); |
aca318e1 |
278 | } |
1367cb8d |
279 | $path = $CFG->dataroot.'/'.$this->question_get_export_dir(); |
aca318e1 |
280 | |
281 | // get the questions (from database) in this category |
282 | // only get q's with no parents (no cloze subquestions specifically) |
283 | $questions = get_questions_category( $this->category, true ); |
284 | |
1e3d6fd8 |
285 | notify( get_string('exportingquestions','quiz') ); |
aca318e1 |
286 | if (!count($questions)) { |
1e3d6fd8 |
287 | notify( get_string('noquestions','quiz') ); |
288 | return false; |
aca318e1 |
289 | } |
290 | $count = 0; |
291 | |
292 | // results are first written into string (and then to a file) |
293 | // so create/initialize the string here |
294 | $expout = ""; |
295 | |
296 | // iterate through questions |
297 | foreach($questions as $question) { |
a9b16aff |
298 | // do not export hidden questions |
299 | if (!empty($question->hidden)) { |
300 | continue; |
301 | } |
302 | |
303 | // do not export random questions |
304 | if ($question->qtype==RANDOM) { |
305 | continue; |
306 | } |
307 | |
308 | // export the question displaying message |
309 | $count++; |
310 | echo "<hr /><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>"; |
311 | $expout .= $this->writequestion( $question ) . "\n"; |
312 | } |
aca318e1 |
313 | |
314 | // final pre-process on exported data |
315 | $expout = $this->presave_process( $expout ); |
316 | |
317 | // write file |
318 | $filepath = $path."/".$filename . $this->export_file_extension(); |
319 | if (!$fh=fopen($filepath,"w")) { |
1e3d6fd8 |
320 | error( get_string('cannotopen','quiz',$filepath) ); |
aca318e1 |
321 | } |
322 | if (!fwrite($fh, $expout)) { |
1e3d6fd8 |
323 | error( get_string('cannotwrite','quiz',$filepath) ); |
aca318e1 |
324 | } |
325 | fclose($fh); |
326 | |
327 | return true; |
328 | } |
329 | |
330 | function exportpostprocess() { |
331 | /// Does any post-processing that may be desired |
332 | |
333 | return true; |
334 | } |
335 | |
336 | function writequestion($question) { |
337 | /// Turns a question object into textual output in the given format |
338 | /// must be overidden |
339 | |
1e3d6fd8 |
340 | // if not overidden, then this is an error. |
341 | $formatnotimplemented = get_string( 'formatnotimplemented','quiz' ); |
342 | echo "<p>$formatnotimplemented</p>"; |
aca318e1 |
343 | |
344 | return NULL; |
345 | } |
346 | |
1367cb8d |
347 | function question_get_export_dir() { |
348 | $dirname = get_string("exportfilename","quiz"); |
349 | $path = $this->course->id.'/backupdata/'.$dirname; // backupdata is protected directory |
350 | return $path; |
351 | } |
352 | |
aca318e1 |
353 | } |
354 | |
355 | ?> |