Merge branch 'MDL-47322-master' of https://github.com/sammarshallou/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 29 Sep 2014 10:12:29 +0000 (11:12 +0100)
committerDan Poltawski <dan@moodle.com>
Mon, 29 Sep 2014 10:12:29 +0000 (11:12 +0100)
availability/classes/tree.php
availability/condition/completion/classes/condition.php
availability/condition/date/classes/condition.php
availability/condition/grade/classes/condition.php
availability/condition/profile/classes/condition.php
availability/tests/tree_test.php
course/modlib.php
course/tests/courselib_test.php

index a7308c0..658ed5f 100644 (file)
@@ -621,6 +621,15 @@ class tree extends tree_node {
         return $result;
     }
 
+    /**
+     * Checks whether this tree is empty (contains no children).
+     *
+     * @return boolean True if empty
+     */
+    public function is_empty() {
+        return count($this->children) === 0;
+    }
+
     /**
      * Recursively gets all children of a particular class (you can use a base
      * class to get all conditions, or a specific class).
index 82d99da..d057c30 100644 (file)
@@ -74,6 +74,21 @@ class condition extends \core_availability\condition {
                 'cm' => $this->cmid, 'e' => $this->expectedcompletion);
     }
 
+    /**
+     * Returns a JSON object which corresponds to a condition of this type.
+     *
+     * Intended for unit testing, as normally the JSON values are constructed
+     * by JavaScript code.
+     *
+     * @param int $cmid Course-module id of other activity
+     * @param int $expectedcompletion Expected completion value (COMPLETION_xx)
+     * @return stdClass Object representing condition
+     */
+    public static function get_json($cmid, $expectedcompletion) {
+        return (object)array('type' => 'completion', 'cm' => (int)$cmid,
+                'e' => (int)$expectedcompletion);
+    }
+
     public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
         $modinfo = $info->get_modinfo();
         $completion = new \completion_info($modinfo->get_course());
index be3be9b..bdedc31 100644 (file)
@@ -77,6 +77,20 @@ class condition extends \core_availability\condition {
                 'd' => $this->direction, 't' => $this->time);
     }
 
+    /**
+     * Returns a JSON object which corresponds to a condition of this type.
+     *
+     * Intended for unit testing, as normally the JSON values are constructed
+     * by JavaScript code.
+     *
+     * @param string $direction DIRECTION_xx constant
+     * @param int $time Time in epoch seconds
+     * @return stdClass Object representing condition
+     */
+    public static function get_json($direction, $time) {
+        return (object)array('type' => 'date', 'd' => $direction, 't' => (int)$time);
+    }
+
     public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
         return $this->is_available_for_all($not);
     }
index 9ae6391..8a4bd5b 100644 (file)
@@ -85,6 +85,28 @@ class condition extends \core_availability\condition {
         return $result;
     }
 
+    /**
+     * Returns a JSON object which corresponds to a condition of this type.
+     *
+     * Intended for unit testing, as normally the JSON values are constructed
+     * by JavaScript code.
+     *
+     * @param int $gradeitemid Grade item id
+     * @param number|null $min Min grade (or null if no min)
+     * @param number|null $max Max grade (or null if no max)
+     * @return stdClass Object representing condition
+     */
+    public static function get_json($gradeitemid, $min = null, $max = null) {
+        $result = (object)array('type' => 'grade', 'id' => (int)$gradeitemid);
+        if (!is_null($min)) {
+            $result->min = $min;
+        }
+        if (!is_null($max)) {
+            $result->max = $max;
+        }
+        return $result;
+    }
+
     public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
         $course = $info->get_course();
         $score = $this->get_cached_grade_score($this->gradeitemid, $course->id, $grabthelot, $userid);
index 0007e87..a156e4b 100644 (file)
@@ -142,6 +142,39 @@ class condition extends \core_availability\condition {
         return $result;
     }
 
+    /**
+     * Returns a JSON object which corresponds to a condition of this type.
+     *
+     * Intended for unit testing, as normally the JSON values are constructed
+     * by JavaScript code.
+     *
+     * @param bool $customfield True if this is a custom field
+     * @param string $fieldname Field name
+     * @param string $operator Operator name (OP_xx constant)
+     * @param string|null $value Value (not required for some operator types)
+     * @return stdClass Object representing condition
+     */
+    public static function get_json($customfield, $fieldname, $operator, $value = null) {
+        $result = (object)array('type' => 'profile', 'op' => $operator);
+        if ($customfield) {
+            $result->cf = $fieldname;
+        } else {
+            $result->sf = $fieldname;
+        }
+        switch ($operator) {
+            case self::OP_IS_EMPTY:
+            case self::OP_IS_NOT_EMPTY:
+                break;
+            default:
+                if (is_null($value)) {
+                    throw new \coding_exception('Operator requires value');
+                }
+                $result->v = $value;
+                break;
+        }
+        return $result;
+    }
+
     public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
         $uservalue = $this->get_cached_user_profile_field($userid);
         $allow = self::is_field_condition_met($this->operator, $uservalue, $this->value);
