MDL-44070 Conditional availability enhancements (1): DB upgrade

Converts existing data to new structure in database as part of
upgrade, including a progress bar.

Deletes the database tables and fields that were used by the old
system and are no longer needed.
This commit is contained in:
sam marshall 2014-03-26 12:02:03 +00:00
parent 15c8c47401
commit 8e97006ad0
6 changed files with 443 additions and 72 deletions

View File

@ -298,10 +298,8 @@
<FIELD NAME="completiongradeitemnumber" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Grade-item number used to track automatic completion, if applicable."/>
<FIELD NAME="completionview" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Controls whether a page view is part of the automatic completion requirements for this activity. 0 = view not required 1 = view required"/>
<FIELD NAME="completionexpected" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Date at which students are expected to complete this activity. This field is used when displaying student progress."/>
<FIELD NAME="availablefrom" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set non-zero, the activity only becomes available from the time given here."/>
<FIELD NAME="availableuntil" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set non-zero, the activity is only available until the time given here."/>
<FIELD NAME="showavailability" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this field is set to 1 and the activity is not available because the 'availablefrom' date has not been reached, or one of the conditions in course_modules_availability is not matched, then the item will be displayed greyed-out (unclickable) with an information message such as 'Available from (date)'. Otherwise, the item will not be displayed to students at all."/>
<FIELD NAME="showdescription" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Some module types support a 'description' which shows within the module pages. This option controls whether it also displays on the course main page. 0 = does not display (default), 1 = displays"/>
<FIELD NAME="availability" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Availability restrictions for viewing this activity, in JSON format. Null if no restrictions."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@ -315,37 +313,6 @@
<INDEX NAME="idnumber-course" UNIQUE="false" FIELDS="idnumber, course" COMMENT="non unique index (although programatically we are guarantying some sort of uniqueness both under this table and the grade_items one). TODO: We need a central store of module idnumbers in the future."/>
</INDEXES>
</TABLE>
<TABLE NAME="course_modules_availability" COMMENT="Table stores conditions that affect whether a module/activity is currently available to students or not.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the module whose availability is being restricted by this condition."/>
<FIELD NAME="sourcecmid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="If this condition is based on completion of another activity, then this is the course-module ID of that activity. Otherwise null."/>
<FIELD NAME="requiredcompletion" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="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."/>
<FIELD NAME="gradeitemid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="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."/>
<FIELD NAME="grademin" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If set, this is the minimum grade percentage that must be reached (greater than or equal) in order for this module to appear. Otherwise null."/>
<FIELD NAME="grademax" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If set, this is the maximum grade percentage that users must be below (less than) in order to display this item. Otherwise null."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="coursemoduleid" TYPE="foreign" FIELDS="coursemoduleid" REFTABLE="course_modules" REFFIELDS="id"/>
<KEY NAME="sourcecmid" TYPE="foreign" FIELDS="sourcecmid" REFTABLE="course_modules" REFFIELDS="id"/>
<KEY NAME="gradeitemid" TYPE="foreign" FIELDS="gradeitemid" REFTABLE="grade_items" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="course_modules_avail_fields" COMMENT="Stores user field conditions that affect whether an activity is currently available.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="coursemoduleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the module whose availability is being restricted by this condition."/>
<FIELD NAME="userfield" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false" COMMENT="The user profile field this record relates to if it is not a custom profile field"/>
<FIELD NAME="customfieldid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="ID for the custom field if this relates to one"/>
<FIELD NAME="operator" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false" COMMENT="The operator, such as less than or equal to, between the field and the value"/>
<FIELD NAME="value" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The required value of the field"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="coursemoduleid" TYPE="foreign" FIELDS="coursemoduleid" REFTABLE="course_modules" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="course_modules_completion" COMMENT="Stores the completion state (completed or not completed, etc) of each user on each activity.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
@ -373,10 +340,7 @@
<FIELD NAME="summaryformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="sequence" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="visible" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
<FIELD NAME="availablefrom" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set non-zero, the section only becomes available from the time given here."/>
<FIELD NAME="availableuntil" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If set non-zero, the section is only available until the time given here."/>
<FIELD NAME="showavailability" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this field is set to 1 and the section is not available because the 'availablefrom' date has not been reached, or one of the conditions in course_sections_availability_cg is not matched, then the item will be displayed greyed-out (unclickable) with an information message such as 'Available from (date)'. Otherwise, the item will not be displayed to students at all."/>
<FIELD NAME="groupingid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Grouping that has access to this section."/>
<FIELD NAME="availability" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Availability restrictions for viewing this section, in JSON format. Null if no restrictions."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@ -385,37 +349,6 @@
<INDEX NAME="course_section" UNIQUE="true" FIELDS="course, section"/>
</INDEXES>
</TABLE>
<TABLE NAME="course_sections_availability" COMMENT="Completion or grade conditions that affect if a section is currently available to students.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="coursesectionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the section whose availability is being restricted by this condition."/>
<FIELD NAME="sourcecmid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="If this condition is based on completion of some activity, then this is the course-module ID of that activity. Otherwise null."/>
<FIELD NAME="requiredcompletion" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="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."/>
<FIELD NAME="gradeitemid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="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."/>
<FIELD NAME="grademin" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If set, this is the minimum grade percentage that must be reached (greater than or equal) in order for this section to appear. Otherwise null."/>
<FIELD NAME="grademax" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If set, this is the maximum grade percentage that users must be below (less than) in order to display this item. Otherwise null."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="coursesectionid" TYPE="foreign" FIELDS="coursesectionid" REFTABLE="course_sections" REFFIELDS="id"/>
<KEY NAME="sourcecmid" TYPE="foreign" FIELDS="sourcecmid" REFTABLE="course_modules" REFFIELDS="id"/>
<KEY NAME="gradeitemid" TYPE="foreign" FIELDS="gradeitemid" REFTABLE="grade_items" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="course_sections_avail_fields" COMMENT="Stores user field conditions that affect whether an activity is currently available.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="coursesectionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="ID of the section whose availability is being restricted by this condition."/>
<FIELD NAME="userfield" TYPE="char" LENGTH="50" NOTNULL="false" SEQUENCE="false" COMMENT="The user profile field this record relates to if it is not a custom profile field"/>
<FIELD NAME="customfieldid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="ID for the custom field if this relates to one"/>
<FIELD NAME="operator" TYPE="char" LENGTH="20" NOTNULL="true" SEQUENCE="false" COMMENT="The operator, such as less than or equal to, between the field and the value"/>
<FIELD NAME="value" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The required value of the field"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="coursesectionid" TYPE="foreign" FIELDS="coursesectionid" REFTABLE="course_sections" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="course_request" COMMENT="course requests">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>

