MDL-44070 Conditional availability enhancements (1): DB upgrade
authorsam marshall <s.marshall@open.ac.uk>
Wed, 26 Mar 2014 12:02:03 +0000 (12:02 +0000)
committersam marshall <s.marshall@open.ac.uk>
Mon, 7 Apr 2014 17:27:43 +0000 (18:27 +0100)
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.

lib/db/install.xml
lib/db/upgrade.php
lib/db/upgradelib.php
lib/setuplib.php
lib/tests/upgradelib_test.php
version.php

index 3627e91..f47c8ce 100644 (file)
         <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"/>
         <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"/>
         <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"/>
         <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"/>
index 298cb26..8a415b5 100644 (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;
 }
index 31637f5..7e92a1f 100644 (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.
-}
\ No newline at end of file
+}
+
+/**
+ * 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;
+    }
+}
index 54c57b7..12c254c 100644 (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 {
index e6a7a3c..4165b7f 100644 (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'),
+                )));
+    }
 }
index b8d718a..84e76d5 100644 (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.