index 714aa8e..b19d62c 100644 (file)
@@ -489,6 +489,21 @@ class tree_testcase extends \advanced_testcase {
                 $tree->get_full_information($info));
     }
 
+    /**
+     * Tests the is_empty() function.
+     */
+    public function test_is_empty() {
+        // Tree with nothing in should be empty.
+        $structure = tree::get_root_json(array(), tree::OP_OR);
+        $tree = new tree($structure);
+        $this->assertTrue($tree->is_empty());
+
+        // Tree with something in is not empty.
+        $structure = tree::get_root_json(array(self::mock(array('m' => '1'))), tree::OP_OR);
+        $tree = new tree($structure);
+        $this->assertFalse($tree->is_empty());
+    }
+
     /**
      * Tests the get_all_children() function.
      */
index da7292d..fa1ac35 100644 (file)
@@ -85,6 +85,15 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
         } else if (property_exists($moduleinfo, 'availability')) {
             $newcm->availability = $moduleinfo->availability;
         }
+        // If there is any availability data, verify it.
+        if ($newcm->availability) {
+            $tree = new \core_availability\tree(json_decode($newcm->availability));
+            // Save time and database space by setting null if the only data
+            // is an empty tree.
+            if ($tree->is_empty()) {
+                $newcm->availability = null;
+            }
+        }
     }
     if (isset($moduleinfo->showdescription)) {
         $newcm->showdescription = $moduleinfo->showdescription;
@@ -494,6 +503,15 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
         } else if (property_exists($moduleinfo, 'availability')) {
             $cm->availability = $moduleinfo->availability;
         }
+        // If there is any availability data, verify it.
+        if ($cm->availability) {
+            $tree = new \core_availability\tree(json_decode($cm->availability));
+            // Save time and database space by setting null if the only data
+            // is an empty tree.
+            if ($tree->is_empty()) {
+                $cm->availability = null;
+            }
+        }
     }
     if (isset($moduleinfo->showdescription)) {
         $cm->showdescription = $moduleinfo->showdescription;
index 9fda767..62ac25a 100644 (file)
@@ -457,13 +457,12 @@ class core_course_courselib_testcase extends advanced_testcase {
 
         // Conditional activity.
         $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
-        $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
-                '{"type":"date","d":">=","t":' . time() . '},' .
-                '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
-                '{"type":"grade","id":' . $coursegradeitem->id . ',"min":10,"max":80},' .
-                '{"type":"profile","sf":"email","op":"contains","v":"@"},'.
-                '{"type":"completion","id":'. $assigncm->id . ',"e":' . COMPLETION_COMPLETE . '}' .
-                ']}';
+        $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
+                array(\availability_date\condition::get_json('>=', time()),
+                \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
+                \availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
+                \availability_profile\condition::get_json(false, 'email', 'contains', '@'),
+                \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
 
         // Grading and Advanced grading.
         require_once($CFG->dirroot . '/rating/lib.php');
@@ -2509,4 +2508,43 @@ class core_course_courselib_testcase extends advanced_testcase {
             $this->assertEquals($value, $newcm->$prop);
         }
     }
+
+    /**
+     * Tests that when creating or updating a module, if the availability settings
+     * are present but set to an empty tree, availability is set to null in
+     * database.
+     */
+    public function test_empty_availability_settings() {
+        global $DB;
+        $this->setAdminUser();
+        $this->resetAfterTest();
+
+        // Enable availability.
+        set_config('enableavailability', 1);
+
+        // Test add.
+        $emptyavailability = json_encode(\core_availability\tree::get_root_json(array()));
+        $course = self::getDataGenerator()->create_course();
+        $label = self::getDataGenerator()->create_module('label', array(
+                'course' => $course, 'availability' => $emptyavailability));
+        $this->assertNull($DB->get_field('course_modules', 'availability',
+                array('id' => $label->cmid)));
+
+        // Test update.
+        $formdata = $DB->get_record('course_modules', array('id' => $label->cmid));
+        unset($formdata->availability);
+        $formdata->availabilityconditionsjson = $emptyavailability;
+        $formdata->modulename = 'label';
+        $formdata->coursemodule = $label->cmid;
+        $draftid = 0;
+        file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id,
+                'mod_label', 'intro', 0);
+        $formdata->introeditor = array(
+            'itemid' => $draftid,
+            'text' => '<p>Yo</p>',
+            'format' => FORMAT_HTML);
+        update_module($formdata);
+        $this->assertNull($DB->get_field('course_modules', 'availability',
+                array('id' => $label->cmid)));
+    }
 }