Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Import outcomes from a file
  19   *
  20   * @package   core_grades
  21   * @copyright 2008 Moodle Pty Ltd (http://moodle.com)
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  require_once(__DIR__.'/../../../config.php');
  26  require_once($CFG->dirroot.'/lib/formslib.php');
  27  require_once($CFG->dirroot.'/grade/lib.php');
  28  require_once($CFG->libdir.'/gradelib.php');
  29  require_once ('import_outcomes_form.php');
  30  
  31  $courseid = optional_param('courseid', 0, PARAM_INT);
  32  $action   = optional_param('action', '', PARAM_ALPHA);
  33  $scope    = optional_param('scope', 'custom', PARAM_ALPHA);
  34  
  35  $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid' => $courseid));
  36  $PAGE->set_url($url);
  37  $PAGE->set_pagelayout('admin');
  38  
  39  /// Make sure they can even access this course
  40  if ($courseid) {
  41      if (!$course = $DB->get_record('course', array('id' => $courseid))) {
  42          throw new \moodle_exception('invalidcourseid');
  43      }
  44      require_login($course);
  45      $context = context_course::instance($course->id);
  46  
  47      if (empty($CFG->enableoutcomes)) {
  48          redirect('../../index.php?id='.$courseid);
  49      }
  50      navigation_node::override_active_url(new moodle_url('/grade/edit/outcome/course.php', ['id' => $courseid]));
  51      $PAGE->navbar->add(get_string('manageoutcomes', 'grades'),
  52          new moodle_url('/grade/edit/outcome/index.php', ['id' => $courseid]));
  53      $PAGE->navbar->add(get_string('importoutcomes', 'grades'),
  54          new moodle_url('/grade/edit/outcome/import.php', ['courseid' => $courseid]));
  55  
  56  } else {
  57      require_once $CFG->libdir.'/adminlib.php';
  58      admin_externalpage_setup('outcomes');
  59      $context = context_system::instance();
  60  }
  61  
  62  require_capability('moodle/grade:manageoutcomes', $context);
  63  
  64  $upload_form = new import_outcomes_form();
  65  
  66  if ($upload_form->is_cancelled()) {
  67      redirect(new moodle_url('/grade/edit/outcome/index.php', ['id' => $courseid]));
  68      die;
  69  }
  70  
  71  print_grade_page_head($courseid, 'outcome', 'import', get_string('importoutcomes', 'grades'),
  72      false, false, false);
  73  
  74  if (!$upload_form->get_data()) { // Display the import form.
  75      $upload_form->display();
  76      echo $OUTPUT->footer();
  77      die;
  78  }
  79  
  80  $imported_file = $CFG->tempdir . '/outcomeimport/importedfile_'.time().'.csv';
  81  make_temp_directory('outcomeimport');
  82  
  83  // copying imported file
  84  if (!$upload_form->save_file('userfile', $imported_file, true)) {
  85      redirect('import.php'. ($courseid ? "?courseid=$courseid" : ''), get_string('importfilemissing', 'grades'));
  86  }
  87  
  88  /// which scope are we importing the outcomes in?
  89  if (isset($courseid) && ($scope  == 'custom')) {
  90      // custom scale
  91      $local_scope = true;
  92  } elseif (($scope == 'global') && has_capability('moodle/grade:manage', context_system::instance())) {
  93      // global scale
  94      $local_scope = false;
  95  } else {
  96      // shouldn't happen .. user might be trying to access this script without the right permissions.
  97      redirect('index.php', get_string('importerror', 'grades'));
  98  }
  99  
 100  // open the file, start importing data
 101  if ($handle = fopen($imported_file, 'r')) {
 102      $line = 0; // will keep track of current line, to give better error messages.
 103      $file_headers = '';
 104  
 105      // $csv_data needs to have at least these columns, the value is the default position in the data file.
 106      $headers = array('outcome_name' => 0, 'outcome_shortname' => 1, 'scale_name' => 3, 'scale_items' => 4);
 107      $optional_headers = array('outcome_description'=>2, 'scale_description' => 5);
 108      $imported_headers = array(); // will later be initialized with the values found in the file
 109  
 110      $fatal_error = false;
 111      $errormessage = '';
 112  
 113      // data should be separated by a ';'.  *NOT* by a comma!  TODO: version 2.0
 114      // or whenever we can depend on PHP5, set the second parameter (8192) to 0 (unlimited line length) : the database can store over 128k per line.
 115      while ( $csv_data = fgetcsv($handle, 8192, ';', '"')) { // if the line is over 8k, it won't work...
 116          $line++;
 117  
 118          // be tolerant on input, as fgetcsv returns "an array comprising a single null field" on blank lines
 119          if ($csv_data == array(null)) {
 120              continue;
 121          }
 122  
 123          // on first run, grab and analyse the header
 124          if ($file_headers == '') {
 125  
 126              $file_headers = array_flip($csv_data); // save the header line ... TODO: use the header line to let import work with columns in arbitrary order
 127  
 128              $error = false;
 129              foreach($headers as $key => $value) {
 130                  // sanity check #1: make sure the file contains all the mandatory headers
 131                  if (!array_key_exists($key, $file_headers)) {
 132                      $error = true;
 133                      break;
 134                  }
 135              }
 136              if ($error) {
 137                  $fatal_error = true;
 138                  $errormessage = get_string('importoutcomenofile', 'grades', $line);
 139                  break;
 140              }
 141  
 142              foreach(array_merge($headers, $optional_headers) as $header => $position) {
 143                  // match given columns to expected columns *into* $headers
 144                  $imported_headers[$header] = $file_headers[$header];
 145              }
 146  
 147              continue; // we don't import headers
 148          }
 149  
 150          // sanity check #2: every line must have the same number of columns as there are
 151          // headers.  If not, processing stops.
 152          if ( count($csv_data) != count($file_headers) ) {
 153              $fatal_error = true;
 154              $errormessage = get_string('importoutcomenofile', 'grades', $line);
 155              break;
 156          }
 157  
 158          // sanity check #3: all required fields must be present on the current line.
 159          foreach ($headers as $header => $position) {
 160              if ($csv_data[$imported_headers[$header]] == '') {
 161                  $fatal_error = true;
 162                  $errormessage = get_string('importoutcomenofile', 'grades', $line);
 163                  break;
 164              }
 165          }
 166  
 167          // MDL-17273 errors in csv are not preventing import from happening. We break from the while loop here
 168          if ($fatal_error) {
 169              break;
 170          }
 171          $params = array($csv_data[$imported_headers['outcome_shortname']]);
 172          $wheresql = 'shortname = ? ';
 173  
 174          if ($local_scope) {
 175              $params[] = $courseid;
 176              $wheresql .= ' AND courseid = ?';
 177          } else {
 178              $wheresql .= ' AND courseid IS NULL';
 179          }
 180  
 181          $outcome = $DB->get_records_select('grade_outcomes', $wheresql, $params);
 182  
 183          if ($outcome) {
 184              // already exists, print a message and skip.
 185              echo $OUTPUT->notification(get_string('importskippedoutcome', 'grades',
 186                  $csv_data[$imported_headers['outcome_shortname']]), 'info', false);
 187              continue;
 188          }
 189  
 190          // new outcome will be added, search for compatible existing scale...
 191          $params = array($csv_data[$imported_headers['scale_name']], $csv_data[$imported_headers['scale_items']], $courseid);
 192          $wheresql = 'name = ? AND scale = ? AND (courseid = ? OR courseid = 0)';
 193          $scale = $DB->get_records_select('scale', $wheresql, $params);
 194  
 195          if ($scale) {
 196              // already exists in the right scope: use it.
 197              $scale_id = key($scale);
 198          } else {
 199              if (!has_capability('moodle/course:managescales', $context)) {
 200                  echo $OUTPUT->notification(get_string('importskippedoutcome', 'grades',
 201                      $csv_data[$imported_headers['outcome_shortname']]), 'warning', false);
 202                  continue;
 203              } else {
 204                  // scale doesn't exists : create it.
 205                  $scale_data = array('name' => $csv_data[$imported_headers['scale_name']],
 206                          'scale' => $csv_data[$imported_headers['scale_items']],
 207                          'description' => $csv_data[$imported_headers['scale_description']],
 208                          'userid' => $USER->id);
 209  
 210                  if ($local_scope) {
 211                      $scale_data['courseid'] = $courseid;
 212                  } else {
 213                      $scale_data['courseid'] = 0; // 'global' : scale use '0', outcomes use null
 214                  }
 215                  $scale = new grade_scale($scale_data);
 216                  $scale_id = $scale->insert();
 217              }
 218          }
 219  
 220          // add outcome
 221          $outcome_data = array('shortname' => $csv_data[$imported_headers['outcome_shortname']],
 222                  'fullname' => $csv_data[$imported_headers['outcome_name']],
 223                  'scaleid' => $scale_id,
 224                  'description' => $csv_data[$imported_headers['outcome_description']],
 225                  'usermodified' => $USER->id);
 226  
 227          if ($local_scope) {
 228              $outcome_data['courseid'] = $courseid;
 229          } else {
 230              $outcome_data['courseid'] = null; // 'global' : scale use '0', outcomes use null
 231          }
 232          $outcome = new grade_outcome($outcome_data);
 233          $outcome_id = $outcome->insert();
 234  
 235          $outcome_success_strings = new StdClass();
 236          $outcome_success_strings->name = $outcome_data['fullname'];
 237          $outcome_success_strings->id = $outcome_id;
 238          echo $OUTPUT->notification(get_string('importoutcomesuccess', 'grades', $outcome_success_strings),
 239              'success', false);
 240      }
 241  
 242      if ($fatal_error) {
 243          echo $OUTPUT->notification($errormessage, 'error', false);
 244          echo $OUTPUT->single_button(new moodle_url('/grade/edit/outcome/import.php', ['courseid' => $courseid]),
 245              get_string('back'), 'get');
 246      } else {
 247          echo $OUTPUT->single_button(new moodle_url('/grade/edit/outcome/index.php', ['id' => $courseid]),
 248              get_string('continue'), 'get');
 249      }
 250  } else {
 251      echo $OUTPUT->box(get_string('importoutcomenofile', 'grades', 0));
 252  }
 253  
 254  // finish
 255  fclose($handle);
 256  // delete temp file
 257  unlink($imported_file);
 258  
 259  echo $OUTPUT->footer();