]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-15499: Conditional availability of activities
authorsam_marshall <sam_marshall>
Wed, 17 Dec 2008 16:37:35 +0000 (16:37 +0000)
committersam_marshall <sam_marshall>
Wed, 17 Dec 2008 16:37:35 +0000 (16:37 +0000)
23 files changed:
admin/settings/development.php
admin/settings/subsystems.php
backup/backuplib.php
backup/restorelib.php
course/lib.php
course/mod.php
course/modedit.php
course/moodleform_mod.php
lang/en_utf8/condition.php [new file with mode: 0644]
lang/en_utf8/help/condition/completioncondition.html [new file with mode: 0644]
lang/en_utf8/help/condition/conditiondates.html [new file with mode: 0644]
lang/en_utf8/help/condition/gradecondition.html [new file with mode: 0644]
lang/en_utf8/help/condition/showavailability.html [new file with mode: 0644]
lib/completionlib.php
lib/conditionlib.php [new file with mode: 0644]
lib/datalib.php
lib/db/install.xml
lib/db/upgrade.php
lib/grade/grade_grade.php
lib/moodlelib.php
lib/simpletest/testconditionlib.php [new file with mode: 0644]
theme/standard/styles_fonts.css
version.php

index c246d3980ed53c01cb4c1eafcb1b95fc03a5b7ff..ba722ae21c109509c964d0daaf01465875fc9ac0 100644 (file)
@@ -14,11 +14,6 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $item->set_updatedcallback('reset_text_filters_cache');
     $temp->add($item);
 
-    // Completion system
-    require_once($CFG->libdir.'/completionlib.php');
-    $temp->add(new admin_setting_configcheckbox('enablecompletion', get_string('enablecompletion','completion'), get_string('configenablecompletion','completion'), COMPLETION_DISABLED));
-    $temp->add(new admin_setting_pickroles('progresstrackedroles', get_string('progresstrackedroles','completion'), get_string('configprogresstrackedroles', 'completion'), array('moodle/legacy:student')));
-
     $ADMIN->add('experimental', $temp);
 
     // DB transfer related pages
index b8610761ad9a4e577333945325af6bb6c512cdcd..28d9518919e2050b6f299927a6eeaf440000ad92 100644 (file)
@@ -29,4 +29,15 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $options = array('off'=>get_string('off', 'mnet'), 'strict'=>get_string('on', 'mnet'));
     $optionalsubsystems->add(new admin_setting_configselect('mnet_dispatcher_mode', get_string('net', 'mnet'), get_string('configmnet', 'mnet'), 'off', $options));
 
+    // Conditional activities: completion and availability
+    $optionalsubsystems->add(new admin_setting_configcheckbox('enablecompletion', 
+        get_string('enablecompletion','completion'), 
+        get_string('configenablecompletion','completion'), 0));
+    $optionalsubsystems->add(new admin_setting_pickroles('progresstrackedroles', 
+        get_string('progresstrackedroles','completion'),
+        get_string('configprogresstrackedroles', 'completion'),
+        array('moodle/legacy:student')));
+    $optionalsubsystems->add(new admin_setting_configcheckbox('enableavailability',
+        get_string('enableavailability','condition'),
+        get_string('configenableavailability','condition'), 0));
 }
index f167b243a13275063dde7ccd188dee8c87409641..e03e4d3306b5e6cb1d3fc8c5bcc89b8f884a6429 100644 (file)
                fwrite ($bf,full_tag("COMPLETIONGRADEITEMNUMBER",6,false,$course_module->completiongradeitemnumber));
                fwrite ($bf,full_tag("COMPLETIONVIEW",6,false,$course_module->completionview));
                fwrite ($bf,full_tag("COMPLETIONEXPECTED",6,false,$course_module->completionexpected));
+               fwrite ($bf,full_tag("AVAILABLEFROM",6,false,$course_module->availablefrom));
+               fwrite ($bf,full_tag("AVAILABLEUNTIL",6,false,$course_module->availableuntil));
+               fwrite ($bf,full_tag("SHOWAVAILABILITY",6,false,$course_module->showavailability));
+
                // get all the role_capabilities overrides in this mod
                write_role_overrides_xml($bf, $context, 6);
                /// write role_assign code here
                    fwrite ($bf,end_tag("COMPLETIONDATA",6,true));
                }
 
+               // Write availability data if enabled
+               require_once($CFG->libdir.'/conditionlib.php');
+               if(!empty($CFG->enableavailability)) {                   
+                   fwrite ($bf,start_tag("AVAILABILITYDATA",6,true));
+                   // Get all availability restrictions for this activity
+                   $data=$DB->get_records('course_modules_availability',
+                       array('coursemoduleid'=>$course_module->id));
+                   $data=$data ? $data : array();
+                   foreach($data as $availability) {
+                       // Write availability record
+                       fwrite ($bf,start_tag("AVAILABILITY",7,true));
+                       fwrite ($bf,full_tag("SOURCECMID",8,false,$availability->sourcecmid));
+                       fwrite ($bf,full_tag("REQUIREDCOMPLETION",8,false,$availability->requiredcompletion));
+                       fwrite ($bf,full_tag("GRADEITEMID",8,false,$availability->gradeitemid));
+                       fwrite ($bf,full_tag("GRADEMIN",8,false,$availability->grademin));
+                       fwrite ($bf,full_tag("GRADEMAX",8,false,$availability->grademax));
+                       fwrite ($bf,end_tag("AVAILABILITY",7,true));                       
+                   }
+                   fwrite ($bf,end_tag("AVAILABILITYDATA",6,true));
+               }
+
                fwrite ($bf,end_tag("MOD",5,true));
            }
            //check for next
index 4899a94774776282afdfe79d7109e4e3f06dcd71..40cfed76ce801523f3b19037f51231df0008a0a6 100644 (file)
@@ -1127,6 +1127,10 @@ define('RESTORE_GROUPS_GROUPINGS', 3);
                                         $course_module->completionview=$mod->completionview;
                                         $course_module->completionexpected=$mod->completionexpected;
 
+                                        $course_module->availablefrom=$mod->availablefrom+$restore->course_startdateoffset;
+                                        $course_module->availableuntil=$mod->availableuntil+$restore->course_startdateoffset;
+                                        $course_module->showavailability=$mod->showavailability;
+
                                         $newidmod = $DB->insert_record("course_modules", $course_module);
                                         if ($newidmod) {
                                             //save old and new module id
@@ -1221,6 +1225,61 @@ define('RESTORE_GROUPS_GROUPINGS', 3);
                 }
             }
 
+            // Store availability information
+            if($status && !empty($info->availabilitydata) && count($info->availabilitydata)>0) {
+
+                foreach($info->availabilitydata as $data) {
+                    // Convert cmid 
+                    $newcmid=backup_getid($restore->backup_unique_code, 'course_modules', $data->coursemoduleid);
+                    if($newcmid) {
+                        $data->coursemoduleid=$newcmid->new_id;
+                    } else {
+                        if (!defined('RESTORE_SILENTLY')) {
+                            echo "<p>Can't find new ID for cm $data->coursemoduleid, ignoring availability condition.</p>";
+                        }
+                        $status=false;
+                        continue;
+                    }
+
+                    // Convert source cmid 
+                    if($data->sourcecmid) {
+                        $newcmid=backup_getid($restore->backup_unique_code, 'course_modules', $data->sourcecmid);
+                        if($newcmid) {
+                            $data->sourcecmid=$newcmid->new_id;
+                        } else {
+                            if (!defined('RESTORE_SILENTLY')) {
+                                echo "<p>Can't find new ID for source cm $data->sourcecmid, ignoring availability condition.</p>";
+                            }
+                            $status=false;
+                            continue;
+                        }
+                    }
+
+                    // Convert grade id
+                    if($data->gradeitemid) {
+                        $newgradeid=backup_getid($restore->backup_unique_code,
+                            'grade_items',$data->gradeitemid);
+                        if($newgradeid) {
+                            $data->gradeitemid=$newgradeid->new_id;
+                        } else {
+                            if (!defined('RESTORE_SILENTLY')) {
+                                echo "<p>Can't find new ID for grade item $data->gradeitemid, ignoring availability condition.</p>";
+                            }
+                            $status=false;
+                            continue;
+                        }                        
+                    }
+
+                    // Add record
+                    if(!$DB->insert_record('course_modules_availability',$data)) {
+                        if (!defined('RESTORE_SILENTLY')) {
+                            echo "<p>Failed to insert availability data record.</p>";
+                        }
+                        $status=false;
+                        continue;
+                    }
+                }
+            }
         } else {
             $status = false;
         }
@@ -5618,6 +5677,12 @@ define('RESTORE_GROUPS_GROUPINGS', 3);
                                 isset($this->info->tempmod->completionview) ? $this->info->tempmod->completionview : 0;
                             $this->info->tempsection->mods[$this->info->tempmod->id]->completionexpected =
                                 isset($this->info->tempmod->completionexpected) ? $this->info->tempmod->completionexpected : 0;
+                            $this->info->tempsection->mods[$this->info->tempmod->id]->availablefrom =
+                                isset($this->info->tempmod->availablefrom) ? $this->info->tempmod->availablefrom : 0;
+                            $this->info->tempsection->mods[$this->info->tempmod->id]->availableuntil =
+                                isset($this->info->tempmod->availableuntil) ? $this->info->tempmod->availableuntil : 0;
+                            $this->info->tempsection->mods[$this->info->tempmod->id]->showavailability =
+                                isset($this->info->tempmod->showavailability) ? $this->info->tempmod->showavailability : 0;
                                 
                             unset($this->info->tempmod);
                     }
@@ -5669,6 +5734,15 @@ define('RESTORE_GROUPS_GROUPINGS', 3);
                         case "COMPLETIONEXPECTED":
                             $this->info->tempmod->completionexpected = $this->getContents();
                             break;
+                        case "AVAILABLEFROM":
+                            $this->info->tempmod->availablefrom = $this->getContents();
+                            break;
+                        case "AVAILABLEUNTIL":
+                            $this->info->tempmod->availableuntil = $this->getContents();
+                            break;
+                        case "SHOWAVAILABILITY":
+                            $this->info->tempmod->showavailability = $this->getContents();
+                            break;
                         default:
                             break;
                     }
@@ -5794,6 +5868,40 @@ define('RESTORE_GROUPS_GROUPINGS', 3);
                         }
                     }
                 }
+
+                if (isset($this->tree[7]) && $this->tree[7] == "AVAILABILITYDATA") {
+                    if($this->level == 8) {
+                        switch($tagName) {
+                            case 'AVAILABILITY':
+                                // Got all data to make completion entry...
+                                $this->info->tempavailability->coursemoduleid=$this->info->tempmod->id;                                
+                                $this->info->availabilitydata[]=$this->info->tempavailability;
+                                unset($this->info->tempavailability);
+                                $this->info->tempavailability=new stdClass;
+                                break;
+                        }
+                    }
+
+                    if($this->level == 9) {
+                        switch($tagName) {
+                            case 'SOURCECMID' :
+                                $this->info->tempavailability->sourcecmid=$this->getContents();
+                                break;
+                            case 'REQUIREDCOMPLETION' :
+                                $this->info->tempavailability->requiredcompletion=$this->getContents();
+                               break;
+                            case 'GRADEITEMID' :
+                                $this->info->tempavailability->gradeitemid=$this->getContents();
+                                break;
+                            case 'GRADEMIN' :
+                                $this->info->tempavailability->grademin=$this->getContents();
+                                break;
+                            case 'GRADEMAX' :
+                                $this->info->tempavailability->grademax=$this->getContents();
+                                break;
+                        }
+                    }
+                }
             }
 
             //Stop parsing if todo = SECTIONS and tagName = SECTIONS (en of the tag, of course)
