Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/lib/conditionlib.php
Go to the documentation of this file.
00001 <?php
00002 // This file is part of Moodle - http://moodle.org/
00003 //
00004 // Moodle is free software: you can redistribute it and/or modify
00005 // it under the terms of the GNU General Public License as published by
00006 // the Free Software Foundation, either version 3 of the License, or
00007 // (at your option) any later version.
00008 //
00009 // Moodle is distributed in the hope that it will be useful,
00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 // GNU General Public License for more details.
00013 //
00014 // You should have received a copy of the GNU General Public License
00015 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
00016 
00027 defined('MOODLE_INTERNAL') || die();
00028 
00030 define('CONDITION_STUDENTVIEW_HIDE',0);
00033 define('CONDITION_STUDENTVIEW_SHOW',1);
00034 
00036 define('CONDITION_MISSING_NOTHING',0);
00039 define('CONDITION_MISSING_EXTRATABLE',1);
00041 define('CONDITION_MISSING_EVERYTHING',2);
00042 
00043 require_once($CFG->libdir.'/completionlib.php');
00044 
00049 global $CONDITIONLIB_PRIVATE;
00050 $CONDITIONLIB_PRIVATE = new stdClass;
00051 // Caches whether completion values are used in availability conditions.
00052 // Array of course => array of cmid => true.
00053 $CONDITIONLIB_PRIVATE->usedincondition = array();
00054 
00059 class condition_info {
00063     private $cm, $gotdata;
00064 
00086     public function __construct($cm, $expectingmissing=CONDITION_MISSING_NOTHING,
00087         $loaddata=true) {
00088         global $DB;
00089 
00090         // Check ID as otherwise we can't do the other queries
00091         if (empty($cm->id)) {
00092             throw new coding_exception("Invalid parameters; course-module ID not included");
00093         }
00094 
00095         // If not loading data, don't do anything else
00096         if (!$loaddata) {
00097             $this->cm = (object)array('id'=>$cm->id);
00098             $this->gotdata = false;
00099             return;
00100         }
00101 
00102         // Missing basic data from course_modules
00103         if (!isset($cm->availablefrom) || !isset($cm->availableuntil) ||
00104             !isset($cm->showavailability) || !isset($cm->course)) {
00105             if ($expectingmissing<CONDITION_MISSING_EVERYTHING) {
00106                 debugging('Performance warning: condition_info constructor is
00107                     faster if you pass in $cm with at least basic fields
00108                     (availablefrom,availableuntil,showavailability,course).
00109                     [This warning can be disabled, see phpdoc.]',
00110                     DEBUG_DEVELOPER);
00111             }
00112             $cm = $DB->get_record('course_modules',array('id'=>$cm->id),
00113                 'id,course,availablefrom,availableuntil,showavailability');
00114         }
00115 
00116         $this->cm = clone($cm);
00117         $this->gotdata = true;
00118 
00119         // Missing extra data
00120         if (!isset($cm->conditionsgrade) || !isset($cm->conditionscompletion)) {
00121             if ($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
00122                 debugging('Performance warning: condition_info constructor is
00123                     faster if you pass in a $cm from get_fast_modinfo.
00124                     [This warning can be disabled, see phpdoc.]',
00125                     DEBUG_DEVELOPER);
00126             }
00127 
00128             self::fill_availability_conditions($this->cm);
00129         }
00130     }
00131 
00140     public static function fill_availability_conditions(&$cm) {
00141         if (empty($cm->id)) {
00142             throw new coding_exception("Invalid parameters; course-module ID not included");
00143         }
00144 
00145         // Does nothing if the variables are already present
00146         if (!isset($cm->conditionsgrade) ||
00147             !isset($cm->conditionscompletion)) {
00148             $cm->conditionsgrade=array();
00149             $cm->conditionscompletion=array();
00150 
00151             global $DB, $CFG;
00152             $conditions = $DB->get_records_sql($sql="
00153 SELECT
00154     cma.id as cmaid, gi.*,cma.sourcecmid,cma.requiredcompletion,cma.gradeitemid,
00155     cma.grademin as conditiongrademin, cma.grademax as conditiongrademax
00156 FROM
00157     {course_modules_availability} cma
00158     LEFT JOIN {grade_items} gi ON gi.id=cma.gradeitemid
00159 WHERE
00160     coursemoduleid=?",array($cm->id));
00161             foreach ($conditions as $condition) {
00162                 if (!is_null($condition->sourcecmid)) {
00163                     $cm->conditionscompletion[$condition->sourcecmid] =
00164                         $condition->requiredcompletion;
00165                 } else {
00166                     $minmax = new stdClass;
00167                     $minmax->min = $condition->conditiongrademin;
00168                     $minmax->max = $condition->conditiongrademax;
00169                     $minmax->name = self::get_grade_name($condition);
00170                     $cm->conditionsgrade[$condition->gradeitemid] = $minmax;
00171                 }
00172             }
00173         }
00174     }
00175 
00184     private static function get_grade_name($gradeitemobj) {
00185         global $CFG;
00186         if (isset($gradeitemobj->id)) {
00187             require_once($CFG->libdir.'/gradelib.php');
00188             $item = new grade_item;
00189             grade_object::set_properties($item, $gradeitemobj);
00190             return $item->get_name();
00191         } else {
00192             return '!missing'; // Ooops, missing grade
00193         }
00194     }
00195 
00201     public function get_full_course_module() {
00202         $this->require_data();
00203         return $this->cm;
00204     }
00205 
00213     public function add_completion_condition($cmid, $requiredcompletion) {
00214         // Add to DB
00215         global $DB;
00216         $DB->insert_record('course_modules_availability',
00217             (object)array('coursemoduleid'=>$this->cm->id,
00218                 'sourcecmid'=>$cmid, 'requiredcompletion'=>$requiredcompletion),
00219             false);
00220 
00221         // Store in memory too
00222         $this->cm->conditionscompletion[$cmid] = $requiredcompletion;
00223     }
00224 
00236     public function add_grade_condition($gradeitemid, $min, $max, $updateinmemory=false) {
00237         // Normalise nulls
00238         if ($min==='') {
00239             $min = null;
00240         }
00241         if ($max==='') {
00242             $max = null;
00243         }
00244         // Add to DB
00245         global $DB;
00246         $DB->insert_record('course_modules_availability',
00247             (object)array('coursemoduleid'=>$this->cm->id,
00248                 'gradeitemid'=>$gradeitemid, 'grademin'=>$min, 'grademax'=>$max),
00249             false);
00250 
00251         // Store in memory too
00252         if ($updateinmemory) {
00253             $this->cm->conditionsgrade[$gradeitemid]=(object)array(
00254                 'min'=>$min, 'max'=>$max);
00255             $this->cm->conditionsgrade[$gradeitemid]->name =
00256                 self::get_grade_name($DB->get_record('grade_items',
00257                     array('id'=>$gradeitemid)));
00258         }
00259     }
00260 
00266     public function wipe_conditions() {
00267         // Wipe from DB
00268         global $DB;
00269         $DB->delete_records('course_modules_availability',
00270             array('coursemoduleid'=>$this->cm->id));
00271 
00272         // And from memory
00273         $this->cm->conditionsgrade = array();
00274         $this->cm->conditionscompletion = array();
00275     }
00276 
00289     public function get_full_information($modinfo=null) {
00290         $this->require_data();
00291         global $COURSE, $DB;
00292 
00293         $information = '';
00294 
00295         // Completion conditions
00296         if(count($this->cm->conditionscompletion)>0) {
00297             if ($this->cm->course==$COURSE->id) {
00298                 $course = $COURSE;
00299             } else {
00300                 $course = $DB->get_record('course',array('id'=>$this->cm->course),'id,enablecompletion,modinfo');
00301             }
00302             foreach ($this->cm->conditionscompletion as $cmid=>$expectedcompletion) {
00303                 if (!$modinfo) {
00304                     $modinfo = get_fast_modinfo($course);
00305                 }
00306                 if (empty($modinfo->cms[$cmid])) {
00307                     continue;
00308                 }
00309                 $information .= get_string(
00310                     'requires_completion_'.$expectedcompletion,
00311                     'condition', $modinfo->cms[$cmid]->name).' ';
00312             }
00313         }
00314 
00315         // Grade conditions
00316         if (count($this->cm->conditionsgrade)>0) {
00317             foreach ($this->cm->conditionsgrade as $gradeitemid=>$minmax) {
00318                 // String depends on type of requirement. We are coy about
00319                 // the actual numbers, in case grades aren't released to
00320                 // students.
00321                 if (is_null($minmax->min) && is_null($minmax->max)) {
00322                     $string = 'any';
00323                 } else if (is_null($minmax->max)) {
00324                     $string = 'min';
00325                 } else if (is_null($minmax->min)) {
00326                     $string = 'max';
00327                 } else {
00328                     $string = 'range';
00329                 }
00330                 $information .= get_string('requires_grade_'.$string, 'condition', $minmax->name).' ';
00331             }
00332         }
00333 
00334         // The date logic is complicated. The intention of this logic is:
00335         // 1) display date without time where possible (whenever the date is
00336         //    midnight)
00337         // 2) when the 'until' date is e.g. 00:00 on the 14th, we display it as
00338         //    'until the 13th' (experience at the OU showed that students are
00339         //    likely to interpret 'until <date>' as 'until the end of <date>').
00340         // 3) This behaviour becomes confusing for 'same-day' dates where there
00341         //    are some exceptions.
00342         // Users in different time zones will typically not get the 'abbreviated'
00343         // behaviour but it should work OK for them aside from that.
00344 
00345         // The following cases are possible:
00346         // a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact)
00347         // b) From 14 Oct until 12:11 on 17 Oct (midnight, exact)
00348         // c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct)
00349         // d) From 14 Oct until 17 Oct (midnight 14 Oct, midnight 18 Oct)
00350         // e) On 14 Oct (midnight 14 Oct, midnight 15 Oct)
00351         // f) From 13:05 on 14 Oct until 0:00 on 15 Oct (exact, midnight, same day)
00352         // g) From 0:00 on 14 Oct until 12:05 on 14 Oct (midnight, exact, same day)
00353         // h) From 13:05 on 14 Oct (exact)
00354         // i) From 14 Oct (midnight)
00355         // j) Until 13:05 on 14 Oct (exact)
00356         // k) Until 14 Oct (midnight 15 Oct)
00357 
00358         // Check if start and end dates are 'midnights', if so we show in short form
00359         $shortfrom = self::is_midnight($this->cm->availablefrom);
00360         $shortuntil = self::is_midnight($this->cm->availableuntil);
00361 
00362         // For some checks and for display, we need the previous day for the 'until'
00363         // value, if we are going to display it in short form
00364         if ($this->cm->availableuntil) {
00365             $daybeforeuntil = strtotime("-1 day", usergetmidnight($this->cm->availableuntil));
00366         }
00367 
00368         // Special case for if one but not both are exact and they are within a day
00369         if ($this->cm->availablefrom && $this->cm->availableuntil &&
00370                 $shortfrom != $shortuntil && $daybeforeuntil < $this->cm->availablefrom) {
00371             // Don't use abbreviated version (see examples f, g above)
00372             $shortfrom = false;
00373             $shortuntil = false;
00374         }
00375 
00376         // When showing short end date, the display time is the 'day before' one
00377         $displayuntil = $shortuntil ? $daybeforeuntil : $this->cm->availableuntil;
00378 
00379         if ($this->cm->availablefrom && $this->cm->availableuntil) {
00380             if ($shortfrom && $shortuntil && $daybeforeuntil == $this->cm->availablefrom) {
00381                 $information .= get_string('requires_date_both_single_day', 'condition',
00382                         self::show_time($this->cm->availablefrom, true));
00383             } else {
00384                 $information .= get_string('requires_date_both', 'condition', (object)array(
00385                          'from' => self::show_time($this->cm->availablefrom, $shortfrom),
00386                          'until' => self::show_time($displayuntil, $shortuntil)));
00387             }
00388         } else if ($this->cm->availablefrom) {
00389             $information .= get_string('requires_date', 'condition',
00390                 self::show_time($this->cm->availablefrom, $shortfrom));
00391         } else if ($this->cm->availableuntil) {
00392             $information .= get_string('requires_date_before', 'condition',
00393                 self::show_time($displayuntil, $shortuntil));
00394         }
00395 
00396         $information = trim($information);
00397         return $information;
00398     }
00399 
00407     private static function is_midnight($time) {
00408         return $time && usergetmidnight($time) == $time;
00409     }
00410 
00437     public function is_available(&$information, $grabthelot=false, $userid=0, $modinfo=null) {
00438         $this->require_data();
00439         global $COURSE,$DB;
00440 
00441         $available = true;
00442         $information = '';
00443 
00444         // Check each completion condition
00445         if(count($this->cm->conditionscompletion)>0) {
00446             if ($this->cm->course==$COURSE->id) {
00447                 $course = $COURSE;
00448             } else {
00449                 $course = $DB->get_record('course',array('id'=>$this->cm->course),'id,enablecompletion,modinfo');
00450             }
00451 
00452             $completion = new completion_info($course);
00453             foreach ($this->cm->conditionscompletion as $cmid=>$expectedcompletion) {
00454                 // If this depends on a deleted module, handle that situation
00455                 // gracefully.
00456                 if (!$modinfo) {
00457                     $modinfo = get_fast_modinfo($course);
00458                 }
00459                 if (empty($modinfo->cms[$cmid])) {
00460                     global $PAGE, $UNITTEST;
00461                     if (!empty($UNITTEST) || (isset($PAGE) && strpos($PAGE->pagetype, 'course-view-')===0)) {
00462                         debugging("Warning: activity {$this->cm->id} '{$this->cm->name}' has condition on deleted activity $cmid (to get rid of this message, edit the named activity)");
00463                     }
00464                     continue;
00465                 }
00466 
00467                 // The completion system caches its own data
00468                 $completiondata = $completion->get_data((object)array('id'=>$cmid),
00469                     $grabthelot, $userid, $modinfo);
00470 
00471                 $thisisok = true;
00472                 if ($expectedcompletion==COMPLETION_COMPLETE) {
00473                     // 'Complete' also allows the pass, fail states
00474                     switch ($completiondata->completionstate) {
00475                         case COMPLETION_COMPLETE:
00476                         case COMPLETION_COMPLETE_FAIL:
00477                         case COMPLETION_COMPLETE_PASS:
00478                             break;
00479                         default:
00480                             $thisisok = false;
00481                     }
00482                 } else {
00483                     // Other values require exact match
00484                     if ($completiondata->completionstate!=$expectedcompletion) {
00485                         $thisisok = false;
00486                     }
00487                 }
00488                 if (!$thisisok) {
00489                     $available = false;
00490                     $information .= get_string(
00491                         'requires_completion_'.$expectedcompletion,
00492                         'condition',$modinfo->cms[$cmid]->name).' ';
00493                 }
00494             }
00495         }
00496 
00497         // Check each grade condition
00498         if (count($this->cm->conditionsgrade)>0) {
00499             foreach ($this->cm->conditionsgrade as $gradeitemid=>$minmax) {
00500                 $score = $this->get_cached_grade_score($gradeitemid, $grabthelot, $userid);
00501                 if ($score===false ||
00502                     (!is_null($minmax->min) && $score<$minmax->min) ||
00503                     (!is_null($minmax->max) && $score>=$minmax->max)) {
00504                     // Grade fail
00505                     $available = false;
00506                     // String depends on type of requirement. We are coy about
00507                     // the actual numbers, in case grades aren't released to
00508                     // students.
00509                     if (is_null($minmax->min) && is_null($minmax->max)) {
00510                         $string = 'any';
00511                     } else if (is_null($minmax->max)) {
00512                         $string = 'min';
00513                     } else if (is_null($minmax->min)) {
00514                         $string = 'max';
00515                     } else {
00516                         $string = 'range';
00517                     }
00518                     $information .= get_string('requires_grade_'.$string, 'condition', $minmax->name).' ';
00519                 }
00520             }
00521         }
00522 
00523         // Test dates
00524         if ($this->cm->availablefrom) {
00525             if (time() < $this->cm->availablefrom) {
00526                 $available = false;
00527 
00528                 $information .= get_string('requires_date', 'condition',
00529                         self::show_time($this->cm->availablefrom,
00530                             self::is_midnight($this->cm->availablefrom)));
00531             }
00532         }
00533 
00534         if ($this->cm->availableuntil) {
00535             if (time() >= $this->cm->availableuntil) {
00536                 $available = false;
00537                 // But we don't display any information about this case. This is
00538                 // because the only reason to set a 'disappear' date is usually
00539                 // to get rid of outdated information/clutter in which case there
00540                 // is no point in showing it...
00541 
00542                 // Note it would be nice if we could make it so that the 'until'
00543                 // date appears below the item while the item is still accessible,
00544                 // unfortunately this is not possible in the current system. Maybe
00545                 // later, or if somebody else wants to add it.
00546             }
00547         }
00548 
00549         $information=trim($information);
00550         return $available;
00551     }
00552 
00560     private function show_time($time, $dateonly) {
00561         return userdate($time,
00562                 get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
00563     }
00564 
00570     public function show_availability() {
00571         $this->require_data();
00572         return $this->cm->showavailability;
00573     }
00574 
00580     private function require_data() {
00581         if (!$this->gotdata) {
00582             throw new coding_exception('Error: cannot call when info was '.
00583                 'constructed without data');
00584         }
00585     }
00586 
00603     private function get_cached_grade_score($gradeitemid, $grabthelot=false, $userid=0) {
00604         global $USER, $DB, $SESSION;
00605         if ($userid==0 || $userid==$USER->id) {
00606             // For current user, go via cache in session
00607             if (empty($SESSION->gradescorecache) || $SESSION->gradescorecacheuserid!=$USER->id) {
00608                 $SESSION->gradescorecache = array();
00609                 $SESSION->gradescorecacheuserid = $USER->id;
00610             }
00611             if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
00612                 if ($grabthelot) {
00613                     // Get all grades for the current course
00614                     $rs = $DB->get_recordset_sql("
00615 SELECT
00616     gi.id,gg.finalgrade,gg.rawgrademin,gg.rawgrademax
00617 FROM
00618     {grade_items} gi
00619     LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
00620 WHERE
00621     gi.courseid=?", array($USER->id, $this->cm->course));
00622                     foreach ($rs as $record) {
00623                         $SESSION->gradescorecache[$record->id] =
00624                             is_null($record->finalgrade)
00625                                 // No grade = false
00626                                 ? false
00627                                 // Otherwise convert grade to percentage
00628                                 : (($record->finalgrade - $record->rawgrademin) * 100) /
00629                                     ($record->rawgrademax - $record->rawgrademin);
00630 
00631                     }
00632                     $rs->close();
00633                     // And if it's still not set, well it doesn't exist (eg
00634                     // maybe the user set it as a condition, then deleted the
00635                     // grade item) so we call it false
00636                     if (!array_key_exists($gradeitemid, $SESSION->gradescorecache)) {
00637                         $SESSION->gradescorecache[$gradeitemid] = false;
00638                     }
00639                 } else {
00640                     // Just get current grade
00641                     $record = $DB->get_record('grade_grades', array(
00642                         'userid'=>$USER->id, 'itemid'=>$gradeitemid));
00643                     if ($record && !is_null($record->finalgrade)) {
00644                         $score = (($record->finalgrade - $record->rawgrademin) * 100) /
00645                             ($record->rawgrademax - $record->rawgrademin);
00646                     } else {
00647                         // Treat the case where row exists but is null, same as
00648                         // case where row doesn't exist
00649                         $score = false;
00650                     }
00651                     $SESSION->gradescorecache[$gradeitemid]=$score;
00652                 }
00653             }
00654             return $SESSION->gradescorecache[$gradeitemid];
00655         } else {
00656             // Not the current user, so request the score individually
00657             $record = $DB->get_record('grade_grades', array(
00658                 'userid'=>$userid, 'itemid'=>$gradeitemid));
00659             if ($record && !is_null($record->finalgrade)) {
00660                 $score = (($record->finalgrade - $record->rawgrademin) * 100) /
00661                     ($record->rawgrademax - $record->rawgrademin);
00662             } else {
00663                 // Treat the case where row exists but is null, same as
00664                 // case where row doesn't exist
00665                 $score = false;
00666             }
00667             return $score;
00668         }
00669     }
00670 
00676     static function wipe_session_cache() {
00677         global $SESSION;
00678         unset($SESSION->gradescorecache);
00679         unset($SESSION->gradescorecacheuserid);
00680     }
00681 
00690     public static function update_cm_from_form($cm, $fromform, $wipefirst=true) {
00691         $ci=new condition_info($cm, CONDITION_MISSING_EVERYTHING, false);
00692         if ($wipefirst) {
00693             $ci->wipe_conditions();
00694         }
00695         foreach ($fromform->conditiongradegroup as $record) {
00696             if($record['conditiongradeitemid']) {
00697                 $ci->add_grade_condition($record['conditiongradeitemid'],
00698                     $record['conditiongrademin'],$record['conditiongrademax']);
00699             }
00700         }
00701         if(isset ($fromform->conditioncompletiongroup)) {
00702             foreach($fromform->conditioncompletiongroup as $record) {
00703                 if($record['conditionsourcecmid']) {
00704                     $ci->add_completion_condition($record['conditionsourcecmid'],
00705                         $record['conditionrequiredcompletion']);
00706                 }
00707             }
00708         }
00709     }
00710 
00720     public static function completion_value_used_as_condition($course, $cm) {
00721         // Have we already worked out a list of required completion values
00722         // for this course? If so just use that
00723         global $CONDITIONLIB_PRIVATE;
00724         if (!array_key_exists($course->id, $CONDITIONLIB_PRIVATE->usedincondition)) {
00725             // We don't have data for this course, build it
00726             $modinfo = get_fast_modinfo($course);
00727             $CONDITIONLIB_PRIVATE->usedincondition[$course->id] = array();
00728             foreach ($modinfo->cms as $othercm) {
00729                 foreach ($othercm->conditionscompletion as $cmid=>$expectedcompletion) {
00730                     $CONDITIONLIB_PRIVATE->usedincondition[$course->id][$cmid] = true;
00731                 }
00732             }
00733         }
00734         return array_key_exists($cm->id, $CONDITIONLIB_PRIVATE->usedincondition[$course->id]);
00735     }
00736 }
 All Data Structures Namespaces Files Functions Variables Enumerations