MDL-24751 fixed default memory limit when deciding to use apache_child_terminate...
[moodle.git] / grade / import / csv / index.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 require_once '../../../config.php';
19 require_once $CFG->libdir.'/gradelib.php';
20 require_once $CFG->dirroot.'/grade/lib.php';
21 require_once '../grade_import_form.php';
22 require_once '../lib.php';
24 $id            = required_param('id', PARAM_INT); // course id
25 $separator     = optional_param('separator', '', PARAM_ALPHA);
26 $verbosescales = optional_param('verbosescales', 1, PARAM_BOOL);
28 $url = new moodle_url('/grade/import/csv/index.php', array('id'=>$id));
29 if ($separator !== '') {
30     $url->param('separator', $separator);
31 }
32 if ($verbosescales !== 1) {
33     $url->param('verbosescales', $verbosescales);
34 }
35 $PAGE->set_url($url);
37 define('GRADE_CSV_LINE_LENGTH', 4096);
39 if (!$course = $DB->get_record('course', array('id'=>$id))) {
40     print_error('nocourseid');
41 }
43 require_login($course);
44 $context = get_context_instance(CONTEXT_COURSE, $id);
45 require_capability('moodle/grade:import', $context);
46 require_capability('gradeimport/csv:view', $context);
48 $separatemode = (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context));
49 $currentgroup = groups_get_course_group($course);
51 // sort out delimiter
52 if (isset($CFG->CSV_DELIMITER)) {
53     $csv_delimiter = $CFG->CSV_DELIMITER;
55     if (isset($CFG->CSV_ENCODE)) {
56         $csv_encode = '/\&\#' . $CFG->CSV_ENCODE . '/';
57     }
58 } else if ($separator == 'tab') {
59     $csv_delimiter = "\t";
60     $csv_encode = "";
61 } else {
62     $csv_delimiter = ",";
63     $csv_encode = '/\&\#44/';
64 }
66 print_grade_page_head($course->id, 'import', 'csv', get_string('importcsv', 'grades'));
68 // set up import form
69 $mform = new grade_import_form(null, array('includeseparator'=>!isset($CFG->CSV_DELIMITER), 'verbosescales'=>true));
71 // set up grade import mapping form
72 $header = '';
73 $gradeitems = array();
74 if ($id) {
75     if ($grade_items = grade_item::fetch_all(array('courseid'=>$id))) {
76         foreach ($grade_items as $grade_item) {
77             // skip course type and category type
78             if ($grade_item->itemtype == 'course' || $grade_item->itemtype == 'category') {
79                 continue;
80             }
82             // this was idnumber
83             $gradeitems[$grade_item->id] = $grade_item->get_name();
84         }
85     }
86 }
88 if ($importcode = optional_param('importcode', '', PARAM_FILE)) {
89     $filename = $CFG->dataroot.'/temp/gradeimport/cvs/'.$USER->id.'/'.$importcode;
90     $fp = fopen($filename, "r");
91     $header = explode($csv_delimiter, fgets($fp,GRADE_CSV_LINE_LENGTH), PARAM_RAW);
92 }
94 $mform2 = new grade_import_mapping_form(null, array('gradeitems'=>$gradeitems, 'header'=>$header));
96 // if import form is submitted
97 if ($formdata = $mform->get_data()) {
99     // Large files are likely to take their time and memory. Let PHP know
100     // that we'll take longer, and that the process should be recycled soon
101     // to free up memory.
102     @set_time_limit(0);
103     @raise_memory_limit("192M");
105     // use current (non-conflicting) time stamp
106     $importcode = get_new_importcode();
107     $filename = make_upload_directory('temp/gradeimport/cvs/'.$USER->id);
108     $filename = $filename.'/'.$importcode;
110     $text = $mform->get_file_content('userfile');
111     // trim utf-8 bom
112     $textlib = textlib_get_instance();
113     /// normalize line endings and do the encoding conversion
114     $text = $textlib->convert($text, $formdata->encoding);
115     $text = $textlib->trim_utf8_bom($text);
116     // Fix mac/dos newlines
117     $text = preg_replace('!\r\n?!',"\n",$text);
118     $fp = fopen($filename, "w");
119     fwrite($fp,$text);
120     fclose($fp);
122     $fp = fopen($filename, "r");
124     // --- get header (field names) ---
125     $header = explode($csv_delimiter, fgets($fp,GRADE_CSV_LINE_LENGTH));
127     // print some preview
128     $numlines = 0; // 0 preview lines displayed
130     echo $OUTPUT->heading(get_string('importpreview', 'grades'));
131     echo '<table>';
132     echo '<tr>';
133     foreach ($header as $h) {
134         $h = clean_param($h, PARAM_RAW);
135         echo '<th>'.$h.'</th>';
136     }
137     echo '</tr>';
138     while (!feof ($fp) && $numlines <= $formdata->previewrows) {
139         $lines = explode($csv_delimiter, fgets($fp,GRADE_CSV_LINE_LENGTH));
140         echo '<tr>';
141         foreach ($lines as $line) {
142             echo '<td>'.$line.'</td>';;
143         }
144         $numlines ++;
145         echo '</tr>';
146     }
147     echo '</table>';
149     // display the mapping form with header info processed
150     $mform2 = new grade_import_mapping_form(null, array('gradeitems'=>$gradeitems, 'header'=>$header));
151     $mform2->set_data(array('importcode'=>$importcode, 'id'=>$id, 'verbosescales'=>$verbosescales, 'separator'=>$separator));
152     $mform2->display();
154 //} else if (($formdata = data_submitted()) && !empty($formdata->map)) {
156 // else if grade import mapping form is submitted
157 } else if ($formdata = $mform2->get_data()) {
159     $importcode = clean_param($formdata->importcode, PARAM_FILE);
160     $filename = $CFG->dataroot.'/temp/gradeimport/cvs/'.$USER->id.'/'.$importcode;
162     if (!file_exists($filename)) {
163         print_error('cannotuploadfile');
164     }
166     if ($fp = fopen($filename, "r")) {
167         // --- get header (field names) ---
168         $header = explode($csv_delimiter, clean_param(fgets($fp,GRADE_CSV_LINE_LENGTH), PARAM_RAW));
170         foreach ($header as $i => $h) {
171             $h = trim($h); $header[$i] = $h; // remove whitespace
172         }
173     } else {
174         print_error('cannotopenfile');
175     }
177     $map = array();
178     // loops mapping_0, mapping_1 .. mapping_n and construct $map array
179     foreach ($header as $i => $head) {
180         $map[$i] = $formdata->{'mapping_'.$i};
181     }
183     // if mapping information is supplied
184     $map[clean_param($formdata->mapfrom, PARAM_RAW)] = clean_param($formdata->mapto, PARAM_RAW);
186     // check for mapto collisions
187     $maperrors = array();
188     foreach ($map as $i=>$j) {
189         if ($j == 0) {
190             // you can have multiple ignores
191             continue;
192         } else {
193             if (!isset($maperrors[$j])) {
194                 $maperrors[$j] = true;
195             } else {
196                 // collision
197                 fclose($fp);
198                 unlink($filename); // needs to be uploaded again, sorry
199                 print_error('cannotmapfield', '', '', $j);
200             }
201         }
202     }
204     // Large files are likely to take their time and memory. Let PHP know
205     // that we'll take longer, and that the process should be recycled soon
206     // to free up memory.
207     @set_time_limit(0);
208     @raise_memory_limit("192M");
210     // we only operate if file is readable
211     if ($fp = fopen($filename, "r")) {
213         // read the first line makes sure this doesn't get read again
214         $header = explode($csv_delimiter, fgets($fp,GRADE_CSV_LINE_LENGTH));
216         $newgradeitems = array(); // temporary array to keep track of what new headers are processed
217         $status = true;
219         while (!feof ($fp)) {
220             // add something
221             $line = explode($csv_delimiter, fgets($fp,GRADE_CSV_LINE_LENGTH));
223             if(count($line) <= 1){
224                 // there is no data on this line, move on
225                 continue;
226             }
228             // array to hold all grades to be inserted
229             $newgrades = array();
230             // array to hold all feedback
231             $newfeedbacks = array();
232             // each line is a student record
233             foreach ($line as $key => $value) {
234                 //decode encoded commas
235                 $value = clean_param($value, PARAM_RAW);
236                 $value = trim($value);
237                 if (!empty($csv_encode)) {
238                     $value = preg_replace($csv_encode, $csv_delimiter, $value);
239                 }
241                 /*
242                  * the options are
243                  * 1) userid, useridnumber, usermail, username - used to identify user row
244                  * 2) new - new grade item
245                  * 3) id - id of the old grade item to map onto
246                  * 3) feedback_id - feedback for grade item id
247                  */
249                 $t = explode("_", $map[$key]);
250                 $t0 = $t[0];
251                 if (isset($t[1])) {
252                     $t1 = (int)$t[1];
253                 } else {
254                     $t1 = '';
255                 }
257                 switch ($t0) {
258                     case 'userid': //
259                         if (!$user = $DB->get_record('user', array('id' => $value))) {
260                             // user not found, abort whole import
261                             import_cleanup($importcode);
262                             echo $OUTPUT->notification("user mapping error, could not find user with id \"$value\"");
263                             $status = false;
264                             break 3;
265                         }
266                         $studentid = $value;
267                     break;
268                     case 'useridnumber':
269                         if (!$user = $DB->get_record('user', array('idnumber' => $value))) {
270                              // user not found, abort whole import
271                             import_cleanup($importcode);
272                             echo $OUTPUT->notification("user mapping error, could not find user with idnumber \"$value\"");
273                             $status = false;
274                             break 3;
275                         }
276                         $studentid = $user->id;
277                     break;
278                     case 'useremail':
279                         if (!$user = $DB->get_record('user', array('email' => $value))) {
280                             import_cleanup($importcode);
281                             echo $OUTPUT->notification("user mapping error, could not find user with email address \"$value\"");
282                             $status = false;
283                             break 3;
284                         }
285                         $studentid = $user->id;
286                     break;
287                     case 'username':
288                         if (!$user = $DB->get_record('user', array('username' => $value))) {
289                             import_cleanup($importcode);
290                             echo $OUTPUT->notification("user mapping error, could not find user with username \"$value\"");
291                             $status = false;
292                             break 3;
293                         }
294                         $studentid = $user->id;
295                     break;
296                     case 'new':
297                         // first check if header is already in temp database
299                         if (empty($newgradeitems[$key])) {
301                             $newgradeitem = new stdClass();
302                             $newgradeitem->itemname = $header[$key];
303                             $newgradeitem->importcode = $importcode;
304                             $newgradeitem->importer   = $USER->id;
306                             // failed to insert into new grade item buffer
307                             $newgradeitems[$key] = $DB->insert_record('grade_import_newitem', $newgradeitem);
308                             // add this to grade_import_newitem table
309                             // add the new id to $newgradeitem[$key]
310                         }
311                         $newgrade = new stdClass();
312                         $newgrade->newgradeitem = $newgradeitems[$key];
313                         $newgrade->finalgrade   = $value;
314                         $newgrades[] = $newgrade;
316                         // if not, put it in
317                         // else, insert grade into the table
318                     break;
319                     case 'feedback':
320                         if ($t1) {
321                             // case of an id, only maps id of a grade_item
322                             // this was idnumber
323                             if (!$gradeitem = new grade_item(array('id'=>$t1, 'courseid'=>$course->id))) {
324                                 // supplied bad mapping, should not be possible since user
325                                 // had to pick mapping
326                                 $status = false;
327                                 import_cleanup($importcode);
328                                 echo $OUTPUT->notification(get_string('importfailed', 'grades'));
329                                 break 3;
330                             }
332                             // t1 is the id of the grade item
333                             $feedback = new stdClass();
334                             $feedback->itemid   = $t1;
335                             $feedback->feedback = $value;
336                             $newfeedbacks[] = $feedback;
337                         }
338                     break;
339                     default:
340                         // existing grade items
341                         if (!empty($map[$key])) {
342                             // case of an id, only maps id of a grade_item
343                             // this was idnumber
344                             if (!$gradeitem = new grade_item(array('id'=>$map[$key], 'courseid'=>$course->id))) {
345                                 // supplied bad mapping, should not be possible since user
346                                 // had to pick mapping
347                                 $status = false;
348                                 import_cleanup($importcode);
349                                 echo $OUTPUT->notification(get_string('importfailed', 'grades'));
350                                 break 3;
351                             }
353                             // check if grade item is locked if so, abort
354                             if ($gradeitem->is_locked()) {
355                                 $status = false;
356                                 import_cleanup($importcode);
357                                 echo $OUTPUT->notification(get_string('gradeitemlocked', 'grades'));
358                                 break 3;
359                             }
361                             $newgrade = new stdClass();
362                             $newgrade->itemid     = $gradeitem->id;
363                             if ($gradeitem->gradetype == GRADE_TYPE_SCALE and $verbosescales) {
364                                 if ($value === '' or $value == '-') {
365                                     $value = null; // no grade
366                                 } else {
367                                     $scale = $gradeitem->load_scale();
368                                     $scales = explode(',', $scale->scale);
369                                     $scales = array_map('trim', $scales); //hack - trim whitespace around scale options
370                                     array_unshift($scales, '-'); // scales start at key 1
371                                     $key = array_search($value, $scales);
372                                     if ($key === false) {
373                                         echo "<br/>t0 is $t0";
374                                         echo "<br/>grade is $value";
375                                         $status = false;
376                                         import_cleanup($importcode);
377                                         echo $OUTPUT->notification(get_string('badgrade', 'grades'));
378                                         break 3;
379                                     }
380                                     $value = $key;
381                                 }
382                                 $newgrade->finalgrade = $value;
383                             } else {
384                                 if ($value === '' or $value == '-') {
385                                     $value = null; // no grade
387                                 } else if (!is_numeric($value)) {
388                                 // non numeric grade value supplied, possibly mapped wrong column
389                                     echo "<br/>t0 is $t0";
390                                     echo "<br/>grade is $value";
391                                     $status = false;
392                                     import_cleanup($importcode);
393                                     echo $OUTPUT->notification(get_string('badgrade', 'grades'));
394                                     break 3;
395                                 }
396                                 $newgrade->finalgrade = $value;
397                             }
398                             $newgrades[] = $newgrade;
399                         } // otherwise, we ignore this column altogether
400                           // because user has chosen to ignore them (e.g. institution, address etc)
401                     break;
402                 }
403             }
405             // no user mapping supplied at all, or user mapping failed
406             if (empty($studentid) || !is_numeric($studentid)) {
407                 // user not found, abort whole import
408                 $status = false;
409                 import_cleanup($importcode);
410                 echo $OUTPUT->notification('user mapping error, could not find user!');
411                 break;
412             }
414             if ($separatemode and !groups_is_member($currentgroup, $studentid)) {
415                 // not allowed to import into this group, abort
416                 $status = false;
417                 import_cleanup($importcode);
418                 echo $OUTPUT->notification('user not member of current group, can not update!');
419                 break;
420             }
422             // insert results of this students into buffer
423             if ($status and !empty($newgrades)) {
425                 foreach ($newgrades as $newgrade) {
427                     // check if grade_grade is locked and if so, abort
428                     if (!empty($newgrade->itemid) and $grade_grade = new grade_grade(array('itemid'=>$newgrade->itemid, 'userid'=>$studentid))) {
429                         if ($grade_grade->is_locked()) {
430                             // individual grade locked
431                             $status = false;
432                             import_cleanup($importcode);
433                             echo $OUTPUT->notification(get_string('gradelocked', 'grades'));
434                             break 2;
435                         }
436                     }
438                     $newgrade->importcode = $importcode;
439                     $newgrade->userid     = $studentid;
440                     $newgrade->importer   = $USER->id;
441                     $DB->insert_record('grade_import_values', $newgrade);
442                 }
443             }
445             // updating/inserting all comments here
446             if ($status and !empty($newfeedbacks)) {
447                 foreach ($newfeedbacks as $newfeedback) {
448                     $sql = "SELECT *
449                               FROM {grade_import_values}
450                              WHERE importcode=? AND userid=? AND itemid=? AND importer=?";
451                     if ($feedback = $DB->get_record_sql($sql, array($importcode, $studentid, $newfeedback->itemid, $USER->id))) {
452                         $newfeedback->id = $feedback->id;
453                         $DB->update_record('grade_import_values', $newfeedback);
455                     } else {
456                         // the grade item for this is not updated
457                         $newfeedback->importcode = $importcode;
458                         $newfeedback->userid     = $studentid;
459                         $newfeedback->importer   = $USER->id;
460                         $DB->insert_record('grade_import_values', $newfeedback);
461                     }
462                 }
463             }
464         }
466         /// at this stage if things are all ok, we commit the changes from temp table
467         if ($status) {
468             grade_import_commit($course->id, $importcode);
469         }
470         // temporary file can go now
471         fclose($fp);
472         unlink($filename);
473     } else {
474         print_error('cannotreadfil');
475     }
477 } else {
478     groups_print_course_menu($course, 'index.php?id='.$id);
479     echo '<div class="clearer"></div>';
481     // display the standard upload file form
482     $mform->display();
485 echo $OUTPUT->footer();