Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/mod/workshop/form/accumulative/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_accumulative_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_accumulative', 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_accumulative/$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_accumulative_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 $descriptionopts;
00086 
00093     public function __construct(workshop $workshop) {
00094         $this->workshop         = $workshop;
00095         $this->dimensions       = $this->load_fields();
00096         $this->descriptionopts  = array('trusttext' => true, 'subdirs' => false, 'maxfiles' => -1);
00097     }
00098 
00104     public function get_edit_strategy_form($actionurl=null) {
00105         global $CFG;    // needed because the included files use it
00106         global $PAGE;
00107 
00108         require_once(dirname(__FILE__) . '/edit_form.php');
00109 
00110         $fields             = $this->prepare_form_fields($this->dimensions);
00111         $nodimensions       = count($this->dimensions);
00112         $norepeatsdefault   = max($nodimensions + self::ADDDIMS, self::MINDIMS);
00113         $norepeats          = optional_param('norepeats', $norepeatsdefault, PARAM_INT);    // number of dimensions
00114         $noadddims          = optional_param('noadddims', '', PARAM_ALPHA);                 // shall we add more?
00115         if ($noadddims) {
00116             $norepeats += self::ADDDIMS;
00117         }
00118 
00119         // Append editor context to editor options, giving preference to existing context.
00120         $this->descriptionopts = array_merge(array('context' => $PAGE->context), $this->descriptionopts);
00121 
00122         // prepare the embeded files
00123         for ($i = 0; $i < $nodimensions; $i++) {
00124             // prepare all editor elements
00125             $fields = file_prepare_standard_editor($fields, 'description__idx_'.$i, $this->descriptionopts,
00126                 $PAGE->context, 'workshopform_accumulative', 'description', $fields->{'dimensionid__idx_'.$i});
00127         }
00128 
00129         $customdata = array();
00130         $customdata['workshop'] = $this->workshop;
00131         $customdata['strategy'] = $this;
00132         $customdata['norepeats'] = $norepeats;
00133         $customdata['descriptionopts'] = $this->descriptionopts;
00134         $customdata['current']  = $fields;
00135         $attributes = array('class' => 'editstrategyform');
00136 
00137         return new workshop_edit_accumulative_strategy_form($actionurl, $customdata, 'post', '', $attributes);
00138     }
00139 
00152     public function save_edit_strategy_form(stdclass $data) {
00153         global $DB, $PAGE;
00154 
00155         $workshopid = $data->workshopid;
00156         $norepeats  = $data->norepeats;
00157 
00158         $data       = $this->prepare_database_fields($data);
00159         $records    = $data->accumulative;  // records to be saved into {workshopform_accumulative}
00160         $todelete   = array();              // dimension ids to be deleted
00161 
00162         for ($i=0; $i < $norepeats; $i++) {
00163             $record = $records[$i];
00164             if (0 == strlen(trim($record->description_editor['text']))) {
00165                 if (!empty($record->id)) {
00166                     // existing record with empty description - to be deleted
00167                     $todelete[] = $record->id;
00168                 }
00169                 continue;
00170             }
00171             if (empty($record->id)) {
00172                 // new field
00173                 $record->id         = $DB->insert_record('workshopform_accumulative', $record);
00174             } else {
00175                 // exiting field
00176                 $DB->update_record('workshopform_accumulative', $record);
00177             }
00178             // re-save with correct path to embeded media files
00179             $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts,
00180                                                       $PAGE->context, 'workshopform_accumulative', 'description', $record->id);
00181             $DB->update_record('workshopform_accumulative', $record);
00182         }
00183         $this->delete_dimensions($todelete);
00184     }
00185 
00195     public function get_assessment_form(moodle_url $actionurl=null, $mode='preview', stdclass $assessment=null, $editable=true, $options=array()) {
00196         global $CFG;    // needed because the included files use it
00197         global $PAGE;
00198         global $DB;
00199         require_once(dirname(__FILE__) . '/assessment_form.php');
00200 
00201         $fields         = $this->prepare_form_fields($this->dimensions);
00202         $nodimensions   = count($this->dimensions);
00203 
00204         // rewrite URLs to the embeded files
00205         for ($i = 0; $i < $nodimensions; $i++) {
00206             $fields->{'description__idx_'.$i} = file_rewrite_pluginfile_urls($fields->{'description__idx_'.$i},
00207                 'pluginfile.php', $PAGE->context->id, 'workshopform_accumulative', 'description', $fields->{'dimensionid__idx_'.$i});
00208         }
00209 
00210         if ('assessment' === $mode and !empty($assessment)) {
00211             // load the previously saved assessment data
00212             $grades = $this->get_current_assessment_data($assessment);
00213             $current = new stdclass();
00214             for ($i = 0; $i < $nodimensions; $i++) {
00215                 $dimid = $fields->{'dimensionid__idx_'.$i};
00216                 if (isset($grades[$dimid])) {
00217                     $current->{'gradeid__idx_'.$i}      = $grades[$dimid]->id;
00218                     $current->{'grade__idx_'.$i}        = $grades[$dimid]->grade;
00219                     $current->{'peercomment__idx_'.$i}  = $grades[$dimid]->peercomment;
00220                 }
00221             }
00222         }
00223 
00224         // set up the required custom data common for all strategies
00225         $customdata['strategy'] = $this;
00226         $customdata['workshop'] = $this->workshop;
00227         $customdata['mode']     = $mode;
00228         $customdata['options']  = $options;
00229 
00230         // set up strategy-specific custom data
00231         $customdata['nodims']   = $nodimensions;
00232         $customdata['fields']   = $fields;
00233         $customdata['current']  = isset($current) ? $current : null;
00234         $attributes = array('class' => 'assessmentform accumulative');
00235 
00236         return new workshop_accumulative_assessment_form($actionurl, $customdata, 'post', '', $attributes, $editable);
00237     }
00238 
00248     public function save_assessment(stdclass $assessment, stdclass $data) {
00249         global $DB;
00250 
00251         if (!isset($data->nodims)) {
00252             throw new coding_exception('You did not send me the number of assessment dimensions to process');
00253         }
00254         for ($i = 0; $i < $data->nodims; $i++) {
00255             $grade = new stdclass();
00256             $grade->id = $data->{'gradeid__idx_' . $i};
00257             $grade->assessmentid = $assessment->id;
00258             $grade->strategy = 'accumulative';
00259             $grade->dimensionid = $data->{'dimensionid__idx_' . $i};
00260             $grade->grade = $data->{'grade__idx_' . $i};
00261             $grade->peercomment = $data->{'peercomment__idx_' . $i};
00262             $grade->peercommentformat = FORMAT_MOODLE;
00263             if (empty($grade->id)) {
00264                 // new grade
00265                 $grade->id = $DB->insert_record('workshop_grades', $grade);
00266             } else {
00267                 // updated grade
00268                 $DB->update_record('workshop_grades', $grade);
00269             }
00270         }
00271         return $this->update_peer_grade($assessment);
00272     }
00273 
00279     public function form_ready() {
00280         if (count($this->dimensions) > 0) {
00281             return true;
00282         }
00283         return false;
00284     }
00285 
00289     public function get_assessments_recordset($restrict=null) {
00290         global $DB;
00291 
00292         $sql = 'SELECT s.id AS submissionid,
00293                        a.id AS assessmentid, a.weight AS assessmentweight, a.reviewerid, a.gradinggrade,
00294                        g.dimensionid, g.grade
00295                   FROM {workshop_submissions} s
00296                   JOIN {workshop_assessments} a ON (a.submissionid = s.id)
00297                   JOIN {workshop_grades} g ON (g.assessmentid = a.id AND g.strategy = :strategy)
00298                  WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont.
00299         $params = array('workshopid' => $this->workshop->id, 'strategy' => $this->workshop->strategy);
00300 
00301         if (is_null($restrict)) {
00302             // update all users - no more conditions
00303         } elseif (!empty($restrict)) {
00304             list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED);
00305             $sql .= " AND a.reviewerid $usql";
00306             $params = array_merge($params, $uparams);
00307         } else {
00308             throw new coding_exception('Empty value is not a valid parameter here');
00309         }
00310 
00311         $sql .= ' ORDER BY s.id'; // this is important for bulk processing
00312 
00313         return $DB->get_recordset_sql($sql, $params);
00314     }
00315 
00319     public function get_dimensions_info() {
00320         global $DB;
00321 
00322         $sql = 'SELECT d.id, d.grade, d.weight, s.scale
00323                   FROM {workshopform_accumulative} d
00324              LEFT JOIN {scale} s ON (d.grade < 0 AND -d.grade = s.id)
00325                  WHERE d.workshopid = :workshopid';
00326         $params = array('workshopid' => $this->workshop->id);
00327         $dimrecords = $DB->get_records_sql($sql, $params);
00328         $diminfo = array();
00329         foreach ($dimrecords as $dimid => $dimrecord) {
00330             $diminfo[$dimid] = new stdclass();
00331             $diminfo[$dimid]->id = $dimid;
00332             $diminfo[$dimid]->weight = $dimrecord->weight;
00333             if ($dimrecord->grade < 0) {
00334                 // the dimension uses a scale
00335                 $diminfo[$dimid]->min = 1;
00336                 $diminfo[$dimid]->max = count(explode(',', $dimrecord->scale));
00337             } else {
00338                 // the dimension uses points
00339                 $diminfo[$dimid]->min = 0;
00340                 $diminfo[$dimid]->max = grade_floatval($dimrecord->grade);
00341             }
00342         }
00343         return $diminfo;
00344     }
00345 
00353     public static function scale_used($scaleid, $workshopid=null) {
00354         global $DB;
00355 
00356         $conditions['grade'] = -$scaleid;
00357         if (!is_null($workshopid)) {
00358             $conditions['workshopid'] = $workshopid;
00359         }
00360         return $DB->record_exists('workshopform_accumulative', $conditions);
00361     }
00362 
00370     public static function delete_instance($workshopid) {
00371         global $DB;
00372 
00373         $DB->delete_records('workshopform_accumulative', array('workshopid' => $workshopid));
00374     }
00375 
00377     // Internal methods                                                           //
00379 
00385     protected function load_fields() {
00386         global $DB;
00387 
00388         $sql = 'SELECT *
00389                   FROM {workshopform_accumulative}
00390                  WHERE workshopid = :workshopid
00391                  ORDER BY sort';
00392         $params = array('workshopid' => $this->workshop->id);
00393 
00394         return $DB->get_records_sql($sql, $params);
00395     }
00396 
00403     protected function prepare_form_fields(array $raw) {
00404 
00405         $formdata = new stdclass();
00406         $key = 0;
00407         foreach ($raw as $dimension) {
00408             $formdata->{'dimensionid__idx_' . $key}             = $dimension->id;
00409             $formdata->{'description__idx_' . $key}             = $dimension->description;
00410             $formdata->{'description__idx_' . $key.'format'}    = $dimension->descriptionformat;
00411             $formdata->{'grade__idx_' . $key}                   = $dimension->grade;
00412             $formdata->{'weight__idx_' . $key}                  = $dimension->weight;
00413             $key++;
00414         }
00415         return $formdata;
00416     }
00417 
00426     protected function delete_dimensions(array $ids) {
00427         global $DB, $PAGE;
00428 
00429         $fs = get_file_storage();
00430         foreach ($ids as $id) {
00431             if (!empty($id)) {   // to prevent accidental removal of all files in the area
00432                 $fs->delete_area_files($PAGE->context->id, 'workshopform_accumulative', 'description', $id);
00433             }
00434         }
00435         $DB->delete_records_list('workshopform_accumulative', 'id', $ids);
00436     }
00437 
00449     protected function prepare_database_fields(stdclass $raw) {
00450         global $PAGE;
00451 
00452         $cook               = new stdclass(); // to be returned
00453         $cook->accumulative = array();        // records to be stored in {workshopform_accumulative}
00454 
00455         for ($i = 0; $i < $raw->norepeats; $i++) {
00456             $cook->accumulative[$i]                     = new stdclass();
00457             $cook->accumulative[$i]->id                 = $raw->{'dimensionid__idx_'.$i};
00458             $cook->accumulative[$i]->workshopid         = $this->workshop->id;
00459             $cook->accumulative[$i]->sort               = $i + 1;
00460             $cook->accumulative[$i]->description_editor = $raw->{'description__idx_'.$i.'_editor'};
00461             $cook->accumulative[$i]->grade              = $raw->{'grade__idx_'.$i};
00462             $cook->accumulative[$i]->weight             = $raw->{'weight__idx_'.$i};
00463         }
00464         return $cook;
00465     }
00466 
00473     protected function get_current_assessment_data(stdclass $assessment) {
00474         global $DB;
00475 
00476         if (empty($this->dimensions)) {
00477             return array();
00478         }
00479         list($dimsql, $dimparams) = $DB->get_in_or_equal(array_keys($this->dimensions), SQL_PARAMS_NAMED);
00480         // beware! the caller may rely on the returned array is indexed by dimensionid
00481         $sql = "SELECT dimensionid, wg.*
00482                   FROM {workshop_grades} wg
00483                  WHERE assessmentid = :assessmentid AND strategy= :strategy AND dimensionid $dimsql";
00484         $params = array('assessmentid' => $assessment->id, 'strategy' => 'accumulative');
00485         $params = array_merge($params, $dimparams);
00486 
00487         return $DB->get_records_sql($sql, $params);
00488     }
00489 
00496     protected function update_peer_grade(stdclass $assessment) {
00497         $grades     = $this->get_current_assessment_data($assessment);
00498         $suggested  = $this->calculate_peer_grade($grades);
00499         if (!is_null($suggested)) {
00500             $this->workshop->set_peer_grade($assessment->id, $suggested);
00501         }
00502         return $suggested;
00503     }
00504 
00512     protected function calculate_peer_grade(array $grades) {
00513 
00514         if (empty($grades)) {
00515             return null;
00516         }
00517         $sumgrades  = 0;
00518         $sumweights = 0;
00519         foreach ($grades as $grade) {
00520             $dimension = $this->dimensions[$grade->dimensionid];
00521             if ($dimension->weight < 0) {
00522                 throw new coding_exception('Negative weights are not supported any more. Something is wrong with your data');
00523             }
00524             if (grade_floats_equal($dimension->weight, 0) or grade_floats_equal($dimension->grade, 0)) {
00525                 // does not influence the final grade
00526                 continue;
00527             }
00528             if ($dimension->grade < 0) {
00529                 // this is a scale
00530                 $scaleid    = -$dimension->grade;
00531                 $sumgrades  += $this->scale_to_grade($scaleid, $grade->grade) * $dimension->weight * 100;
00532                 $sumweights += $dimension->weight;
00533             } else {
00534                 // regular grade
00535                 $sumgrades  += ($grade->grade / $dimension->grade) * $dimension->weight * 100;
00536                 $sumweights += $dimension->weight;
00537             }
00538         }
00539 
00540         if ($sumweights === 0) {
00541             return 0;
00542         }
00543         return grade_floatval($sumgrades / $sumweights);
00544     }
00545 
00556     protected function scale_to_grade($scaleid, $item) {
00557         global $DB;
00558 
00560         static $numofscaleitems = array();
00561 
00562         if (!isset($numofscaleitems[$scaleid])) {
00563             $scale = $DB->get_field('scale', 'scale', array('id' => $scaleid), MUST_EXIST);
00564             $items = explode(',', $scale);
00565             $numofscaleitems[$scaleid] = count($items);
00566             unset($scale);
00567             unset($items);
00568         }
00569 
00570         if ($numofscaleitems[$scaleid] <= 1) {
00571             throw new coding_exception('Invalid scale definition, no scale items found');
00572         }
00573 
00574         if ($item <= 0 or $numofscaleitems[$scaleid] < $item) {
00575             throw new coding_exception('Invalid scale item number');
00576         }
00577 
00578         return ($item - 1) / ($numofscaleitems[$scaleid] - 1);
00579     }
00580 }
 All Data Structures Namespaces Files Functions Variables Enumerations