index 22b4036411d36ccd19f7086b4f9a80b8eff93461..f897235c308bf8074db06d82f8adec3907abca87 100644 (file)
@@ -982,6 +982,9 @@ function get_array_of_activities($courseid) {
 //  groupmembersonly - is this instance visible to group members only
 //  extra - contains extra string to include in any link
     global $CFG, $DB;
+    if(!empty($CFG->enableavailability)) {
+        require_once($CFG->libdir.'/conditionlib.php');
+    }
 
     $mod = array();
 
@@ -1005,7 +1008,17 @@ function get_array_of_activities($courseid) {
                    $mod[$seq]->groupmode        = $rawmods[$seq]->groupmode;
                    $mod[$seq]->groupingid       = $rawmods[$seq]->groupingid;
                    $mod[$seq]->groupmembersonly = $rawmods[$seq]->groupmembersonly;
+                   $mod[$seq]->indent           = $rawmods[$seq]->indent;
+                   $mod[$seq]->completion       = $rawmods[$seq]->completion;
                    $mod[$seq]->extra            = "";
+                   if(!empty($CFG->enableavailability)) {
+                       condition_info::fill_availability_conditions($rawmods[$seq]);
+                       $mod[$seq]->availablefrom    = $rawmods[$seq]->availablefrom;
+                       $mod[$seq]->availableuntil   = $rawmods[$seq]->availableuntil;
+                       $mod[$seq]->showavailability = $rawmods[$seq]->showavailability;
+                       $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion;
+                       $mod[$seq]->conditionsgrade  = $rawmods[$seq]->conditionsgrade;
+                   }
 
                    $modname = $mod[$seq]->mod;
                    $functionname = $modname."_get_coursemodule_info";
@@ -1048,6 +1061,9 @@ function get_array_of_activities($courseid) {
  */
 function &get_fast_modinfo(&$course, $userid=0) {
     global $CFG, $USER, $DB;
+    if(!empty($CFG->enableavailability)) {
+        require_once($CFG->libdir.'/conditionlib.php');
+    }
 
     static $cache = array();
 
@@ -1132,9 +1148,27 @@ function &get_fast_modinfo(&$course, $userid=0) {
         $cm->groupmode        = $mod->groupmode;
         $cm->groupingid       = $mod->groupingid;
         $cm->groupmembersonly = $mod->groupmembersonly;
+        $cm->indent           = $mod->indent;
+        $cm->completion       = $mod->completion;
         $cm->extra            = isset($mod->extra) ? urldecode($mod->extra) : '';
         $cm->icon             = isset($mod->icon) ? $mod->icon : '';
         $cm->uservisible      = true;
+        if(!empty($CFG->enableavailability)) {
+            // We must have completion information from modinfo. If it's not
+            // there, cache needs rebuilding
+            if(!isset($mod->availablefrom)) {
+                debugging('enableavailability option was changed; rebuilding '.
+                    'cache for course '.$course->id);
+                rebuild_course_cache($course->id,true);
+                // Re-enter this routine to do it all properly
+                return get_fast_modinfo($course,$userid);
+            }
+            $cm->availablefrom    = $mod->availablefrom;
+            $cm->availableuntil   = $mod->availableuntil;
+            $cm->showavailability = $mod->showavailability;
+            $cm->conditionscompletion = $mod->conditionscompletion;
+            $cm->conditionsgrade  = $mod->conditionsgrade;
+        }
 
         // preload long names plurals and also check module is installed properly
         if (!isset($modlurals[$cm->modname])) {
@@ -1145,7 +1179,29 @@ function &get_fast_modinfo(&$course, $userid=0) {
         }
         $cm->modplural = $modlurals[$cm->modname];
 
-        if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', $contexts[$cm->id], $userid)) {
+        if(!empty($CFG->enableavailability)) {
+            // Unfortunately the next call really wants to call 
+            // get_fast_modinfo, but that would be recursive, so we fake up a 
+            // modinfo for it already
+            if(empty($minimalmodinfo)) {
+                $minimalmodinfo=new stdClass();
+                $minimalmodinfo->cms=array();
+                foreach($info as $mod) {
+                    $minimalcm=new stdClass();
+                    $minimalcm->id=$mod->cm;
+                    $minimalcm->name=urldecode($mod->name);
+                    $minimalmodinfo->cms[$minimalcm->id]=$minimalcm;
+                }
+            }
+
+            // Get availability information
+            $ci = new condition_info($cm);
+            $cm->available=$ci->is_available($cm->availableinfo,true,$userid,
+                $minimalmodinfo);
+        } else {
+            $cm->available=true;
+        }
+        if ((!$cm->visible or !$cm->available) and !has_capability('moodle/course:viewhiddenactivities', $contexts[$cm->id], $userid)) {
             $cm->uservisible = false;
 
         } else if (!empty($CFG->enablegroupings) and !empty($cm->groupmembersonly)
@@ -1183,7 +1239,7 @@ function &get_fast_modinfo(&$course, $userid=0) {
  * Returns a number of useful structures for course displays
  */
 function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modnamesused) {
-    global $DB;
+    global $DB,$COURSE;
 
     $mods          = array();    // course modules indexed by id
     $modnames      = array();    // all course module names (except resource!)
@@ -1202,7 +1258,10 @@ function get_all_mods($courseid, &$mods, &$modnames, &$modnamesplural, &$modname
         print_error("nomodules", 'debug');
     }
 
-    if ($rawmods = get_course_mods($courseid)) {
+    $course = ($courseid==$COURSE->id) ? $COURSE : $DB->get_record('course',array('id'=>$courseid));
+    $modinfo = get_fast_modinfo($course);
+
+    if ($rawmods=$modinfo->cms) {
         foreach($rawmods as $mod) {    // Index the mods
             if (empty($modnames[$mod->modname])) {
                 continue;
@@ -1336,7 +1395,8 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
             }
 
             if (isset($modinfo->cms[$modnumber])) {
-                if (!$modinfo->cms[$modnumber]->uservisible) {
+                if (!$modinfo->cms[$modnumber]->uservisible &&
+                    empty($modinfo->cms[$modnumber]->showavailability)) {
                     // visibility shortcut
                     continue;
                 }
@@ -1345,7 +1405,8 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     // module not installed
                     continue;
                 }
-                if (!coursemodule_visible_for_user($mod)) {
+                if (!coursemodule_visible_for_user($mod) &&
+                    empty($mod->showavailability)) {
                     // full visibility check
                     continue;
                 }
@@ -1366,11 +1427,11 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
 
             $extra = '';
             if (!empty($modinfo->cms[$modnumber]->extra)) {
-            $extra = $modinfo->cms[$modnumber]->extra;
+                $extra = $modinfo->cms[$modnumber]->extra;
             }
 
             if ($mod->modname == "label") {
-                if (!$mod->visible) {
+                if (!$mod->visible || !$mod->uservisible) {
                     echo "<div class=\"dimmed_text\">";
                 }
                 echo format_text($extra, FORMAT_HTML, $labelformatoptions);
@@ -1416,17 +1477,27 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     $altname = get_accesshide(' '.$altname);
                 }
 
-                $linkcss = $mod->visible ? "" : " class=\"dimmed\" ";
-                echo '<a '.$linkcss.' '.$extra.        // Title unnecessary!
-                     ' href="'.$CFG->wwwroot.'/mod/'.$mod->modname.'/view.php?id='.$mod->id.'">'.
-                     '<img src="'.$icon.'" class="activityicon" alt="" /> <span>'.
-                     $instancename.$altname.'</span></a>';
-
-                if (!empty($CFG->enablegroupings) && !empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
-                    if (!isset($groupings)) {
-                        $groupings = groups_get_all_groupings($course->id);
+                // We may be displaying this just in order to show information
+                // about visibility, without the actual link
+                if($mod->uservisible) {
+                    // Display normal module link
+                    $linkcss = $mod->visible ? "" : " class=\"dimmed\" ";
+                    echo '<a '.$linkcss.' '.$extra.        // Title unnecessary!
+                         ' href="'.$CFG->wwwroot.'/mod/'.$mod->modname.'/view.php?id='.$mod->id.'">'.
+                         '<img src="'.$icon.'" class="activityicon" alt="" /> <span>'.
+                         $instancename.$altname.'</span></a>';
+
+                    if (!empty($CFG->enablegroupings) && !empty($mod->groupingid) && has_capability('moodle/course:managegroups', get_context_instance(CONTEXT_COURSE, $course->id))) {
+                        if (!isset($groupings)) {
+                            $groupings = groups_get_all_groupings($course->id);
+                        }
+                        echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
                     }
-                    echo " <span class=\"groupinglabel\">(".format_string($groupings[$mod->groupingid]->name).')</span>';
+                } else {
+                    // Display greyed-out text of link
+                    echo '<span class="dimmed_text" '.$extra.' >'.
+                         '<img src="'.$icon.'" class="activityicon" alt="" /> <span>'.
+                         $instancename.$altname.'</span></span>';
                 }
             }
             if ($usetracking && $mod->modname == 'forum') {
@@ -1460,7 +1531,8 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
             $completion=$hidecompletion
                 ? COMPLETION_TRACKING_NONE
                 : $completioninfo->is_enabled($mod);
-            if($completion!=COMPLETION_TRACKING_NONE && isloggedin() && !isguestuser()) {
+            if($completion!=COMPLETION_TRACKING_NONE && isloggedin() && 
+                !isguestuser() && $mod->uservisible) {
                $completiondata=$completioninfo->get_data($mod,true);
                 $completionicon='';
                 if($isediting) {
@@ -1524,6 +1596,21 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                 }
             }
 
+            // Show availability information (for someone who isn't allowed to 
+            // see the activity itself, or for staff)
+            if(!$mod->uservisible) {
+                echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
+            } else if($isediting && !empty($CFG->enableavailability)) {
+                $ci = new condition_info($mod);
+                $fullinfo=$ci->get_full_information();
+                if($fullinfo) {
+                    echo '<div class="availabilityinfo">'.get_string($mod->showavailability 
+                        ? 'userrestriction_visible'
+                        : 'userrestriction_hidden','condition',
+                        $fullinfo).'</div>';
+                }
+            }
+
             echo "</li>\n";
         }
 
index 399a3dc375dd23bdc99930de913d7dcba639149a..77044a02de050492d8cb93e1dc222e7e01f624e2 100644 (file)
             print_error('cannotupdatelevel');
         }
 
+        rebuild_course_cache($cm->course);
+
         if (SITEID == $cm->course) {
             redirect($CFG->wwwroot);
         } else {
index 69a2a7ea2cfc96ae43c20d035f84112e87f7f3ee..eea05a9fa48e214fb09816cf98f522a7d7db2961 100644 (file)
@@ -6,6 +6,7 @@
     require_once("lib.php");
     require_once($CFG->libdir.'/gradelib.php');
     require_once($CFG->libdir.'/completionlib.php');
+    require_once($CFG->libdir.'/conditionlib.php');
 
     $add    = optional_param('add', 0, PARAM_ALPHA);
     $update = optional_param('update', 0, PARAM_INT);
         $form->completionview     = $cm->completionview;
         $form->completionexpected = $cm->completionexpected;
         $form->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
+        if(!empty($CFG->enableavailability)) {
+            $form->availablefrom      = $cm->availablefrom;
+            $form->availableuntil     = $cm->availableuntil;
+            $form->showavailability   = $cm->showavailability;
+        }
 
         if ($items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$form->modulename,
                                                  'iteminstance'=>$form->instance, 'courseid'=>$course->id))) {
                 $cm->completionview            = $fromform->completionview;
                 $cm->completionexpected        = $fromform->completionexpected;
             }
+            if(!empty($CFG->enableavailability)) {
+                $cm->availablefrom             = $fromform->availablefrom;
+                $cm->availableuntil            = $fromform->availableuntil;
+                $cm->showavailability          = $fromform->showavailability;
+                condition_info::update_cm_from_form($cm,$fromform,true);
+            }
 
             if (!$DB->update_record('course_modules', $cm)) {
                 print_error('cannotupdatecoursemodule');
                 $newcm->completionview            = $fromform->completionview;
                 $newcm->completionexpected        = $fromform->completionexpected;
             }
+            if(!empty($CFG->enableavailability)) {
+                $newcm->availablefrom             = $fromform->availablefrom;
+                $newcm->availableuntil            = $fromform->availableuntil;
+                $newcm->showavailability          = $fromform->showavailability;
+            }
 
             if (!$fromform->coursemodule = add_course_module($newcm)) {
                 print_error('cannotaddcoursemodule');
                 set_coursemodule_idnumber($fromform->coursemodule, $fromform->cmidnumber);
             }
 
+            // Set up conditions
+            if($CFG->enableavailability) {
+                condition_info::update_cm_from_form(
+                    (object)array('id'=>$fromform->coursemodule),$fromform,false);
+            }
+
             add_to_log($course->id, "course", "add mod",
                        "../mod/$fromform->modulename/view.php?id=$fromform->coursemodule",
                        "$fromform->modulename $fromform->instance");
index 5ededc05cc03d30022711a92ac61e20c3d1940ee..4abf1d07cb619da0f12bb21fdf1d02b99bb13a29 100644 (file)
@@ -1,6 +1,11 @@
 <?php  //$Id$
 require_once ($CFG->libdir.'/formslib.php');
-require_once($CFG->libdir.'/completionlib.php');
+if(!empty($CFG->enablecompletion)) {
+    require_once($CFG->libdir.'/completionlib.php');
+}
+if(!empty($CFG->enableavailability)) {
+    require_once($CFG->libdir.'/conditionlib.php');
+}
 
 /**
  * This class adds extra methods to form wrapper specific to be used for module
@@ -174,6 +179,33 @@ class moodleform_mod extends moodleform {
                 $mform->freeze($this->_customcompletionelements);
             } 
         }
+
+        // Availability conditions
+        if (!empty($CFG->enableavailability) && $this->_cm) {
+            $ci = new condition_info($this->_cm);
+            $fullcm=$ci->get_full_course_module();
+
+            $num=0;
+            foreach($fullcm->conditionsgrade as $gradeitemid=>$minmax) {
+                $groupelements=$mform->getElement('conditiongradegroup['.$num.']')->getElements();
+                $groupelements[0]->setValue($gradeitemid);
+                // These numbers are always in the format 0.00000 - the rtrims remove any final zeros and,
+                // if it is a whole number, the decimal place.
+                $groupelements[2]->setValue(is_null($minmax->min)?'':rtrim(rtrim($minmax->min,'0'),'.'));
+                $groupelements[4]->setValue(is_null($minmax->max)?'':rtrim(rtrim($minmax->max,'0'),'.'));
+                $num++;
+            }
+
+            if ($completion->is_enabled()) {
+                $num=0;
+                foreach($fullcm->conditionscompletion as $othercmid=>$state) {
+                    $groupelements=$mform->getElement('conditioncompletiongroup['.$num.']')->getElements();
+                    $groupelements[0]->setValue($othercmid);
+                    $groupelements[1]->setValue($state);
+                    $num++;
+                }
+            }
+        }
     }
 
     // form verification
@@ -230,8 +262,9 @@ class moodleform_mod extends moodleform {
         if (is_object($default_values)) {
             $default_values = (array)$default_values;
         }
-        $this->data_preprocessing($default_values);
-        parent::set_data($default_values); //never slashed for moodleform_mod
+
+        $this->data_preprocessing($default_values);        
+        parent::set_data($default_values);
     }
 
     /**
@@ -352,8 +385,90 @@ class moodleform_mod extends moodleform {
             $mform->addElement('select', 'gradecat', get_string('gradecategory', 'grades'), $categories);
         }
 
+        if (!empty($CFG->enableavailability)) {
+            // Conditional availability
+            $mform->addElement('header', '', get_string('availabilityconditions', 'condition'));
+            $mform->addElement('date_selector', 'availablefrom', get_string('availablefrom', 'condition'), array('optional'=>true));
+            $mform->setHelpButton('availablefrom', array('conditiondates', get_string('help_conditiondates', 'condition'), 'condition'));
+            $mform->addElement('date_selector', 'availableuntil', get_string('availableuntil', 'condition'), array('optional'=>true));
+            $mform->setHelpButton('availableuntil', array('conditiondates', get_string('help_conditiondates', 'condition'), 'condition'));
+
+            // Conditions based on grades
+            $gradeoptions=array();
+            $items=grade_item::fetch_all(array('courseid'=>$COURSE->id));
+            foreach($items as $id=>$item) {
+                $gradeoptions[$id]=$item->get_name();
+            }
+            asort($gradeoptions);
+            $gradeoptions=array(0=>get_string('none','condition'))+$gradeoptions;
+
+            $grouparray=array();
+            $grouparray[] =& $mform->createElement('select','conditiongradeitemid','',$gradeoptions);
+            $grouparray[] =& $mform->createElement('static', '', '',' '.get_string('grade_atleast','condition'));        
+            $grouparray[] =& $mform->createElement('text', 'conditiongrademin','',array('size'=>3));
+            $grouparray[] =& $mform->createElement('static', '', '',' '.get_string('grade_upto','condition'));        
+            $grouparray[] =& $mform->createElement('text', 'conditiongrademax','',array('size'=>3));
+            $mform->setType('conditiongrademin',PARAM_FLOAT);            
+            $mform->setType('conditiongrademax',PARAM_FLOAT);            
+            $group = $mform->createElement('group','conditiongradegroup', 
+                get_string('gradecondition', 'condition'),$grouparray);
+
+            // Get version with condition info and store it so we don't ask
+            // twice
+            if(!empty($this->_cm)) {           
+                $ci = new condition_info($this->_cm,CONDITION_MISSING_EXTRATABLE);
+                $this->_cm=$ci->get_full_course_module();
+                $count=count($this->_cm->conditionsgrade)+1;
+            } else {
+                $count=1;
+            }
+
+            $this->repeat_elements(array($group),$count,array(),'conditiongraderepeats','conditiongradeadds',2,
+                get_string('addgrades','condition'),true);
+            $mform->setHelpButton('conditiongradegroup[0]', array('gradecondition', get_string('help_gradecondition', 'condition'), 'condition'));
+
+            // Conditions based on completion
+            $completion = new completion_info($COURSE);
+            if ($completion->is_enabled()) {
+                $completionoptions=array();
+                $modinfo=get_fast_modinfo($COURSE);
+                foreach($modinfo->cms as $id=>$cm) {
+                    $completionoptions[$id]=$cm->name;
+                }
+                asort($completionoptions);
+                $completionoptions=array(0=>get_string('none','condition'))+$completionoptions;
+
+                $completionvalues=array(
+                    COMPLETION_COMPLETE=>get_string('completion_complete','condition'),
+                    COMPLETION_INCOMPLETE=>get_string('completion_incomplete','condition'),
+                    COMPLETION_COMPLETE_PASS=>get_string('completion_pass','condition'),
+                    COMPLETION_COMPLETE_FAIL=>get_string('completion_fail','condition'));
+
+                $grouparray=array();        
+                $grouparray[] =& $mform->createElement('select','conditionsourcecmid','',$completionoptions);
+                $grouparray[] =& $mform->createElement('select','conditionrequiredcompletion','',$completionvalues);
+                $group = $mform->createElement('group','conditioncompletiongroup', 
+                    get_string('completioncondition', 'condition'),$grouparray);
+
+                $count=empty($this->_cm) ? 1 : count($this->_cm->conditionscompletion)+1;
+                $this->repeat_elements(array($group),$count,array(),
+                    'conditioncompletionrepeats','conditioncompletionadds',2,
+                    get_string('addcompletions','condition'),true);
+                $mform->setHelpButton('conditioncompletiongroup[0]', array('completioncondition', get_string('help_completioncondition', 'condition'), 'condition'));
+            }
+
+            // Do we display availability info to students?        
+            $mform->addElement('select', 'showavailability', get_string('showavailability', 'condition'), 
+                    array(CONDITION_STUDENTVIEW_SHOW=>get_string('showavailability_show', 'condition'), 
+                    CONDITION_STUDENTVIEW_HIDE=>get_string('showavailability_hide', 'condition')));
+            $mform->setDefault('showavailability', CONDITION_STUDENTVIEW_SHOW);                
+            $mform->setHelpButton('showavailability', array('showavailability', get_string('help_showavailability', 'condition'), 'condition'));
+        }
+
         // Conditional activities: completion tracking section 
-        $completion = new completion_info($COURSE);
+        if(!isset($completion)) {
+            $completion = new completion_info($COURSE);
+        }
         if ($completion->is_enabled()) {
             $mform->addElement('header', '', get_string('activitycompletion', 'completion'));
 
@@ -362,7 +477,7 @@ class moodleform_mod extends moodleform {
             $mform->addElement('submit', 'unlockcompletion', get_string('unlockcompletion', 'completion'));
             $mform->registerNoSubmitButton('unlockcompletion');
             $mform->addElement('hidden', 'completionunlocked', 0);
-            
+
             $mform->addElement('select', 'completion', get_string('completion', 'completion'), 
                 array(COMPLETION_TRACKING_NONE=>get_string('completion_none', 'completion'), 
                 COMPLETION_TRACKING_MANUAL=>get_string('completion_manual', 'completion')));
diff --git a/lang/en_utf8/condition.php b/lang/en_utf8/condition.php
new file mode 100644 (file)
index 0000000..7aa8121
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+$string['addgrades']='Add {no} grade conditions to form';
+$string['addcompletions']='Add {no} activity conditions to form';
+$string['availabilityconditions']='Restrict availability';
+$string['availablefrom']='Only available from';
+$string['availableuntil']='Only available until';
+$string['completion_complete']=' must be marked complete';
+$string['completion_incomplete']=' must not be marked complete';
+$string['completion_pass']=' must be complete with pass grade';
+$string['completion_fail']=' must be complete with fail grade';
+$string['configenableavailability']='When enabled, this lets you set conditions (based on date, grade, or completion) that control whether an activity is available.';
+$string['enableavailability']='Enable conditional availability';
+$string['grade_atleast']='must be &ge;';
+$string['grade_upto']='and &lt;';
+$string['gradecondition']='Grade condition';
+$string['completioncondition']='Activity completion condition';
+$string['help_conditiondates']='available dates';
+$string['help_showavailability']='display of unavailable activities';
+$string['none']='(none)';
+$string['requires_completion_0']='Not available unless the activity <strong>$a</strong> is incomplete.';
+$string['requires_completion_1']='Not available until the activity <strong>$a</strong> is marked complete.';
+$string['requires_completion_2']='Not available until the activity <strong>$a</strong> is complete and passed.';
+$string['requires_completion_3']='Not available unless the activity <strong>$a</strong> is complete and failed.';
+$string['requires_date']='Not available until $a.';
+$string['requires_date_before']='Not available from $a.';
+$string['requires_grade_any']='Not available until you have a grade in <strong>$a</strong>.';
+$string['requires_grade_min']='Not available until you achieve a required score in <strong>$a</strong>.';
+$string['requires_grade_max']='Not available unless you get an appropriate score in <strong>$a</strong>.';
+$string['requires_grade_range']='Not available unless you get a particular score in <strong>$a</strong>.';
+$string['showavailability']='Before activity is available';
+$string['showavailability_show']='Show activity greyed-out, with restriction information';
+$string['showavailability_hide']='Hide activity entirely';
+$string['userrestriction_visible']='Activity conditionally restricted: &lsquo;$a&rsquo;';
+$string['userrestriction_hidden']='Activity conditionally restricted (completely hidden, no message): &lsquo;$a&rsquo;';
+?>
diff --git a/lang/en_utf8/help/condition/completioncondition.html b/lang/en_utf8/help/condition/completioncondition.html
new file mode 100644 (file)
index 0000000..1e2b83d
--- /dev/null
@@ -0,0 +1,20 @@
+<h1>Activity completion condition</h1>
+
+<p>
+You can set a condition based on whether the user has completed another 
+activity.
+</p>
+
+<p>
+This feature uses the completion options that have been configured for the
+other activity. You can choose whether the activity must be complete, 
+incomplete, complete and passed, or complete and failed. The final two 
+options only work if you use grade-based completion and set a pass mark on
+the grade item. (Please look at the documentation for activity completion
+if this is unclear.)
+</p>
+
+<p>
+You can add more than one completion condition. All conditions must be met in 
+order for the activity to appear.
+</p>
diff --git a/lang/en_utf8/help/condition/conditiondates.html b/lang/en_utf8/help/condition/conditiondates.html
new file mode 100644 (file)
index 0000000..d26bf49
--- /dev/null
@@ -0,0 +1,23 @@
+<h1>Available dates</h1>
+
+<p>
+Using the 'Only available from' and 'Only available until' dates, you can make 
+an activity appear or disappear. The activity is only shown to students from the
+'available from' date, and it disappears on the 'available until' date. Students 
+cannot access it outside those times, even if they guess the URL.
+</p>
+
+<p>
+By default both dates are disabled, meaning that the activity is available at
+any time (as long as the student can access the course).
+</p> 
+
+<ul>
+<li> If you choose to show information about an activity that is unavailable,
+  then before the 'available from' date, students will see the activity 
+  greyed-out, with informational text about the date that it appears.</li>
+<li> The activity completely vanishes on the 'available to' date, even if 
+  you've chosen to show information.</li>
+<li> Setting 'Visible' to 'Hide' overrides these settings. If you set 'Visible'
+  to 'Hide', the activity is never available regardless of date.</li>
+</ul>
diff --git a/lang/en_utf8/help/condition/gradecondition.html b/lang/en_utf8/help/condition/gradecondition.html
new file mode 100644 (file)
index 0000000..de98b5d
--- /dev/null
@@ -0,0 +1,31 @@
+<h1>Grade condition</h1>
+
+<p>
+You can specify a condition on any grade in the course: the full course grade,
+the grade for any activity, or a custom grade that you create manually.
+</p>
+
+<p>
+You can enter either a minimum value (&ge;), a maximum value (&lt;), both, or 
+neither. The activity will only appear if the student has a value for the 
+specified grade, and if it falls within any specified number range.
+</p> 
+
+<p>
+You can add more than one grade condition. All conditions must be met in order
+for the activity to appear.
+</p>
+
+<ul>
+<li>The range numbers can be fractional (with up to five decimal places) if 
+    necessary. </li>
+<li>Be careful with the maximum value; if the maximum is 7, a student who 
+    scores exactly 7 will not see the activity. You could set it to 7.01 if
+    you really wanted to include 7.</li>
+<li>If creating several different activities that appear according to grade 
+    ranges, use the same number for the maximum of one activity, and the minimum
+    of the next. For example, you might create one activity with a maximum of 7
+    and another with a minimum of 7. The first would appear to everyone scoring
+    between 0 and 6.99999, and the second would appear to everyone scoring 7.00000
+    to 10. This guarantees that everyone with a grade will see one or other.</li>
+</ul>
diff --git a/lang/en_utf8/help/condition/showavailability.html b/lang/en_utf8/help/condition/showavailability.html
new file mode 100644 (file)
index 0000000..387601e
--- /dev/null
@@ -0,0 +1,27 @@
+<h1>Unavailable activity display</h1>
+
+<p>
+When an activity is unavailable due to the restrictions in this box, there are 
+two possibilities:
+</p>
+
+<ol>
+<li> The activity displays to users, but as greyed-out text instead of a link.
+  Informational text below the activity indicates when, or under what 
+  conditions, it will become available.</li>
+<li> The activity does not display to users at all.</li>
+</ol>
+
+<p>
+In both cases, once the activity becomes available, it displays as normal.
+</p>
+
+<ul>
+<li>Users with the 'view hidden activities' capability can still see 
+  unavailable activities, regardless of this setting. The informational text
+  always appears to them.</li>
+<li>This option does not affect the standard 'visibility' option (also controlled
+  by the eye icon). You can still use this icon to quickly and completely hide 
+  any activity from students, for example if you find a problem with the 
+  activity.</li>
+</ul>
index dc8a0f2339532e0b384b9933190027a4ca216c95..110b72eb0dc29500cb1abffc6dbbed2ba380d3c8 100644 (file)
@@ -394,13 +394,15 @@ class completion_info {
      * Obtains completion data for a particular activity and user (from the
      * session cache if available, or by SQL query)
      *
-     * @param object $cm Activity
+     * @param object $cm Activity; only required field is ->id
      * @param bool $wholecourse If true (default false) then, when necessary to
      *   fill the cache, retrieves information from the entire course not just for
      *   this one activity
      * @param int $userid User ID or 0 (default) for current user
-     * @param array $modinfo For unit testing only, supply the value
-     *   here. Otherwise the method calls get_fast_modinfo
+     * @param array $modinfo Supply the value here - this is used for unit 
+     *   testing and so that it can be called recursively from within 
+     *   get_fast_modinfo. (Needs only list of all CMs with IDs.)
+     *   Otherwise the method calls get_fast_modinfo itself.
      * @return object Completion data (record from course_modules_completion)
      * @throws Exception In some cases where the requested course-module is not
      *   found on the specified course
@@ -632,7 +634,7 @@ class completion_info {
         foreach ($users as $user) {
             $userids[] = $user->id;
             $resultobject->users[$user->id]=$user;
-            $resultobject->users[$user->id]->progress=array();            
+            $resultobject->users[$user->id]->progress=array();
         }
 
         for($i=0; $i<count($userids); $i+=1000) {
@@ -716,22 +718,24 @@ WHERE
     }
 
     /**
-     * This temporary function is intended to be replaced once a Moodle exception
-     * system is agreed. Code that used to call this function should instead
-     * throw an exception, so this function should be deleted. The function is
-     * only used internally.
-     *
      * This is to be used only for system errors (things that shouldn't happen)
      * and not user-level errors.
      *
      * @param string $error Error string (will not be displayed to user unless
      *   debugging is enabled)
+     * @throws moodle_exception Exception with the error string as debug info
      */
     function internal_systemerror($error) {
         global $CFG;
+        throw new moodle_exception('err_system','completion',
+            $CFG->wwwroot.'/course/view.php?id='.$this->course->id,null,$error);
+    }
 
-        debugging($error, DEBUG_ALL);
-        print_error('err_system', 'completion', $CFG->wwwroot.'/course/view.php?id='.$this->course->id);
+    /** For testing only. Wipes information cached in user session. */
+    static function wipe_session_cache() {
+        global $SESSION;
+        unset($SESSION->completioncache);
+        unset($SESSION->completioncacheuserid);
     }
 }
 
diff --git a/lib/conditionlib.php b/lib/conditionlib.php
new file mode 100644 (file)
index 0000000..f82d2c4
--- /dev/null
@@ -0,0 +1,538 @@
+<?php
+// Used for tracking conditions that apply before activities are displayed
+// to students ('conditional availability').
+
+/** The activity is not displayed to students at all when conditions aren't met. */
+define('CONDITION_STUDENTVIEW_HIDE',0);
+/** The activity is displayed to students as a greyed-out name, with informational
+    text that explains the conditions under which it will be available. */
+define('CONDITION_STUDENTVIEW_SHOW',1);
+
+/** The $cm variable is expected to contain all completion-related data */
+define('CONDITION_MISSING_NOTHING',0);
+/** The $cm variable is expected to contain the fields from course_modules but 
+    not the course_modules_availability data */
+define('CONDITION_MISSING_EXTRATABLE',1);
+/** The $cm variable is expected to contain nothing except the ID */
+define('CONDITION_MISSING_EVERYTHING',2);
+
+class condition_info {
+    private $cm,$gotdata;
+
+    /**
+     * Constructs with course-module details.
+     *
+     * @param object $cm Moodle course-module object. May have extra fields
+     *   ->conditionsgrade, ->conditionscompletion which should come from 
+     *   get_fast_modinfo. Should have ->availablefrom, ->availableuntil, 
+     *   and ->showavailability, ->course; but the only required thing is ->id.
+     * @param int $expectingmissing Used to control whether or not a developer
+     *   debugging message (performance warning) will be displayed if some of 
+     *   the above data is missing and needs to be retrieved; a 
+     *   CONDITION_MISSING_xx constant
+     * @param bool $loaddata If you need a 'write-only' object, set this value
+     *   to false to prevent database access from constructor
+     * @return condition_info Object which can retrieve information about the
+     *   activity
+     */
+    public function __construct($cm,$expectingmissing=CONDITION_MISSING_NOTHING,
+        $loaddata=true) {
+        global $DB;
+
+        // Check ID as otherwise we can't do the other queries
+        if(empty($cm->id)) {
+            throw new coding_exception("Invalid parameters; course-module ID not included");
+        }
+
+        // If not loading data, don't do anything else
+        if(!$loaddata) {
+            $this->cm=(object)array('id'=>$cm->id);
+            $this->gotdata=false;
+            return;
+        }
+
+        // Missing basic data from course_modules
+        if(!isset($cm->availablefrom) || !isset($cm->availableuntil) || 
+            !isset($cm->showavailability) || !isset($cm->course)) {
+            if($expectingmissing<CONDITION_MISSING_EVERYTHING) {
+                debugging('Performance warning: condition_info constructor is 
+                    faster if you pass in $cm with at least basic fields 
+                    (availablefrom,availableuntil,showavailability,course). 
+                    [This warning can be disabled, see phpdoc.]',
+                    DEBUG_DEVELOPER);
+            }
+            $cm=$DB->get_record('course_modules',array('id'=>$cm->id),
+                'id,course,availablefrom,availableuntil,showavailability');
+        }
+
+        $this->cm=clone($cm);
+        $this->gotdata=true;
+
+        // Missing extra data
+        if(!isset($cm->conditionsgrade) || !isset($cm->conditionscompletion)) {
+            if($expectingmissing<CONDITION_MISSING_EXTRATABLE) {
+                debugging('Performance warning: condition_info constructor is 
+                    faster if you pass in a $cm from get_fast_modinfo.
+                    [This warning can be disabled, see phpdoc.]',
+                    DEBUG_DEVELOPER);
+            }
+
+            self::fill_availability_conditions($this->cm);
+        }
+    }
+
+    /**
+     * Adds the extra availability conditions (if any) into the given 
+     * course-module object.
+     *
+     * @param object &$cm Moodle course-module data object
+     */
+    public static function fill_availability_conditions(&$cm) {
+        if(empty($cm->id)) {
+            throw new coding_exception("Invalid parameters; course-module ID not included");
+        }
+
+        // Does nothing if the variables are already present
+        if(!isset($cm->conditionsgrade) ||
+            !isset($cm->conditionscompletion)) {
+            $cm->conditionsgrade=array();
+            $cm->conditionscompletion=array();
+
+            global $DB,$CFG;
+            $conditions=$DB->get_records_sql($sql="
+SELECT 
+    cma.id as cmaid, gi.*,cma.sourcecmid,cma.requiredcompletion,cma.gradeitemid,
+    cma.grademin as conditiongrademin, cma.grademax as conditiongrademax
+FROM
+    {course_modules_availability} cma
+    LEFT JOIN {grade_items} gi ON gi.id=cma.gradeitemid
+WHERE
+    coursemoduleid=?",array($cm->id));
+            foreach($conditions as $condition) {
+                if(!is_null($condition->sourcecmid)) {
+                    $cm->conditionscompletion[$condition->sourcecmid]=
+                        $condition->requiredcompletion;
+                } else {                    
+                    $minmax=new stdClass;
+                    $minmax->min=$condition->conditiongrademin;
+                    $minmax->max=$condition->conditiongrademax;
+                    $minmax->name=self::get_grade_name($condition);
+                    $cm->conditionsgrade[$condition->gradeitemid]=$minmax;
+                }
+            }
+        }
+    }
+    
+    /**
+     * Obtains the name of a grade item.
+     * @param object $gradeitemobj Object from get_record on grade_items table,
+     *     (can be empty if you want to just get !missing)
+     * @return string Name of item of !missing if it didn't exist
+     */
+    private static function get_grade_name($gradeitemobj) {
+        global $CFG;
+        if(isset($gradeitemobj->id)) {
+            require_once($CFG->libdir.'/gradelib.php');
+            $item=new grade_item;
+            grade_object::set_properties($item,$gradeitemobj);    
+            return $item->get_name();
+        } else {
+            return '!missing'; // Ooops, missing grade
+        }
+    }
+
+    /**
+     * @return A course-module object with all the information required to
+     *   determine availability.
+     * @throws coding_exception If data wasn't loaded
+     */
+    public function get_full_course_module() {
+        $this->require_data();
+        return $this->cm;
+    }
+
+    /**
+     * Adds to the database a condition based on completion of another module.
+     * @param int $cmid ID of other module
+     * @param int $requiredcompletion COMPLETION_xx constant
+     */
+    public function add_completion_condition($cmid,$requiredcompletion) {
+        // Add to DB
+        global $DB;
+        $DB->insert_record('course_modules_availability',
+            (object)array('coursemoduleid'=>$this->cm->id,
+                'sourcecmid'=>$cmid,'requiredcompletion'=>$requiredcompletion),
+            false);
+
+        // Store in memory too
+        $this->cm->conditionscompletion[$cmid]=$requiredcompletion;
+    }
+
+    /**
+     * Adds to the database a condition based on the value of a grade item.
+     * @param int $gradeitemid ID of grade item
+     * @param float $min Minimum grade (>=), up to 5 decimal points, or null if none
+     * @param float $max Maximum grade (<), up to 5 decimal points, or null if none
+     * @param bool $updateinmemory If true, updates data in memory; otherwise,
+     *   memory version may be out of date (this has performance consequences,
+     *   so don't do it unless it really needs updating)
+     */
+    public function add_grade_condition($gradeitemid,$min,$max,$updateinmemory=false) {
+        // Normalise nulls
+        if($min==='') {
+            $min=null;
+        }
+        if($max==='') {
+            $max=null;
+        }
+        // Add to DB
+        global $DB;
+        $DB->insert_record('course_modules_availability',
+            (object)array('coursemoduleid'=>$this->cm->id,
+                'gradeitemid'=>$gradeitemid,'grademin'=>$min,'grademax'=>$max),
+            false);
+
+        // Store in memory too
+        if($updateinmemory) {
+            $this->cm->conditionsgrade[$gradeitemid]=(object)array(
+                'min'=>$min,'max'=>$max);
+            $this->cm->conditionsgrade[$gradeitemid]->name=
+                self::get_grade_name($DB->get_record('grade_items',
+                    array('id'=>$gradeitemid)));
+        }
+    }
+
+    /**
+     * Erases from the database all conditions for this activity.
+     */
+    public function wipe_conditions() {
+        // Wipe from DB
+        global $DB;
+        $DB->delete_records('course_modules_availability',
+            array('coursemoduleid'=>$this->cm->id));
+
+        // And from memory
+        $this->cm->conditionsgrade=array();
+        $this->cm->conditionscompletion=array();
+    }
+
+    /**
+     * Obtains a string describing all availability restrictions (even if
+     * they do not apply any more).
+     * @param object $modinfo Usually leave as null for default. Specify when
+     *   calling recursively from inside get_fast_modinfo. The value supplied 
+     *   here must include list of all CMs with 'id' and 'name'
+     * @return string Information string (for admin) about all restrictions on 
+     *   this item
+     * @throws coding_exception If data wasn't loaded
+     */
+    public function get_full_information($modinfo=null) {
+        $this->require_data();                
+        global $COURSE,$DB;
+
+        $information='';
+
+        // Completion conditions
+        if(count($this->cm->conditionscompletion)>0) {
+            if($this->cm->course==$COURSE->id) {
+                $course=$COURSE;
+            } else {
+                $course=$DB->get_record('course',array('id'=>$this->cm->course),'id,enablecompletion,modinfo');
+            }
+            foreach($this->cm->conditionscompletion as $cmid=>$expectedcompletion) {
+                if(!$modinfo) {
+                    $modinfo=get_fast_modinfo($course);
+                }
+                $information.=get_string(
+                    'requires_completion_'.$expectedcompletion,
+                    'condition',$modinfo->cms[$cmid]->name).' ';
+            }
+        }
+
+        // Grade conditions
+        if(count($this->cm->conditionsgrade)>0) {
+            foreach($this->cm->conditionsgrade as $gradeitemid=>$minmax) {
+                // String depends on type of requirement. We are coy about
+                // the actual numbers, in case grades aren't released to
+                // students.
+                if(is_null($minmax->min) && is_null($minmax->max)) {
+                    $string='any';
+                } else if(is_null($minmax->max)) {
+                    $string='min';
+                } else if(is_null($minmax->min)) {
+                    $string='max';
+                } else {
+                    $string='range';
+                }
+                $information.=get_string('requires_grade_'.$string,'condition',$minmax->name).' ';
+            }
+        }
+
+        // Dates
+        if($this->cm->availablefrom) {
+            $information.=get_string('requires_date','condition',userdate(
+                $this->cm->availablefrom,get_string('strftimedate','langconfig')));
+        }
+
+        if($this->cm->availableuntil) {
+            $information.=get_string('requires_date_before','condition',userdate(
+                $this->cm->availableuntil,get_string('strftimedate','langconfig')));
+        }
+
+        $information=trim($information);
+        return $information;
+    }
+
+    /**
+     * Determines whether this particular course-module is currently available
+     * according to these criteria. 
+     * 
+     * - This does not include the 'visible' setting (i.e. this might return 
+     *   true even if visible is false); visible is handled independently.
+     * - This does not take account of the viewhiddenactivities capability.
+     *   That should apply later.
+     *
+     * @param string &$information If the item has availability restrictions,
+     *   a string that describes the conditions will be stored in this variable; 
+     *   if this variable is set blank, that means don't display anything
+     * @param bool $grabthelot Performance hint: if true, caches information 
+     *   required for all course-modules, to make the front page and similar
+     *   pages work more quickly (works only for current user)
+     * @param int $userid If set, specifies a different user ID to check availability for
+     * @param object $modinfo Usually leave as null for default. Specify when
+     *   calling recursively from inside get_fast_modinfo. The value supplied 
+     *   here must include list of all CMs with 'id' and 'name'
+     * @return bool True if this item is available to the user, false otherwise
+     * @throws coding_exception If data wasn't loaded
+     */
+    public function is_available(&$information,$grabthelot=false,$userid=0,$modinfo=null) {
+        $this->require_data();                
+        global $COURSE,$DB;
+
+        $available=true;
+        $information='';
+
+        // Check each completion condition
+        if(count($this->cm->conditionscompletion)>0) {
+            if($this->cm->course==$COURSE->id) {
+                $course=$COURSE;
+            } else {
+                $course=$DB->get_record('course',array('id'=>$this->cm->course),'id,enablecompletion,modinfo');
+            }
+
+            $completion=new completion_info($course);
+            foreach($this->cm->conditionscompletion as $cmid=>$expectedcompletion) {
+                // The completion system caches its own data
+                $completiondata=$completion->get_data((object)array('id'=>$cmid),
+                    $grabthelot,$userid,$modinfo);
+
+                $thisisok=true;
+                if($expectedcompletion==COMPLETION_COMPLETE) {
+                    // 'Complete' also allows the pass, fail states
+                    switch($completiondata->completionstate) {
+                        case COMPLETION_COMPLETE:
+                        case COMPLETION_COMPLETE_FAIL:
+                        case COMPLETION_COMPLETE_PASS:
+                            break;
+                        default:
+                            $thisisok=false;
+                    }
+                } else {
+                    // Other values require exact match
+                    if($completiondata->completionstate!=$expectedcompletion) {
+                        $thisisok=false;
+                    }
+                }
+                if(!$thisisok) {
+                    $available=false;
+                    if(!$modinfo) {
+                        $modinfo=get_fast_modinfo($course);
+                    }
+                    $information.=get_string(
+                        'requires_completion_'.$expectedcompletion,
+                        'condition',$modinfo->cms[$cmid]->name).' ';
+                }
+            }
+        }
+
+        // Check each grade condition
+        if(count($this->cm->conditionsgrade)>0) {
+            foreach($this->cm->conditionsgrade as $gradeitemid=>$minmax) {
+                $score=$this->get_cached_grade_score($gradeitemid,$grabthelot,$userid);
+                if($score===false ||
+                    (!is_null($minmax->min) && $score<$minmax->min) || 
+                    (!is_null($minmax->max) && $score>=$minmax->max)) {
+                    // Grade fail
+                    $available=false;
+                    // String depends on type of requirement. We are coy about
+                    // the actual numbers, in case grades aren't released to
+                    // students.
+                    if(is_null($minmax->min) && is_null($minmax->max)) {
+                        $string='any';
+                    } else if(is_null($minmax->max)) {
+                        $string='min';
+                    } else if(is_null($minmax->min)) {
+                        $string='max';
+                    } else {
+                        $string='range';
+                    }
+                    $information.=get_string('requires_grade_'.$string,'condition',$minmax->name).' ';
+                }
+            }
+        }
+
+        // Test dates
+        if($this->cm->availablefrom) {
+            if(time() < $this->cm->availablefrom) {
+                $available=false;
+                $information.=get_string('requires_date','condition',userdate(
+                    $this->cm->availablefrom,get_string('strftimedate','langconfig')));
+            }
+        }
+
+        if($this->cm->availableuntil) {
+            if(time() >= $this->cm->availableuntil) {
+                $available=false;
+                // But we don't display any information about this case. This is
+                // because the only reason to set a 'disappear' date is usually
+                // to get rid of outdated information/clutter in which case there
+                // is no point in showing it...
+
+                // Note it would be nice if we could make it so that the 'until'
+                // date appears below the item while the item is still accessible,
+                // unfortunately this is not possible in the current system. Maybe
+                // later, or if somebody else wants to add it.
+            }
+        }
+
+        $information=trim($information);
+        return $available;
+    }
+
+    /**
+     * @return bool True if information about availability should be shown to
+     *   normal users
+     * @throws coding_exception If data wasn't loaded
+     */
+    public function show_availability() {
+        $this->require_data();
+        return $this->cm->showavailability;
+    }
+    
+    /**
+     * Internal function cheks that data was loaded.
+     * @throws coding_exception If data wasn't loaded
+     */
+    private function require_data() {
+        if(!$this->gotdata) {
+            throw new coding_exception('Error: cannot call when info was '.
+                'constructed without data');
+        }
+    }
+
+    /**
+     * Obtains a grade score. Note that this score should not be displayed to 
+     * the user, because gradebook rules might prohibit that. It may be a 
+     * non-final score subject to adjustment later.
+     *
+     * @param int $gradeitemid Grade item ID we're interested in
+     * @param bool $grabthelot If true, grabs all scores for current user on 
+     *   this course, so that later ones come from cache
+     * @param int $userid Set if requesting grade for a different user (does 
+     * not use cache)
+     * @return float Grade score, or false if user does not have a grade yet
+     */
+    private function get_cached_grade_score($gradeitemid,$grabthelot=false,$userid=0) {
+        global $USER, $DB, $SESSION;
+        if($userid==0 || $userid=$USER->id) {
+            // For current user, go via cache in session
+            if(empty($SESSION->gradescorecache) || $SESSION->gradescorecacheuserid!=$USER->id) {
+                $SESSION->gradescorecache=array();
+                $SESSION->gradescorecacheuserid=$USER->id;
+            } 
+            if(!array_key_exists($gradeitemid,$SESSION->gradescorecache)) {
+                if($grabthelot) {
+                    // Get all grades for the current course
+                    $rs=$DB->get_recordset_sql("
+SELECT
+    gi.id,gg.finalgrade 
+FROM 
+    {grade_items} gi
+    LEFT JOIN {grade_grades} gg ON gi.id=gg.itemid AND gg.userid=?
+WHERE
+    gi.courseid=?",array($USER->id,$this->cm->course));
+                    foreach($rs as $record) {
+                        $SESSION->gradescorecache[$record->id]=
+                            is_null($record->finalgrade)
+                                ? false 
+                                : $record->finalgrade;
+
+                    }
+                    $rs->close();
+                    // And if it's still not set, well it doesn't exist (eg
+                    // maybe the user set it as a condition, then deleted the
+                    // grade item) so we call it false
+                    if(!array_key_exists($gradeitemid,$SESSION->gradescorecache)) {
+                        $SESSION->gradescorecache[$gradeitemid]=false;
+                    }
+                } else {
+                    // Just get current grade
+                    $score=$DB->get_field('grade_grades','finalgrade',array(
+                        'userid'=>$USER->id,'itemid'=>$gradeitemid));
+                    // Treat the case where row exists but is null, same as
+                    // case where row doesn't exist
+                    if(is_null($score)) {
+                        $score=false;
+                    }
+                    $SESSION->gradescorecache[$gradeitemid]=$score;
+                }
+            }
+            return $SESSION->gradescorecache[$gradeitemid];
+        } else {
+            // Not the current user, so request the score individually
+            $score=$DB->get_field('grade_grades','finalgrade',array(
+                'userid'=>$userid,'itemid'=>$gradeitemid));
+            if($score===null) {
+                $score=false;
+            }
+            return $score;
+        }
+    }
+
+    /** For testing only. Wipes information cached in user session. */
+    static function wipe_session_cache() {
+        global $SESSION;
+        unset($SESSION->gradescorecache);
+        unset($SESSION->gradescorecacheuserid);
+    }
+
+    /**
+     * Utility function called by modedit.php; updates the 
+     * course_modules_availability table based on the module form data.
+     *
+     * @param object $cm Course-module with as much data as necessary, min id
+     * @param unknown_type $fromform
+     * @param unknown_type $wipefirst
+     */
+    public static function update_cm_from_form($cm,$fromform,$wipefirst=true) {
+        $ci=new condition_info($cm,CONDITION_MISSING_EVERYTHING,false);
+        if($wipefirst) {
+            $ci->wipe_conditions();
+        }
+        foreach($fromform->conditiongradegroup as $record) {
+            if($record['conditiongradeitemid']) {
+                $ci->add_grade_condition($record['conditiongradeitemid'],
+                    $record['conditiongrademin'],$record['conditiongrademax']);
+            }
+        }
+        if(isset($fromform->conditioncompletiongroup)) {
+            foreach($fromform->conditioncompletiongroup as $record) {
+                if($record['conditionsourcecmid']) {
+                    $ci->add_completion_condition($record['conditionsourcecmid'],
+                        $record['conditionrequiredcompletion']);
+                }
+            }
+        }
+    }
+}
+?>
index f3cddee1158869f7fc462964a186880efbf461a4..f5d60abce296f6a1ed272121746ecfd5ea19d5c0 100644 (file)
@@ -1835,7 +1835,7 @@ function instance_is_visible($moduletype, $module) {
  * @return bool
  */
 function coursemodule_visible_for_user($cm, $userid=0) {
-    global $USER;
+    global $USER,$CFG;
 
     if (empty($cm->id)) {
         debugging("Incorrect course module parameter!", DEBUG_DEVELOPER);
@@ -1847,6 +1847,15 @@ function coursemodule_visible_for_user($cm, $userid=0) {
     if (!$cm->visible and !has_capability('moodle/course:viewhiddenactivities', get_context_instance(CONTEXT_MODULE, $cm->id), $userid)) {
         return false;
     }
+    if ($CFG->enableavailability) {
+        require_once($CFG->libdir.'/conditionlib.php');
+        $ci=new condition_info($cm,CONDITION_MISSING_EXTRATABLE);
+        if(!$ci->is_available($cm->availableinfo,false,$userid) and 
+            !has_capability('moodle/course:viewhiddenactivities', 
+                get_context_instance(CONTEXT_MODULE, $cm->id), $userid)) {
+            return false;
+        }
+    }
     return groups_course_module_visible($cm, $userid);
 }
 
index c3a4313bfba8688bf22dd172b7a065643f01f046..e8552477079493b376ab32c1af6f70f8f118135b 100644 (file)
         <FIELD NAME="sourcecmid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="If this condition is based on completion of another activity, then this is the course-module ID of that activity. Otherwise null." PREVIOUS="coursemoduleid" NEXT="requiredcompletion"/>
         <FIELD NAME="requiredcompletion" TYPE="int" LENGTH="1" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="If this condition is on a module's completion, then this should be set to the required completion state. Otherwise null. Suitable values are 1 = completed, 2 = completed-passed, 3 = completed-failed." PREVIOUS="sourcecmid" NEXT="gradeitemid"/>
         <FIELD NAME="gradeitemid" TYPE="int" LENGTH="10" NOTNULL="false" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="If this condition is based on a gradebook score, the item ID is given here (and the item will now not be available until a value is achieved for that grade). Otherwise null." PREVIOUS="requiredcompletion" NEXT="grademin"/>
-        <FIELD NAME="grademin" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" ENUM="false" DECIMALS="5" COMMENT="If set, this is the minimum grade that must be reached in order for this module to appear. Otherwise null." PREVIOUS="gradeitemid" NEXT="grademax"/>
-        <FIELD NAME="grademax" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" ENUM="false" DECIMALS="5" COMMENT="If set, this is the maximum grade that can be reached in order to display this item. Otherwise null." PREVIOUS="grademin"/>
+        <FIELD NAME="grademin" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" ENUM="false" DECIMALS="5" COMMENT="If set, this is the minimum grade that must be reached (greater than or equal) in order for this module to appear. Otherwise null." PREVIOUS="gradeitemid" NEXT="grademax"/>
+        <FIELD NAME="grademax" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" ENUM="false" DECIMALS="5" COMMENT="If set, this is the maximum grade that users must be below (less than) in order to display this item. Otherwise null." PREVIOUS="grademin"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="coursemoduleid"/>
index 7364cb16441397fa9ececf6fe68d1352291d407f..8fc44a831eb7dea3597822b31ca36105e3b23969 100644 (file)
@@ -1056,7 +1056,52 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint($result, 2008111801);
     }
 
-    if ($result && $oldversion < 2008112400) {
+    if ($result && $oldversion < 2008120700) {
+
+    /// Changing precision of field shortname on table course_request to (100)
+        $table = new xmldb_table('course_request');
+        $field = new xmldb_field('shortname', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, null, null, 'fullname');
+
+    /// Launch change of precision for field shortname
+        $dbman->change_field_precision($table, $field);
+
+    /// Main savepoint reached
+        upgrade_main_savepoint($result, 2008120700);
+    }
+
+    /// For MDL-17501. Ensure that any role that has moodle/course:update also
+    /// has moodle/course:visibility.
+    if ($result && $oldversion < 2008120800) {
+    /// Get the roles with 'moodle/course:update'.
+        $systemcontext = get_context_instance(CONTEXT_SYSTEM);
+        $roles = get_roles_with_capability('moodle/course:update', CAP_ALLOW, $systemcontext);
+
+    /// Give those roles 'moodle/course:visibility'.
+        foreach ($roles as $role) {
+            assign_capability('moodle/course:visibility', CAP_ALLOW, $role->id, $systemcontext->id);
+        }
+
+    /// Force all sessions to refresh access data.
+        mark_context_dirty($systemcontext->path);
+
+    /// Main savepoint reached
+        upgrade_main_savepoint($result, 2008120800);
+    }
+
+    if ($result && $oldversion < 2008120801) {
+
+    /// Changing precision of field shortname on table mnet_enrol_course to (100)
+        $table = new xmldb_table('mnet_enrol_course');
+        $field = new xmldb_field('shortname', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, null, null, 'fullname');
+
+    /// Launch change of precision for field shortname
+        $dbman->change_field_precision($table, $field);
+
+    /// Main savepoint reached
+        upgrade_main_savepoint($result, 2008120801);
+    }
+
+    if ($result && $oldversion < 2008121701) {
 
     /// Define field availablefrom to be added to course_modules
         $table = new xmldb_table('course_modules');
@@ -1074,7 +1119,7 @@ function xmldb_main_upgrade($oldversion) {
         if (!$dbman->field_exists($table, $field)) {
             $dbman->add_field($table, $field);
         }
-        
+
     /// Define field showavailability to be added to course_modules
         $field = new xmldb_field('showavailability', XMLDB_TYPE_INTEGER, '1', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, '0', 'availableuntil');
 
@@ -1082,7 +1127,7 @@ function xmldb_main_upgrade($oldversion) {
         if (!$dbman->field_exists($table, $field)) {
             $dbman->add_field($table, $field);
         }
-        
+
     /// Define table course_modules_availability to be created
         $table = new xmldb_table('course_modules_availability');
 
@@ -1106,53 +1151,18 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->create_table($table);
         }
 
-    /// Main savepoint reached
-        upgrade_main_savepoint($result, 2008112400);
-    }
-
-    if ($result && $oldversion < 2008120700) {
-
-    /// Changing precision of field shortname on table course_request to (100)
-        $table = new xmldb_table('course_request');
-        $field = new xmldb_field('shortname', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, null, null, 'fullname');
-
-    /// Launch change of precision for field shortname
-        $dbman->change_field_precision($table, $field);
-
-    /// Main savepoint reached
-        upgrade_main_savepoint($result, 2008120700);
-    }
-
-    /// For MDL-17501. Ensure that any role that has moodle/course:update also
-    /// has moodle/course:visibility.
-    if ($result && $oldversion < 2008120800) {
-    /// Get the roles with 'moodle/course:update'.
-        $systemcontext = get_context_instance(CONTEXT_SYSTEM);
-        $roles = get_roles_with_capability('moodle/course:update', CAP_ALLOW, $systemcontext);
+    /// Changes to modinfo mean we need to rebuild course cache
+        rebuild_course_cache(0,true);
 
-    /// Give those roles 'moodle/course:visibility'.
-        foreach ($roles as $role) {
-            assign_capability('moodle/course:visibility', CAP_ALLOW, $role->id, $systemcontext->id);
+    /// For developer upgrades, turn on the conditional activities and completion
+    /// features automatically (to gain more testing)
+        if(debugging('',DEBUG_DEVELOPER)) {
+            set_config('enableavailability',1);
+            set_config('enablecompletion',1);
         }
 
-    /// Force all sessions to refresh access data.
-        mark_context_dirty($systemcontext->path);
-
-    /// Main savepoint reached
-        upgrade_main_savepoint($result, 2008120800);
-    }
-
-    if ($result && $oldversion < 2008120801) {
-
-    /// Changing precision of field shortname on table mnet_enrol_course to (100)
-        $table = new xmldb_table('mnet_enrol_course');
-        $field = new xmldb_field('shortname', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null, null, null, 'fullname');
-
-    /// Launch change of precision for field shortname
-        $dbman->change_field_precision($table, $field);
-
     /// Main savepoint reached
-        upgrade_main_savepoint($result, 2008120801);
+        upgrade_main_savepoint($result, 2008121701);
     }
 
     return $result;
index d1b71cacb9bab44f996cf4a78ddbe5d18b7abddc..3326954c79fddfe1a2008b34512d950aff30e709 100644 (file)
@@ -738,10 +738,16 @@ class grade_grade extends grade_object {
     
     /**
      * Used to notify the completion system (if necessary) that a user's grade
-     * has changed.
+     * has changed, and clear up a possible score cache.
      * @param bool deleted True if grade was actually deleted
      */
     function notify_changed($deleted) {
+        // Grades may be cached in user session
+        global $USER,$SESSION;
+        if($USER->id==$this->userid) {
+            unset($SESSION->gradescorecache[$this->itemid]);
+        }
+
         // Ignore during restore
         // TODO There should be a proper way to determine when we are in restore
         // so that this hack looking for a $restore global is not needed.
index 3d16c9c4fcc7cd87a79235c76d1e5802b7dd1090..ee999932cc92447d006cd2c7773b565cc764159f 100644 (file)
@@ -2047,6 +2047,30 @@ function require_login($courseorid=0, $autologinguest=true, $cm=null, $setwantsu
             print_error('nocontext');
         }
     }
+
+    // Conditional activity access control
+    if(!empty($CFG->enableavailability) and $cm) {
+        // We cache conditional access in session
+        if(!isset($SESSION->conditionaccessok)) {
+            $SESSION->conditionaccessok=array();
+        }
+        // If you have been allowed into the module once then you are allowed
+        // in for rest of session, no need to do conditional checks
+        if(!array_key_exists($cm->id,$SESSION->conditionaccessok)) {
+            // Get condition info (does a query for the availability table)
+            require_once($CFG->libdir.'/conditionlib.php');        
+            $ci=new condition_info($cm,CONDITION_MISSING_EXTRATABLE);
+            // Check condition for user (this will do a query if the availability
+            // information depends on grade or completion information)
+            if($ci->is_available($junk) || 
+                has_capability('moodle/course:viewhiddenactivities', $COURSE->context)) {
+                $SESSION->conditionaccessok[$cm->id]=true;
+            } else {
+                print_error('activityiscurrentlyhidden');
+            }
+        }
+    }
+
     if ($COURSE->id == SITEID) {
         /// Eliminate hidden site activities straight away
         if (!empty($cm) && !$cm->visible
diff --git a/lib/simpletest/testconditionlib.php b/lib/simpletest/testconditionlib.php
new file mode 100644 (file)
index 0000000..f5b5469
--- /dev/null
@@ -0,0 +1,344 @@
+<?php
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');
+}
+
+require_once($CFG->dirroot . '/lib/conditionlib.php');
+
+class conditionlib_test extends MoodleUnitTestCase {
+    var $oldcfg;
+
+    public function setUp() {
+        parent::setUp();
+        global $CFG;
+        $this->oldcfg=clone $CFG;
+        $CFG->enableavailability=true;
+        $CFG->enablecompletion=true;
+    }
+
+    /**
+     * Method called after each test method. Doesn't do anything extraordinary except restore the global $DB to the real one.
+     */
+    public function tearDown() {
+        $CFG->enableavailability=$this->oldcfg->enableavailability;
+        $CFG->enablecompletion=$this->oldcfg->enablecompletion;
+        parent::tearDown();
+    }
+
+    function test_constructor() {
+        global $DB,$CFG;
+        $cm=new stdClass;
+
+        // Test records
+        $id=$DB->insert_record('course_modules',(object)array(
+            'showavailability'=>1,'availablefrom'=>17,'availableuntil'=>398,'course'=>64));
+
+        // no ID
+        try { 
+            $test=new condition_info($cm);
+            $this->fail();
+        } catch(coding_exception $e) {
+        }
+
+        // no other data
+        $cm->id=$id;
+        $test=new condition_info($cm,CONDITION_MISSING_EVERYTHING);
+        $this->assertEqual(
+            (object)array('id'=>$id,'showavailability'=>1,
+                'availablefrom'=>17,'availableuntil'=>398,'course'=>64,
+                'conditionsgrade'=>array(), 'conditionscompletion'=>array()),
+            $test->get_full_course_module());
+
+        // just the course_modules stuff; check it doesn't request that from db
+        $cm->showavailability=0;
+        $cm->availablefrom=2;
+        $cm->availableuntil=74;
+        $cm->course=38;
+        $test=new condition_info($cm,CONDITION_MISSING_EXTRATABLE);
+        $this->assertEqual(
+            (object)array('id'=>$id,'showavailability'=>0,
+                'availablefrom'=>2,'availableuntil'=>74,'course'=>38,
+                'conditionsgrade'=>array(), 'conditionscompletion'=>array()),
+            $test->get_full_course_module());
+
+        // Now let's add some actual grade/completion conditions
+        $DB->insert_record('course_modules_availability',(object)array(
+            'coursemoduleid'=>$id,
+            'sourcecmid'=>42,
+            'requiredcompletion'=>2
+        ));
+        $DB->insert_record('course_modules_availability',(object)array(
+            'coursemoduleid'=>$id,
+            'sourcecmid'=>666,
+            'requiredcompletion'=>1
+        ));
+        $DB->insert_record('course_modules_availability',(object)array(
+            'coursemoduleid'=>$id,
+            'gradeitemid'=>37,
+            'grademin'=>5.5
+        ));
+
+        $cm=(object)array('id'=>$id);
+        $test=new condition_info($cm,CONDITION_MISSING_EVERYTHING);
+        $fullcm=$test->get_full_course_module();
+        $this->assertEqual(array(42=>2,666=>1),$fullcm->conditionscompletion);
+        $this->assertEqual(array(37=>(object)array('min'=>5.5,'max'=>null,'name'=>'!missing')),
+            $fullcm->conditionsgrade);
+    }
+
+    private function make_course() {
+        global $DB;
+        $categoryid=$DB->insert_record('course_categories',(object)array());
+        return $DB->insert_record('course',(object)array(
+            'fullname'=>'Condition test','shortname'=>'CT1',
+            'category'=>$categoryid,'enablecompletion'=>1));
+    }
+
+    private function make_course_module($courseid,$params=array()) {
+        global $DB;
+        static $moduleid=0;
+        if(!$moduleid) {
+            $moduleid=$DB->get_field('modules','id',array('name'=>'resource'));
+        }
+
+        $rid=$DB->insert_record('resource',(object)array('course'=>$courseid,
+            'name'=>'xxx','alltext'=>'','popup'=>''));
+        $settings=(object)array(
+            'course'=>$courseid,'module'=>$moduleid,'instance'=>$rid);
+        foreach($params as $name=>$value) {
+            $settings->{$name}=$value;
+        }
+        return $DB->insert_record('course_modules',$settings);
+    }
+
+    private function make_section($courseid,$cmids,$sectionnum=0) {
+        global $DB;
+        $DB->insert_record('course_sections',(object)array(
+            'course'=>$courseid,'sequence'=>implode(',',$cmids),'section'=>$sectionnum));
+    }
+
+    function test_modinfo() {
+        global $DB;
+
+        // Let's make a course
+        $courseid=$this->make_course();
+
+        // Now let's make a couple modules on that course
+        $cmid1=$this->make_course_module($courseid,array(
+            'showavailability'=>1,'availablefrom'=>17,'availableuntil'=>398,
+            'completion'=>COMPLETION_TRACKING_MANUAL));
+        $cmid2=$this->make_course_module($courseid,array(
+            'showavailability'=>0,'availablefrom'=>0,'availableuntil'=>0));
+        $this->make_section($courseid,array($cmid1,$cmid2));
+
+        // Add a fake grade item
+        $gradeitemid=$DB->insert_record('grade_items',(object)array(
+            'courseid'=>$courseid,'itemname'=>'frog'));
+
+        // One of the modules has grade and completion conditions, other doesn't
+        $DB->insert_record('course_modules_availability',(object)array(
+            'coursemoduleid'=>$cmid2,
+            'sourcecmid'=>$cmid1,
+            'requiredcompletion'=>1
+        ));
+        $DB->insert_record('course_modules_availability',(object)array(
+            'coursemoduleid'=>$cmid2,
+            'gradeitemid'=>$gradeitemid,
+            'grademin'=>5.5
+        ));
+
+        // Okay sweet, now get modinfo
+        $modinfo=get_fast_modinfo($DB->get_record('course',array('id'=>$courseid)));
+
+        // Test basic data
+        $this->assertEqual(1,$modinfo->cms[$cmid1]->showavailability);
+        $this->assertEqual(17,$modinfo->cms[$cmid1]->availablefrom);
+        $this->assertEqual(398,$modinfo->cms[$cmid1]->availableuntil);
+        $this->assertEqual(0,$modinfo->cms[$cmid2]->showavailability);
+        $this->assertEqual(0,$modinfo->cms[$cmid2]->availablefrom);
+        $this->assertEqual(0,$modinfo->cms[$cmid2]->availableuntil);
+
+        // Test condition arrays
+        $this->assertEqual(array(),$modinfo->cms[$cmid1]->conditionscompletion);
+        $this->assertEqual(array(),$modinfo->cms[$cmid1]->conditionsgrade);
+        $this->assertEqual(array($cmid1=>1),
+            $modinfo->cms[$cmid2]->conditionscompletion);
+        $this->assertEqual(array($gradeitemid=>(object)array('min'=>5.5,'max'=>null,'name'=>'frog')),
+            $modinfo->cms[$cmid2]->conditionsgrade);
+    }
+
+    function test_add_and_remove() {
+        global $DB;
+        // Make course and module
+        $courseid=$this->make_course();
+        $cmid=$this->make_course_module($courseid,array(
+            'showavailability'=>0,'availablefrom'=>0,'availableuntil'=>0));
+        $this->make_section($courseid,array($cmid));
+
+        // Check it has no conditions
+        $test1=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $cm=$test1->get_full_course_module();
+        $this->assertEqual(array(),$cm->conditionscompletion);
+        $this->assertEqual(array(),$cm->conditionsgrade);
+
+        // Add conditions of each type
+        $test1->add_completion_condition(13,3);
+        $this->assertEqual(array(13=>3),$cm->conditionscompletion);
+        $test1->add_grade_condition(666,0.4,null,true);
+        $this->assertEqual(array(666=>(object)array('min'=>0.4,'max'=>null,'name'=>'!missing')),
+            $cm->conditionsgrade);
+
+        // Check they were really added in db
+        $test2=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $cm=$test2->get_full_course_module();
+        $this->assertEqual(array(13=>3),$cm->conditionscompletion);
+        $this->assertEqual(array(666=>(object)array('min'=>0.4,'max'=>null,'name'=>'!missing')),
+            $cm->conditionsgrade);
+
+        // Wipe conditions
+        $test2->wipe_conditions();
+        $this->assertEqual(array(),$cm->conditionscompletion);
+        $this->assertEqual(array(),$cm->conditionsgrade);
+
+        // Check they were really wiped
+        $test3=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $cm=$test3->get_full_course_module();
+        $this->assertEqual(array(),$cm->conditionscompletion);
+        $this->assertEqual(array(),$cm->conditionsgrade);
+    }
+
+    function test_is_available() {
+        global $DB,$USER;
+        $courseid=$this->make_course();
+
+        // No conditions
+        $cmid=$this->make_course_module($courseid);
+        $ci=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $this->assertTrue($ci->is_available($text,false,0));
+        $this->assertEqual('',$text);
+
+        // Time (from)
+        $time=time()+100;
+        $cmid=$this->make_course_module($courseid,array('availablefrom'=>$time));
+        $ci=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $this->assertFalse($ci->is_available($text));
+        $this->assert(new PatternExpectation(
+            '/'.preg_quote(userdate($time,get_string('strftimedate','langconfig'))).'/'),$text);
+
+        $time=time()-100;
+        $cmid=$this->make_course_module($courseid,array('availablefrom'=>$time));
+        $ci=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $this->assertTrue($ci->is_available($text));
+        $this->assertEqual('',$text);
+        $this->assert(new PatternExpectation(
+            '/'.preg_quote(userdate($time,get_string('strftimedate','langconfig'))).'/'),$ci->get_full_information());
+
+        // Time (until)
+        $cmid=$this->make_course_module($courseid,array('availableuntil'=>time()-100));
+        $ci=new condition_info((object)array('id'=>$cmid),
+            CONDITION_MISSING_EVERYTHING);
+        $this->assertFalse($ci->is_available($text));
+        $this->assertEqual('',$text);
+
+        // Completion
+        $oldid=$cmid;
+        $cmid=$this->make_course_module($courseid);
+        $this->make_section($courseid,array($oldid,$cmid));
+        $oldcm=$DB->get_record('course_modules',array('id'=>$oldid));
+        $oldcm->completion=COMPLETION_TRACKING_MANUAL;
+        $DB->update_record('course_modules',$oldcm);
+
+        $ci=new condition_info((object)array('id'=>$cmid),CONDITION_MISSING_EVERYTHING);
+        $ci->add_completion_condition($oldid,COMPLETION_COMPLETE);
+        $this->assertFalse($ci->is_available($text,false));
+        $this->assertEqual(get_string('requires_completion_1','condition','xxx'),$text);
+
+        $completion=new completion_info($DB->get_record('course',array('id'=>$courseid)));
+        $completion->update_state($oldcm,COMPLETION_COMPLETE);
+        completion_info::wipe_session_cache();
+        condition_info::wipe_session_cache();
+
+        $this->assertTrue($ci->is_available($text));
+        $this->assertFalse($ci->is_available($text,false,$USER->id+1));
+        completion_info::wipe_session_cache();
+        condition_info::wipe_session_cache();
+        $completion=new completion_info($DB->get_record('course',array('id'=>$courseid)));
+        $completion->update_state($oldcm,COMPLETION_INCOMPLETE);
+        $this->assertFalse($ci->is_available($text));
+
+        $ci->wipe_conditions();
+        $ci->add_completion_condition($oldid,COMPLETION_INCOMPLETE);
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text));
+        $this->assertTrue($ci->is_available($text,false,$USER->id+1));
+
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text,true));
+
+        // Grade
+        $ci->wipe_conditions();
+        // Add a fake grade item
+        $gradeitemid=$DB->insert_record('grade_items',(object)array(
+            'courseid'=>$courseid,'itemname'=>'frog'));
+        // Add a condition on a value existing...
+        $ci->add_grade_condition($gradeitemid,null,null,true);
+        $this->assertFalse($ci->is_available($text));
+        $this->assertEqual(get_string('requires_grade_any','condition','frog'),$text);
+
+        // Fake it existing
+        $DB->insert_record('grade_grades',(object)array(
+            'itemid'=>$gradeitemid,'userid'=>$USER->id,'finalgrade'=>3.78));
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text));
+
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text,true));
+
+        // Now require that user gets more than 3.78001
+        $ci->wipe_conditions();
+        $ci->add_grade_condition($gradeitemid,3.78001,null,true);
+        condition_info::wipe_session_cache();
+        $this->assertFalse($ci->is_available($text));
+        $this->assertEqual(get_string('requires_grade_min','condition','frog'),$text);
+
+        // ...just on 3.78...
+        $ci->wipe_conditions();
+        $ci->add_grade_condition($gradeitemid,3.78,null,true);
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text));
+
+        // ...less than 3.78
+        $ci->wipe_conditions();
+        $ci->add_grade_condition($gradeitemid,null,3.78,true);
+        condition_info::wipe_session_cache();
+        $this->assertFalse($ci->is_available($text));
+        $this->assertEqual(get_string('requires_grade_max','condition','frog'),$text);
+
+        // ...less than 3.78001
+        $ci->wipe_conditions();
+        $ci->add_grade_condition($gradeitemid,null,3.78001,true);
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text));
+
+        // ...in a range that includes it
+        $ci->wipe_conditions();
+        $ci->add_grade_condition($gradeitemid,3,4,true);
+        condition_info::wipe_session_cache();
+        $this->assertTrue($ci->is_available($text));
+
+        // ...in a range that doesn't include it
+        $ci->wipe_conditions();
+        $ci->add_grade_condition($gradeitemid,4,5,true);
+        condition_info::wipe_session_cache();
+        $this->assertFalse($ci->is_available($text));
+        $this->assertEqual(get_string('requires_grade_range','condition','frog'),$text);
+    }
+
+}
+?>
index 22caea6b7a262de73aa54a357c9f7c0d37a6dcb2..d614c826de5214ec419668bb358dedfd230be5b9 100644 (file)
@@ -569,6 +569,19 @@ h2.headingblock {
   color: #666666;
 }
 
+#course-view .availabilityinfo {
+  font-size:0.85em;
+  color:#aaa;
+}
+#course-view .availabilityinfo strong {
+  font-weight:normal;
+  color:black;
+}
+#course-view .dimmed_text img {
+  opacity:0.3;
+  filter: alpha(opacity='30'); 
+}
+
 /***
  *** Doc
  ***/
@@ -1057,6 +1070,10 @@ body#mod-forum-index .generalbox .cell {
 #mod-quiz-edit .questionbankwindow div.header{
     font-weight:bold;
 }
+#mod-quiz-edit a.configurerandomquestion{
+    font-size:small;
+    text-decoration:underline;
+}
 
 
     /***
index 9059e854a0c77b59b445653efa051c0d6731188c..cc2508eece52a5e73f168ba630b4b1b42f937e0c 100644 (file)
@@ -6,7 +6,7 @@
 // This is compared against the values stored in the database to determine
 // whether upgrades should be performed (see lib/db/*.php)
 
-    $version = 2008121000;  // YYYYMMDD   = date of the last version bump
+    $version = 2008121701;  // YYYYMMDD   = date of the last version bump
                             //         XX = daily increments
 
     $release = '2.0 dev (Build: 20081217)';  // Human-friendly version name