Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/mod/workshop/form/numerrors/lib.php
Go to the documentation of this file.
00001 <?php
00002 
00003 // This file is part of Moodle - http://moodle.org/
00004 //
00005 // Moodle is free software: you can redistribute it and/or modify
00006 // it under the terms of the GNU General Public License as published by
00007 // the Free Software Foundation, either version 3 of the License, or
00008 // (at your option) any later version.
00009 //
00010 // Moodle is distributed in the hope that it will be useful,
00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013 // GNU General Public License for more details.
00014 //
00015 // You should have received a copy of the GNU General Public License
00016 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00017 
00027 defined('MOODLE_INTERNAL') || die();
00028 
00029 require_once(dirname(dirname(__FILE__)) . '/lib.php');  // interface definition
00030 require_once($CFG->libdir . '/gradelib.php');           // to handle float vs decimal issues
00031 
00032 function workshopform_numerrors_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload) {
00033     global $DB;
00034 
00035     if ($context->contextlevel != CONTEXT_MODULE) {
00036         return false;
00037     }
00038 
00039     require_login($course, true, $cm);
00040 
00041     if ($filearea !== 'description') {
00042         return false;
00043     }
00044 
00045     $itemid = (int)array_shift($args); // the id of the assessment form dimension
00046     if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) {
00047         send_file_not_found();
00048     }
00049 
00050     if (!$dimension = $DB->get_record('workshopform_numerrors', array('id' => $itemid ,'workshopid' => $workshop->id))) {
00051         send_file_not_found();
00052     }
00053 
00054     // TODO now make sure the user is allowed to see the file
00055     // (media embedded into the dimension description)
00056     $fs = get_file_storage();
00057     $relativepath = implode('/', $args);
00058     $fullpath = "/$context->id/workshopform_numerrors/$filearea/$itemid/$relativepath";
00059     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
00060         return false;
00061     }
00062 
00063     // finally send the file
00064     send_stored_file($file);
00065 }
00066 
00070 class workshop_numerrors_strategy implements workshop_strategy {
00071 
00073     const MINDIMS = 3;
00074 
00076     const ADDDIMS = 2;
00077 
00079     protected $workshop;
00080 
00082     protected $dimensions = null;
00083 
00085     protected $mappings = null;
00086 
00088     protected $descriptionopts;
00089 
00096     public function __construct(workshop $workshop) {
00097         $this->workshop         = $workshop;
00098         $this->dimensions       = $this->load_fields();
00099         $this->mappings         = $this->load_mappings();
00100         $this->descriptionopts  = array('trusttext' => true, 'subdirs' => false, 'maxfiles' => -1);
00101     }
00102 
00108     public function get_edit_strategy_form($actionurl=null) {
00109         global $CFG;    // needed because the included files use it
00110         global $PAGE;
00111 
00112         require_once(dirname(__FILE__) . '/edit_form.php');
00113 
00114         $fields             = $this->prepare_form_fields($this->dimensions, $this->mappings);
00115         $nodimensions       = count($this->dimensions);
00116         $norepeatsdefault   = max($nodimensions + self::ADDDIMS, self::MINDIMS);
00117         $norepeats          = optional_param('norepeats', $norepeatsdefault, PARAM_INT);    // number of dimensions
00118         $noadddims          = optional_param('noadddims', '', PARAM_ALPHA);                 // shall we add more?
00119         if ($noadddims) {
00120             $norepeats += self::ADDDIMS;
00121         }
00122 
00123         // Append editor context to editor options, giving preference to existing context.
00124         $this->descriptionopts = array_merge(array('context' => $PAGE->context), $this->descriptionopts);
00125 
00126         // prepare the embeded files
00127         for ($i = 0; $i < $nodimensions; $i++) {
00128             // prepare all editor elements
00129             $fields = file_prepare_standard_editor($fields, 'description__idx_'.$i, $this->descriptionopts,
00130                 $PAGE->context, 'workshopform_numerrors', 'description', $fields->{'dimensionid__idx_'.$i});
00131         }
00132 
00133         $customdata = array();
00134         $customdata['workshop'] = $this->workshop;
00135         $customdata['strategy'] = $this;
00136         $customdata['norepeats'] = $norepeats;
00137         $customdata['nodimensions'] = $nodimensions;
00138         $customdata['descriptionopts'] = $this->descriptionopts;
00139         $customdata['current']  = $fields;
00140         $attributes = array('class' => 'editstrategyform');
00141 
00142         return new workshop_edit_numerrors_strategy_form($actionurl, $customdata, 'post', '', $attributes);
00143     }
00144 
00157     public function save_edit_strategy_form(stdclass $data) {
00158         global $DB, $PAGE;
00159 
00160         $workshopid = $data->workshopid;
00161         $norepeats  = $data->norepeats;
00162 
00163         $data       = $this->prepare_database_fields($data);
00164         $records    = $data->numerrors; // data to be saved into {workshopform_numerrors}
00165         $mappings   = $data->mappings;  // data to be saved into {workshopform_numerrors_map}
00166         $todelete   = array();          // dimension ids to be deleted
00167         $maxnonegative = 0;             // maximum number of (weighted) negative responses
00168 
00169         for ($i=0; $i < $norepeats; $i++) {
00170             $record = $records[$i];
00171             if (0 == strlen(trim($record->description_editor['text']))) {
00172                 if (!empty($record->id)) {
00173                     // existing dimension record with empty description - to be deleted
00174                     $todelete[] = $record->id;
00175                 }
00176                 continue;
00177             }
00178             if (empty($record->id)) {
00179                 // new field
00180                 $record->id = $DB->insert_record('workshopform_numerrors', $record);
00181             } else {
00182                 // exiting field
00183                 $DB->update_record('workshopform_numerrors', $record);
00184             }
00185             $maxnonegative += $record->weight;
00186             // re-save with correct path to embeded media files
00187             $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts, $PAGE->context,
00188                                                       'workshopform_numerrors', 'description', $record->id);
00189             $DB->update_record('workshopform_numerrors', $record);
00190         }
00191         $this->delete_dimensions($todelete);
00192 
00193         // re-save the mappings
00194         $todelete = array();
00195         foreach ($data->mappings as $nonegative => $grade) {
00196             if (is_null($grade)) {
00197                 // no grade set for this number of negative responses
00198                 $todelete[] = $nonegative;
00199                 continue;
00200             }
00201             if (isset($this->mappings[$nonegative])) {
00202                 $DB->set_field('workshopform_numerrors_map', 'grade', $grade,
00203                             array('workshopid' => $this->workshop->id, 'nonegative' => $nonegative));
00204             } else {
00205                 $DB->insert_record('workshopform_numerrors_map',
00206                             (object)array('workshopid' => $this->workshop->id, 'nonegative' => $nonegative, 'grade' => $grade));
00207             }
00208         }
00209         // clear mappings that are not valid any more
00210         if (!empty($todelete)) {
00211             list($insql, $params) = $DB->get_in_or_equal($todelete, SQL_PARAMS_NAMED);
00212             $insql = "nonegative $insql OR ";
00213         } else {
00214             $insql = '';
00215         }
00216         $sql = "DELETE FROM {workshopform_numerrors_map}
00217                       WHERE (($insql nonegative > :maxnonegative) AND (workshopid = :workshopid))";
00218         $params['maxnonegative'] = $maxnonegative;
00219         $params['workshopid']   = $this->workshop->id;
00220         $DB->execute($sql, $params);
00221     }
00222 
00232     public function get_assessment_form(moodle_url $actionurl=null, $mode='preview', stdclass $assessment=null, $editable=true, $options=array()) {
00233         global $CFG;    // needed because the included files use it
00234         global $PAGE;
00235         global $DB;
00236         require_once(dirname(__FILE__) . '/assessment_form.php');
00237 
00238         $fields         = $this->prepare_form_fields($this->dimensions, $this->mappings);
00239         $nodimensions   = count($this->dimensions);
00240 
00241         // rewrite URLs to the embeded files
00242         for ($i = 0; $i < $nodimensions; $i++) {
00243             $fields->{'description__idx_'.$i} = file_rewrite_pluginfile_urls($fields->{'description__idx_'.$i},
00244                 'pluginfile.php', $PAGE->context->id, 'workshopform_numerrors', 'description', $fields->{'dimensionid__idx_'.$i});
00245         }
00246 
00247         if ('assessment' === $mode and !empty($assessment)) {
00248             // load the previously saved assessment data
00249             $grades = $this->get_current_assessment_data($assessment);
00250             $current = new stdclass();
00251             for ($i = 0; $i < $nodimensions; $i++) {
00252                 $dimid = $fields->{'dimensionid__idx_'.$i};
00253                 if (isset($grades[$dimid])) {
00254                     $current->{'gradeid__idx_'.$i}      = $grades[$dimid]->id;
00255                     $current->{'grade__idx_'.$i}        = ($grades[$dimid]->grade == 0 ? -1 : 1);
00256                     $current->{'peercomment__idx_'.$i}  = $grades[$dimid]->peercomment;
00257                 }
00258             }
00259         }
00260 
00261         // set up the required custom data common for all strategies
00262         $customdata['workshop'] = $this->workshop;
00263         $customdata['strategy'] = $this;
00264         $customdata['mode']     = $mode;
00265         $customdata['options']  = $options;
00266 
00267         // set up strategy-specific custom data
00268         $customdata['nodims']   = $nodimensions;
00269         $customdata['fields']   = $fields;
00270         $customdata['current']  = isset($current) ? $current : null;
00271         $attributes = array('class' => 'assessmentform numerrors');
00272 
00273         return new workshop_numerrors_assessment_form($actionurl, $customdata, 'post', '', $attributes, $editable);
00274     }
00275 
00285     public function save_assessment(stdclass $assessment, stdclass $data) {
00286         global $DB;
00287 
00288         if (!isset($data->nodims)) {
00289             throw new coding_exception('You did not send me the number of assessment dimensions to process');
00290         }
00291         for ($i = 0; $i < $data->nodims; $i++) {
00292             $grade = new stdclass();
00293             $grade->id                  = $data->{'gradeid__idx_' . $i};
00294             $grade->assessmentid        = $assessment->id;
00295             $grade->strategy            = 'numerrors';
00296             $grade->dimensionid         = $data->{'dimensionid__idx_' . $i};
00297             $grade->grade               = ($data->{'grade__idx_' . $i} <= 0 ? 0 : 1);
00298             $grade->peercomment         = $data->{'peercomment__idx_' . $i};
00299             $grade->peercommentformat   = FORMAT_HTML;
00300             if (empty($grade->id)) {
00301                 // new grade
00302                 $grade->id = $DB->insert_record('workshop_grades', $grade);
00303             } else {
00304                 // updated grade
00305                 $DB->update_record('workshop_grades', $grade);
00306             }
00307         }
00308         return $this->update_peer_grade($assessment);
00309     }
00310 
00316     public function form_ready() {
00317         if (count($this->dimensions) > 0) {
00318             return true;
00319         }
00320         return false;
00321     }
00322 
00326     public function get_assessments_recordset($restrict=null) {
00327        global $DB;
00328 
00329         $sql = 'SELECT s.id AS submissionid,
00330                        a.id AS assessmentid, a.weight AS assessmentweight, a.reviewerid, a.gradinggrade,
00331                        g.dimensionid, g.grade
00332                   FROM {workshop_submissions} s
00333                   JOIN {workshop_assessments} a ON (a.submissionid = s.id)
00334                   JOIN {workshop_grades} g ON (g.assessmentid = a.id AND g.strategy = :strategy)
00335                  WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
00336         $params = array('workshopid' => $this->workshop->id, 'strategy' => $this->workshop->strategy);
00337 
00338         if (is_null($restrict)) {
00339             // update all users - no more conditions
00340         } elseif (!empty($restrict)) {
00341             list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
00342             $sql .= " AND a.reviewerid $usql";
00343             $params = array_merge($params, $uparams);
00344         } else {
00345             throw new coding_exception('Empty value is not a valid parameter here');
00346         }
00347 
00348         $sql .= ' ORDER BY s.id'; // this is important for bulk processing
00349 
00350         return $DB->get_recordset_sql($sql, $params);
00351 
00352     }
00353 
00357     public function get_dimensions_info() {
00358         global $DB;
00359 
00360         $params = array('workshopid' => $this->workshop->id);
00361         $dimrecords = $DB->get_records('workshopform_numerrors', array('workshopid' => $this->workshop->id), 'sort', 'id,weight');
00362         foreach ($dimrecords as $dimid => $dimrecord) {
00363             $dimrecords[$dimid]->min = 0;
00364             $dimrecords[$dimid]->max = 1;
00365         }
00366         return $dimrecords;
00367     }
00368 
00378     public static function scale_used($scaleid, $workshopid=null) {
00379         return false;
00380     }
00381 
00389     public static function delete_instance($workshopid) {
00390         global $DB;
00391 
00392         $DB->delete_records('workshopform_numerrors', array('workshopid' => $workshopid));
00393         $DB->delete_records('workshopform_numerrors_map', array('workshopid' => $workshopid));
00394     }
00395 
00397     // Internal methods                                                           //
00399 
00405     protected function load_fields() {
00406         global $DB;
00407 
00408         $sql = 'SELECT *
00409                   FROM {workshopform_numerrors}
00410                  WHERE workshopid = :workshopid
00411                  ORDER BY sort';
00412         $params = array('workshopid' => $this->workshop->id);
00413 
00414         return $DB->get_records_sql($sql, $params);
00415     }
00416 
00422     protected function load_mappings() {
00423         global $DB;
00424         return $DB->get_records('workshopform_numerrors_map', array('workshopid' => $this->workshop->id), 'nonegative',
00425                                 'nonegative,grade'); // we can use nonegative as key here as it must be unique within workshop
00426     }
00427 
00435     protected function prepare_form_fields(array $dims, array $maps) {
00436 
00437         $formdata = new stdclass();
00438         $key = 0;
00439         foreach ($dims as $dimension) {
00440             $formdata->{'dimensionid__idx_' . $key}             = $dimension->id;
00441             $formdata->{'description__idx_' . $key}             = $dimension->description;
00442             $formdata->{'description__idx_' . $key.'format'}    = $dimension->descriptionformat;
00443             $formdata->{'grade0__idx_' . $key}                  = $dimension->grade0;
00444             $formdata->{'grade1__idx_' . $key}                  = $dimension->grade1;
00445             $formdata->{'weight__idx_' . $key}                  = $dimension->weight;
00446             $key++;
00447         }
00448 
00449         foreach ($maps as $nonegative => $map) {
00450             $formdata->{'map__idx_' . $nonegative} = $map->grade;
00451         }
00452 
00453         return $formdata;
00454     }
00455 
00464     protected function delete_dimensions(array $ids) {
00465         global $DB, $PAGE;
00466 
00467         $fs         = get_file_storage();
00468         foreach ($ids as $id) {
00469             $fs->delete_area_files($PAGE->context->id, 'workshopform_numerrors', 'description', $id);
00470         }
00471         $DB->delete_records_list('workshopform_numerrors', 'id', $ids);
00472     }
00473 
00485     protected function prepare_database_fields(stdclass $raw) {
00486         global $PAGE;
00487 
00488         $cook               = new stdclass();   // to be returned
00489         $cook->numerrors    = array();          // to be stored in {workshopform_numerrors}
00490         $cook->mappings     = array();          // to be stored in {workshopform_numerrors_map}
00491 
00492         for ($i = 0; $i < $raw->norepeats; $i++) {
00493             $cook->numerrors[$i]                        = new stdclass();
00494             $cook->numerrors[$i]->id                    = $raw->{'dimensionid__idx_'.$i};
00495             $cook->numerrors[$i]->workshopid            = $this->workshop->id;
00496             $cook->numerrors[$i]->sort                  = $i + 1;
00497             $cook->numerrors[$i]->description_editor    = $raw->{'description__idx_'.$i.'_editor'};
00498             $cook->numerrors[$i]->grade0                = $raw->{'grade0__idx_'.$i};
00499             $cook->numerrors[$i]->grade1                = $raw->{'grade1__idx_'.$i};
00500             $cook->numerrors[$i]->weight                = $raw->{'weight__idx_'.$i};
00501         }
00502 
00503         $i = 1;
00504         while (isset($raw->{'map__idx_'.$i})) {
00505             if (is_numeric($raw->{'map__idx_'.$i})) {
00506                 $cook->mappings[$i] = $raw->{'map__idx_'.$i}; // should be a value from 0 to 100
00507             } else {
00508                 $cook->mappings[$i] = null; // the user did not set anything
00509             }
00510             $i++;
00511         }
00512 
00513         return $cook;
00514     }
00515 
00522     protected function get_current_assessment_data(stdclass $assessment) {
00523         global $DB;
00524 
00525         if (empty($this->dimensions)) {
00526             return array();
00527         }
00528         list($dimsql, $dimparams) = $DB->get_in_or_equal(array_keys($this->dimensions), SQL_PARAMS_NAMED);
00529         // beware! the caller may rely on the returned array is indexed by dimensionid
00530         $sql = "SELECT dimensionid, wg.*
00531                   FROM {workshop_grades} wg
00532                  WHERE assessmentid = :assessmentid AND strategy= :strategy AND dimensionid $dimsql";
00533         $params = array('assessmentid' => $assessment->id, 'strategy' => 'numerrors');
00534         $params = array_merge($params, $dimparams);
00535 
00536         return $DB->get_records_sql($sql, $params);
00537     }
00538 
00545     protected function update_peer_grade(stdclass $assessment) {
00546         $grades     = $this->get_current_assessment_data($assessment);
00547         $suggested  = $this->calculate_peer_grade($grades);
00548         if (!is_null($suggested)) {
00549             $this->workshop->set_peer_grade($assessment->id, $suggested);
00550         }
00551         return $suggested;
00552     }
00553 
00560     protected function calculate_peer_grade(array $grades) {
00561         if (empty($grades)) {
00562             return null;
00563         }
00564         $sumerrors  = 0;    // sum of the weighted errors (i.e. the negative responses)
00565         foreach ($grades as $grade) {
00566             if (grade_floats_different($grade->grade, 1.00000)) {
00567                 // negative reviewer's response
00568                 $sumerrors += $this->dimensions[$grade->dimensionid]->weight;
00569             }
00570         }
00571         return $this->errors_to_grade($sumerrors);
00572     }
00573 
00593     protected function errors_to_grade($numerrors) {
00594         $grade = 100.00000;
00595         for ($i = 1; $i <= $numerrors; $i++) {
00596             if (isset($this->mappings[$i])) {
00597                 $grade = $this->mappings[$i]->grade;
00598             }
00599         }
00600         if ($grade > 100.00000) {
00601             $grade = 100.00000;
00602         }
00603         if ($grade < 0.00000) {
00604             $grade = 0.00000;
00605         }
00606         return grade_floatval($grade);
00607     }
00608 }
 All Data Structures Namespaces Files Functions Variables Enumerations