| 1 | efrain | 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 |  * A class for loading and preparing grade data from import.
 | 
        
           |  |  | 19 |  *
 | 
        
           |  |  | 20 |  * @package   gradeimport_csv
 | 
        
           |  |  | 21 |  * @copyright 2014 Adrian Greeve <adrian@moodle.com>
 | 
        
           |  |  | 22 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 |   | 
        
           |  |  | 25 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | /**
 | 
        
           |  |  | 28 |  * A class for loading and preparing grade data from import.
 | 
        
           |  |  | 29 |  *
 | 
        
           |  |  | 30 |  * @package   gradeimport_csv
 | 
        
           |  |  | 31 |  * @copyright 2014 Adrian Greeve <adrian@moodle.com>
 | 
        
           |  |  | 32 |  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 33 |  */
 | 
        
           |  |  | 34 | class gradeimport_csv_load_data {
 | 
        
           |  |  | 35 |   | 
        
           |  |  | 36 |     /** @var string $error csv import error. */
 | 
        
           |  |  | 37 |     protected $error;
 | 
        
           |  |  | 38 |     /** @var int $iid Unique identifier for these csv records. */
 | 
        
           |  |  | 39 |     protected $iid;
 | 
        
           |  |  | 40 |     /** @var array $headers Column names for the data. */
 | 
        
           |  |  | 41 |     protected $headers;
 | 
        
           |  |  | 42 |     /** @var array $previewdata A subsection of the csv imported data. */
 | 
        
           |  |  | 43 |     protected $previewdata;
 | 
        
           |  |  | 44 |   | 
        
           |  |  | 45 |     // The map_user_data_with_value variables.
 | 
        
           |  |  | 46 |     /** @var array $newgrades Grades to be inserted into the gradebook. */
 | 
        
           |  |  | 47 |     protected $newgrades;
 | 
        
           |  |  | 48 |     /** @var array $newfeedbacks Feedback to be inserted into the gradebook. */
 | 
        
           |  |  | 49 |     protected $newfeedbacks;
 | 
        
           |  |  | 50 |     /** @var int $studentid Student ID*/
 | 
        
           |  |  | 51 |     protected $studentid;
 | 
        
           |  |  | 52 |   | 
        
           |  |  | 53 |     // The prepare_import_grade_data() variables.
 | 
        
           |  |  | 54 |     /** @var bool $status The current status of the import. True = okay, False = errors. */
 | 
        
           |  |  | 55 |     protected $status;
 | 
        
           |  |  | 56 |     /** @var int $importcode The code for this batch insert. */
 | 
        
           |  |  | 57 |     protected $importcode;
 | 
        
           |  |  | 58 |     /** @var array $gradebookerrors An array of errors from trying to import into the gradebook. */
 | 
        
           |  |  | 59 |     protected $gradebookerrors;
 | 
        
           |  |  | 60 |     /** @var array $newgradeitems An array of new grade items to be inserted into the gradebook. */
 | 
        
           |  |  | 61 |     protected $newgradeitems;
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |     /**
 | 
        
           |  |  | 64 |      * Load CSV content for previewing.
 | 
        
           |  |  | 65 |      *
 | 
        
           |  |  | 66 |      * @param string $text The grade data being imported.
 | 
        
           |  |  | 67 |      * @param string $encoding The type of encoding the file uses.
 | 
        
           |  |  | 68 |      * @param string $separator The separator being used to define each field.
 | 
        
           |  |  | 69 |      * @param int $previewrows How many rows are being previewed.
 | 
        
           |  |  | 70 |      */
 | 
        
           |  |  | 71 |     public function load_csv_content($text, $encoding, $separator, $previewrows) {
 | 
        
           |  |  | 72 |         $this->raise_limits();
 | 
        
           |  |  | 73 |   | 
        
           |  |  | 74 |         $this->iid = csv_import_reader::get_new_iid('grade');
 | 
        
           |  |  | 75 |         $csvimport = new csv_import_reader($this->iid, 'grade');
 | 
        
           |  |  | 76 |   | 
        
           |  |  | 77 |         $csvimport->load_csv_content($text, $encoding, $separator);
 | 
        
           |  |  | 78 |         $this->error = $csvimport->get_error();
 | 
        
           |  |  | 79 |   | 
        
           |  |  | 80 |         // If there are no import errors then proceed.
 | 
        
           |  |  | 81 |         if (empty($this->error)) {
 | 
        
           |  |  | 82 |   | 
        
           |  |  | 83 |             // Get header (field names).
 | 
        
           |  |  | 84 |             $this->headers = $csvimport->get_columns();
 | 
        
           |  |  | 85 |             $this->trim_headers();
 | 
        
           |  |  | 86 |   | 
        
           |  |  | 87 |             $csvimport->init();
 | 
        
           |  |  | 88 |             $this->previewdata = array();
 | 
        
           |  |  | 89 |   | 
        
           |  |  | 90 |             for ($numlines = 0; $numlines <= $previewrows; $numlines++) {
 | 
        
           |  |  | 91 |                 $lines = $csvimport->next();
 | 
        
           |  |  | 92 |                 if ($lines) {
 | 
        
           |  |  | 93 |                     $this->previewdata[] = $lines;
 | 
        
           |  |  | 94 |                 }
 | 
        
           |  |  | 95 |             }
 | 
        
           |  |  | 96 |         }
 | 
        
           |  |  | 97 |     }
 | 
        
           |  |  | 98 |   | 
        
           |  |  | 99 |     /**
 | 
        
           |  |  | 100 |      * Gets all of the grade items in this course.
 | 
        
           |  |  | 101 |      *
 | 
        
           |  |  | 102 |      * @param int $courseid Course id;
 | 
        
           |  |  | 103 |      * @return array An array of grade items for the course.
 | 
        
           |  |  | 104 |      */
 | 
        
           |  |  | 105 |     public static function fetch_grade_items($courseid) {
 | 
        
           |  |  | 106 |         $gradeitems = null;
 | 
        
           |  |  | 107 |         if ($allgradeitems = grade_item::fetch_all(array('courseid' => $courseid))) {
 | 
        
           |  |  | 108 |             foreach ($allgradeitems as $gradeitem) {
 | 
        
           |  |  | 109 |                 // Skip course type and category type.
 | 
        
           |  |  | 110 |                 if ($gradeitem->itemtype == 'course' || $gradeitem->itemtype == 'category') {
 | 
        
           |  |  | 111 |                     continue;
 | 
        
           |  |  | 112 |                 }
 | 
        
           |  |  | 113 |   | 
        
           |  |  | 114 |                 $displaystring = null;
 | 
        
           |  |  | 115 |                 if (!empty($gradeitem->itemmodule)) {
 | 
        
           |  |  | 116 |                     $displaystring = get_string('modulename', $gradeitem->itemmodule).get_string('labelsep', 'langconfig')
 | 
        
           |  |  | 117 |                             .$gradeitem->get_name();
 | 
        
           |  |  | 118 |                 } else {
 | 
        
           |  |  | 119 |                     $displaystring = $gradeitem->get_name();
 | 
        
           |  |  | 120 |                 }
 | 
        
           |  |  | 121 |                 $gradeitems[$gradeitem->id] = $displaystring;
 | 
        
           |  |  | 122 |             }
 | 
        
           |  |  | 123 |         }
 | 
        
           |  |  | 124 |         return $gradeitems;
 | 
        
           |  |  | 125 |     }
 | 
        
           |  |  | 126 |   | 
        
           |  |  | 127 |     /**
 | 
        
           |  |  | 128 |      * Cleans the column headers from the CSV file.
 | 
        
           |  |  | 129 |      */
 | 
        
           |  |  | 130 |     protected function trim_headers() {
 | 
        
           |  |  | 131 |         foreach ($this->headers as $i => $h) {
 | 
        
           |  |  | 132 |             $h = trim($h); // Remove whitespace.
 | 
        
           |  |  | 133 |             $h = clean_param($h, PARAM_RAW); // Clean the header.
 | 
        
           |  |  | 134 |             $this->headers[$i] = $h;
 | 
        
           |  |  | 135 |         }
 | 
        
           |  |  | 136 |     }
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 |     /**
 | 
        
           |  |  | 139 |      * Raises the php execution time and memory limits for importing the CSV file.
 | 
        
           |  |  | 140 |      */
 | 
        
           |  |  | 141 |     protected function raise_limits() {
 | 
        
           |  |  | 142 |         // Large files are likely to take their time and memory. Let PHP know
 | 
        
           |  |  | 143 |         // that we'll take longer, and that the process should be recycled soon
 | 
        
           |  |  | 144 |         // to free up memory.
 | 
        
           |  |  | 145 |         core_php_time_limit::raise();
 | 
        
           |  |  | 146 |         raise_memory_limit(MEMORY_EXTRA);
 | 
        
           |  |  | 147 |     }
 | 
        
           |  |  | 148 |   | 
        
           |  |  | 149 |     /**
 | 
        
           |  |  | 150 |      * Inserts a record into the grade_import_values table. This also adds common record information.
 | 
        
           |  |  | 151 |      *
 | 
        
           |  |  | 152 |      * @param stdClass $record The grade record being inserted into the database.
 | 
        
           |  |  | 153 |      * @param int $studentid The student ID.
 | 
        
           |  |  | 154 |      * @param grade_item $gradeitem Grade item.
 | 
        
           |  |  | 155 |      * @return mixed true or insert id on success. Null if the grade value is too high or too low or grade item not exist.
 | 
        
           |  |  | 156 |      */
 | 
        
           |  |  | 157 |     protected function insert_grade_record(stdClass $record, int $studentid, grade_item $gradeitem): mixed {
 | 
        
           |  |  | 158 |         global $DB, $USER, $CFG;
 | 
        
           |  |  | 159 |         $record->importcode = $this->importcode;
 | 
        
           |  |  | 160 |         $record->userid     = $studentid;
 | 
        
           |  |  | 161 |         $record->importer   = $USER->id;
 | 
        
           |  |  | 162 |         // If the record final grade is set then check that the grade value isn't too high.
 | 
        
           |  |  | 163 |         // Final grade will not be set if we are inserting feedback.
 | 
        
           |  |  | 164 |         $gradepointmaximum = $gradeitem->grademax;
 | 
        
           |  |  | 165 |         $gradepointminimum = $gradeitem->grademin;
 | 
        
           |  |  | 166 |   | 
        
           |  |  | 167 |         $finalgradeinrange =
 | 
        
           |  |  | 168 |             isset($record->finalgrade) && $record->finalgrade <= $gradepointmaximum && $record->finalgrade >= $gradepointminimum;
 | 
        
           |  |  | 169 |         if (!isset($record->finalgrade) || $finalgradeinrange || $CFG->unlimitedgrades) {
 | 
        
           |  |  | 170 |             return $DB->insert_record('grade_import_values', $record);
 | 
        
           |  |  | 171 |         } else {
 | 
        
           |  |  | 172 |             if ($record->finalgrade > $gradepointmaximum) {
 | 
        
           |  |  | 173 |                 $this->cleanup_import(get_string('gradevaluetoobig', 'grades', format_float($gradepointmaximum)));
 | 
        
           |  |  | 174 |             } else {
 | 
        
           |  |  | 175 |                 $this->cleanup_import(get_string('gradevaluetoosmall', 'grades', format_float($gradepointminimum)));
 | 
        
           |  |  | 176 |             }
 | 
        
           |  |  | 177 |             return null;
 | 
        
           |  |  | 178 |         }
 | 
        
           |  |  | 179 |     }
 | 
        
           |  |  | 180 |   | 
        
           |  |  | 181 |     /**
 | 
        
           |  |  | 182 |      * Insert the new grade into the grade item buffer table.
 | 
        
           |  |  | 183 |      *
 | 
        
           |  |  | 184 |      * @param array $header The column headers from the CSV file.
 | 
        
           |  |  | 185 |      * @param int $key Current row identifier.
 | 
        
           |  |  | 186 |      * @param string $value The value for this row (final grade).
 | 
        
           |  |  | 187 |      * @return stdClass new grade that is ready for commiting to the gradebook.
 | 
        
           |  |  | 188 |      */
 | 
        
           |  |  | 189 |     protected function import_new_grade_item($header, $key, $value) {
 | 
        
           |  |  | 190 |         global $DB, $USER;
 | 
        
           |  |  | 191 |   | 
        
           |  |  | 192 |         // First check if header is already in temp database.
 | 
        
           |  |  | 193 |         if (empty($this->newgradeitems[$key])) {
 | 
        
           |  |  | 194 |   | 
        
           |  |  | 195 |             $newgradeitem = new stdClass();
 | 
        
           |  |  | 196 |             $newgradeitem->itemname = $header[$key];
 | 
        
           |  |  | 197 |             $newgradeitem->importcode = $this->importcode;
 | 
        
           |  |  | 198 |             $newgradeitem->importer = $USER->id;
 | 
        
           |  |  | 199 |   | 
        
           |  |  | 200 |             // Insert into new grade item buffer.
 | 
        
           |  |  | 201 |             $this->newgradeitems[$key] = $DB->insert_record('grade_import_newitem', $newgradeitem);
 | 
        
           |  |  | 202 |         }
 | 
        
           |  |  | 203 |         $newgrade = new stdClass();
 | 
        
           |  |  | 204 |         $newgrade->newgradeitem = $this->newgradeitems[$key];
 | 
        
           |  |  | 205 |   | 
        
           |  |  | 206 |         $trimmed = trim($value);
 | 
        
           |  |  | 207 |         if ($trimmed === '' or $trimmed == '-') {
 | 
        
           |  |  | 208 |             // Blank or dash grade means null, ie "no grade".
 | 
        
           |  |  | 209 |             $newgrade->finalgrade = null;
 | 
        
           |  |  | 210 |         } else {
 | 
        
           |  |  | 211 |             // We have an actual grade.
 | 
        
           |  |  | 212 |             $newgrade->finalgrade = $value;
 | 
        
           |  |  | 213 |         }
 | 
        
           |  |  | 214 |         $this->newgrades[] = $newgrade;
 | 
        
           |  |  | 215 |         return $newgrade;
 | 
        
           |  |  | 216 |     }
 | 
        
           |  |  | 217 |   | 
        
           |  |  | 218 |     /**
 | 
        
           |  |  | 219 |      * Check that the user is in the system.
 | 
        
           |  |  | 220 |      *
 | 
        
           |  |  | 221 |      * @param string $value The value, from the csv file, being mapped to identify the user.
 | 
        
           |  |  | 222 |      * @param array $userfields Contains the field and label being mapped from.
 | 
        
           |  |  | 223 |      * @return int Returns the user ID if it exists, otherwise null.
 | 
        
           |  |  | 224 |      */
 | 
        
           |  |  | 225 |     protected function check_user_exists($value, $userfields) {
 | 
        
           |  |  | 226 |         global $DB;
 | 
        
           |  |  | 227 |   | 
        
           |  |  | 228 |         $user = null;
 | 
        
           |  |  | 229 |         $errorkey = false;
 | 
        
           |  |  | 230 |         // The user may use the incorrect field to match the user. This could result in an exception.
 | 
        
           |  |  | 231 |         try {
 | 
        
           |  |  | 232 |             $field = $userfields['field'];
 | 
        
           |  |  | 233 |             // Fields that can be queried in a case-insensitive manner.
 | 
        
           |  |  | 234 |             $caseinsensitivefields = [
 | 
        
           |  |  | 235 |                 'email',
 | 
        
           |  |  | 236 |                 'username',
 | 
        
           |  |  | 237 |             ];
 | 
        
           |  |  | 238 |             // Build query predicate.
 | 
        
           |  |  | 239 |             if (in_array($field, $caseinsensitivefields)) {
 | 
        
           |  |  | 240 |                 // Case-insensitive.
 | 
        
           |  |  | 241 |                 $select = $DB->sql_equal($field, ':' . $field, false);
 | 
        
           |  |  | 242 |             } else {
 | 
        
           |  |  | 243 |                 // Exact-value.
 | 
        
           |  |  | 244 |                 $select = "{$field} = :{$field}";
 | 
        
           |  |  | 245 |             }
 | 
        
           |  |  | 246 |   | 
        
           |  |  | 247 |             // Validate if the user id value is numerical.
 | 
        
           |  |  | 248 |             if ($field === 'id' && !is_numeric($value)) {
 | 
        
           |  |  | 249 |                 $errorkey = 'usermappingerror';
 | 
        
           |  |  | 250 |             }
 | 
        
           |  |  | 251 |             // Make sure the record exists and that there's only one matching record found.
 | 
        
           |  |  | 252 |             $user = $DB->get_record_select('user', $select, array($userfields['field'] => $value), '*', MUST_EXIST);
 | 
        
           |  |  | 253 |         } catch (dml_missing_record_exception $missingex) {
 | 
        
           |  |  | 254 |             $errorkey = 'usermappingerror';
 | 
        
           |  |  | 255 |         } catch (dml_multiple_records_exception $multiex) {
 | 
        
           |  |  | 256 |             $errorkey = 'usermappingerrormultipleusersfound';
 | 
        
           |  |  | 257 |         }
 | 
        
           |  |  | 258 |         // Field may be fine, but no records were returned.
 | 
        
           |  |  | 259 |         if ($errorkey) {
 | 
        
           |  |  | 260 |             $usermappingerrorobj = new stdClass();
 | 
        
           |  |  | 261 |             $usermappingerrorobj->field = $userfields['label'];
 | 
        
           |  |  | 262 |             $usermappingerrorobj->value = $value;
 | 
        
           |  |  | 263 |             $this->cleanup_import(get_string($errorkey, 'grades', $usermappingerrorobj));
 | 
        
           |  |  | 264 |             unset($usermappingerrorobj);
 | 
        
           |  |  | 265 |             return null;
 | 
        
           |  |  | 266 |         }
 | 
        
           |  |  | 267 |         return $user->id;
 | 
        
           |  |  | 268 |     }
 | 
        
           |  |  | 269 |   | 
        
           |  |  | 270 |     /**
 | 
        
           |  |  | 271 |      * Check to see if the feedback matches a grade item.
 | 
        
           |  |  | 272 |      *
 | 
        
           |  |  | 273 |      * @param int $courseid The course ID.
 | 
        
           |  |  | 274 |      * @param int $itemid The ID of the grade item that the feedback relates to.
 | 
        
           |  |  | 275 |      * @param string $value The actual feedback being imported.
 | 
        
           |  |  | 276 |      * @return object Creates a feedback object with the item ID and the feedback value.
 | 
        
           |  |  | 277 |      */
 | 
        
           |  |  | 278 |     protected function create_feedback($courseid, $itemid, $value) {
 | 
        
           |  |  | 279 |         // Case of an id, only maps id of a grade_item.
 | 
        
           |  |  | 280 |         // This was idnumber.
 | 
        
           |  |  | 281 |         if (!new grade_item(array('id' => $itemid, 'courseid' => $courseid))) {
 | 
        
           |  |  | 282 |             // Supplied bad mapping, should not be possible since user
 | 
        
           |  |  | 283 |             // had to pick mapping.
 | 
        
           |  |  | 284 |             $this->cleanup_import(get_string('importfailed', 'grades'));
 | 
        
           |  |  | 285 |             return null;
 | 
        
           |  |  | 286 |         }
 | 
        
           |  |  | 287 |   | 
        
           |  |  | 288 |         // The itemid is the id of the grade item.
 | 
        
           |  |  | 289 |         $feedback = new stdClass();
 | 
        
           |  |  | 290 |         $feedback->itemid   = $itemid;
 | 
        
           |  |  | 291 |         $feedback->feedback = $value;
 | 
        
           |  |  | 292 |         return $feedback;
 | 
        
           |  |  | 293 |     }
 | 
        
           |  |  | 294 |   | 
        
           |  |  | 295 |     /**
 | 
        
           |  |  | 296 |      * This updates existing grade items.
 | 
        
           |  |  | 297 |      *
 | 
        
           |  |  | 298 |      * @param int $courseid The course ID.
 | 
        
           |  |  | 299 |      * @param array $map Mapping information provided by the user.
 | 
        
           |  |  | 300 |      * @param int $key The line that we are currently working on.
 | 
        
           |  |  | 301 |      * @param bool $verbosescales Form setting for grading with scales.
 | 
        
           |  |  | 302 |      * @param string $value The grade value.
 | 
        
           |  |  | 303 |      * @return array grades to be updated.
 | 
        
           |  |  | 304 |      */
 | 
        
           |  |  | 305 |     protected function update_grade_item($courseid, $map, $key, $verbosescales, $value) {
 | 
        
           |  |  | 306 |         // Case of an id, only maps id of a grade_item.
 | 
        
           |  |  | 307 |         // This was idnumber.
 | 
        
           |  |  | 308 |         if (!$gradeitem = new grade_item(array('id' => $map[$key], 'courseid' => $courseid))) {
 | 
        
           |  |  | 309 |             // Supplied bad mapping, should not be possible since user
 | 
        
           |  |  | 310 |             // had to pick mapping.
 | 
        
           |  |  | 311 |             $this->cleanup_import(get_string('importfailed', 'grades'));
 | 
        
           |  |  | 312 |             return null;
 | 
        
           |  |  | 313 |         }
 | 
        
           |  |  | 314 |   | 
        
           |  |  | 315 |         // Check if grade item is locked if so, abort.
 | 
        
           |  |  | 316 |         if ($gradeitem->is_locked()) {
 | 
        
           |  |  | 317 |             $this->cleanup_import(get_string('gradeitemlocked', 'grades'));
 | 
        
           |  |  | 318 |             return null;
 | 
        
           |  |  | 319 |         }
 | 
        
           |  |  | 320 |   | 
        
           |  |  | 321 |         $newgrade = new stdClass();
 | 
        
           |  |  | 322 |         $newgrade->itemid = $gradeitem->id;
 | 
        
           |  |  | 323 |         if ($gradeitem->gradetype == GRADE_TYPE_SCALE and $verbosescales) {
 | 
        
           |  |  | 324 |             if ($value === '' or $value == '-') {
 | 
        
           |  |  | 325 |                 $value = null; // No grade.
 | 
        
           |  |  | 326 |             } else {
 | 
        
           |  |  | 327 |                 $scale = $gradeitem->load_scale();
 | 
        
           |  |  | 328 |                 $scales = explode(',', $scale->scale);
 | 
        
           |  |  | 329 |                 $scales = array_map('trim', $scales); // Hack - trim whitespace around scale options.
 | 
        
           |  |  | 330 |                 array_unshift($scales, '-'); // Scales start at key 1.
 | 
        
           |  |  | 331 |                 $key = array_search($value, $scales);
 | 
        
           |  |  | 332 |                 if ($key === false) {
 | 
        
           |  |  | 333 |                     $this->cleanup_import(get_string('badgrade', 'grades'));
 | 
        
           |  |  | 334 |                     return null;
 | 
        
           |  |  | 335 |                 }
 | 
        
           |  |  | 336 |                 $value = $key;
 | 
        
           |  |  | 337 |             }
 | 
        
           |  |  | 338 |             $newgrade->finalgrade = $value;
 | 
        
           |  |  | 339 |         } else {
 | 
        
           |  |  | 340 |             if ($value === '' or $value == '-') {
 | 
        
           |  |  | 341 |                 $value = null; // No grade.
 | 
        
           |  |  | 342 |             } else {
 | 
        
           |  |  | 343 |                 // If the value has a local decimal or can correctly be unformatted, do it.
 | 
        
           |  |  | 344 |                 $validvalue = unformat_float($value, true);
 | 
        
           |  |  | 345 |                 if ($validvalue !== false) {
 | 
        
           |  |  | 346 |                     $value = $validvalue;
 | 
        
           |  |  | 347 |                 } else {
 | 
        
           |  |  | 348 |                     // Non numeric grade value supplied, possibly mapped wrong column.
 | 
        
           |  |  | 349 |                     $this->cleanup_import(get_string('badgrade', 'grades'));
 | 
        
           |  |  | 350 |                     return null;
 | 
        
           |  |  | 351 |                 }
 | 
        
           |  |  | 352 |             }
 | 
        
           |  |  | 353 |             $newgrade->finalgrade = $value;
 | 
        
           |  |  | 354 |         }
 | 
        
           |  |  | 355 |         $this->newgrades[] = $newgrade;
 | 
        
           |  |  | 356 |         return $this->newgrades;
 | 
        
           |  |  | 357 |     }
 | 
        
           |  |  | 358 |   | 
        
           |  |  | 359 |     /**
 | 
        
           |  |  | 360 |      * Clean up failed CSV grade import. Clears the temp table for inserting grades.
 | 
        
           |  |  | 361 |      *
 | 
        
           |  |  | 362 |      * @param string $notification The error message to display from the unsuccessful grade import.
 | 
        
           |  |  | 363 |      */
 | 
        
           |  |  | 364 |     protected function cleanup_import($notification) {
 | 
        
           |  |  | 365 |         $this->status = false;
 | 
        
           |  |  | 366 |         import_cleanup($this->importcode);
 | 
        
           |  |  | 367 |         $this->gradebookerrors[] = $notification;
 | 
        
           |  |  | 368 |     }
 | 
        
           |  |  | 369 |   | 
        
           |  |  | 370 |     /**
 | 
        
           |  |  | 371 |      * Check user mapping.
 | 
        
           |  |  | 372 |      *
 | 
        
           |  |  | 373 |      * @param string $mappingidentifier The user field that we are matching together.
 | 
        
           |  |  | 374 |      * @param string $value The value we are checking / importing.
 | 
        
           |  |  | 375 |      * @param array $header The column headers of the csv file.
 | 
        
           |  |  | 376 |      * @param array $map Mapping information provided by the user.
 | 
        
           |  |  | 377 |      * @param int $key Current row identifier.
 | 
        
           |  |  | 378 |      * @param int $courseid The course ID.
 | 
        
           |  |  | 379 |      * @param int $feedbackgradeid The ID of the grade item that the feedback relates to.
 | 
        
           |  |  | 380 |      * @param bool $verbosescales Form setting for grading with scales.
 | 
        
           |  |  | 381 |      */
 | 
        
           |  |  | 382 |     protected function map_user_data_with_value($mappingidentifier, $value, $header, $map, $key, $courseid, $feedbackgradeid,
 | 
        
           |  |  | 383 |             $verbosescales) {
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |         // Fields that the user can be mapped from.
 | 
        
           |  |  | 386 |         $userfields = array(
 | 
        
           |  |  | 387 |             'userid' => array(
 | 
        
           |  |  | 388 |                 'field' => 'id',
 | 
        
           |  |  | 389 |                 'label' => 'id',
 | 
        
           |  |  | 390 |             ),
 | 
        
           |  |  | 391 |             'useridnumber' => array(
 | 
        
           |  |  | 392 |                 'field' => 'idnumber',
 | 
        
           |  |  | 393 |                 'label' => 'idnumber',
 | 
        
           |  |  | 394 |             ),
 | 
        
           |  |  | 395 |             'useremail' => array(
 | 
        
           |  |  | 396 |                 'field' => 'email',
 | 
        
           |  |  | 397 |                 'label' => 'email address',
 | 
        
           |  |  | 398 |             ),
 | 
        
           |  |  | 399 |             'username' => array(
 | 
        
           |  |  | 400 |                 'field' => 'username',
 | 
        
           |  |  | 401 |                 'label' => 'username',
 | 
        
           |  |  | 402 |             ),
 | 
        
           |  |  | 403 |         );
 | 
        
           |  |  | 404 |   | 
        
           |  |  | 405 |         switch ($mappingidentifier) {
 | 
        
           |  |  | 406 |             case 'userid':
 | 
        
           |  |  | 407 |             case 'useridnumber':
 | 
        
           |  |  | 408 |             case 'useremail':
 | 
        
           |  |  | 409 |             case 'username':
 | 
        
           |  |  | 410 |                 $this->studentid = $this->check_user_exists($value, $userfields[$mappingidentifier]);
 | 
        
           |  |  | 411 |             break;
 | 
        
           |  |  | 412 |             case 'new':
 | 
        
           |  |  | 413 |                 $this->import_new_grade_item($header, $key, $value);
 | 
        
           |  |  | 414 |             break;
 | 
        
           |  |  | 415 |             case 'feedback':
 | 
        
           |  |  | 416 |                 if ($feedbackgradeid) {
 | 
        
           |  |  | 417 |                     $feedback = $this->create_feedback($courseid, $feedbackgradeid, $value);
 | 
        
           |  |  | 418 |                     if (isset($feedback)) {
 | 
        
           |  |  | 419 |                         $this->newfeedbacks[] = $feedback;
 | 
        
           |  |  | 420 |                     }
 | 
        
           |  |  | 421 |                 }
 | 
        
           |  |  | 422 |             break;
 | 
        
           |  |  | 423 |             default:
 | 
        
           |  |  | 424 |                 // Existing grade items.
 | 
        
           |  |  | 425 |                 if (!empty($map[$key])) {
 | 
        
           |  |  | 426 |                     $this->newgrades = $this->update_grade_item($courseid, $map, $key, $verbosescales, $value,
 | 
        
           |  |  | 427 |                             $mappingidentifier);
 | 
        
           |  |  | 428 |                 }
 | 
        
           |  |  | 429 |                 // Otherwise, we ignore this column altogether because user has chosen
 | 
        
           |  |  | 430 |                 // to ignore them (e.g. institution, address etc).
 | 
        
           |  |  | 431 |             break;
 | 
        
           |  |  | 432 |         }
 | 
        
           |  |  | 433 |     }
 | 
        
           |  |  | 434 |   | 
        
           |  |  | 435 |     /**
 | 
        
           |  |  | 436 |      * Checks and prepares grade data for inserting into the gradebook.
 | 
        
           |  |  | 437 |      *
 | 
        
           |  |  | 438 |      * @param array $header Column headers of the CSV file.
 | 
        
           |  |  | 439 |      * @param object $formdata Mapping information from the preview page.
 | 
        
           |  |  | 440 |      * @param object $csvimport csv import reader object for iterating over the imported CSV file.
 | 
        
           |  |  | 441 |      * @param int $courseid The course ID.
 | 
        
           |  |  | 442 |      * @param bool $separatemode If we have groups are they separate?
 | 
        
           |  |  | 443 |      * @param mixed $currentgroup current group information.
 | 
        
           |  |  | 444 |      * @param bool $verbosescales Form setting for grading with scales.
 | 
        
           |  |  | 445 |      * @return bool True if the status for importing is okay, false if there are errors.
 | 
        
           |  |  | 446 |      */
 | 
        
           |  |  | 447 |     public function prepare_import_grade_data($header, $formdata, $csvimport, $courseid, $separatemode, $currentgroup,
 | 
        
           |  |  | 448 |             $verbosescales) {
 | 
        
           |  |  | 449 |         global $DB, $USER;
 | 
        
           |  |  | 450 |   | 
        
           |  |  | 451 |         // The import code is used for inserting data into the grade tables.
 | 
        
           |  |  | 452 |         $this->importcode = $formdata->importcode;
 | 
        
           |  |  | 453 |         $this->status = true;
 | 
        
           |  |  | 454 |         $this->headers = $header;
 | 
        
           |  |  | 455 |         $this->studentid = null;
 | 
        
           |  |  | 456 |         $this->gradebookerrors = null;
 | 
        
           |  |  | 457 |         $forceimport = $formdata->forceimport;
 | 
        
           |  |  | 458 |         // Temporary array to keep track of what new headers are processed.
 | 
        
           |  |  | 459 |         $this->newgradeitems = array();
 | 
        
           |  |  | 460 |         $this->trim_headers();
 | 
        
           |  |  | 461 |         $timeexportkey = null;
 | 
        
           |  |  | 462 |         $map = array();
 | 
        
           |  |  | 463 |         // Loops mapping_0, mapping_1 .. mapping_n and construct $map array.
 | 
        
           |  |  | 464 |         foreach ($header as $i => $head) {
 | 
        
           |  |  | 465 |             if (isset($formdata->{'mapping_'.$i})) {
 | 
        
           |  |  | 466 |                 $map[$i] = $formdata->{'mapping_'.$i};
 | 
        
           |  |  | 467 |             }
 | 
        
           |  |  | 468 |             if ($head == get_string('timeexported', 'gradeexport_txt')) {
 | 
        
           |  |  | 469 |                 $timeexportkey = $i;
 | 
        
           |  |  | 470 |             }
 | 
        
           |  |  | 471 |         }
 | 
        
           |  |  | 472 |   | 
        
           |  |  | 473 |         // If mapping information is supplied.
 | 
        
           |  |  | 474 |         $map[clean_param($formdata->mapfrom, PARAM_RAW)] = clean_param($formdata->mapto, PARAM_RAW);
 | 
        
           |  |  | 475 |   | 
        
           |  |  | 476 |         // Check for mapto collisions.
 | 
        
           |  |  | 477 |         $maperrors = array();
 | 
        
           |  |  | 478 |         foreach ($map as $i => $j) {
 | 
        
           |  |  | 479 |             if (($j == 0) || ($j == 'new')) {
 | 
        
           |  |  | 480 |                 // You can have multiple ignores or multiple new grade items.
 | 
        
           |  |  | 481 |                 continue;
 | 
        
           |  |  | 482 |             } else {
 | 
        
           |  |  | 483 |                 if (!isset($maperrors[$j])) {
 | 
        
           |  |  | 484 |                     $maperrors[$j] = true;
 | 
        
           |  |  | 485 |                 } else {
 | 
        
           |  |  | 486 |                     // Collision.
 | 
        
           |  |  | 487 |                     throw new \moodle_exception('cannotmapfield', '', '', $j);
 | 
        
           |  |  | 488 |                 }
 | 
        
           |  |  | 489 |             }
 | 
        
           |  |  | 490 |         }
 | 
        
           |  |  | 491 |   | 
        
           |  |  | 492 |         $this->raise_limits();
 | 
        
           |  |  | 493 |   | 
        
           |  |  | 494 |         $csvimport->init();
 | 
        
           |  |  | 495 |   | 
        
           |  |  | 496 |         while ($line = $csvimport->next()) {
 | 
        
           |  |  | 497 |             if (count($line) <= 1) {
 | 
        
           |  |  | 498 |                 // There is no data on this line, move on.
 | 
        
           |  |  | 499 |                 continue;
 | 
        
           |  |  | 500 |             }
 | 
        
           |  |  | 501 |   | 
        
           |  |  | 502 |             // Array to hold all grades to be inserted.
 | 
        
           |  |  | 503 |             $this->newgrades = array();
 | 
        
           |  |  | 504 |             // Array to hold all feedback.
 | 
        
           |  |  | 505 |             $this->newfeedbacks = array();
 | 
        
           |  |  | 506 |             // Each line is a student record.
 | 
        
           |  |  | 507 |             foreach ($line as $key => $value) {
 | 
        
           |  |  | 508 |   | 
        
           |  |  | 509 |                 $value = clean_param($value, PARAM_RAW);
 | 
        
           |  |  | 510 |                 $value = trim($value);
 | 
        
           |  |  | 511 |   | 
        
           |  |  | 512 |                 /*
 | 
        
           |  |  | 513 |                  * the options are
 | 
        
           |  |  | 514 |                  * 1) userid, useridnumber, usermail, username - used to identify user row
 | 
        
           |  |  | 515 |                  * 2) new - new grade item
 | 
        
           |  |  | 516 |                  * 3) id - id of the old grade item to map onto
 | 
        
           |  |  | 517 |                  * 3) feedback_id - feedback for grade item id
 | 
        
           |  |  | 518 |                  */
 | 
        
           |  |  | 519 |   | 
        
           |  |  | 520 |                 // Explode the mapping for feedback into a label 'feedback' and the identifying number.
 | 
        
           |  |  | 521 |                 $mappingbase = explode("_", $map[$key]);
 | 
        
           |  |  | 522 |                 $mappingidentifier = $mappingbase[0];
 | 
        
           |  |  | 523 |                 // Set the feedback identifier if it exists.
 | 
        
           |  |  | 524 |                 if (isset($mappingbase[1])) {
 | 
        
           |  |  | 525 |                     $feedbackgradeid = (int)$mappingbase[1];
 | 
        
           |  |  | 526 |                 } else {
 | 
        
           |  |  | 527 |                     $feedbackgradeid = '';
 | 
        
           |  |  | 528 |                 }
 | 
        
           |  |  | 529 |   | 
        
           |  |  | 530 |                 $this->map_user_data_with_value($mappingidentifier, $value, $header, $map, $key, $courseid, $feedbackgradeid,
 | 
        
           |  |  | 531 |                         $verbosescales);
 | 
        
           |  |  | 532 |                 if ($this->status === false) {
 | 
        
           |  |  | 533 |                     return $this->status;
 | 
        
           |  |  | 534 |                 }
 | 
        
           |  |  | 535 |             }
 | 
        
           |  |  | 536 |   | 
        
           |  |  | 537 |             // No user mapping supplied at all, or user mapping failed.
 | 
        
           |  |  | 538 |             if (empty($this->studentid) || !is_numeric($this->studentid)) {
 | 
        
           |  |  | 539 |                 // User not found, abort whole import.
 | 
        
           |  |  | 540 |                 $this->cleanup_import(get_string('usermappingerrorusernotfound', 'grades'));
 | 
        
           |  |  | 541 |                 break;
 | 
        
           |  |  | 542 |             }
 | 
        
           |  |  | 543 |   | 
        
           |  |  | 544 |             if ($separatemode and !groups_is_member($currentgroup, $this->studentid)) {
 | 
        
           |  |  | 545 |                 // Not allowed to import into this group, abort.
 | 
        
           |  |  | 546 |                 $this->cleanup_import(get_string('usermappingerrorcurrentgroup', 'grades'));
 | 
        
           |  |  | 547 |                 break;
 | 
        
           |  |  | 548 |             }
 | 
        
           |  |  | 549 |   | 
        
           |  |  | 550 |             // Insert results of this students into buffer.
 | 
        
           |  |  | 551 |             if ($this->status and !empty($this->newgrades)) {
 | 
        
           |  |  | 552 |   | 
        
           |  |  | 553 |                 foreach ($this->newgrades as $newgrade) {
 | 
        
           |  |  | 554 |   | 
        
           |  |  | 555 |                     // Check if grade_grade is locked and if so, abort.
 | 
        
           |  |  | 556 |                     if (!empty($newgrade->itemid) and $gradegrade = new grade_grade(array('itemid' => $newgrade->itemid,
 | 
        
           |  |  | 557 |                             'userid' => $this->studentid))) {
 | 
        
           |  |  | 558 |                         if ($gradegrade->is_locked()) {
 | 
        
           |  |  | 559 |                             // Individual grade locked.
 | 
        
           |  |  | 560 |                             $this->cleanup_import(get_string('gradelocked', 'grades'));
 | 
        
           |  |  | 561 |                             return $this->status;
 | 
        
           |  |  | 562 |                         }
 | 
        
           |  |  | 563 |                         // Check if the force import option is disabled and the last exported date column is present.
 | 
        
           |  |  | 564 |                         if (!$forceimport && !empty($timeexportkey)) {
 | 
        
           |  |  | 565 |                             $exportedtime = $line[$timeexportkey];
 | 
        
           |  |  | 566 |                             if (clean_param($exportedtime, PARAM_INT) != $exportedtime || $exportedtime > time() ||
 | 
        
           |  |  | 567 |                                     $exportedtime < strtotime("-1 year", time())) {
 | 
        
           |  |  | 568 |                                 // The date is invalid, or in the future, or more than a year old.
 | 
        
           |  |  | 569 |                                 $this->cleanup_import(get_string('invalidgradeexporteddate', 'grades'));
 | 
        
           |  |  | 570 |                                 return $this->status;
 | 
        
           |  |  | 571 |   | 
        
           |  |  | 572 |                             }
 | 
        
           |  |  | 573 |                             $timemodified = $gradegrade->get_dategraded();
 | 
        
           |  |  | 574 |                             if (!empty($timemodified) && ($exportedtime < $timemodified)) {
 | 
        
           |  |  | 575 |                                 // The item was graded after we exported it, we return here not to override it.
 | 
        
           |  |  | 576 |                                 $user = core_user::get_user($this->studentid);
 | 
        
           |  |  | 577 |                                 $this->cleanup_import(get_string('gradealreadyupdated', 'grades', fullname($user)));
 | 
        
           |  |  | 578 |                                 return $this->status;
 | 
        
           |  |  | 579 |                             }
 | 
        
           |  |  | 580 |                         }
 | 
        
           |  |  | 581 |                     }
 | 
        
           |  |  | 582 |                     if (isset($newgrade->itemid)) {
 | 
        
           |  |  | 583 |                         $gradeitem = new grade_item(['id' => $newgrade->itemid]);
 | 
        
           |  |  | 584 |                     } else if (isset($newgrade->newgradeitem)) {
 | 
        
           |  |  | 585 |                         $gradeitem = new grade_item(['id' => $newgrade->newgradeitem]);
 | 
        
           |  |  | 586 |                     }
 | 
        
           |  |  | 587 |                     $insertid = isset($gradeitem) ? self::insert_grade_record($newgrade, $this->studentid, $gradeitem) : null;
 | 
        
           |  |  | 588 |                     // Check to see if the insert was successful.
 | 
        
           |  |  | 589 |                     if (empty($insertid)) {
 | 
        
           |  |  | 590 |                         return null;
 | 
        
           |  |  | 591 |                     }
 | 
        
           |  |  | 592 |                 }
 | 
        
           |  |  | 593 |             }
 | 
        
           |  |  | 594 |   | 
        
           |  |  | 595 |             // Updating/inserting all comments here.
 | 
        
           |  |  | 596 |             if ($this->status and !empty($this->newfeedbacks)) {
 | 
        
           |  |  | 597 |                 foreach ($this->newfeedbacks as $newfeedback) {
 | 
        
           |  |  | 598 |                     $sql = "SELECT *
 | 
        
           |  |  | 599 |                               FROM {grade_import_values}
 | 
        
           |  |  | 600 |                              WHERE importcode=? AND userid=? AND itemid=? AND importer=?";
 | 
        
           |  |  | 601 |                     if ($feedback = $DB->get_record_sql($sql, array($this->importcode, $this->studentid, $newfeedback->itemid,
 | 
        
           |  |  | 602 |                             $USER->id))) {
 | 
        
           |  |  | 603 |                         $newfeedback->id = $feedback->id;
 | 
        
           |  |  | 604 |                         $DB->update_record('grade_import_values', $newfeedback);
 | 
        
           |  |  | 605 |   | 
        
           |  |  | 606 |                     } else {
 | 
        
           |  |  | 607 |                         // The grade item for this is not updated.
 | 
        
           |  |  | 608 |                         $newfeedback->importonlyfeedback = true;
 | 
        
           |  |  | 609 |                         $insertid = self::insert_grade_record($newfeedback, $this->studentid, new grade_item(['id' => $newfeedback->itemid]));
 | 
        
           |  |  | 610 |                         // Check to see if the insert was successful.
 | 
        
           |  |  | 611 |                         if (empty($insertid)) {
 | 
        
           |  |  | 612 |                             return null;
 | 
        
           |  |  | 613 |                         }
 | 
        
           |  |  | 614 |                     }
 | 
        
           |  |  | 615 |                 }
 | 
        
           |  |  | 616 |             }
 | 
        
           |  |  | 617 |         }
 | 
        
           |  |  | 618 |         return $this->status;
 | 
        
           |  |  | 619 |     }
 | 
        
           |  |  | 620 |   | 
        
           |  |  | 621 |     /**
 | 
        
           |  |  | 622 |      * Returns the headers parameter for this class.
 | 
        
           |  |  | 623 |      *
 | 
        
           |  |  | 624 |      * @return array returns headers parameter for this class.
 | 
        
           |  |  | 625 |      */
 | 
        
           |  |  | 626 |     public function get_headers() {
 | 
        
           |  |  | 627 |         return $this->headers;
 | 
        
           |  |  | 628 |     }
 | 
        
           |  |  | 629 |   | 
        
           |  |  | 630 |     /**
 | 
        
           |  |  | 631 |      * Returns the error parameter for this class.
 | 
        
           |  |  | 632 |      *
 | 
        
           |  |  | 633 |      * @return string returns error parameter for this class.
 | 
        
           |  |  | 634 |      */
 | 
        
           |  |  | 635 |     public function get_error() {
 | 
        
           |  |  | 636 |         return $this->error;
 | 
        
           |  |  | 637 |     }
 | 
        
           |  |  | 638 |   | 
        
           |  |  | 639 |     /**
 | 
        
           |  |  | 640 |      * Returns the iid parameter for this class.
 | 
        
           |  |  | 641 |      *
 | 
        
           |  |  | 642 |      * @return int returns iid parameter for this class.
 | 
        
           |  |  | 643 |      */
 | 
        
           |  |  | 644 |     public function get_iid() {
 | 
        
           |  |  | 645 |         return $this->iid;
 | 
        
           |  |  | 646 |     }
 | 
        
           |  |  | 647 |   | 
        
           |  |  | 648 |     /**
 | 
        
           |  |  | 649 |      * Returns the preview_data parameter for this class.
 | 
        
           |  |  | 650 |      *
 | 
        
           |  |  | 651 |      * @return array returns previewdata parameter for this class.
 | 
        
           |  |  | 652 |      */
 | 
        
           |  |  | 653 |     public function get_previewdata() {
 | 
        
           |  |  | 654 |         return $this->previewdata;
 | 
        
           |  |  | 655 |     }
 | 
        
           |  |  | 656 |   | 
        
           |  |  | 657 |     /**
 | 
        
           |  |  | 658 |      * Returns the gradebookerrors parameter for this class.
 | 
        
           |  |  | 659 |      *
 | 
        
           |  |  | 660 |      * @return array returns gradebookerrors parameter for this class.
 | 
        
           |  |  | 661 |      */
 | 
        
           |  |  | 662 |     public function get_gradebookerrors() {
 | 
        
           |  |  | 663 |         return $this->gradebookerrors;
 | 
        
           |  |  | 664 |     }
 | 
        
           |  |  | 665 | }
 |