View File

@ -3360,5 +3360,228 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2014032700.02);
}
if ($oldversion < 2014040401.00) {
// Define field availability to be added to course_modules.
$table = new xmldb_table('course_modules');
$field = new xmldb_field('availability', XMLDB_TYPE_TEXT, null, null, null, null, null, 'showdescription');
// Conditionally launch add field availability.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define field availability to be added to course_sections.
$table = new xmldb_table('course_sections');
$field = new xmldb_field('availability', XMLDB_TYPE_TEXT, null, null, null, null, null, 'groupingid');
// Conditionally launch add field availability.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Update existing conditions to new format. This could be a slow
// process, so begin by counting the number of affected modules/sections.
// (Performance: On the OU system, these took ~0.3 seconds, with about
// 20,000 results out of about 400,000 total rows in those tables.)
$cmcount = $DB->count_records_sql("
SELECT COUNT(1)
FROM {course_modules} cm
WHERE cm.availablefrom != 0 OR
cm.availableuntil != 0 OR
EXISTS (SELECT 1 FROM {course_modules_availability} WHERE coursemoduleid = cm.id) OR
EXISTS (SELECT 1 FROM {course_modules_avail_fields} WHERE coursemoduleid = cm.id)");
$sectcount = $DB->count_records_sql("
SELECT COUNT(1)
FROM {course_sections} cs
WHERE cs.groupingid != 0 OR
cs.availablefrom != 0 OR
cs.availableuntil != 0 OR
EXISTS (SELECT 1 FROM {course_sections_availability} WHERE coursesectionid = cs.id) OR
EXISTS (SELECT 1 FROM {course_sections_avail_fields} WHERE coursesectionid = cs.id)");
if ($cmcount + $sectcount > 0) {
// Show progress bar and start db transaction.
$transaction = $DB->start_delegated_transaction();
$pbar = new progress_bar('availupdate', 500, true);
// Loop through all course-modules.
// (Performance: On the OU system, the query took <1 second for ~20k
// results; updating all those entries took ~3 minutes.)
$done = 0;
$lastupdate = 0;
$rs = $DB->get_recordset_sql("
SELECT cm.id, cm.availablefrom, cm.availableuntil, cm.showavailability,
COUNT (DISTINCT cma.id) AS availcount,
COUNT (DISTINCT cmf.id) AS fieldcount
FROM {course_modules} cm
LEFT JOIN {course_modules_availability} cma ON cma.coursemoduleid = cm.id
LEFT JOIN {course_modules_avail_fields} cmf ON cmf.coursemoduleid = cm.id
WHERE cm.availablefrom != 0 OR
cm.availableuntil != 0 OR
cma.id IS NOT NULL OR
cmf.id IS NOT NULL
GROUP BY cm.id, cm.availablefrom, cm.availableuntil, cm.showavailability");
foreach ($rs as $rec) {
// Update progress initially and then once per second.
if (time() != $lastupdate) {
$lastupdate = time();
$pbar->update($done, $cmcount + $sectcount,
"Updating activity availability settings ($done/$cmcount)");
}
// Get supporting records - only if there are any (to reduce the
// number of queries where just date/group is used).
if ($rec->availcount) {
$availrecs = $DB->get_records('course_modules_availability',
array('coursemoduleid' => $rec->id));
} else {
$availrecs = array();
}
if ($rec->fieldcount) {
$fieldrecs = $DB->get_records_sql("
SELECT cmaf.userfield, cmaf.operator, cmaf.value, uif.shortname
FROM {course_modules_avail_fields} cmaf
LEFT JOIN {user_info_field} uif ON uif.id = cmaf.customfieldid
WHERE cmaf.coursemoduleid = ?", array($rec->id));
} else {
$fieldrecs = array();
}
// Update item.
$availability = upgrade_availability_item(0, 0,
$rec->availablefrom, $rec->availableuntil,
$rec->showavailability, $availrecs, $fieldrecs);
if ($availability) {
$DB->set_field('course_modules', 'availability', $availability, array('id' => $rec->id));
}
// Update progress.
$done++;
}
$rs->close();
// Loop through all course-sections.
// (Performance: On the OU system, this took <1 second for, er, 150 results.)
$done = 0;
$rs = $DB->get_recordset_sql("
SELECT cs.id, cs.groupingid, cs.availablefrom,
cs.availableuntil, cs.showavailability,
COUNT (DISTINCT csa.id) AS availcount,
COUNT (DISTINCT csf.id) AS fieldcount
FROM {course_sections} cs
LEFT JOIN {course_sections_availability} csa ON csa.coursesectionid = cs.id
LEFT JOIN {course_sections_avail_fields} csf ON csf.coursesectionid = cs.id
WHERE cs.groupingid != 0 OR
cs.availablefrom != 0 OR
cs.availableuntil != 0 OR
csa.id IS NOT NULL OR
csf.id IS NOT NULL
GROUP BY cs.id, cs.groupingid, cs.availablefrom,
cs.availableuntil, cs.showavailability");
foreach ($rs as $rec) {
// Update progress once per second.
if (time() != $lastupdate) {
$lastupdate = time();
$pbar->update($done + $cmcount, $cmcount + $sectcount,
"Updating section availability settings ($done/$sectcount)");
}
// Get supporting records - only if there are any (to reduce the
// number of queries where just date/group is used).
if ($rec->availcount) {
$availrecs = $DB->get_records('course_sections_availability',
array('coursesectionid' => $rec->id));
} else {
$availrecs = array();
}
if ($rec->fieldcount) {
$fieldrecs = $DB->get_records_sql("
SELECT csaf.userfield, csaf.operator, csaf.value, uif.shortname
FROM {course_sections_avail_fields} csaf
LEFT JOIN {user_info_field} uif ON uif.id = csaf.customfieldid
WHERE csaf.coursesectionid = ?", array($rec->id));
} else {
$fieldrecs = array();
}
// Update item.
$availability = upgrade_availability_item($rec->groupingid ? 1 : 0,
$rec->groupingid, $rec->availablefrom, $rec->availableuntil,
$rec->showavailability, $availrecs, $fieldrecs);
if ($availability) {
$DB->set_field('course_sections', 'availability', $availability, array('id' => $rec->id));
}
// Update progress.
$done++;
}
$rs->close();
// Final progress update for 100%.
$pbar->update($done + $cmcount, $cmcount + $sectcount,
'Availability settings updated for ' . ($cmcount + $sectcount) .
' activities and sections');
$transaction->allow_commit();
}
// Drop tables which are not necessary because they are covered by the
// new availability fields.
$table = new xmldb_table('course_modules_availability');
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
$table = new xmldb_table('course_modules_avail_fields');
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
$table = new xmldb_table('course_sections_availability');
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
$table = new xmldb_table('course_sections_avail_fields');
if ($dbman->table_exists($table)) {
$dbman->drop_table($table);
}
// Drop unnnecessary fields from course_modules.
$table = new xmldb_table('course_modules');
$field = new xmldb_field('availablefrom');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
$field = new xmldb_field('availableuntil');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
$field = new xmldb_field('showavailability');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
// Drop unnnecessary fields from course_sections.
$table = new xmldb_table('course_sections');
$field = new xmldb_field('availablefrom');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
$field = new xmldb_field('availableuntil');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
$field = new xmldb_field('showavailability');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
$field = new xmldb_field('groupingid');
if ($dbman->field_exists($table, $field)) {
$dbman->drop_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2014040401.00);
}
return true;
}

View File

@ -347,4 +347,112 @@ function upgrade_course_modules_sequences() {
unset($sections);
// Note that we don't need to reset course cache here because it is reset automatically after upgrade.
}
}
/**
* Updates a single item (course module or course section) to transfer the
* availability settings from the old to the new format.
*
* Note: We do not convert groupmembersonly for modules at present. If we did,
* $groupmembersonly would be set to the groupmembersonly option for the
* module. Since we don't, it will be set to 0 for modules, and 1 for sections
* if they have a grouping.
*
* @param int $groupmembersonly 1 if activity has groupmembersonly option
* @param int $groupingid Grouping id (0 = none)
* @param int $availablefrom Available from time (0 = none)
* @param int $availableuntil Available until time (0 = none)
* @param int $showavailability Show availability (1) or hide activity entirely
* @param array $availrecs Records from course_modules/sections_availability
* @param array $fieldrecs Records from course_modules/sections_avail_fields
*/
function upgrade_availability_item($groupmembersonly, $groupingid,
$availablefrom, $availableuntil, $showavailability,
array $availrecs, array $fieldrecs) {
global $CFG, $DB;
$conditions = array();
$shows = array();
// Group members only condition (if enabled).
if ($CFG->enablegroupmembersonly && $groupmembersonly) {
if ($groupingid) {
$conditions[] = '{"type":"grouping"' .
($groupingid ? ',"id":' . $groupingid : '') . '}';
} else {
// No grouping specified, so allow any group.
$conditions[] = '{"type":"group"}';
}
// Group members only condition was not displayed to students.
$shows[] = 'false';
// In the unlikely event that the site had enablegroupmembers only
// but NOT enableavailability, we need to turn this on now.
if (!$CFG->enableavailability) {
set_config('enableavailability', 1);
}
}
// Date conditions.
if ($availablefrom) {
$conditions[] = '{"type":"date","d":">=","t":' . $availablefrom . '}';
$shows[] = $showavailability ? 'true' : 'false';
}
if ($availableuntil) {
$conditions[] = '{"type":"date","d":"<","t":' . $availableuntil . '}';
// Until dates never showed to students.
$shows[] = 'false';
}
// Conditions from _availability table.
foreach ($availrecs as $rec) {
if (!empty($rec->sourcecmid)) {
// Completion condition.
$conditions[] = '{"type":"completion","cm":' . $rec->sourcecmid .
',"e":' . $rec->requiredcompletion . '}';
} else {
// Grade condition.
$minmax = '';
if (!empty($rec->grademin)) {
$minmax .= ',"min":' . sprintf('%.5f', $rec->grademin);
}
if (!empty($rec->grademax)) {
$minmax .= ',"max":' . sprintf('%.5f', $rec->grademax);
}
$conditions[] = '{"type":"grade","id":' . $rec->gradeitemid . $minmax . '}';
}
$shows[] = $showavailability ? 'true' : 'false';
}
// Conditions from _fields table.
foreach ($fieldrecs as $rec) {
if (isset($rec->userfield)) {
// Standard field.
$fieldbit = ',"sf":' . json_encode($rec->userfield);
} else {
// Custom field.
$fieldbit = ',"cf":' . json_encode($rec->shortname);
}
// Value is not included for certain operators.
switch($rec->operator) {
case 'isempty':
case 'isnotempty':
$valuebit = '';
break;
default:
$valuebit = ',"v":' . json_encode($rec->value);
break;
}
$conditions[] = '{"type":"profile","op":"' . $rec->operator . '"' .
$fieldbit . $valuebit . '}';
$shows[] = $showavailability ? 'true' : 'false';
}
// If there are some conditions, set them into database.
if ($conditions) {
return '{"op":"&","showc":[' . implode(',', $shows) . '],' .
'"c":[' . implode(',', $conditions) . ']}';
} else {
return null;
}
}

View File

@ -1261,7 +1261,7 @@ function disable_output_buffering() {
*/
function redirect_if_major_upgrade_required() {
global $CFG;
$lastmajordbchanges = 2014031900.00;
$lastmajordbchanges = 2014040401.00;
if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
during_initial_install() or !empty($CFG->adminsetuppending)) {
try {

View File

@ -200,4 +200,111 @@ class core_upgradelib_testcase extends advanced_testcase {
// Verify the hash is correctly created.
$this->assertSame($oldrecord->pathnamehash, $newrecord->pathnamehash);
}
/**
* Tests the upgrade of an individual course-module or section from the
* old to new availability system. (This test does not use the database
* so it can run any time.)
*/
public function test_upgrade_availability_item() {
global $CFG;
$this->resetAfterTest();
// This function is in the other upgradelib.
require_once($CFG->libdir . '/db/upgradelib.php');
// Groupmembersonly (or nothing). Show option on but ignored.
$CFG->enablegroupmembersonly = 0;
$this->assertNull(
upgrade_availability_item(1, 0, 0, 0, 1, array(), array()));
$CFG->enablegroupmembersonly = 1;
$this->assertNull(
upgrade_availability_item(0, 0, 0, 0, 1, array(), array()));
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"group"}]}',
upgrade_availability_item(1, 0, 0, 0, 1, array(), array()));
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"grouping","id":4}]}',
upgrade_availability_item(1, 4, 0, 0, 1, array(), array()));
// Dates (with show/hide options - until date always hides).
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"date","d":">=","t":996}]}',
upgrade_availability_item(0, 0, 996, 0, 1, array(), array()));
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"date","d":">=","t":997}]}',
upgrade_availability_item(0, 0, 997, 0, 0, array(), array()));
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"date","d":"<","t":998}]}',
upgrade_availability_item(0, 0, 0, 998, 1, array(), array()));
$this->assertEquals(
'{"op":"&","showc":[true,false],"c":[' .
'{"type":"date","d":">=","t":995},{"type":"date","d":"<","t":999}]}',
upgrade_availability_item(0, 0, 995, 999, 1, array(), array()));
// Grade (show option works as normal).
$availrec = (object)array(
'sourcecmid' => null, 'requiredcompletion' => null,
'gradeitemid' => 13, 'grademin' => null, 'grademax' => null);
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"grade","id":13}]}',
upgrade_availability_item(0, 0, 0, 0, 1, array($availrec), array()));
$availrec->grademin = 4.1;
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"grade","id":13,"min":4.10000}]}',
upgrade_availability_item(0, 0, 0, 0, 0, array($availrec), array()));
$availrec->grademax = 9.9;
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"grade","id":13,"min":4.10000,"max":9.90000}]}',
upgrade_availability_item(0, 0, 0, 0, 1, array($availrec), array()));
$availrec->grademin = null;
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"grade","id":13,"max":9.90000}]}',
upgrade_availability_item(0, 0, 0, 0, 1, array($availrec), array()));
// Completion (show option normal).
$availrec->grademax = null;
$availrec->gradeitemid = null;
$availrec->sourcecmid = 666;
$availrec->requiredcompletion = 1;
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"completion","cm":666,"e":1}]}',
upgrade_availability_item(0, 0, 0, 0, 1, array($availrec), array()));
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"completion","cm":666,"e":1}]}',
upgrade_availability_item(0, 0, 0, 0, 0, array($availrec), array()));
// Profile conditions (custom/standard field, values/not, show option normal).
$fieldrec = (object)array('userfield' => 'email', 'operator' => 'isempty',
'value' => '', 'shortname' => null);
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"profile","op":"isempty","sf":"email"}]}',
upgrade_availability_item(0, 0, 0, 0, 1, array(), array($fieldrec)));
$fieldrec->value = '@';
$fieldrec->operator = 'contains';
$this->assertEquals(
'{"op":"&","showc":[true],"c":[{"type":"profile","op":"contains","sf":"email","v":"@"}]}',
upgrade_availability_item(0, 0, 0, 0, 1, array(), array($fieldrec)));
$fieldrec->operator = 'isnotempty';
$fieldrec->userfield = null;
$fieldrec->shortname = 'frogtype';
$this->assertEquals(
'{"op":"&","showc":[false],"c":[{"type":"profile","op":"isnotempty","cf":"frogtype"}]}',
upgrade_availability_item(0, 0, 0, 0, 0, array(), array($fieldrec)));
// Everything at once.
$this->assertEquals('{"op":"&","showc":[false,true,false,true,true,true],' .
'"c":[{"type":"grouping","id":13},' .
'{"type":"date","d":">=","t":990},' .
'{"type":"date","d":"<","t":991},' .
'{"type":"grade","id":665,"min":70.00000},' .
'{"type":"completion","cm":42,"e":2},' .
'{"type":"profile","op":"isempty","sf":"email"}]}',
upgrade_availability_item(1, 13, 990, 991, 1, array(
(object)array('sourcecmid' => null, 'gradeitemid' => 665, 'grademin' => 70),
(object)array('sourcecmid' => 42, 'gradeitemid' => null, 'requiredcompletion' => 2)
), array(
(object)array('userfield' => 'email', 'shortname' => null, 'operator' => 'isempty'),
)));
}
}

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2014040300.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2014040401.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.