Merged from STABLE. Addition function to find or create category path.
[moodle.git] / question / format.php
CommitLineData
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 11class 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?>