Merge branch 'wip-MDL-51095-master' of git://github.com/marinaglancy/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 17 Aug 2015 04:06:47 +0000 (12:06 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 17 Aug 2015 04:06:47 +0000 (12:06 +0800)
105 files changed:
admin/tool/behat/tests/behat/edit_permissions.feature
admin/tool/behat/tests/behat/nasty_strings.feature
auth/db/auth.php
availability/condition/grade/classes/condition.php
availability/tests/component_test.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/moodle2_course_format_test.php
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/tests/behat/add_url.feature [new file with mode: 0644]
blocks/tags/tests/behat/tagcloud.feature
blocks/tests/behat/restrict_available_blocks.feature
blog/tests/behat/blog_visibility.feature
cache/classes/helper.php
cache/stores/file/lib.php
cache/stores/file/tests/file_test.php
cache/tests/cache_test.php
calendar/set.php
completion/tests/behat/behat_completion.php
composer.json
composer.lock
course/modlib.php
course/tests/behat/navigate_course_list.feature
course/tests/behat/restrict_available_activities.feature
grade/edit/tree/item.php
grade/edit/tree/outcomeitem.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_aggregation_changes.feature [new file with mode: 0644]
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/behat/grade_view.feature
group/tests/behat/create_groups.feature
install/lang/gl/error.php
install/lang/oc_gsc/langconfig.php
install/lang/pt_br/error.php
install/lang/sma/langconfig.php [moved from install/lang/sr/langconfig.php with 94% similarity]
install/lang/smj/langconfig.php [moved from install/lang/sr_cr_bo/langconfig.php with 94% similarity]
lang/en/grades.php
lib/adodb/adodb.inc.php
lib/adodb/readme_moodle.txt
lib/badgeslib.php
lib/behat/behat_files.php
lib/dml/mssql_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/grade/grade_category.php
lib/grade/grade_item.php
lib/grade/tests/grade_item_test.php
lib/outputrenderers.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/base_testcase.php [new file with mode: 0644]
lib/phpunit/classes/basic_testcase.php
lib/phpunit/classes/hint_resultprinter.php
lib/phpunit/classes/util.php
lib/phpunit/lib.php
lib/phpunit/phpunit.xsd
lib/tablelib.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_permissions.php
lib/upgrade.txt
lib/upgradelib.php
message/tests/behat/display_history.feature
message/tests/behat/manage_contacts.feature
message/tests/behat/message_participants.feature
message/tests/behat/recent_conversations.feature
message/tests/behat/search_history.feature
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/styles.css
mod/data/templates.php
mod/forum/discuss.php
mod/forum/lang/en/deprecated.txt [new file with mode: 0644]
mod/forum/lang/en/forum.php
mod/forum/renderer.php
mod/forum/tests/behat/discussion_display.feature
mod/forum/tests/behat/discussion_navigation.feature
mod/forum/tests/behat/edit_post_student.feature
mod/forum/tests/behat/edit_post_teacher.feature
mod/forum/tests/behat/my_forum_posts.feature
mod/forum/tests/behat/post_to_multiple_groups.feature
mod/forum/tests/behat/separate_group_discussions.feature
mod/forum/tests/behat/separate_group_single_group_discussions.feature
mod/forum/tests/behat/single_forum_discussion.feature
mod/forum/tests/behat/track_read_posts.feature
mod/lesson/tests/behat/lesson_practice.feature
mod/lti/grade.php [deleted file]
mod/lti/lib.php
mod/lti/typessettings.php
mod/quiz/styles.css
mod/scorm/locallib.php
mod/workshop/lib.php
my/tests/behat/restrict_available_blocks.feature
phpunit.xml.dist
question/format/xml/format.php
question/format/xml/tests/xmlformat_test.php
report/outline/tests/behat/user.feature
repository/filepicker.js
theme/base/style/blocks.css
theme/base/style/filemanager.css
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/style/moodle.css
user/lib.php
user/tests/behat/view_full_profile.feature
user/tests/userlib_test.php
version.php

index 26d4f42..29f7981 100644 (file)
@@ -15,7 +15,6 @@ Feature: Edit capabilities
       | user | course | role |
       | teacher1 | C1 | editingteacher |
 
-  @javascript
   Scenario: Default system capabilities modification
     Given I log in as "admin"
     And I set the following system permissions of "Teacher" role:
@@ -30,7 +29,6 @@ Feature: Edit capabilities
     And "moodle/grade:managesharedforms" capability has "Prevent" permission
     And "moodle/course:request" capability has "Prohibit" permission
 
-  @javascript
   Scenario: Course capabilities overrides
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -41,11 +39,11 @@ Feature: Edit capabilities
       | mod/forum:editanypost | Prevent |
       | mod/forum:addquestion | Allow |
     When I set the field "Advanced role override" to "Student (3)"
+    And I press "Go"
     Then "mod/forum:deleteanypost" capability has "Prohibit" permission
     And "mod/forum:editanypost" capability has "Prevent" permission
     And "mod/forum:addquestion" capability has "Allow" permission
 
-  @javascript
   Scenario: Module capabilities overrides
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -60,6 +58,7 @@ Feature: Edit capabilities
       | mod/forum:editanypost | Prevent |
       | mod/forum:addquestion | Allow |
     When I set the field "Advanced role override" to "Student (3)"
+    And I press "Go"
     Then "mod/forum:deleteanypost" capability has "Prohibit" permission
     And "mod/forum:editanypost" capability has "Prevent" permission
     And "mod/forum:addquestion" capability has "Allow" permission
index 41fac8d..4b4120d 100644 (file)
@@ -13,7 +13,6 @@ Feature: Transform steps arguments
     And I follow "Preferences" in the user menu
     And I follow "Edit profile"
 
-  @javascript
   Scenario: Use nasty strings on steps arguments
     When I set the field "Surname" to "$NASTYSTRING1"
     And I set the field "Description" to "$NASTYSTRING2"
@@ -24,7 +23,6 @@ Feature: Transform steps arguments
     And the field "Surname" matches value "$NASTYSTRING1"
     And the field "City/town" matches value "$NASTYSTRING3"
 
-  @javascript
   Scenario: Use nasty strings on table nodes
     When I set the following fields to these values:
       | Surname | $NASTYSTRING1 |
@@ -36,7 +34,6 @@ Feature: Transform steps arguments
     And the field "Surname" matches value "$NASTYSTRING1"
     And the field "City/town" matches value "$NASTYSTRING3"
 
-  @javascript
   Scenario: Use double quotes
     When I set the following fields to these values:
       | First name | va"lue1 |
@@ -49,7 +46,6 @@ Feature: Transform steps arguments
     And the field "Description" matches value "va\\"lue2"
     And the field "City/town" matches value "va\"lue3"
 
-  @javascript
   Scenario: Nasty strings with other contents
     When I set the field "First name" to "My Firstname $NASTYSTRING1"
     And I set the following fields to these values:
index f03cbef..147ef60 100644 (file)
@@ -110,7 +110,7 @@ class auth_plugin_db extends auth_plugin_base {
 
             $authdb = $this->db_init();
 
-            $rs = $authdb->Execute("SELECT {$this->config->fieldpass} AS userpass
+            $rs = $authdb->Execute("SELECT {$this->config->fieldpass}
                                       FROM {$this->config->table}
                                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
             if (!$rs) {
@@ -125,7 +125,7 @@ class auth_plugin_db extends auth_plugin_base {
             }
 
             $fields = array_change_key_case($rs->fields, CASE_LOWER);
-            $fromdb = $fields['userpass'];
+            $fromdb = $fields[strtolower($this->config->fieldpass)];
             $rs->Close();
             $authdb->Close();
 
@@ -217,18 +217,21 @@ class auth_plugin_db extends auth_plugin_base {
         if ($selectfields) {
             $select = array();
             foreach ($selectfields as $localname=>$externalname) {
-                $select[] = "$externalname AS $localname";
+                $select[] = "$externalname";
             }
             $select = implode(', ', $select);
             $sql = "SELECT $select
                       FROM {$this->config->table}
                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
+
             if ($rs = $authdb->Execute($sql)) {
                 if (!$rs->EOF) {
-                    $fields_obj = $rs->FetchObj();
-                    $fields_obj = (object)array_change_key_case((array)$fields_obj , CASE_LOWER);
-                    foreach ($selectfields as $localname=>$externalname) {
-                        $result[$localname] = core_text::convert($fields_obj->{strtolower($localname)}, $this->config->extencoding, 'utf-8');
+                    $fields = $rs->FetchRow();
+                    // Convert the associative array to an array of its values so we don't have to worry about the case of its keys.
+                    $fields = array_values($fields);
+                    foreach (array_keys($selectfields) as $index => $localname) {
+                        $value = $fields[$index];
+                        $result[$localname] = core_text::convert($value, $this->config->extencoding, 'utf-8');
                      }
                  }
                  $rs->Close();
@@ -487,15 +490,15 @@ class auth_plugin_db extends auth_plugin_base {
         $authdb = $this->db_init();
 
         // Fetch userlist.
-        $rs = $authdb->Execute("SELECT {$this->config->fielduser} AS username
+        $rs = $authdb->Execute("SELECT {$this->config->fielduser}
                                   FROM {$this->config->table} ");
 
         if (!$rs) {
             print_error('auth_dbcantconnect','auth_db');
         } else if (!$rs->EOF) {
             while ($rec = $rs->FetchRow()) {
-                $rec = (object)array_change_key_case((array)$rec , CASE_LOWER);
-                array_push($result, $rec->username);
+                $rec = array_change_key_case((array)$rec, CASE_LOWER);
+                array_push($result, $rec[strtolower($this->config->fielduser)]);
             }
         }
 
index 8a4bd5b..440683c 100644 (file)
@@ -228,7 +228,9 @@ class condition extends \core_availability\condition {
                         WHERE
                             gi.courseid = ?', array($userid, $courseid));
                 foreach ($rs as $record) {
-                    if (is_null($record->finalgrade)) {
+                    // This function produces division by zero error warnings when rawgrademax and rawgrademin
+                    // are equal. Below change does not affect function behavior, just avoids the warning.
+                    if (is_null($record->finalgrade) || $record->rawgrademax == $record->rawgrademin) {
                         // No grade = false.
                         $cachedgrades[$record->id] = false;
                     } else {
@@ -249,7 +251,9 @@ class condition extends \core_availability\condition {
                 // Just get current grade.
                 $record = $DB->get_record('grade_grades', array(
                     'userid' => $userid, 'itemid' => $gradeitemid));
-                if ($record && !is_null($record->finalgrade)) {
+                // This function produces division by zero error warnings when rawgrademax and rawgrademin
+                // are equal. Below change does not affect function behavior, just avoids the warning.
+                if ($record && !is_null($record->finalgrade) && $record->rawgrademax != $record->rawgrademin) {
                     $score = (($record->finalgrade - $record->rawgrademin) * 100) /
                         ($record->rawgrademax - $record->rawgrademin);
                 } else {
index 74c3a63..de05104 100644 (file)
@@ -49,7 +49,11 @@ class core_availability_component_testcase extends advanced_testcase {
         // fail, but it's obvious when running test at least.
         $pluginmanager = core_plugin_manager::instance();
         $list = $pluginmanager->get_enabled_plugins('availability');
-        $this->assertEquals(array('completion', 'date', 'grade', 'group', 'grouping', 'profile'),
-                array_keys($list));
+        $this->assertArrayHasKey('completion', $list);
+        $this->assertArrayHasKey('date', $list);
+        $this->assertArrayHasKey('grade', $list);
+        $this->assertArrayHasKey('group', $list);
+        $this->assertArrayHasKey('grouping', $list);
+        $this->assertArrayHasKey('profile', $list);
     }
 }
index 769e7ff..587015d 100644 (file)
@@ -1428,6 +1428,15 @@ class restore_process_categories_and_questions extends restore_execution_step {
  * as needed, rebuilding course cache and other friends
  */
 class restore_section_structure_step extends restore_structure_step {
+    /** @var array Cache: Array of id => course format */
+    private static $courseformats = array();
+
+    /**
+     * Resets a static cache of course formats. Required for unit testing.
+     */
+    public static function reset_caches() {
+        self::$courseformats = array();
+    }
 
     protected function define_structure() {
         global $CFG;
@@ -1600,14 +1609,13 @@ class restore_section_structure_step extends restore_structure_step {
 
     public function process_course_format_options($data) {
         global $DB;
-        static $courseformats = array();
         $courseid = $this->get_courseid();
-        if (!array_key_exists($courseid, $courseformats)) {
+        if (!array_key_exists($courseid, self::$courseformats)) {
             // It is safe to have a static cache of course formats because format can not be changed after this point.
-            $courseformats[$courseid] = $DB->get_field('course', 'format', array('id' => $courseid));
+            self::$courseformats[$courseid] = $DB->get_field('course', 'format', array('id' => $courseid));
         }
         $data = (array)$data;
-        if ($courseformats[$courseid] === $data['format']) {
+        if (self::$courseformats[$courseid] === $data['format']) {
             // Import section format options only if both courses (the one that was backed up
             // and the one we are restoring into) have same formats.
             $params = array(
index 5d2d6e0..787db96 100644 (file)
@@ -105,7 +105,7 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
                       'numdaystocomplete' => 2);
         $courseobject->update_section_format_options($data);
 
-        $this->backup_and_restore($course, $course);
+        $this->backup_and_restore($course, $course, backup::TARGET_EXISTING_ADDING);
 
         $sectionoptions = $courseobject->get_format_options(1);
         $this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
@@ -126,37 +126,49 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
 
         $this->resetAfterTest(true);
         $this->setAdminUser();
-        $CFG->enableavailability = true;
-        $CFG->enablecompletion = true;
 
-        // Create a course with some availability data set.
+        // Create a source course using the test_cs2_options format.
         $generator = $this->getDataGenerator();
         $course = $generator->create_course(
             array('format' => 'test_cs2_options', 'numsections' => 3,
                 'enablecompletion' => COMPLETION_ENABLED),
             array('createsections' => true));
+
+        // Create a target course using test_cs_options format.
         $newcourse = $generator->create_course(
             array('format' => 'test_cs_options', 'numsections' => 3,
                 'enablecompletion' => COMPLETION_ENABLED),
             array('createsections' => true));
 
+        // Set section 2 to have both options, and a name.
         $courseobject = format_base::instance($course->id);
         $section = $DB->get_record('course_sections',
-            array('course' => $course->id, 'section' => 1), '*', MUST_EXIST);
-
+            array('course' => $course->id, 'section' => 2), '*', MUST_EXIST);
         $data = array('id' => $section->id,
             'numdaystocomplete' => 2,
-            'secondparameter' => 8);
+            'secondparameter' => 8
+        );
         $courseobject->update_section_format_options($data);
-        // Backup and restore it.
-        $this->backup_and_restore($course, $newcourse);
+        $DB->set_field('course_sections', 'name', 'Frogs', array('id' => $section->id));
+
+        // Backup and restore to the new course using 'add to existing' so it
+        // keeps the current (test_cs_options) format.
+        $this->backup_and_restore($course, $newcourse, backup::TARGET_EXISTING_ADDING);
 
+        // Check that the section contains the options suitable for the new
+        // format and that even the one with the same name as from the old format
+        // has NOT been set.
         $newcourseobject = format_base::instance($newcourse->id);
-        $sectionoptions = $newcourseobject->get_format_options(1);
+        $sectionoptions = $newcourseobject->get_format_options(2);
         $this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
-        $this->assertArrayHasKey('secondparameter', $sectionoptions);
+        $this->assertArrayNotHasKey('secondparameter', $sectionoptions);
         $this->assertEquals(0, $sectionoptions['numdaystocomplete']);
-        $this->assertEquals(0, $sectionoptions['secondparameter']);
+
+        // However, the name should have been changed, as this does not depend
+        // on the format.
+        $modinfo = get_fast_modinfo($newcourse->id);
+        $section = $modinfo->get_section_info(2);
+        $this->assertEquals('Frogs', $section->name);
     }
 
     /**
@@ -164,9 +176,11 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
      *
      * @param stdClass $srccourse Course object to backup
      * @param stdClass $dstcourse Course object to restore into
+     * @param int $target Target course mode (backup::TARGET_xx)
      * @return int ID of newly restored course
      */
-    protected function backup_and_restore($srccourse, $dstcourse = null) {
+    protected function backup_and_restore($srccourse, $dstcourse = null,
+            $target = backup::TARGET_NEW_COURSE) {
         global $USER, $CFG;
 
         // Turn off file logging, otherwise it can't delete the file (Windows).
@@ -190,7 +204,7 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
         }
         $rc = new restore_controller($backupid, $newcourseid,
                 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
-                backup::TARGET_NEW_COURSE);
+                $target);
 
         $this->assertTrue($rc->execute_precheck());
         $rc->execute_plan();
@@ -226,6 +240,12 @@ class format_test_cs_options extends format_topics {
 class format_test_cs2_options extends format_test_cs_options {
     public function section_format_options($foreditform = false) {
         return array(
+            'numdaystocomplete' => array(
+                 'type' => PARAM_INT,
+                 'label' => 'Test days',
+                 'element_type' => 'text',
+                 'default' => 0,
+             ),
             'secondparameter' => array(
                 'type' => PARAM_INT,
                 'label' => 'Test Parmater',
index e28c75b..cd26cbc 100644 (file)
@@ -47,7 +47,7 @@ class block_site_main_menu extends block_list {
             return $this->content;
         }
 
-        $course = $this->page->course;
+        $course = get_site();
         require_once($CFG->dirroot.'/course/lib.php');
         $context = context_course::instance($course->id);
         $isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
@@ -73,8 +73,9 @@ class block_site_main_menu extends block_list {
                         $attrs['title'] = $cm->modfullname;
                         $attrs['class'] = $cm->extraclasses . ' activity-action';
                         if ($cm->onclick) {
-                            $attrs['id'] = html_writer::random_id('onclick');
-                            $OUTPUT->add_action_handler(new component_action('click', $cm->onclick), $attrs['id']);
+                            // Get on-click attribute value if specified and decode the onclick - it
+                            // has already been encoded for display.
+                            $attrs['onclick'] = htmlspecialchars_decode($cm->onclick);
                         }
                         if (!$cm->visible) {
                             $attrs['class'] .= ' dimmed';
@@ -161,8 +162,9 @@ class block_site_main_menu extends block_list {
                         $attrs['title'] = $mod->modfullname;
                         $attrs['class'] = $mod->extraclasses . ' activity-action';
                         if ($mod->onclick) {
-                            $attrs['id'] = html_writer::random_id('onclick');
-                            $OUTPUT->add_action_handler(new component_action('click', $mod->onclick), $attrs['id']);
+                            // Get on-click attribute value if specified and decode the onclick - it
+                            // has already been encoded for display.
+                            $attrs['onclick'] = htmlspecialchars_decode($mod->onclick);
                         }
                         if (!$mod->visible) {
                             $attrs['class'] .= ' dimmed';
diff --git a/blocks/site_main_menu/tests/behat/add_url.feature b/blocks/site_main_menu/tests/behat/add_url.feature
new file mode 100644 (file)
index 0000000..f391d7b
--- /dev/null
@@ -0,0 +1,18 @@
+@block @block_main_menu
+Feature: Add URL to main menu block
+  In order to add helpful resources for students
+  As a admin
+  I need to add URLs to the main menu block and check it works.
+
+  @javascript
+  Scenario: Add a URL in menu block and ensure it appears
+    Given I log in as "admin"
+    And I am on site homepage
+    And I navigate to "Turn editing on" node in "Front page settings"
+    When I add a "URL" to section "0" and I fill the form with:
+      | Name | google |
+      | Description | gooooooooogle |
+      | External URL | http://www.google.com |
+      | id_display | In pop-up |
+    Then "google" "link" should exist in the "Main menu" "block"
+    And "Add an activity or resource" "link" should exist in the "Main menu" "block"
index 0265fe9..2851ae7 100644 (file)
@@ -27,7 +27,6 @@ Feature: Block tags displaying tag cloud
     And I press "Update profile"
     And I log out
 
-  @javascript
   Scenario: Add Tags block on a front page
     When I log in as "admin"
     And I am on site homepage
@@ -41,7 +40,6 @@ Feature: Block tags displaying tag cloud
     And I click on "Dogs" "link" in the "Tags" "block"
     And I should see "Log in to the site" in the ".breadcrumb" "css_element"
 
-  @javascript
   Scenario: Add Tags block in a course
     When I log in as "teacher1"
     And I follow "Course 1"
index 4b1fd5a..363714c 100644 (file)
@@ -24,7 +24,6 @@ Feature: Allowed blocks controls
     Then I should see "Activities" in the "Activities" "block"
     And I should see "Course completion status" in the "Course completion status" "block"
 
-  @javascript
   Scenario: Blocks can not be added when the admin restricts the permissions
     Given I log in as "admin"
     And I set the following system permissions of "Teacher" role:
index bd38cad..dfb5c8f 100644 (file)
@@ -24,7 +24,6 @@ Feature: Blogs can be set to be only visible by the author.
     And I press "Save changes"
     And I log out
 
-  @javascript
   Scenario: A student can not see another student's blog entries.
     Given I log in as "testuser"
     And I follow "Course 1"
index eca785f..c25369e 100644 (file)
@@ -354,7 +354,7 @@ class cache_helper {
     protected static function ensure_ready_for_stats($store, $definition, $mode = cache_store::MODE_APPLICATION) {
         // This function is performance-sensitive, so exit as quickly as possible
         // if we do not need to do anything.
-        if (isset(self::$stats[$definition][$store])) {
+        if (isset(self::$stats[$definition]['stores'][$store])) {
             return;
         }
         if (!array_key_exists($definition, self::$stats)) {
@@ -368,7 +368,7 @@ class cache_helper {
                     )
                 )
             );
-        } else if (!array_key_exists($store, self::$stats[$definition])) {
+        } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
             self::$stats[$definition]['stores'][$store] = array(
                 'hits' => 0,
                 'misses' => 0,
index 514a4bd..e044c91 100644 (file)
@@ -341,8 +341,8 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
             $maxtime = cache::now() - $ttl;
         }
         $readfile = false;
-        if ($this->prescan && array_key_exists($key, $this->keys)) {
-            if (!$ttl || $this->keys[$filename] >= $maxtime && file_exists($file)) {
+        if ($this->prescan && array_key_exists($filename, $this->keys)) {
+            if ((!$ttl || $this->keys[$filename] >= $maxtime) && file_exists($file)) {
                 $readfile = true;
             } else {
                 $this->delete($key);
index be321f2..887c545 100644 (file)
@@ -44,4 +44,34 @@ class cachestore_file_test extends cachestore_tests {
     protected function get_class_name() {
         return 'cachestore_file';
     }
+
+    /**
+     * Testing cachestore_file::get with prescan enabled and with
+     * deleting the cache between the prescan and the call to get.
+     *
+     * The deleting of cache simulates some other process purging
+     * the cache.
+     */
+    public function test_cache_get_with_prescan_and_purge() {
+        global $CFG;
+
+        $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'cachestore_file', 'phpunit_test');
+        $name = 'File test';
+
+        $path = make_cache_directory('cachestore_file_test');
+        $cache = new cachestore_file($name, array('path' => $path, 'prescan' => true));
+        $cache->initialise($definition);
+
+        $cache->set('testing', 'value');
+
+        $path  = make_cache_directory('cachestore_file_test');
+        $cache = new cachestore_file($name, array('path' => $path, 'prescan' => true));
+        $cache->initialise($definition);
+
+        // Let's pretend that some other process purged caches.
+        remove_dir($CFG->cachedir.'/cachestore_file_test', true);
+        make_cache_directory('cachestore_file_test');
+
+        $cache->get('testing');
+    }
 }
\ No newline at end of file
index 8300854..0d6daa7 100644 (file)
@@ -1875,4 +1875,155 @@ class core_cache_testcase extends advanced_testcase {
         $returnedinstance1->name = 'b';
         $this->assertEquals('b', $returnedinstance2->name);
     }
+
+    public function test_performance_debug() {
+        global $CFG;
+        $this->resetAfterTest(true);
+        $CFG->perfdebug = 15;
+
+        $instance = cache_config_testing::instance();
+        $applicationid = 'phpunit/applicationperf';
+        $instance->phpunit_add_definition($applicationid, array(
+            'mode' => cache_store::MODE_APPLICATION,
+            'component' => 'phpunit',
+            'area' => 'applicationperf'
+        ));
+        $sessionid = 'phpunit/sessionperf';
+        $instance->phpunit_add_definition($sessionid, array(
+            'mode' => cache_store::MODE_SESSION,
+            'component' => 'phpunit',
+            'area' => 'sessionperf'
+        ));
+        $requestid = 'phpunit/requestperf';
+        $instance->phpunit_add_definition($requestid, array(
+            'mode' => cache_store::MODE_REQUEST,
+            'component' => 'phpunit',
+            'area' => 'requestperf'
+        ));
+
+        $application = cache::make('phpunit', 'applicationperf');
+        $session = cache::make('phpunit', 'sessionperf');
+        $request = cache::make('phpunit', 'requestperf');
+
+        // Check that no stats are recorded for these definitions yet.
+        $stats = cache_helper::get_stats();
+        $this->assertArrayNotHasKey($applicationid, $stats);
+        $this->assertArrayHasKey($sessionid, $stats);       // Session cache sets a key on construct.
+        $this->assertArrayNotHasKey($requestid, $stats);
+
+        // Check that stores register misses.
+        $this->assertFalse($application->get('missMe'));
+        $this->assertFalse($application->get('missMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(1, $endstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets']);
+
+        $startstats = cache_helper::get_stats();
+
+        // Check that stores register sets.
+        $this->assertTrue($application->set('setMe1', 1));
+        $this->assertTrue($application->set('setMe2', 2));
+        $this->assertTrue($session->set('setMe1', 1));
+        $this->assertTrue($session->set('setMe2', 2));
+        $this->assertTrue($session->set('setMe3', 3));
+        $this->assertTrue($request->set('setMe1', 1));
+        $this->assertTrue($request->set('setMe2', 2));
+        $this->assertTrue($request->set('setMe3', 3));
+        $this->assertTrue($request->set('setMe4', 4));
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
+            $startstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
+            $startstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
+            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+
+        $startstats = cache_helper::get_stats();
+
+        // Check that stores register hits.
+        $this->assertEquals($application->get('setMe1'), 1);
+        $this->assertEquals($application->get('setMe2'), 2);
+        $this->assertEquals($session->get('setMe1'), 1);
+        $this->assertEquals($session->get('setMe2'), 2);
+        $this->assertEquals($session->get('setMe3'), 3);
+        $this->assertEquals($request->get('setMe1'), 1);
+        $this->assertEquals($request->get('setMe2'), 2);
+        $this->assertEquals($request->get('setMe3'), 3);
+        $this->assertEquals($request->get('setMe4'), 4);
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
+            $startstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
+            $startstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
+            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+
+        $startstats = cache_helper::get_stats();
+
+        // Check that stores register through get_many.
+        $application->get_many(array('setMe1', 'setMe2'));
+        $session->get_many(array('setMe1', 'setMe2', 'setMe3'));
+        $request->get_many(array('setMe1', 'setMe2', 'setMe3', 'setMe4'));
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
+            $startstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
+            $startstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
+            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+    }
 }
index 345f9e7..fab8aa5 100644 (file)
@@ -41,8 +41,6 @@
 require_once('../config.php');
 require_once($CFG->dirroot.'/calendar/lib.php');
 
-require_sesskey();
-
 $var = required_param('var', PARAM_ALPHA);
 $return = clean_param(base64_decode(required_param('return', PARAM_RAW)), PARAM_LOCALURL);
 $courseid = optional_param('id', -1, PARAM_INT);
@@ -51,6 +49,12 @@ if ($courseid != -1) {
 } else {
     $return = new moodle_url($return);
 }
+
+if (!confirm_sesskey()) {
+    // Do not call require_sesskey() since this page may be accessed without session (for example by bots).
+    redirect($return);
+}
+
 $url = new moodle_url('/calendar/set.php', array('return'=>base64_encode($return->out_as_local_url(false)), 'course' => $courseid, 'var'=>$var, 'sesskey'=>sesskey()));
 $PAGE->set_url($url);
 $PAGE->set_context(context_system::instance());
index 5125a88..6790dbf 100644 (file)
@@ -27,7 +27,8 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Behat\Behat\Context\Step\Given,
+    Behat\Behat\Context\Step\Then,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
@@ -56,7 +57,7 @@ class behat_completion extends behat_base {
 
         return array(
             new Given('I go to the current course activity completion report'),
-            new Given('I hover "' . $this->escape($xpath) . '" "xpath_element"')
+            new Then('"' . $this->escape($xpath) . '" "xpath_element" should exist')
         );
     }
 
@@ -75,7 +76,7 @@ class behat_completion extends behat_base {
             "/descendant::img[contains(@title, $titleliteral)]";
         return array(
             new Given('I go to the current course activity completion report'),
-            new Given('I hover "' . $this->escape($xpath) . '" "xpath_element"')
+            new Then('"' . $this->escape($xpath) . '" "xpath_element" should exist')
         );
 
         return $steps;
index de96736..26e49e4 100644 (file)
@@ -1,7 +1,7 @@
 {
     "require-dev": {
-        "phpunit/phpunit": "3.7.*",
-        "phpunit/dbUnit": "1.2.*",
+        "phpunit/phpunit": "4.7.*",
+        "phpunit/dbUnit": "1.4.*",
         "moodlehq/behat-extension": "1.30.0"
     }
 }
index cec8279..719ac08 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "3ddf5ab21f539f6f64c7e80174be48bb",
+    "hash": "a85d8c9e61ccba5e235093157021f7b5",
     "packages": [],
     "packages-dev": [
         {
             ],
             "time": "2014-12-20 21:24:13"
         },
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14 21:17:01"
+        },
         {
             "name": "doctrine/lexer",
             "version": "v1.0.1",
             ],
             "time": "2015-05-15 02:00:06"
         },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "dflydev/markdown": "~1.0",
+                "erusev/parsedown": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "phpDocumentor": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "time": "2015-02-03 12:10:50"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+                "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "phpdocumentor/reflection-docblock": "~2.0",
+                "sebastian/comparator": "~1.1"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Prophecy\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2015-04-27 22:15:08"
+        },
         {
             "name": "phpunit/dbunit",
-            "version": "1.2.3",
+            "version": "1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/dbunit.git",
-                "reference": "8386782a2d55153e44a06eb1a9d13d6ed35d9c2d"
+                "reference": "1afe25c90834ec499f007f48dd73767fdec3bf4f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/8386782a2d55153e44a06eb1a9d13d6ed35d9c2d",
-                "reference": "8386782a2d55153e44a06eb1a9d13d6ed35d9c2d",
+                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/1afe25c90834ec499f007f48dd73767fdec3bf4f",
+                "reference": "1afe25c90834ec499f007f48dd73767fdec3bf4f",
                 "shasum": ""
             },
             "require": {
                 "ext-pdo": "*",
                 "ext-simplexml": "*",
                 "php": ">=5.3.3",
-                "phpunit/phpunit": ">=3.7.0@stable"
+                "phpunit/phpunit": "~4.0",
+                "symfony/yaml": "~2.1"
             },
             "bin": [
-                "dbunit.php"
+                "composer/bin/dbunit"
             ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
                 "testing",
                 "xunit"
             ],
-            "time": "2013-03-01 11:50:46"
+            "time": "2015-05-21 21:11:02"
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "1.2.18",
+            "version": "2.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b"
+                "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b",
-                "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c",
+                "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3",
-                "phpunit/php-file-iterator": ">=1.3.0@stable",
-                "phpunit/php-text-template": ">=1.2.0@stable",
-                "phpunit/php-token-stream": ">=1.1.3,<1.3.0"
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "3.7.*@dev"
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
             },
             "suggest": {
                 "ext-dom": "*",
-                "ext-xdebug": ">=2.0.5"
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2.x-dev"
+                    "dev-master": "2.2.x-dev"
                 }
             },
             "autoload": {
                 "classmap": [
-                    "PHP/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
                 "testing",
                 "xunit"
             ],
-            "time": "2014-09-02 10:13:14"
+            "time": "2015-08-04 03:42:39"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "1.4.0",
+            "version": "1.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb"
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb",
-                "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
                 "shasum": ""
             },
             "require": {
                 "filesystem",
                 "iterator"
             ],
-            "time": "2015-04-02 05:19:05"
+            "time": "2015-06-21 13:08:43"
         },
         {
             "name": "phpunit/php-text-template",
         },
         {
             "name": "phpunit/php-timer",
-            "version": "1.0.6",
+            "version": "1.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d"
+                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/83fe1bdc5d47658b727595c14da140da92b3d66d",
-                "reference": "83fe1bdc5d47658b727595c14da140da92b3d66d",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
+                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
                 "shasum": ""
             },
             "require": {
             "keywords": [
                 "timer"
             ],
-            "time": "2015-06-13 07:35:30"
+            "time": "2015-06-21 08:01:12"
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "1.2.2",
+            "version": "1.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32"
+                "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32",
-                "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9",
+                "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9",
                 "shasum": ""
             },
             "require": {
                 "ext-tokenizer": "*",
                 "php": ">=5.3.3"
             },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2-dev"
+                    "dev-master": "1.4-dev"
                 }
             },
             "autoload": {
                 "classmap": [
-                    "PHP/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Wrapper around PHP's tokenizer extension.",
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2014-03-03 05:10:30"
+            "time": "2015-06-19 03:43:16"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "3.7.38",
+            "version": "4.7.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6"
+                "reference": "9b97f9d807b862c2de2a36e86690000801c85724"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6",
-                "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b97f9d807b862c2de2a36e86690000801c85724",
+                "reference": "9b97f9d807b862c2de2a36e86690000801c85724",
                 "shasum": ""
             },
             "require": {
-                "ext-ctype": "*",
                 "ext-dom": "*",
                 "ext-json": "*",
                 "ext-pcre": "*",
                 "ext-reflection": "*",
                 "ext-spl": "*",
                 "php": ">=5.3.3",
-                "phpunit/php-code-coverage": "~1.2",
-                "phpunit/php-file-iterator": "~1.3",
-                "phpunit/php-text-template": "~1.1",
-                "phpunit/php-timer": "~1.0",
-                "phpunit/phpunit-mock-objects": "~1.2",
-                "symfony/yaml": "~2.0"
-            },
-            "require-dev": {
-                "pear-pear.php.net/pear": "1.9.4"
+                "phpspec/prophecy": "~1.3,>=1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": ">=1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.1",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.2",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
             },
             "suggest": {
                 "phpunit/php-invoker": "~1.1"
             },
             "bin": [
-                "composer/bin/phpunit"
+                "phpunit"
             ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.7.x-dev"
+                    "dev-master": "4.7.x-dev"
                 }
             },
             "autoload": {
                 "classmap": [
-                    "PHPUnit/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                "",
-                "../../symfony/yaml/"
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
                 }
             ],
             "description": "The PHP Unit Testing framework.",
-            "homepage": "http://www.phpunit.de/",
+            "homepage": "https://phpunit.de/",
             "keywords": [
                 "phpunit",
                 "testing",
                 "xunit"
             ],
-            "time": "2014-10-17 09:04:17"
+            "time": "2015-07-13 11:28:34"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
-            "version": "1.2.3",
+            "version": "2.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875"
+                "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875",
-                "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
+                "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
                 "shasum": ""
             },
             "require": {
+                "doctrine/instantiator": "~1.0,>=1.0.2",
                 "php": ">=5.3.3",
-                "phpunit/php-text-template": ">=1.1.1@stable"
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
             },
             "suggest": {
                 "ext-soap": "*"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
             "autoload": {
                 "classmap": [
-                    "PHPUnit/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
                 "mock",
                 "xunit"
             ],
-            "time": "2013-01-13 10:24:48"
+            "time": "2015-07-10 06:54:24"
         },
         {
             "name": "psr/log",
             ],
             "time": "2012-12-21 11:40:51"
         },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2015-07-26 15:48:44"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3",
+                "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "http://www.github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2015-02-22 15:13:53"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44",
+                "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2015-08-03 06:14:51"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2015-06-21 07:55:53"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
+                "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2014-10-06 09:23:50"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "994d4a811bafe801fb06dccbee797863ba2792ba"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba",
+                "reference": "994d4a811bafe801fb06dccbee797863ba2792ba",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2015-06-21 08:04:50"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2015-06-21 13:59:46"
+        },
         {
             "name": "symfony/icu",
             "version": "v1.2.2",
         },
         {
             "name": "twig/twig",
-            "version": "v1.18.2",
+            "version": "v1.19.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig.git",
-                "reference": "e8e6575abf6102af53ec283f7f14b89e304fa602"
+                "reference": "edbeaf43b0a606cdaadc32a11d2673614a377b90"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig/zipball/e8e6575abf6102af53ec283f7f14b89e304fa602",
-                "reference": "e8e6575abf6102af53ec283f7f14b89e304fa602",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/edbeaf43b0a606cdaadc32a11d2673614a377b90",
+                "reference": "edbeaf43b0a606cdaadc32a11d2673614a377b90",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.18-dev"
+                    "dev-master": "1.19-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "templating"
             ],
-            "time": "2015-06-06 23:31:24"
+            "time": "2015-07-31 13:45:26"
         }
     ],
     "aliases": [],
index 3d49264..89588dc 100644 (file)
@@ -232,17 +232,6 @@ function edit_module_post_actions($moduleinfo, $course) {
                 // Use updated grade_item.
                 $grade_item = $items[$itemid];
             }
-            $gradecategory = $grade_item->get_parent_category();
-            if (!empty($moduleinfo->add)) {
-                if (grade_category::aggregation_uses_aggregationcoef($gradecategory->aggregation)) {
-                    if ($gradecategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
-                        $grade_item->aggregationcoef = 1;
-                    } else {
-                        $grade_item->aggregationcoef = 0;
-                    }
-                    $grade_item->update();
-                }
-            }
         }
     }
 
@@ -301,17 +290,6 @@ function edit_module_post_actions($moduleinfo, $course) {
                 } else if (isset($moduleinfo->gradecat)) {
                     $outcome_item->set_parent($moduleinfo->gradecat);
                 }
-                $gradecategory = $outcome_item->get_parent_category();
-                if ($outcomeexists == false) {
-                    if (grade_category::aggregation_uses_aggregationcoef($gradecategory->aggregation)) {
-                        if ($gradecategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
-                            $outcome_item->aggregationcoef = 1;
-                        } else {
-                            $outcome_item->aggregationcoef = 0;
-                        }
-                        $outcome_item->update();
-                    }
-                }
             }
         }
     }
index 529ae6a..cabeace 100644 (file)
@@ -17,7 +17,6 @@ Feature: Browse course list and return back from enrolment page
       | Sample course | C1        | 0        |
       | Course 1      | COURSE1   | CAT1     |
 
-  @javascript
   Scenario: A user can return to the category page from enrolment page
     When I log in as "user2"
     And I click on "Courses" "link" in the "Navigation" "block"
@@ -42,7 +41,6 @@ Feature: Browse course list and return back from enrolment page
     And I press "Continue"
     Then I should see "Edit profile" in the ".breadcrumb-nav" "css_element"
 
-  @javascript
   Scenario: User can return to the choice activity from enrolment page
     Given the following "roles" exist:
       | name                   | shortname | description      | archetype      |
index 2140e16..6fc0568 100644 (file)
@@ -29,7 +29,6 @@ Feature: Restrict activities availability
     Then I should see "Test glossary name"
     And I should see "Test chat name"
 
-  @javascript
   Scenario: Activities can not be added when the admin restricts the permissions
     Given I log in as "admin"
     And I set the following system permissions of "Teacher" role:
index acbf581..75d3901 100644 (file)
@@ -110,13 +110,19 @@ if ($mform->is_cancelled()) {
     redirect($returnurl);
 
 } else if ($data = $mform->get_data(false)) {
-    // If unset, give the aggregationcoef a default based on parent aggregation method
+
+    // This is a new item, and the category chosen is different than the default category.
+    if (empty($grade_item->id) && isset($data->parentcategory) && $parent_category->id != $data->parentcategory) {
+        $parent_category = grade_category::fetch(array('id' => $data->parentcategory));
+    }
+
+    // If unset, give the aggregation values a default based on parent aggregation method.
+    $defaults = grade_category::get_default_aggregation_coefficient_values($parent_category->aggregation);
     if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') {
-        if ($parent_category->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
-            $data->aggregationcoef = 1;
-        } else {
-            $data->aggregationcoef = 0;
-        }
+        $data->aggregationcoef = $defaults['aggregationcoef'];
+    }
+    if (!isset($data->weightoverride)) {
+        $data->weightoverride = $defaults['weightoverride'];
     }
 
     if (!isset($data->gradepass) || $data->gradepass == '') {
@@ -145,6 +151,8 @@ if ($mform->is_cancelled()) {
     }
     if (isset($data->aggregationcoef2) && $parent_category->aggregation == GRADE_AGGREGATE_SUM) {
         $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0;
+    } else {
+        $data->aggregationcoef2 = $defaults['aggregationcoef2'];
     }
 
     $grade_item = new grade_item(array('id'=>$id, 'courseid'=>$courseid));
@@ -162,7 +170,7 @@ if ($mform->is_cancelled()) {
 
         // set parent if needed
         if (isset($data->parentcategory)) {
-            $grade_item->set_parent($data->parentcategory, 'gradebook');
+            $grade_item->set_parent($data->parentcategory, false);
         }
 
     } else {
index 52bc618..02c76a8 100644 (file)
@@ -114,8 +114,18 @@ $mform->set_data($item);
 
 if ($data = $mform->get_data()) {
 
-    if (!isset($data->aggregationcoef)) {
-        $data->aggregationcoef = 0;
+    // This is a new item, and the category chosen is different than the default category.
+    if (empty($grade_item->id) && isset($data->parentcategory) && $parent_category->id != $data->parentcategory) {
+        $parent_category = grade_category::fetch(array('id' => $data->parentcategory));
+    }
+
+    // If unset, give the aggregation values a default based on parent aggregation method.
+    $defaults = grade_category::get_default_aggregation_coefficient_values($parent_category->aggregation);
+    if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') {
+        $data->aggregationcoef = $defaults['aggregationcoef'];
+    }
+    if (!isset($data->weightoverride)) {
+        $data->weightoverride = $defaults['weightoverride'];
     }
 
     if (property_exists($data, 'calculation')) {
@@ -140,6 +150,8 @@ if ($data = $mform->get_data()) {
     }
     if (isset($data->aggregationcoef2) && $parent_category->aggregation == GRADE_AGGREGATE_SUM) {
         $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0;
+    } else {
+        $data->aggregationcoef2 = $defaults['aggregationcoef2'];
     }
 
     $grade_item = new grade_item(array('id'=>$id, 'courseid'=>$courseid));
@@ -200,7 +212,7 @@ if ($data = $mform->get_data()) {
         } else {
             // set parent if needed
             if (isset($data->parentcategory)) {
-                $grade_item->set_parent($data->parentcategory, 'gradebook');
+                $grade_item->set_parent($data->parentcategory, false);
             }
         }
 
index e87d71b..d10d14a 100644 (file)
@@ -62,7 +62,6 @@ Feature: We can use calculated grade totals
     And I set the field "Grade display type" to "Real (percentage)"
     And I press "Save changes"
 
-  @javascript
   Scenario: Mean of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Mean of grades |
@@ -81,7 +80,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "30.42 (30.42 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Weighted mean of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Weighted mean of grades |
@@ -104,7 +102,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "26.94 (26.94 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Simple weighted mean of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Simple weighted mean of grades |
@@ -125,7 +122,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "48.57 (48.57 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Mean of grades (with extra credits) aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Mean of grades (with extra credits) |
@@ -146,7 +142,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "47.22 (47.22 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Median of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Median of grades |
@@ -165,7 +160,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "25.83 (25.83 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Lowest grade aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Lowest grade |
@@ -188,7 +182,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "0.00 (0.00 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Highest grade aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Highest grade |
@@ -209,7 +202,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "50.00 (50.00 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Mode of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Mode of grades |
@@ -230,7 +222,6 @@ Feature: We can use calculated grade totals
     And I follow "Grades" in the user menu
     And I should see "50.00 (50.00 %)" in the "overview-grade" "table"
 
-  @javascript
   Scenario: Natural aggregation on outcome items with natural weights
     And the following config values are set as admin:
       | enableoutcomes | 1 |
@@ -292,7 +283,6 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And "Test outcome item one" row "Grade" column of "user-grade" table should contain "Excellent (100.00 %)"
 
-  @javascript
   Scenario: Natural aggregation on outcome items with modified weights
     And the following config values are set as admin:
       | enableoutcomes | 1 |
@@ -329,7 +319,6 @@ Feature: We can use calculated grade totals
     And I follow "Course 1"
     And "Test outcome item one" row "Grade" column of "user-grade" table should contain "Excellent (100.00 %)"
 
-  @javascript
   Scenario: Natural aggregation
     And I set the following settings for grade item "Sub category 1":
       | Aggregation          | Natural |
@@ -353,8 +342,8 @@ Feature: We can use calculated grade totals
     And I set the field "Show contribution to course total" to "Show"
     And I set the field "Show weightings" to "Show"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
-    And I set the field "Select all or one user" to "Student 1"
+    And I select "User report" from the "Grade report" singleselect
+    And I select "Student 1" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Range | Contribution to course total |
       | Test assignment five | 28.57 % | 10.00 (50.00 %) | 0–20 | 1.03 % |
@@ -382,7 +371,6 @@ Feature: We can use calculated grade totals
       | Test assignment three | 30.93 %( Extra credit ) | 40.00 (26.67 %) | 0–150 | 8.25 % |
       | Test assignment four | 30.93 % | - | 0–150 | 0.00 % |
 
-  @javascript
   Scenario: Natural aggregation with drop lowest
     When I log out
     And I log in as "admin"
@@ -401,7 +389,6 @@ Feature: We can use calculated grade totals
       | Exclude empty grades | 0       |
     And I navigate to "Categories and items" node in "Grade administration > Setup"
     And I press "Add category"
-    And I click on "Show more" "link"
     And I set the following fields to these values:
       | Category name | Sub category 3 |
       | Aggregation | Natural |
@@ -465,7 +452,7 @@ Feature: We can use calculated grade totals
 
   @javascript
   Scenario: Natural aggregation from the setup screen
-    And I set the field "Grade report" to "Categories and items"
+    And I select "Categories and items" from the "Grade report" singleselect
     And I set the following settings for grade item "Course 1":
       | Aggregation          | Natural |
     And I set the following settings for grade item "Sub category 1":
@@ -527,7 +514,7 @@ Feature: We can use calculated grade totals
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
     And I turn editing mode off
-    And I set the field "Grade report" to "Categories and items"
+    And I select "Categories and items" from the "Grade report" singleselect
     And I set the field "Override weight of Test assignment one" to "1"
     And I set the field "Weight of Test assignment one" to "0"
     And I set the field "Override weight of Test assignment six" to "1"
@@ -542,8 +529,8 @@ Feature: We can use calculated grade totals
     And I set the field "Show weightings" to "Show"
     And I press "Save changes"
     Then I should see "75.00 (16.85 %)" in the ".course" "css_element"
-    And I set the field "Grade report" to "User report"
-    And I set the field "Select all or one user" to "Student 1"
+    And I select "User report" from the "Grade report" singleselect
+    And I select "Student 1" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
       | Grade item            | Calculated weight | Grade           | Contribution to course total |
       | Test assignment five  | 57.14 %           | 10.00 (50.00 %) | 2.25 %                        |
diff --git a/grade/tests/behat/grade_aggregation_changes.feature b/grade/tests/behat/grade_aggregation_changes.feature
new file mode 100644 (file)
index 0000000..7453a73
--- /dev/null
@@ -0,0 +1,472 @@
+@core @core_grades
+Feature: Changing the aggregation of an item affects its weight and extra credit definition
+  In order to switch to another aggregation method
+  As an teacher
+  I need to be able to edit the grade category settings
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Course 1 | C1        |
+    And the following "grade categories" exist:
+      | fullname      | course | aggregation |
+      | Cat mean      | C1     | 0           |
+      | Cat median    | C1     | 2           |
+      | Cat min       | C1     | 4           |
+      | Cat max       | C1     | 6           |
+      | Cat mode      | C1     | 8           |
+      | Cat weighted  | C1     | 10          |
+      | Cat weighted2 | C1     | 10          |
+      | Cat simple    | C1     | 11          |
+      | Cat ec        | C1     | 12          |
+      | Cat natural   | C1     | 13          |
+    And the following "grade items" exist:
+      | itemname  | course | category    | aggregationcoef | aggregationcoef2 | weightoverride |
+      | Item a1   | C1     | ?           | 0               | 0                | 0              |
+      | Item a2   | C1     | ?           | 0               | 0.40             | 1              |
+      | Item a3   | C1     | ?           | 1               | 0.10             | 1              |
+      | Item a4   | C1     | ?           | 1               | 0                | 0              |
+      | Item b1   | C1     | Cat natural | 0               | 0                | 0              |
+      | Item b2   | C1     | Cat natural | 0               | 0.40             | 1              |
+      | Item b3   | C1     | Cat natural | 1               | 0.10             | 1              |
+      | Item b4   | C1     | Cat natural | 1               | 0                | 0              |
+    And I log in as "admin"
+    And I set the following administration settings values:
+      | grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
+    And I am on site homepage
+    And I follow "Course 1"
+    And I navigate to "Grades" node in "Course administration"
+    And I navigate to "Grader report" node in "Grade administration"
+    And I turn editing mode on
+    And I follow "Edit   Cat mean"
+    And I set the following fields to these values:
+      | Weight adjusted     | 1  |
+      | Weight              | 20 |
+      | Extra credit        | 0  |
+    And I press "Save changes"
+    And I follow "Edit   Cat median"
+    And I set the following fields to these values:
+      | Weight adjusted     | 1  |
+      | Weight              | 5  |
+      | Extra credit        | 0  |
+    And I press "Save changes"
+    And I follow "Edit   Cat min"
+    And I set the following fields to these values:
+      | Weight adjusted     | 0  |
+      | Weight              | 0  |
+      | Extra credit        | 1  |
+    And I press "Save changes"
+    And I follow "Edit   Item a1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Weight adjusted" matches value "1"
+    And the field "id_aggregationcoef2" matches value "40.0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Weight adjusted" matches value "1"
+    And the field "id_aggregationcoef2" matches value "10.0"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item b1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b2"
+    And the field "Weight adjusted" matches value "1"
+    And the field "id_aggregationcoef2" matches value "40.0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b3"
+    And the field "Weight adjusted" matches value "1"
+    And the field "id_aggregationcoef2" matches value "10.0"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item b4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+
+  Scenario: Switching a category from Natural aggregation to Mean of grades and back
+    Given I follow "Edit   Course 1"
+    And I set the field "Aggregation" to "Mean of grades"
+    When I press "Save changes"
+    And I follow "Edit   Item a1"
+    Then I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Cat mean"
+    And I expand all fieldsets
+    And I should not see "Weight adjusted"
+    And I should not see "Weight" in the "#id_headerparent" "css_element"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Cat median"
+    And I expand all fieldsets
+    And I should not see "Weight adjusted"
+    And I should not see "Weight" in the "#id_headerparent" "css_element"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Cat min"
+    And I expand all fieldsets
+    And I should not see "Weight adjusted"
+    And I should not see "Weight" in the "#id_headerparent" "css_element"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Cat natural"
+    And I set the field "Aggregation" to "Mean of grades"
+    And I press "Save changes"
+    And I follow "Edit   Item b1"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Item b2"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Item b3"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    And I follow "Edit   Item b4"
+    And I should not see "Weight adjusted"
+    And I should not see "Weight"
+    And I should not see "Extra credit"
+    And I press "Cancel"
+    # Switching back.
+    And I follow "Edit   Course 1"
+    And I set the field "Aggregation" to "Natural"
+    And I press "Save changes"
+    And I follow "Edit   Item a1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat mean"
+    And I expand all fieldsets
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat median"
+    And I expand all fieldsets
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat min"
+    And I expand all fieldsets
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat natural"
+    And I set the field "Aggregation" to "Natural"
+    And I press "Save changes"
+    And I follow "Edit   Item b1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b2"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b3"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+
+  Scenario: Switching a category from Natural aggregation to Weighted mean of grades and back
+    Given I follow "Edit   Course 1"
+    And I set the field "Aggregation" to "Weighted mean of grades"
+    When I press "Save changes"
+    And I follow "Edit   Item a1"
+    Then I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Cat mean"
+    And I expand all fieldsets
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Cat median"
+    And I expand all fieldsets
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Cat min"
+    And I expand all fieldsets
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Cat natural"
+    And I set the field "Aggregation" to "Weighted mean of grades"
+    And I press "Save changes"
+    And I follow "Edit   Item b1"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item b2"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item b3"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item b4"
+    And I should not see "Weight adjusted"
+    And I should not see "Extra credit"
+    And the field "Item weight" matches value "1"
+    And I press "Cancel"
+    # Switching back.
+    And I follow "Edit   Course 1"
+    And I set the field "Aggregation" to "Natural"
+    And I press "Save changes"
+    And I follow "Edit   Item a1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat mean"
+    And I expand all fieldsets
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat median"
+    And I expand all fieldsets
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat min"
+    And I expand all fieldsets
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Cat natural"
+    And I set the field "Aggregation" to "Natural"
+    And I press "Save changes"
+    And I follow "Edit   Item b1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b2"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b3"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item b4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+
+  @javascript
+  Scenario: Switching grade items between categories
+    # Move to same aggregation (Natural).
+    Given I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I set the field "Select Item a1" to "1"
+    And I set the field "Select Item a2" to "1"
+    And I set the field "Select Item a3" to "1"
+    And I set the field "Select Item a4" to "1"
+    When I select "Cat natural" from the "Move selected items to" singleselect
+    And I navigate to "Grader report" node in "Grade administration"
+    And I follow "Edit   Item a1"
+    Then the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Weight adjusted" matches value "1"
+    And the field "id_aggregationcoef2" matches value "40.0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Weight adjusted" matches value "1"
+    And the field "id_aggregationcoef2" matches value "10.0"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    # Move to Mean of grades (with extra credit).
+    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I set the field "Select Item a1" to "1"
+    And I set the field "Select Item a2" to "1"
+    And I set the field "Select Item a3" to "1"
+    And I set the field "Select Item a4" to "1"
+    And I select "Cat ec" from the "Move selected items to" singleselect
+    And I navigate to "Grader report" node in "Grade administration"
+    And I follow "Edit   Item a1"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    # Move to Simple weight mean of grades.
+    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I set the field "Select Item a1" to "1"
+    And I set the field "Select Item a2" to "1"
+    And I set the field "Select Item a3" to "1"
+    And I set the field "Select Item a4" to "1"
+    And I select "Cat simple" from the "Move selected items to" singleselect
+    And I navigate to "Grader report" node in "Grade administration"
+    And I follow "Edit   Item a1"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Extra credit" matches value "1"
+    And I press "Cancel"
+    # Move to Weighted mean of grades.
+    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I set the field "Select Item a1" to "1"
+    And I set the field "Select Item a2" to "1"
+    And I set the field "Select Item a3" to "1"
+    And I set the field "Select Item a4" to "1"
+    And I select "Cat weighted" from the "Move selected items to" singleselect
+    And I navigate to "Grader report" node in "Grade administration"
+    And I follow "Edit   Item a1"
+    And the field "Item weight" matches value "1"
+    And I set the field "Item weight" to "2"
+    And I press "Save changes"
+    And I follow "Edit   Item a2"
+    And the field "Item weight" matches value "1"
+    And I set the field "Item weight" to "5"
+    And I press "Save changes"
+    And I follow "Edit   Item a3"
+    And the field "Item weight" matches value "1"
+    And I set the field "Item weight" to "8"
+    And I press "Save changes"
+    And I follow "Edit   Item a4"
+    And the field "Item weight" matches value "1"
+    And I set the field "Item weight" to "11"
+    And I press "Save changes"
+    # Move to same (Weighted mean of grades).
+    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I set the field "Select Item a1" to "1"
+    And I set the field "Select Item a2" to "1"
+    And I set the field "Select Item a3" to "1"
+    And I set the field "Select Item a4" to "1"
+    And I select "Cat weighted2" from the "Move selected items to" singleselect
+    And I wait "2" seconds
+    And I navigate to "Grader report" node in "Grade administration"
+    And I follow "Edit   Item a1"
+    And the field "Item weight" matches value "2"
+    And I press "Save changes"
+    And I follow "Edit   Item a2"
+    And the field "Item weight" matches value "5"
+    And I press "Save changes"
+    And I follow "Edit   Item a3"
+    And the field "Item weight" matches value "8"
+    And I press "Save changes"
+    And I follow "Edit   Item a4"
+    And the field "Item weight" matches value "11"
+    And I press "Save changes"
+    # Move back to Natural.
+    And I navigate to "Categories and items" node in "Grade administration > Setup"
+    And I set the field "Select Item a1" to "1"
+    And I set the field "Select Item a2" to "1"
+    And I set the field "Select Item a3" to "1"
+    And I set the field "Select Item a4" to "1"
+    And I select "Course 1" from the "Move selected items to" singleselect
+    And I navigate to "Grader report" node in "Grade administration"
+    And I follow "Edit   Item a1"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a2"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a3"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
+    And I follow "Edit   Item a4"
+    And the field "Weight adjusted" matches value "0"
+    And the field "Extra credit" matches value "0"
+    And I press "Cancel"
index d45e489..5cd48b0 100644 (file)
@@ -70,7 +70,6 @@ Feature: View gradebook when scales are used
     And I follow "Grader report"
     And I turn editing mode on
 
-  @javascript
   Scenario: Test displaying scales in gradebook in aggregation method Natural
     When I turn editing mode off
     Then the following should exist in the "user-grades" table:
@@ -85,14 +84,13 @@ Feature: View gradebook when scales are used
       | Range              | F–A      | 0.00–5.00      | 0.00–5.00    |
       | Overall average    | C        | 3.00           | 3.00         |
     And I follow "User report"
-    And I set the field "Select all or one user" to "Student 3"
-    And I click on "Select all or one user" "select"
+    And I select "Student 3" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
       | Grade item          | Grade | Range | Percentage | Contribution to course total |
       | Test assignment one | C     | F–A   | 50.00 %    | 60.00 %                      |
       | Sub category 1 total      | 3.00  | 0–5   | 60.00 %    | -                            |
       | Course total        | 3.00  | 0–5   | 60.00 %    | -                            |
-    And I set the field "jump" to "Categories and items"
+    And I select "Categories and items" from the "Grade report" singleselect
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 5.00      |
@@ -108,7 +106,6 @@ Feature: View gradebook when scales are used
       | Sub category 1 total      | 4.00  | 0–5   | 80.00 %    | -                            |
       | Course total        | 4.00  | 0–5   | 80.00 %    | -                            |
 
-  @javascript
   Scenario Outline: Test displaying scales in gradebook in all other aggregation methods
     When I follow "Edit   Course 1"
     And I set the field "Aggregation" to "<aggregation>"
@@ -133,14 +130,13 @@ Feature: View gradebook when scales are used
       | Range              | F–A      | 1.00–5.00      | 0.00–100.00    |
       | Overall average    | C        | 3.00           | <overallavg>   |
     And I follow "User report"
-    And I set the field "Select all or one user" to "Student 3"
-    And I click on "Select all or one user" "select"
+    And I select "Student 3" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
       | Grade item                   | Grade          | Range | Percentage    | Contribution to course total |
       | Test assignment one          | C              | F–A   | 50.00 %       | <contrib3>                   |
       | Sub category (<aggregation>) total<aggregation>. | 3.00           | 1–5   | 50.00 %       | -                            |
       | Course total<aggregation>.   | <coursetotal3> | 0–100 | <courseperc3> | -                            |
-    And I set the field "jump" to "Categories and items"
+    And I select "Categories and items" from the "Grade report" singleselect
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | A (5)     |
@@ -159,7 +155,7 @@ Feature: View gradebook when scales are used
     Examples:
       | aggregation                         | coursetotal1 | coursetotal2 | coursetotal3 | coursetotal4 | coursetotal5 |overallavg | courseperc2 | courseperc3 | contrib2 | contrib3 |
       | Mean of grades                      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
-      | Weighted mean of grades             | -            | -            | -            | -            | -            | -         | -           | -           | 0.00 %   | 0.00 %   |
+      | Weighted mean of grades             | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
       | Simple weighted mean of grades      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
       | Mean of grades (with extra credits) | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
       | Median of grades                    | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
index e902e63..867c70c 100644 (file)
@@ -125,7 +125,7 @@ Feature: View gradebook when single item scales are used
     Examples:
       | aggregation                         | contrib1 | cattotal1 | coursetotal1 | catavg | overallavg |
       | Mean of grades                      | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
-      | Weighted mean of grades             | 0.00 %   | 100.00    | -            | 100.00 | -          |
+      | Weighted mean of grades             | 0.00 %   | 100.00    | 100.00       | 100.00 | 100.00     |
       | Simple weighted mean of grades      | 0.00 %   | -         | -            | -      | -          |
       | Mean of grades (with extra credits) | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
       | Median of grades                    | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
index 5662e39..9aa8684 100644 (file)
@@ -57,9 +57,8 @@ Feature: We can enter in grades and view reports from the gradebook
     And I give the grade "90.00" to the user "Student 1" for the grade item "Test assignment name 2"
     And I press "Save changes"
 
-  @javascript
   Scenario: Grade a grade item and ensure the results display correctly in the gradebook
-    When I set the field "Grade report" to "User report"
+    When I select "User report" from the "Grade report" singleselect
     And the "Grade report" select box should contain "Grader report"
     And the "Grade report" select box should contain "Outcomes report"
     And the "Grade report" select box should contain "User report"
@@ -80,14 +79,13 @@ Feature: We can enter in grades and view reports from the gradebook
     And "Course 1" row "Grade" column of "overview-grade" table should contain "170.00"
     And "Course 1" row "Grade" column of "overview-grade" table should not contain "90.00"
 
-  @javascript
   Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
-    When I set the field "Grade report" to "Categories and items"
+    When I select "Categories and items" from the "Grade report" singleselect
     And I set the following settings for grade item "Course 1":
       | Aggregation | Weighted mean of grades |
     And I set the field "Extra credit value for Test assignment name" to "0.72"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I select "User report" from the "Grade report" singleselect
     And I navigate to "Course grade settings" node in "Grade administration > Setup"
     And I set the following fields to these values:
       | Show weightings | Show |
index 3e34a4c..4d07c44 100644 (file)
@@ -57,7 +57,6 @@ Feature: Organize students into groups
     And I should see "Student 3"
     And I should not see "Student 0"
 
-  @javascript
   Scenario: Create groups and groupings without the 'moodle/course:changeidnumber' capability
     Given the following "courses" exist:
       | fullname | shortname | category | groupmode |
index dfa4b25..06f0cb7 100644 (file)
@@ -30,6 +30,9 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['cannotcreatedboninstall'] = '<p>Non é posíbel crear a base de datos.</p>
+<p>A base de datos especificada non existe e o usuario indicado non ten permiso para crear a base de datos.</p>
+<p>O administrador do sitio debería verificar a configuración da base de datos.</p>';
 $string['cannotcreatelangdir'] = 'Non se pode crear o directorio de idioma';
 $string['cannotcreatetempdir'] = 'Non se pode crear un directorio temporal';
 $string['cannotdownloadcomponents'] = 'Non foi posíbel descargar compoñentes';
@@ -39,9 +42,12 @@ $string['cannotsavemd5file'] = 'Non é posíbel gardar o ficheiro md5';
 $string['cannotsavezipfile'] = 'Non é posíbel gardar o ficheiro ZIP';
 $string['cannotunzipfile'] = 'Non é posíbel descomprimir o ficheiro';
 $string['componentisuptodate'] = 'O compoñente está actualizado';
+$string['dmlexceptiononinstall'] = '<p>Produciuse un erro na base de datos [{$a->errorcode}].<br />{$a->debuginfo}</p>';
 $string['downloadedfilecheckfailed'] = 'A comprobación do ficheiro descargado non foi satisfactoria';
 $string['invalidmd5'] = 'md5 non válido';
 $string['missingrequiredfield'] = 'Falta algún campo obrigatorio';
+$string['remotedownloaderror'] = '<p>Fallo a descarga do compoñente cara o seu servidor. Recomendase encarecidamente que verifiqoe os axustes do proxy, extensión PHP cURL.</p>
+<p>Debe descargar o ficheiro <a href="{$a->url}">{$a->url}</a> manualmente, copialo en «{$a->dest}» no seu servidor e descomprimilo alí.</p>';
 $string['wrongdestpath'] = 'Camiño de destino errado.';
 $string['wrongsourcebase'] = 'URL da fonte errado.';
 $string['wrongzipfilename'] = 'Nome de ficheiro ZIP errado.';
index f725a3e..a62d23a 100644 (file)
@@ -30,4 +30,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['parentlanguage'] = '';
+$string['thisdirection'] = 'ltr';
 $string['thislanguage'] = 'Gascon';
index 4eac84f..65e5af6 100644 (file)
@@ -46,7 +46,7 @@ $string['dmlexceptiononinstall'] = '<p> Ocorreu um erro no banco de dados [{$a->
 $string['downloadedfilecheckfailed'] = 'A verificação do arquivo baixado falhou';
 $string['invalidmd5'] = 'A variável de verificação estava errada - tente novamente';
 $string['missingrequiredfield'] = 'Faltam informações obrigatórias';
-$string['remotedownloaderror'] = 'O download do componente falhou, por favor verifique as configurações do proxy. A extensão cURL do PHP é altamente recomendada.<br /><br />Você precisar baixar o <a href="{$a->url}">arquivo</a> manualmente, copiar para "{$a->dest}" e descompactar lá.';
+$string['remotedownloaderror'] = '<p>O download do componente falhou, por favor verifique as configurações do proxy. A extensão cURL do PHP é altamente recomendada.<p/><p>Você precisar baixar o <a href="{$a->url}">arquivo</a> manualmente, copiar para "{$a->dest}" e descompactar lá.<p/>';
 $string['wrongdestpath'] = 'Caminho do destino errado';
 $string['wrongsourcebase'] = 'URL do recurso errada';
 $string['wrongzipfilename'] = 'Nome do arquivo ZIP errado';
similarity index 94%
rename from install/lang/sr/langconfig.php
rename to install/lang/sma/langconfig.php
index 9e1d4de..6503a84 100644 (file)
@@ -30,5 +30,5 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['thisdirection'] = 'ltr';
-$string['thislanguage'] = 'Српски';
+$string['parentlanguage'] = 'no';
+$string['thislanguage'] = 'Sørsamisk';
similarity index 94%
rename from install/lang/sr_cr_bo/langconfig.php
rename to install/lang/smj/langconfig.php
index 9e1d4de..ac1d7cb 100644 (file)
@@ -30,5 +30,5 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$string['thisdirection'] = 'ltr';
-$string['thislanguage'] = 'Српски';
+$string['parentlanguage'] = 'no';
+$string['thislanguage'] = 'Lulesamisk';
index bd9273e..02676d6 100644 (file)
@@ -308,7 +308,7 @@ $string['gradepass'] = 'Grade to pass';
 $string['gradepass_help'] = 'This setting determines the minimum grade required to pass. The value is used in activity and course completion, and in the gradebook, where pass grades are highlighted in green and fail grades in red.';
 $string['gradepassgreaterthangrade'] = 'The grade to pass can not be greater than the maximum possible grade {$a}';
 $string['gradepointdefault'] = 'Grade point default';
-$string['gradepointdefault_help'] = 'This setting determines the default value for the grade point value available in an activity.';
+$string['gradepointdefault_help'] = 'This setting determines the default value for the grade point value available in a grade item.';
 $string['gradepointdefault_validateerror'] = 'This setting must be an integer between 1 and the grade point maximum.';
 $string['gradepointmax'] = 'Grade point maximum';
 $string['gradepointmax_help'] = 'This setting determines the maximum grade point value available in an activity.';
index c6f08bc..a561772 100644 (file)
@@ -3502,8 +3502,7 @@ http://www.stanford.edu/dept/itss/docs/oracle/10g/server.101/b10759/statements_1
                                        $key = $o->name;
                                        break;
                        }
-                       $val = $this->fetchMode == ADODB_FETCH_ASSOC ? $o->name : $i;
-                       $this->bind[$key] = $val;
+                       $this->bind[$key] = $i;
                }
        }
 
index 5df1f81..ead9b79 100644 (file)
@@ -22,5 +22,9 @@ Added:
 
 Our changes:
  * Removed random seed initialization from lib/adodb/adodb.inc.php:177 (see 038f546 and MDL-41198).
+ * Added commit to fix associative fetch mode https://github.com/ADOdb/ADOdb/commit/97c3afacb3e4f98195908101bf3621e5cc847635
+   When upgrading ADODB to 5.20 or higher, check
+   whether that commit was included and if not
+   remove this item
 
 skodak, iarenaza, moodler, stronk7
index c8af3f8..8e3d7e5 100644 (file)
@@ -472,7 +472,7 @@ class badge {
                     $wheresql = ' WHERE u.id ' . $earnedsql;
                 }
                 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->get_context(), 'moodle/badges:earnbadge', 0, true);
-                $sql = "SELECT u.id
+                $sql = "SELECT DISTINCT u.id
                         FROM {user} u
                         {$extrajoin}
                         JOIN ({$enrolledsql}) je ON je.id = u.id " . $wheresql . $extrawhere;
index 34afbdb..8ad9f23 100644 (file)
@@ -193,6 +193,14 @@ class behat_files extends behat_base {
         $this->ensure_node_is_visible($add);
         $add->click();
 
+        // Wait for the default repository (if any) to load. This checks that
+        // the relevant div exists and that it does not include the loading image.
+        $this->ensure_element_exists(
+                "//div[contains(concat(' ', normalize-space(@class), ' '), ' file-picker ')]" .
+                "//div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content ')]" .
+                "[not(descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' fp-content-loading ')])]",
+                'xpath_element');
+
         // Getting the repository link and opening it.
         $repoexception = new ExpectationException('The "' . $repositoryname . '" repository has not been found', $this->getSession());
 
@@ -210,7 +218,11 @@ class behat_files extends behat_base {
 
         // Selecting the repo.
         $this->ensure_node_is_visible($repositorylink);
-        $repositorylink->click();
+        if (!$repositorylink->getParent()->getParent()->hasClass('active')) {
+            // If the repository link is active, then the repository is already loaded.
+            // Clicking it while it's active causes issues, so only click it when it isn't (see MDL-51014).
+            $repositorylink->click();
+        }
     }
 
     /**
index 44ddc60..c6a6b9c 100644 (file)
@@ -1207,11 +1207,7 @@ class mssql_native_moodle_database extends moodle_database {
         for ($n=count($elements)-1; $n > 0 ; $n--) {
             array_splice($elements, $n, 0, $separator);
         }
-        $s = implode(' + ', $elements);
-        if ($s === '') {
-            return " '' ";
-        }
-        return " $s ";
+        return call_user_func_array(array($this, 'sql_concat'), $elements);
     }
 
    public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
index f761398..36d7893 100644 (file)
@@ -1272,12 +1272,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
         for ($n = count($elements) - 1; $n > 0; $n--) {
             array_splice($elements, $n, 0, $separator);
         }
-        $s = implode(' + ', $elements);
-
-        if ($s === '') {
-            return " '' ";
-        }
-        return " $s ";
+        return call_user_func_array(array($this, 'sql_concat'), $elements);
     }
 
     public function sql_isempty($tablename, $fieldname, $nullablefield, $textfield) {
index 60137f1..b064d99 100644 (file)
@@ -3988,12 +3988,65 @@ class core_dml_testcase extends database_driver_testcase {
 
     }
 
-    public function test_concat_join() {
+    public function sql_concat_join_provider() {
+        return array(
+            // All strings.
+            array(
+                "' '",
+                array("'name'", "'name2'", "'name3'"),
+                array(),
+                'name name2 name3',
+            ),
+            // All strings using placeholders
+            array(
+                "' '",
+                array("?", "?", "?"),
+                array('name', 'name2', 'name3'),
+                'name name2 name3',
+            ),
+            // All integers.
+            array(
+                "' '",
+                array(1, 2, 3),
+                array(),
+                '1 2 3',
+            ),
+            // All integers using placeholders
+            array(
+                "' '",
+                array("?", "?", "?"),
+                array(1, 2, 3),
+                '1 2 3',
+            ),
+            // Mix of strings and integers.
+            array(
+                "' '",
+                array(1, "'2'", 3),
+                array(),
+                '1 2 3',
+            ),
+            // Mix of strings and integers using placeholders.
+            array(
+                "' '",
+                array(1, '2', 3),
+                array(),
+                '1 2 3',
+            ),
+        );
+    }
+
+    /**
+     * @dataProvider sql_concat_join_provider
+     * @param string $concat The string to use when concatanating.
+     * @param array $fields The fields to concatanate
+     * @param array $params Any parameters to provide to the query
+     * @param @string $expected The expected result
+     */
+    public function test_concat_join($concat, $fields, $params, $expected) {
         $DB = $this->tdb;
-        $sql = "SELECT ".$DB->sql_concat_join("' '", array("?", "?", "?"))." AS fullname ".$DB->sql_null_from_clause();
-        $params = array("name", "name2", "name3");
+        $sql = "SELECT " . $DB->sql_concat_join($concat, $fields) . " AS result" . $DB->sql_null_from_clause();
         $result = $DB->get_field_sql($sql, $params);
-        $this->assertEquals("name name2 name3", $result);
+        $this->assertEquals($expected, $result);
     }
 
     public function test_sql_fullname() {
index fc80f24..f024bcb 100644 (file)
@@ -2057,9 +2057,11 @@ class grade_category extends grade_object {
         unset($items); // not needed
         unset($cats); // not needed
 
-        $children_array = grade_category::_get_children_recursion($category);
-
-        ksort($children_array);
+        $children_array = array();
+        if (is_object($category)) {
+            $children_array = grade_category::_get_children_recursion($category);
+            ksort($children_array);
+        }
 
         return $children_array;
 
@@ -2428,33 +2430,36 @@ class grade_category extends grade_object {
     public static function set_properties(&$instance, $params) {
         global $DB;
 
-        parent::set_properties($instance, $params);
+        $fromaggregation = $instance->aggregation;
 
-        //if they've changed aggregation type we made need to do some fiddling to provide appropriate defaults
-        if (!empty($params->aggregation)) {
+        parent::set_properties($instance, $params);
 
-            //weight and extra credit share a column :( Would like a default of 1 for weight and 0 for extra credit
-            //Flip from the default of 0 to 1 (or vice versa) if ALL items in the category are still set to the old default.
-            if (self::aggregation_uses_aggregationcoef($params->aggregation)) {
-                $sql = $defaultaggregationcoef = null;
+        // The aggregation method is changing and this category has already been saved.
+        if (isset($params->aggregation) && !empty($instance->id)) {
+            $achildwasdupdated = false;
 
-                if (!self::aggregation_uses_extracredit($params->aggregation)) {
-                    //if all items in this category have aggregation coefficient of 0 we can change it to 1 ie evenly weighted
-                    $sql = "select count(id) from {grade_items} where categoryid=:categoryid and aggregationcoef!=0";
-                    $defaultaggregationcoef = 1;
-                } else {
-                    //if all items in this category have aggregation coefficient of 1 we can change it to 0 ie no extra credit
-                    $sql = "select count(id) from {grade_items} where categoryid=:categoryid and aggregationcoef!=1";
-                    $defaultaggregationcoef = 0;
+            // Get all its children.
+            $children = $instance->get_children();
+            foreach ($children as $child) {
+                $item = $child['object'];
+                if ($child['type'] == 'category') {
+                    $item = $item->load_grade_item();
                 }
 
-                $params = array('categoryid'=>$instance->id);
-                $count = $DB->count_records_sql($sql, $params);
-                if ($count===0) { //category is either empty or all items are set to a default value so we can switch defaults
-                    $params['aggregationcoef'] = $defaultaggregationcoef;
-                    $DB->execute("update {grade_items} set aggregationcoef=:aggregationcoef where categoryid=:categoryid",$params);
+                // Set the new aggregation fields.
+                if ($item->set_aggregation_fields_for_aggregation($fromaggregation, $params->aggregation)) {
+                    $item->update();
+                    $achildwasdupdated = true;
                 }
             }
+
+            // If this is the course category, it is possible that its grade item was set as needsupdate
+            // by one of its children. If we keep a reference to that stale object we might cause the
+            // needsupdate flag to be lost. It's safer to just reload the grade_item from the database.
+            if ($achildwasdupdated && !empty($instance->grade_item) && $instance->is_course_category()) {
+                $instance->grade_item = null;
+                $instance->load_grade_item();
+            }
         }
     }
 
@@ -2562,4 +2567,29 @@ class grade_category extends grade_object {
         $sql = "UPDATE {grade_items} SET needsupdate=? WHERE itemtype=? or itemtype=?";
         $DB->execute($sql, $params);
     }
+
+    /**
+     * Determine the default aggregation values for a given aggregation method.
+     *
+     * @param int $aggregationmethod The aggregation method constant value.
+     * @return array Containing the keys 'aggregationcoef', 'aggregationcoef2' and 'weightoverride'.
+     */
+    public static function get_default_aggregation_coefficient_values($aggregationmethod) {
+        $defaultcoefficients = array(
+            'aggregationcoef' => 0,
+            'aggregationcoef2' => 0,
+            'weightoverride' => 0
+        );
+
+        switch ($aggregationmethod) {
+            case GRADE_AGGREGATE_WEIGHTED_MEAN:
+                $defaultcoefficients['aggregationcoef'] = 1;
+                break;
+            case GRADE_AGGREGATE_SUM:
+                $defaultcoefficients['aggregationcoef2'] = 1;
+                break;
+        }
+
+        return $defaultcoefficients;
+    }
 }
index 267e6ad..145b6de 100644 (file)
@@ -257,6 +257,20 @@ class grade_item extends grade_object {
      */
     public $dependson_cache = null;
 
+    /**
+     * Constructor. Optionally (and by default) attempts to fetch corresponding row from the database
+     *
+     * @param array $params An array with required parameters for this grade object.
+     * @param bool $fetch Whether to fetch corresponding row from the database or not,
+     *        optional fields might not be defined if false used
+     */
+    public function __construct($params = null, $fetch = true) {
+        global $CFG;
+        // Set grademax from $CFG->gradepointdefault .
+        self::set_properties($this, array('grademax' => $CFG->gradepointdefault));
+        parent::__construct($params, $fetch);
+    }
+
     /**
      * In addition to update() as defined in grade_object, handle the grade_outcome and grade_scale objects.
      * Force regrading if necessary, rounds the float numbers using php function,
@@ -1329,9 +1343,12 @@ class grade_item extends grade_object {
      * Sets this item's categoryid. A generic method shared by objects that have a parent id of some kind.
      *
      * @param int $parentid The ID of the new parent
+     * @param bool $updateaggregationfields Whether or not to convert the aggregation fields when switching between category.
+     *                          Set this to false when the aggregation fields have been updated in prevision of the new
+     *                          category, typically when the item is freshly created.
      * @return bool True if success
      */
-    public function set_parent($parentid) {
+    public function set_parent($parentid, $updateaggregationfields = true) {
         if ($this->is_course_item() or $this->is_category_item()) {
             print_error('cannotsetparentforcatoritem');
         }
@@ -1345,11 +1362,10 @@ class grade_item extends grade_object {
             return false;
         }
 
-        // MDL-19407 If moving from a non-SWM category to a SWM category, convert aggregationcoef to 0
         $currentparent = $this->load_parent_category();
 
-        if ($currentparent->aggregation != GRADE_AGGREGATE_WEIGHTED_MEAN2 && $parent_category->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) {
-            $this->aggregationcoef = 0;
+        if ($updateaggregationfields) {
+            $this->set_aggregation_fields_for_aggregation($currentparent->aggregation, $parent_category->aggregation);
         }
 
         $this->force_regrading();
@@ -1361,6 +1377,61 @@ class grade_item extends grade_object {
         return $this->update();
     }
 
+    /**
+     * Update the aggregation fields when the aggregation changed.
+     *
+     * This method should always be called when the aggregation has changed, but also when
+     * the item was moved to another category, even it if uses the same aggregation method.
+     *
+     * Some values such as the weight only make sense within a category, once moved the
+     * values should be reset to let the user adapt them accordingly.
+     *
+     * Note that this method does not save the grade item.
+     * {@link grade_item::update()} has to be called manually after using this method.
+     *
+     * @param  int $from Aggregation method constant value.
+     * @param  int $to   Aggregation method constant value.
+     * @return boolean   True when at least one field was changed, false otherwise
+     */
+    public function set_aggregation_fields_for_aggregation($from, $to) {
+        $defaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $origaggregationcoef = $this->aggregationcoef;
+        $origaggregationcoef2 = $this->aggregationcoef2;
+        $origweighoverride = $this->weightoverride;
+
+        if ($from == GRADE_AGGREGATE_SUM && $to == GRADE_AGGREGATE_SUM && $this->weightoverride) {
+            // Do nothing. We are switching from SUM to SUM and the weight is overriden,
+            // a teacher would not expect any change in this situation.
+
+        } else if ($from == GRADE_AGGREGATE_WEIGHTED_MEAN && $to == GRADE_AGGREGATE_WEIGHTED_MEAN) {
+            // Do nothing. The weights can be kept in this case.
+
+        } else if (in_array($from, array(GRADE_AGGREGATE_SUM,  GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2))
+                && in_array($to, array(GRADE_AGGREGATE_SUM,  GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2))) {
+
+            // Reset all but the the extra credit field.
+            $this->aggregationcoef2 = $defaults['aggregationcoef2'];
+            $this->weightoverride = $defaults['weightoverride'];
+
+            if ($to != GRADE_AGGREGATE_EXTRACREDIT_MEAN) {
+                // Normalise extra credit, except for 'Mean with extra credit' which supports higher values than 1.
+                $this->aggregationcoef = min(1, $this->aggregationcoef);
+            }
+        } else {
+            // Reset all.
+            $this->aggregationcoef = $defaults['aggregationcoef'];
+            $this->aggregationcoef2 = $defaults['aggregationcoef2'];
+            $this->weightoverride = $defaults['weightoverride'];
+        }
+
+        $acoefdiff       = grade_floats_different($origaggregationcoef, $this->aggregationcoef);
+        $acoefdiff2      = grade_floats_different($origaggregationcoef2, $this->aggregationcoef2);
+        $weightoverride  = grade_floats_different($origweighoverride, $this->weightoverride);
+
+        return $acoefdiff || $acoefdiff2 || $weightoverride;
+    }
+
     /**
      * Makes sure value is a valid grade value.
      *
index 1655466..de723e4 100644 (file)
@@ -734,4 +734,182 @@ class core_grade_item_testcase extends grade_base_testcase {
 
         return $DB->get_record('grade_items', array('id' => $item->id));
     }
+
+    public function test_set_aggregation_fields_for_aggregation() {
+        $course = $this->getDataGenerator()->create_course();
+        $gi = new grade_item(array('courseid' => $course->id, 'itemtype' => 'manual'), false);
+
+        $methods = array(GRADE_AGGREGATE_MEAN, GRADE_AGGREGATE_MEDIAN, GRADE_AGGREGATE_MIN, GRADE_AGGREGATE_MAX,
+            GRADE_AGGREGATE_MODE, GRADE_AGGREGATE_WEIGHTED_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2,
+            GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_SUM);
+
+        // Switching from and to the same aggregation using the defaults.
+        foreach ($methods as $method) {
+            $defaults = grade_category::get_default_aggregation_coefficient_values($method);
+            $gi->aggregationcoef = $defaults['aggregationcoef'];
+            $gi->aggregationcoef2 = $defaults['aggregationcoef2'];
+            $gi->weightoverride = $defaults['weightoverride'];
+            $this->assertFalse($gi->set_aggregation_fields_for_aggregation($method, $method));
+            $this->assertEquals($defaults['aggregationcoef'], $gi->aggregationcoef);
+            $this->assertEquals($defaults['aggregationcoef2'], $gi->aggregationcoef2);
+            $this->assertEquals($defaults['weightoverride'], $gi->weightoverride);
+        }
+
+        // Extra credit is kept across aggregation methods that support it.
+        foreach ($methods as $from) {
+            $fromsupportsec = grade_category::aggregation_uses_extracredit($from);
+            $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
+
+            foreach ($methods as $to) {
+                $tosupportsec = grade_category::aggregation_uses_extracredit($to);
+                $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+                // Set the item to be extra credit, if supported.
+                if ($fromsupportsec) {
+                    $gi->aggregationcoef = 1;
+                } else {
+                    $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
+                }
+
+                // We ignore those fields, we know it is never used for extra credit.
+                $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
+                $gi->weightoverride = $todefaults['weightoverride'];
+
+                if ($fromsupportsec && $tosupportsec) {
+                    $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                    $this->assertEquals(1, $gi->aggregationcoef);
+
+                } else if ($fromsupportsec && !$tosupportsec) {
+                    if ($to == GRADE_AGGREGATE_WEIGHTED_MEAN) {
+                        // Special case, aggregationcoef is used but for weights.
+                        $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+                    } else {
+                        $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+                    }
+                } else {
+                    // The source does not support extra credit, everything will be reset.
+                    if (($from == GRADE_AGGREGATE_WEIGHTED_MEAN || $to == GRADE_AGGREGATE_WEIGHTED_MEAN) && $from != $to) {
+                        // Special case, aggregationcoef is used but for weights.
+                        $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+                    } else {
+                        $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+                    }
+                }
+            }
+        }
+
+        // Extra credit can be higher than one for GRADE_AGGREGATE_EXTRACREDIT_MEAN, but will be normalised for others.
+        $from = GRADE_AGGREGATE_EXTRACREDIT_MEAN;
+        $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
+
+        foreach ($methods as $to) {
+            if (!grade_category::aggregation_uses_extracredit($to)) {
+                continue;
+            }
+
+            $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+            $gi->aggregationcoef = 8;
+
+            // Ignore those fields, they are not used for extra credit.
+            $gi->aggregationcoef2 = $todefaults['aggregationcoef2'];
+            $gi->weightoverride = $todefaults['weightoverride'];
+
+            if ($to == $from) {
+                $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                $this->assertEquals(8, $gi->aggregationcoef);
+            } else {
+                $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+                $this->assertEquals(1, $gi->aggregationcoef);
+            }
+        }
+
+        // Weights are reset.
+        $from = GRADE_AGGREGATE_SUM;
+        $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
+
+        $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
+        $gi->aggregationcoef2 = 0.321;
+        $gi->weightoverride = $fromdefaults['weightoverride'];
+
+        $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
+        $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+        $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
+        $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
+
+        $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
+        $gi->aggregationcoef2 = 0.321;
+        $gi->weightoverride = $fromdefaults['weightoverride'];
+
+        $to = GRADE_AGGREGATE_SUM;
+        $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+        $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
+        $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
+
+        // Weight is kept when using SUM with weight override.
+        $from = GRADE_AGGREGATE_SUM;
+        $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
+
+        $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
+        $gi->aggregationcoef2 = 0.321;
+        $gi->weightoverride = 1;
+
+        $to = GRADE_AGGREGATE_SUM;
+        $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+        $this->assertEquals(0.321, $gi->aggregationcoef2);
+        $this->assertEquals(1, $gi->weightoverride);
+
+        $gi->aggregationcoef2 = 0.321;
+        $gi->aggregationcoef = $fromdefaults['aggregationcoef'];
+        $gi->weightoverride = 1;
+
+        $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
+        $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+        $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
+        $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
+
+        // Weight is kept when staying in weighted mean.
+        $from = GRADE_AGGREGATE_WEIGHTED_MEAN;
+        $fromdefaults = grade_category::get_default_aggregation_coefficient_values($from);
+
+        $gi->aggregationcoef = 18;
+        $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
+        $gi->weightoverride = $fromdefaults['weightoverride'];
+
+        $to = GRADE_AGGREGATE_WEIGHTED_MEAN;
+        $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $this->assertFalse($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+        $this->assertEquals(18, $gi->aggregationcoef);
+        $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
+        $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
+
+        $gi->aggregationcoef = 18;
+        $gi->aggregationcoef2 = $fromdefaults['aggregationcoef2'];
+        $gi->weightoverride = $fromdefaults['weightoverride'];
+
+        $to = GRADE_AGGREGATE_SUM;
+        $todefaults = grade_category::get_default_aggregation_coefficient_values($to);
+
+        $this->assertTrue($gi->set_aggregation_fields_for_aggregation($from, $to), "From: $from, to: $to");
+        $this->assertEquals($todefaults['aggregationcoef'], $gi->aggregationcoef);
+        $this->assertEquals($todefaults['aggregationcoef2'], $gi->aggregationcoef2);
+        $this->assertEquals($todefaults['weightoverride'], $gi->weightoverride);
+    }
+
 }
index 53e5e70..70307a4 100644 (file)
@@ -3182,7 +3182,7 @@ EOD;
         }
 
         // Get some navigation opts.
-        $opts = user_get_user_navigation_info($user, $this->page, $this->page->course);
+        $opts = user_get_user_navigation_info($user, $this->page);
 
         $avatarclasses = "avatars";
         $avatarcontents = html_writer::span($opts->metadata['useravatar'], 'avatar current');
index ac02a97..1acdba7 100644 (file)
@@ -32,7 +32,7 @@
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class advanced_testcase extends PHPUnit_Framework_TestCase {
+abstract class advanced_testcase extends base_testcase {
     /** @var bool automatically reset everything? null means log changes */
     private $resetAfterTest;
 
diff --git a/lib/phpunit/classes/base_testcase.php b/lib/phpunit/classes/base_testcase.php
new file mode 100644 (file)
index 0000000..dd1dc8f
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Base test case class.
+ *
+ * @package    core
+ * @category   test
+ * @author     Tony Levi <tony.levi@blackboard.com>
+ * @copyright  2015 Blackboard (http://www.blackboard.com)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * Base class for PHPUnit test cases customised for Moodle
+ *
+ * It is intended for functionality common to both basic and advanced_testcase.
+ *
+ * @package    core
+ * @category   test
+ * @author     Tony Levi <tony.levi@blackboard.com>
+ * @copyright  2015 Blackboard (http://www.blackboard.com)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class base_testcase extends PHPUnit_Framework_TestCase {
+    /**
+     * Note: we are overriding this method to remove the deprecated error
+     * @see https://tracker.moodle.org/browse/MDL-47129
+     *
+     * @param  array   $matcher
+     * @param  string  $actual
+     * @param  string  $message
+     * @param  boolean $ishtml
+     *
+     * @deprecated 3.0
+     */
+    public static function assertTag($matcher, $actual, $message = '', $ishtml = true) {
+        $dom = PHPUnit_Util_XML::load($actual, $ishtml);
+        $tags = PHPUnit_Util_XML::findNodes($dom, $matcher, $ishtml);
+        $matched = count($tags) > 0 && $tags[0] instanceof DOMNode;
+        self::assertTrue($matched, $message);
+    }
+
+    /**
+     * Note: we are overriding this method to remove the deprecated error
+     * @see https://tracker.moodle.org/browse/MDL-47129
+     *
+     * @param  array   $matcher
+     * @param  string  $actual
+     * @param  string  $message
+     * @param  boolean $ishtml
+     *
+     * @deprecated 3.0
+     */
+    public static function assertNotTag($matcher, $actual, $message = '', $ishtml = true) {
+        $dom = PHPUnit_Util_XML::load($actual, $ishtml);
+        $tags = PHPUnit_Util_XML::findNodes($dom, $matcher, $ishtml);
+        $matched = count($tags) > 0 && $tags[0] instanceof DOMNode;
+        self::assertFalse($matched, $message);
+    }
+}
index 6ca3900..8e53d15 100644 (file)
@@ -34,7 +34,7 @@
  * @copyright  2012 Petr Skoda {@link http://skodak.org}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class basic_testcase extends PHPUnit_Framework_TestCase {
+abstract class basic_testcase extends base_testcase {
 
     /**
      * Constructs a test case with the given name.
index 2d5dfe7..51d9416 100644 (file)
@@ -45,7 +45,7 @@ class Hint_ResultPrinter extends PHPUnit_TextUI_ResultPrinter {
             }
         }
         // Fallback if something goes wrong.
-        parent::__construct(null, false, false, false);
+        parent::__construct(null, false, self::COLOR_DEFAULT, false);
     }
 
     protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect) {
@@ -135,7 +135,7 @@ class Hacky_TextUI_Command_reader extends PHPUnit_TextUI_Command {
         $verbose = isset($config['verbose']) ? $config['verbose'] : false;
         $verbose = isset($arguments['verbose']) ? $arguments['verbose'] : $verbose;
 
-        $colors = isset($config['colors']) ? $config['colors'] : false;
+        $colors = isset($config['colors']) ? $config['colors'] : Hint_ResultPrinter::COLOR_DEFAULT;
         $colors = isset($arguments['colors']) ? $arguments['colors'] : $colors;
 
         $debug = isset($config['debug']) ? $config['debug'] : false;
index 79f17db..114a55b 100644 (file)
@@ -216,6 +216,11 @@ class phpunit_util extends testing_util {
         filter_manager::reset_caches();
         core_filetypes::reset_caches();
 
+        // Reset static unit test options.
+        if (class_exists('\availability_date\condition', false)) {
+            \availability_date\condition::set_current_time_for_test(0);
+        }
+
         // Reset internal users.
         core_user::reset_internal_users();
 
@@ -239,6 +244,11 @@ class phpunit_util extends testing_util {
             \core\update\deployer::reset_caches(true);
         }
 
+        // Clear static cache within restore.
+        if (class_exists('restore_section_structure_step')) {
+            restore_section_structure_step::reset_caches();
+        }
+
         // purge dataroot directory
         self::reset_dataroot();
 
index e01c804..daccb17 100644 (file)
@@ -30,6 +30,7 @@ require_once(__DIR__.'/classes/event_mock.php');
 require_once(__DIR__.'/classes/event_sink.php');
 require_once(__DIR__.'/classes/message_sink.php');
 require_once(__DIR__.'/classes/phpmailer_sink.php');
+require_once(__DIR__.'/classes/base_testcase.php');
 require_once(__DIR__.'/classes/basic_testcase.php');
 require_once(__DIR__.'/classes/database_driver_testcase.php');
 require_once(__DIR__.'/classes/arraydataset.php');
index 6180253..700f41a 100644 (file)
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!--moodle: this schema was generated using code from https://github.com/gooh/phpunit-schema-->
-<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <xs:annotation>
-    <xs:documentation source="http://www.phpunit.de/manual/current/en/appendixes.configuration.html">
-            This Schema file defines the rules by which the XML configuration file of PHPUnit may be structured.
-        </xs:documentation>
+    <xs:documentation source="https://phpunit.de/manual/4.7/en/appendixes.configuration.html">
+      This Schema file defines the rules by which the XML configuration file of PHPUnit 4.7 may be structured.
+    </xs:documentation>
+    <xs:appinfo source="http://www.phpunit.de/manual/current/en/appendixes.configuration.html"/>
   </xs:annotation>
   <xs:element name="phpunit" type="phpUnitType">
     <xs:annotation>
@@ -35,7 +35,8 @@
   <xs:complexType name="whiteListType">
     <xs:complexContent>
       <xs:extension base="filterType">
-        <xs:attribute name="addUncoveredFilesFromWhitelist" default="true" type="xs:boolean"/>
+        <xs:attribute name="addUncoveredFilesFromWhitelist" default="false" type="xs:boolean"/>
+        <xs:attribute name="processUncoveredFilesFromWhitelist" default="true" type="xs:boolean"/>
       </xs:extension>
     </xs:complexContent>
   </xs:complexType>
       <xs:simpleType>
         <xs:restriction base="xs:string">
           <xs:enumeration value="coverage-html"/>
+          <xs:enumeration value="coverage-text"/>
           <xs:enumeration value="coverage-clover"/>
+          <xs:enumeration value="coverage-crap4j"/>
           <xs:enumeration value="json"/>
           <xs:enumeration value="plain"/>
           <xs:enumeration value="tap"/>
       </xs:simpleType>
     </xs:attribute>
     <xs:attribute name="target" type="xs:anyURI"/>
-    <xs:attribute name="title" type="xs:string"/>
-    <xs:attribute name="charset" type="xs:string" default="UTF-8"/>
-    <xs:attribute name="yui" type="xs:boolean" default="true"/>
-    <xs:attribute name="highlight" type="xs:boolean" default="false"/>
     <xs:attribute name="lowUpperBound" type="xs:nonNegativeInteger" default="35"/>
     <xs:attribute name="highLowerBound" type="xs:nonNegativeInteger" default="70"/>
     <xs:attribute name="logIncompleteSkipped" type="xs:boolean" default="false"/>
+    <xs:attribute name="showUncoveredFiles" type="xs:boolean" default="false"/>
   </xs:complexType>
   <xs:group name="pathGroup">
     <xs:sequence>
     <xs:attribute name="bootstrap" type="xs:anyURI"/>
     <xs:attribute name="cacheTokens" type="xs:boolean"/>
     <xs:attribute name="colors" type="xs:boolean" default="false"/>
+    <xs:attribute name="columns" type="xs:integer" default="80"/>
     <xs:attribute name="convertErrorsToExceptions" type="xs:boolean" default="true"/>
     <xs:attribute name="convertNoticesToExceptions" type="xs:boolean" default="true"/>
     <xs:attribute name="convertWarningsToExceptions" type="xs:boolean" default="true"/>
     <xs:attribute name="stopOnError" type="xs:boolean" default="false"/>
     <xs:attribute name="stopOnFailure" type="xs:boolean" default="false"/>
     <xs:attribute name="stopOnIncomplete" type="xs:boolean" default="false"/>
+    <xs:attribute name="stopOnRisky" type="xs:boolean" default="false"/>
     <xs:attribute name="stopOnSkipped" type="xs:boolean" default="false"/>
+    <xs:attribute name="beStrictAboutTestsThatDoNotTestAnything" type="xs:boolean" default="false"/>
+    <xs:attribute name="beStrictAboutOutputDuringTests" type="xs:boolean" default="false"/>
+    <xs:attribute name="beStrictAboutTestSize" type="xs:boolean" default="false"/>
+    <xs:attribute name="beStrictAboutTodoAnnotatedTests" type="xs:boolean" default="false"/>
+    <xs:attribute name="beStrictAboutChangesToGlobalState" type="xs:boolean" default="false"/>
+    <xs:attribute name="checkForUnintentionallyCoveredCode" type="xs:boolean" default="false"/>
     <xs:attribute name="strict" type="xs:boolean" default="false"/>
     <xs:attribute name="testSuiteLoaderClass" type="xs:string" default="PHPUnit_Runner_StandardTestSuiteLoader"/>
     <xs:attribute name="testSuiteLoaderFile" type="xs:anyURI"/>
     <xs:attribute name="timeoutForMediumTests" type="xs:integer" default="10"/>
     <xs:attribute name="timeoutForLargeTests" type="xs:integer" default="60"/>
     <xs:attribute name="verbose" type="xs:boolean" default="false"/>
+    <xs:attribute name="stderr" type="xs:boolean" default="false"/>
   </xs:attributeGroup>
   <xs:group name="configGroup">
     <xs:all>
   <xs:element name="testsuites" type="testSuitesType" substitutionGroup="testSuiteFacet"/>
   <xs:complexType name="testSuitesType">
     <xs:sequence>
-      <xs:element name="testsuite" type="testSuiteType" maxOccurs="unbounded"/><!--moodle: added macOccures-->
+      <xs:element name="testsuite" type="testSuiteType" maxOccurs="unbounded"/>
     </xs:sequence>
   </xs:complexType>
   <xs:complexType name="testSuiteType">
-    <xs:group ref="pathGroup"/>
+    <xs:sequence>
+      <xs:group ref="pathGroup"/>
+      <xs:element name="exclude" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
+    </xs:sequence>
     <xs:attribute name="name" type="xs:string" use="required"/>
   </xs:complexType>
 </xs:schema>
index 39afa90..c348e1f 100644 (file)
@@ -56,6 +56,11 @@ class flexible_table {
     var $uniqueid        = NULL;
     var $attributes      = array();
     var $headers         = array();
+
+    /**
+     * @var string For create header with help icon.
+     */
+    private $helpforheaders = array();
     var $columns         = array();
     var $column_style    = array();
     var $column_class    = array();
@@ -424,6 +429,17 @@ class flexible_table {
         $this->headers = $headers;
     }
 
+    /**
+     * Defines a help icon for the header
+     *
+     * Always use this function if you need to create header with sorting and help icon.
+     *
+     * @param renderable[] $helpicons An array of renderable objects to be used as help icons
+     */
+    public function define_help_for_headers($helpicons) {
+        $this->helpforheaders = $helpicons;
+    }
+
     /**
      * Must be called after table is defined. Use methods above first. Cannot
      * use functions below till after calling this method.
@@ -1246,7 +1262,11 @@ class flexible_table {
                                     $name, $primarysortcolumn === $name, $primarysortorder);
                             $this->headers[$index] .= $sortname . ' / ';
                         }
-                        $this->headers[$index] = substr($this->headers[$index], 0, -3);
+                        $helpicon = '';
+                        if (isset($this->helpforheaders[$index])) {
+                            $helpicon = $OUTPUT->render($this->helpforheaders[$index]);
+                        }
+                        $this->headers[$index] = substr($this->headers[$index], 0, -3). $helpicon;
                     }
                 }
                 break;
@@ -1257,8 +1277,12 @@ class flexible_table {
 
                 default:
                 if ($this->is_sortable($column)) {
+                    $helpicon = '';
+                    if (isset($this->helpforheaders[$index])) {
+                        $helpicon = $OUTPUT->render($this->helpforheaders[$index]);
+                    }
                     $this->headers[$index] = $this->sort_link($this->headers[$index],
-                            $column, $primarysortcolumn == $column, $primarysortorder);
+                            $column, $primarysortcolumn == $column, $primarysortorder) . $helpicon;
                 }
             }
 
@@ -1274,7 +1298,11 @@ class flexible_table {
                 if (is_array($this->column_style[$column])) {
                     $attributes['style'] = $this->make_styles_string($this->column_style[$column]);
                 }
-                $content = $this->headers[$index] . html_writer::tag('div',
+                $helpicon = '';
+                if (isset($this->helpforheaders[$index]) && !$this->is_sortable($column)) {
+                    $helpicon  = $OUTPUT->render($this->helpforheaders[$index]);
+                }
+                $content = $this->headers[$index] . $helpicon . html_writer::tag('div',
                         $icon_hide, array('class' => 'commands'));
             }
             echo html_writer::tag('th', $content, $attributes);
index b2fa587..d7f2ec0 100644 (file)
@@ -287,6 +287,16 @@ class behat_data_generators extends behat_base {
         if (isset($data['gradetype'])) {
             $data['gradetype'] = constant("GRADE_TYPE_" . strtoupper($data['gradetype']));
         }
+
+        if (!empty($data['category']) && !empty($data['courseid'])) {
+            $cat = grade_category::fetch(array('fullname' => $data['category'], 'courseid' => $data['courseid']));
+            if (!$cat) {
+                throw new Exception('Could not resolve category with name "' . $data['category'] . '"');
+            }
+            unset($data['category']);
+            $data['categoryid'] = $cat->id;
+        }
+
         return $data;
     }
 
index 9ae6964..3b57e54 100644 (file)
@@ -141,27 +141,24 @@ class behat_navigation extends behat_base {
      * Click on an entry in the user menu.
      * @Given /^I follow "(?P<nodetext_string>(?:[^"]|\\")*)" in the user menu$/
      *
-     * @throws ExpectationException
      * @param string $nodetext
      * @return bool|void
      */
     public function i_follow_in_the_user_menu($nodetext) {
+        $steps = array();
 
-        // The user menu is broken without javascript.
-        if (!$this->running_javascript()) {
-            throw new DriverException('I follow in the user menu step is not available with Javascript disabled');
+        if ($this->running_javascript()) {
+            // The user menu must be expanded when JS is enabled.
+            $xpath = "//div[@class='usermenu']//a[contains(concat(' ', @class, ' '), ' toggle-display ')]";
+            $steps[] = new When('I click on "'.$xpath.'" "xpath_element"');
         }
 
-        $steps = array();
-
-        $xpath = "//div[@class='usermenu']//a[contains(concat(' ', @class, ' '), ' toggle-display ')]";
+        // Now select the link.
+        // The CSS path is always present, with or without JS.
         $csspath = ".usermenu [data-rel='menu-content']";
-
-        $steps[] = new When('I click on "'.$xpath.'" "xpath_element"');
         $steps[] = new When('I click on "'.$nodetext.'" "link" in the "'.$csspath.'" "css_element"');
 
         return $steps;
-
     }
 
     /**
index cc1ef38..4b932b6 100644 (file)
@@ -74,12 +74,15 @@ class behat_permissions extends behat_base {
         // We don't know the number of overrides so we have to get it to match the option contents.
         $roleoption = $this->find('xpath', '//select[@name="roleid"]/option[contains(.,"' . $this->escape($rolename) . '")]');
 
-        return array(
+        $result = array(
             new Given('I set the field "' . get_string('advancedoverride', 'role') .
-                '" to "' . $this->escape($roleoption->getText()) . '"'),
-            new Given('I fill the capabilities form with the following permissions:', $table),
-            new Given('I press "' . get_string('savechanges') . '"')
-        );
+                '" to "' . $this->escape($roleoption->getText()) . '"'));
+        if (!$this->running_javascript()) {
+            $result[] = new Given('I press "' . get_string('go') . '"');
+        }
+        $result[] = new Given('I fill the capabilities form with the following permissions:', $table);
+        $result[] = new Given('I press "' . get_string('savechanges') . '"');
+        return $result;
     }
 
     /**
@@ -132,7 +135,8 @@ class behat_permissions extends behat_base {
 
             // Here we wait for the element to appear and exception if it does not exist.
             $radio = $this->find('xpath', '//input[@name="' . $capability . '" and @value="' . $permissionvalue . '"]');
-            $radio->click();
+            $field = behat_field_manager::get_field_instance('radio', $radio, $this->getSession());
+            $field->set_value(1);
         }
     }
 
index b787dd8..a842ea7 100644 (file)
@@ -3,6 +3,7 @@ information provided here is intended especially for developers.
 
 === 3.0 ===
 
+* PHPUnit is upgraded to 4.7. Some tests using deprecated assertions etc may need changes to work correctly.
 * get_referer() has been deprecated, please use the get_local_referer function instead.
 * \core\progress\null is renamed to \core\progress\none for improved PHP7 compatibility as null is a reserved word (see MDL-50453).
 * \webservice_xmlrpc_client now respects proxy server settings. If your XMLRPC server is available on your local network and not via your proxy server, you may need to add it to the list of proxy
index 8cc30c3..d2d0772 100644 (file)
@@ -371,6 +371,8 @@ function upgrade_stale_php_files_present() {
     global $CFG;
 
     $someexamplesofremovedfiles = array(
+        // Removed in 3.0.
+        '/mod/lti/grade.php',
         // Removed in 2.9.
         '/lib/timezone.txt',
         // Removed in 2.8.
index d56bffa..9083023 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_message @javascript
+@core @core_message
 Feature: Message history displays correctly
   In order to read messages between two users
   As a user
index b722ca9..9514618 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_message @javascrript
+@core @core_message
 Feature: Manage contacts
   In order to easily access the users I interact more with
   As a user
index b15a393..849e57b 100644 (file)
@@ -4,7 +4,6 @@ Feature: An user can message course participants
   As a teacher
   I need to message them all
 
-  @javascript
   Scenario: An user can message multiple course participants including him/her self
     Given the following "users" exist:
       | username | firstname | lastname | email |
@@ -22,15 +21,16 @@ Feature: An user can message course participants
     And I log in as "teacher1"
     And I follow "Course 1"
     And I follow "Participants"
-    When I click on "input[type='checkbox']" "css_element" in the "Teacher 1" "table_row"
-    And I click on "input[type='checkbox']" "css_element" in the "Student 1" "table_row"
+    When I set the field with xpath "//tr[contains(normalize-space(.), 'Teacher 1')]//input[@type='checkbox']" to "1"
+    And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 1')]//input[@type='checkbox']" to "1"
     And I set the field "With selected users..." to "Send a message"
+    And I press "OK"
     And I set the following fields to these values:
       | messagebody | Here it is, the message content |
     And I press "Preview"
     And I press "Send message"
     And I follow "Messages" in the user menu
-    And I set the field "Message navigation:" to "Recent conversations"
+    And I select "Recent conversations" from the "Message navigation:" singleselect
     Then I should see "Here it is, the message content"
     And I should see "Student 1"
     And I click on "this conversation" "link" in the "//div[@class='singlemessage'][contains(., 'Teacher 1')]" "xpath_element"
index 6ba4ff3..a6b3533 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_message @javascript
+@core @core_message
 Feature: Recent conversations contains my recent conversations
   In order to view my recent conversations
   As a user
@@ -14,7 +14,7 @@ Feature: Recent conversations contains my recent conversations
   Scenario: View that I don't have recent conversations
     Given I log in as "user1"
     And I follow "Messages" in the user menu
-    When I set the field "Message navigation:" to "Recent conversations"
+    When I select "Recent conversations" from the "Message navigation:" singleselect
     Then I should not see "User Two"
     And I should not see "User Three"
 
@@ -23,7 +23,7 @@ Feature: Recent conversations contains my recent conversations
     And I send "Message from user1 to user2" message to "User Two" user
     And I send "Message from user1 to user3" message to "User Three" user
     And I follow "Messages" in the user menu
-    When I set the field "Message navigation:" to "Recent conversations"
+    When I select "Recent conversations" from the "Message navigation:" singleselect
     Then I should see "User Two"
     And I should see "User Three"
     And I should see "Message from user1 to user2"
@@ -31,6 +31,6 @@ Feature: Recent conversations contains my recent conversations
     And I log out
     And I log in as "user2"
     And I follow "Messages" in the user menu
-    And I set the field "Message navigation:" to "Recent conversations"
+    And I select "Recent conversations" from the "Message navigation:" singleselect
     And I should see "Message from user1 to user2"
     And I should not see "Message from user1 to user3"
index 1f3ddf4..0a5e681 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_message @javascript
+@core @core_message
 Feature: Users can search their message history
   In order to read old messages
   As a user
index f9ec9dc..cf3f7dc 100644 (file)
@@ -31,6 +31,7 @@ $string['addtemplate'] = 'Add template';
 $string['advancedsearch'] = 'Advanced search';
 $string['allowcomments'] = 'Allow comments on entries';
 $string['alttext'] = 'Alternative text';
+$string['approvalstatus'] = 'Approval status';
 $string['approve'] = 'Approve';
 $string['approved'] = 'Approved';
 $string['areacontent'] = 'Fields';
index 9bd39ce..2a43b9a 100644 (file)
@@ -552,7 +552,7 @@ function data_generate_default_template(&$data, $template, $recordid=0, $form=fa
     if ($fields = $DB->get_records('data_fields', array('dataid'=>$data->id), 'id')) {
 
         $table = new html_table();
-        $table->attributes['class'] = 'mod-data-default-template';
+        $table->attributes['class'] = 'mod-data-default-template ##approvalstatus##';
         $table->colclasses = array('template-field', 'template-token');
         $table->data = array();
         foreach ($fields as $field) {
@@ -1327,6 +1327,15 @@ function data_print_template($template, $records, $data, $search='', $page=0, $r
             $replacement[] = '';
         }
 
+        $patterns[] = '##approvalstatus##';
+        if (!$data->approval) {
+            $replacement[] = '';
+        } else if ($record->approved) {
+            $replacement[] = 'approved';
+        } else {
+            $replacement[] = 'notapproved';
+        }
+
         $patterns[]='##comments##';
         if (($template == 'listtemplate') && ($data->comments)) {
 
index 3c28c32..61b0883 100644 (file)
@@ -64,6 +64,7 @@
 .mod-data-default-template .template-token {text-align:left;}
 .mod-data-default-template .controls {text-align:center;}
 .mod-data-default-template searchcontrols {text-align:right;}
+.mod-data-default-template.notapproved {background-color:#FFCCCC;}
 #page-mod-data-templates td.save_template,
 #page-mod-data-templates .template_heading {
      text-align:center;
index f2c0388..bcf374d 100644 (file)
@@ -277,6 +277,7 @@ if ($mode != 'csstemplate' and $mode != 'jstemplate') {
         echo '<option value="##timemodified##">'.get_string('timemodified', 'data'). ' - ##timemodified##</option>';
         echo '<option value="##user##">' .get_string('user'). ' - ##user##</option>';
         echo '<option value="##userpicture##">' . get_string('userpic') . ' - ##userpicture##</option>';
+        echo '<option value="##approvalstatus##">' .get_string('approvalstatus', 'data'). ' - ##approvalstatus##</option>';
         if ($mode != 'singletemplate') {
             // more points to single template - not useable there
             echo '<option value="##comments##">' .get_string('comments', 'data'). ' - ##comments##</option>';
index a99fae5..d5fed0b 100644 (file)
@@ -326,7 +326,7 @@ if ($forum->type != 'single'
             echo '<div class="movediscussionoption">';
             $select = new url_select($forummenu, '',
                     array(''=>get_string("movethisdiscussionto", "forum")),
-                    'forummenu', get_string('move'));
+                    'forummenu');
             echo $OUTPUT->render($select);
             echo "</div>";
         }
diff --git a/mod/forum/lang/en/deprecated.txt b/mod/forum/lang/en/deprecated.txt
new file mode 100644 (file)
index 0000000..b437f9f
--- /dev/null
@@ -0,0 +1 @@
+subscribersto,mod_forum
index dd59694..e5f2e4c 100644 (file)
@@ -462,7 +462,7 @@ $string['subscribeenrolledonly'] = 'Sorry, only enrolled users are allowed to su
 $string['subscribed'] = 'Subscribed';
 $string['subscribenone'] = 'Unsubscribe everyone from this forum';
 $string['subscribers'] = 'Subscribers';
-$string['subscribersto'] = 'Subscribers to \'{$a}\'';
+$string['subscriberstowithcount'] = 'Subscribers to "{$a->name}" ({$a->count})';
 $string['subscribestart'] = 'Send me notifications of new posts in this forum';
 $string['subscribestop'] = 'I don\'t want to be notified of new posts in this forum';
 $string['subscription'] = 'Subscription';
@@ -518,3 +518,6 @@ $string['warnformorepost'] = 'Warning! There is more than one discussion in this
 $string['yournewquestion'] = 'Your new question';
 $string['yournewtopic'] = 'Your new discussion topic';
 $string['yourreply'] = 'Your reply';
+
+// Deprecated since Moodle 3.0.
+$string['subscribersto'] = 'Subscribers to "{$a->name}"';
index 07fcdd5..bcfb656 100644 (file)
@@ -124,7 +124,10 @@ class mod_forum_renderer extends plugin_renderer_base {
         } else {
             $cm = $modinfo->instances['forum'][$forum->id];
             $canviewemail = in_array('email', get_extra_user_fields(context_module::instance($cm->id)));
-            $output .= $this->output->heading(get_string("subscribersto","forum", "'".format_string($forum->name)."'"));
+            $strparams = new stdclass();
+            $strparams->name = format_string($forum->name);
+            $strparams->count = count($users);
+            $output .= $this->output->heading(get_string("subscriberstowithcount", "forum", $strparams));
             $table = new html_table();
             $table->cellpadding = 5;
             $table->cellspacing = 5;
index 300d393..b12f8c6 100644 (file)
@@ -37,12 +37,11 @@ Feature: Students can choose from 4 discussion display options and their choice
     And I log in as "student1"
     And I follow "Course 1"
 
-  @javascript
   Scenario: Display replies flat, with oldest first
     Given I reply "Discussion 1" post from "Test forum name" forum with:
       | Subject | Reply 2 to discussion 1 |
       | Message | Discussion contents 1, third message |
-    When I set the field "mode" to "Display replies flat, with oldest first"
+    When I select "Display replies flat, with oldest first" from the "mode" singleselect
     Then I should see "Discussion contents 1, first message" in the "div.firstpost.starter" "css_element"
     And I should see "Discussion contents 1, second message" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ') and not(contains(@class, 'starter'))]" "xpath_element"
     And I reply "Discussion 2" post from "Test forum name" forum with:
@@ -52,12 +51,11 @@ Feature: Students can choose from 4 discussion display options and their choice
     And I should see "Discussion contents 2, first message" in the "div.firstpost.starter" "css_element"
     And I should see "Discussion contents 2, second message" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ') and not(contains(@class, 'starter'))]" "xpath_element"
 
-  @javascript
   Scenario: Display replies flat, with newest first
     Given I reply "Discussion 1" post from "Test forum name" forum with:
       | Subject | Reply 2 to discussion 1 |
       | Message | Discussion contents 1, third message |
-    When I set the field "mode" to "Display replies flat, with newest first"
+    When I select "Display replies flat, with newest first" from the "mode" singleselect
     Then I should see "Discussion contents 1, first message" in the "div.firstpost.starter" "css_element"
     And I should see "Discussion contents 1, third message" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ') and not(contains(@class, 'starter'))]" "xpath_element"
     And I reply "Discussion 2" post from "Test forum name" forum with:
@@ -67,11 +65,10 @@ Feature: Students can choose from 4 discussion display options and their choice
     And I should see "Discussion contents 2, first message" in the "div.firstpost.starter" "css_element"
     And I should see "Discussion contents 2, third message" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ') and not(contains(@class, 'starter'))]" "xpath_element"
 
-  @javascript
   Scenario: Display replies in threaded form
     Given I follow "Test forum name"
     And I follow "Discussion 1"
-    When I set the field "mode" to "Display replies in threaded form"
+    When I select "Display replies in threaded form" from the "mode" singleselect
     Then I should see "Discussion contents 1, first message"
     And I should see "Reply 1 to discussion 1" in the "span.forumthread" "css_element"
     And I follow "Test forum name"
@@ -80,11 +77,10 @@ Feature: Students can choose from 4 discussion display options and their choice
     And I should see "Discussion contents 2, first message"
     And I should see "Reply 1 to discussion 2" in the "span.forumthread" "css_element"
 
-  @javascript
   Scenario: Display replies in nested form
     Given I follow "Test forum name"
     And I follow "Discussion 1"
-    When I set the field "mode" to "Display replies in nested form"
+    When I select "Display replies in nested form" from the "mode" singleselect
     Then I should see "Discussion contents 1, first message" in the "div.firstpost.starter" "css_element"
     And I should see "Discussion contents 1, second message" in the "div.indent div.forumpost" "css_element"
     And I follow "Test forum name"
index a84d660..36a0c79 100644 (file)
@@ -6,39 +6,36 @@ Feature: A user can navigate to previous and next discussions
 
   Background:
     Given the following "users" exist:
-      | username | firstname | lastname | email |
-      | student1 | Student | 1 | student1@example.com |
-      | student2 | Student | 2 | student2@example.com |
+      | username | firstname | lastname | email                 |
+      | teacher1 | Teacher   | 1        | teacher1@example.com  |
+      | student1 | Student   | 1        | student1@example.com  |
+      | student2 | Student   | 2        | student2@example.com  |
     And the following "courses" exist:
-      | fullname | shortname | category |
-      | Course 1 | C1 | 0 |
+      | fullname | shortname  | category  |
+      | Course 1 | C1         | 0         |
     And the following "course enrolments" exist:
       | user | course | role |
+      | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
       | student2 | C1 | student |
-    And I log in as "admin"
-    And I am on site homepage
-    And I follow "Course 1"
-    And I navigate to "Groups" node in "Users"
-    And I press "Create group"
-    And I set the following fields to these values:
-      | Group name | Group 1 |
-    And I press "Save changes"
-    And I press "Create group"
-    And I set the following fields to these values:
-      | Group name | Group 2 |
-    And I press "Save changes"
-    And I add "Student 1" user to "Group 1" group members
-    And I add "Student 2" user to "Group 2" group members
-    And I am on site homepage
-    And I follow "Course 1"
-    And I turn editing mode on
+    And the following "groups" exist:
+      | name | course | idnumber |
+      | Group 1 | C1 | G1 |
+      | Group 2 | C1 | G2 |
+    And the following "group members" exist:
+      | user | group |
+      | teacher1 | G1 |
+      | teacher1 | G2 |
+      | student1 | G1 |
+      | student2 | G2 |
 
-  @javascript
   Scenario: A user can navigate between discussions
-    Given I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Test forum name |
-      | Description | Test forum description |
+    Given the following "activities" exist:
+      | activity   | name                   | intro             | course | idnumber     | groupmode |
+      | forum      | Test forum name        | Test forum name   | C1     | forum        | 0         |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test forum name"
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Discussion 1 |
       | Message | Test post message |
@@ -61,6 +58,7 @@ Feature: A user can navigate to previous and next discussions
     And I set the following fields to these values:
       | Message | Answer to discussion |
     And I press "Post to forum"
+    And I wait to be redirected
     And I should not see "Discussion 2"
     And I should see "Discussion 3"
     And I follow "Discussion 3"
@@ -70,12 +68,13 @@ Feature: A user can navigate to previous and next discussions
     And I should not see "Discussion 1"
     And I should see "Discussion 3"
 
-  @javascript
   Scenario: A user can navigate between discussions with visible groups
-    Given I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Test forum name |
-      | Description | Test forum description |
-      | Group mode | Visible groups |
+    Given the following "activities" exist:
+      | activity   | name                   | intro             | course | idnumber     | groupmode |
+      | forum      | Test forum name        | Test forum name   | C1     | forum        | 2         |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test forum name"
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Discussion 1 Group 0 |
       | Message | Test post message |
@@ -102,7 +101,7 @@ Feature: A user can navigate to previous and next discussions
     When I log in as "student1"
     And I follow "Course 1"
     And I follow "Test forum name"
-    And I set the field "Visible groups" to "All participants"
+    And I select "All participants" from the "Visible groups" singleselect
     And I follow "Discussion 1 Group 0"
     Then I should see "Discussion 2 Group 0"
     And I should not see "Group 1"
@@ -118,7 +117,7 @@ Feature: A user can navigate to previous and next discussions
     And I should see "Discussion 2 Group 1"
     And I should see "Discussion 2 Group 2"
     And I follow "Test forum name"
-    And I set the field "Visible groups" to "Group 1"
+    And I select "Group 1" from the "Visible groups" singleselect
     And I follow "Discussion 1 Group 1"
     Then I should see "Discussion 2 Group 0"
     And I should see "Discussion 2 Group 1"
@@ -126,12 +125,13 @@ Feature: A user can navigate to previous and next discussions
     And I should see "Discussion 1 Group 1"
     And I should not see "Group 2"
 
-  @javascript
   Scenario: A user can navigate between discussions with separate groups
-    Given I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Test forum name |
-      | Description | Test forum description |
-      | Group mode | Separate groups |
+    Given the following "activities" exist:
+      | activity   | name                   | intro             | course | idnumber     | groupmode |
+      | forum      | Test forum name        | Test forum name   | C1     | forum        | 1         |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Test forum name"
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Discussion 1 Group 0 |
       | Message | Test post message |
index e5aecd2..8f42c7f 100644 (file)
@@ -14,30 +14,19 @@ Feature: Students can edit or delete their forum posts within a set time limit
     And the following "course enrolments" exist:
       | user | course | role |
       | student1 | C1 | student |
-    And I log in as "admin"
-    And I expand "Site administration" node
-    And I expand "Security" node
-    And I follow "Site policies"
-    And I set the field "Maximum time to edit posts" to "1 minutes"
-    And I press "Save changes"
-    And I am on site homepage
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Test forum name |
-      | Forum type | Standard forum for general use |
-      | Description | Test forum description |
-    And I log out
-    And I follow "Course 1"
+    And the following "activities" exist:
+      | activity   | name                   | intro                   | course  | idnumber  |
+      | forum      | Test forum name        | Test forum description  | C1      | forum     |
     And I log in as "student1"
+    And I follow "Course 1"
     And I add a new discussion to "Test forum name" forum with:
       | Subject | Forum post subject |
       | Message | This is the body |
 
   Scenario: Edit forum post
-    When I follow "Forum post subject"
+    Given I follow "Forum post subject"
     And I follow "Edit"
-    And I set the following fields to these values:
+    When I set the following fields to these values:
       | Subject | Edited post subject |
       | Message | Edited post body |
     And I press "Save changes"
@@ -45,16 +34,32 @@ Feature: Students can edit or delete their forum posts within a set time limit
     Then I should see "Edited post subject"
     And I should see "Edited post body"
 
-  @javascript
   Scenario: Delete forum post
-    When I follow "Forum post subject"
-    And I follow "Delete"
+    Given I follow "Forum post subject"
+    When I follow "Delete"
     And I press "Continue"
     Then I should not see "Forum post subject"
 
   @javascript
   Scenario: Time limit expires
-    When I wait "70" seconds
+    Given I log out
+    And I log in as "admin"
+    And I expand "Site administration" node
+    And I expand "Security" node
+    And I follow "Site policies"
+    And I set the field "Maximum time to edit posts" to "1 minutes"
+    And I press "Save changes"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Forum type | Standard forum for general use |
+      | Description | Test forum description |
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    When I wait "61" seconds
     And I follow "Forum post subject"
     Then I should not see "Edit" in the "region-main" "region"
     And I should not see "Delete" in the "region-main" "region"
index 3cc1dfd..42db613 100644 (file)
@@ -32,7 +32,6 @@ Feature: Teachers can edit or delete any forum post
       | Subject | Student post subject |
       | Message | Student post message |
 
-  @javascript
   Scenario: A teacher can delete another user's posts
     Given I log out
     And I log in as "teacher1"
@@ -44,7 +43,6 @@ Feature: Teachers can edit or delete any forum post
     Then I should not see "Student post subject"
     And I should not see "Student post message"
 
-  @javascript
   Scenario: A teacher can edit another user's posts
     Given I log out
     And I log in as "teacher1"
@@ -59,7 +57,6 @@ Feature: Teachers can edit or delete any forum post
     Then I should see "Edited student subject"
     And I should see "Edited by Teacher 1 - original submission"
 
-  @javascript
   Scenario: A student can't edit or delete another user's posts
     When I follow "Teacher post subject"
     Then I should not see "Edit" in the "//div[contains(concat(' ', normalize-space(@class), ' '), ' forumpost ')][contains(., 'Teacher post subject')]" "xpath_element"
index 45a474a..c5fd76d 100644 (file)
@@ -1,4 +1,4 @@
-@mod @mod_forum @javascript
+@mod @mod_forum
 Feature: A user can view their posts and discussions
   In order to ensure a user can view their posts and discussions
   As a student
@@ -7,23 +7,16 @@ Feature: A user can view their posts and discussions
   Scenario: View the student's posts and discussions
     Given the following "users" exist:
       | username | firstname | lastname | email |
-      | teacher1 | Teacher | 1 | teacher1@example.com |
       | student1 | Student | 1 | student1@example.com |
     And the following "courses" exist:
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
     And the following "course enrolments" exist:
       | user | course | role |
-      | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
-    And I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Test forum name |
-      | Forum type | Standard forum for general use |
-      | Description | Test forum description |
-    And I log out
+    And the following "activities" exist:
+      | activity   | name                   | intro       | course | idnumber     | groupmode |
+      | forum      | Test forum name        | Test forum  | C1     | forum        | 0         |
     And I log in as "student1"
     And I follow "Course 1"
     And I add a new discussion to "Test forum name" forum with:
index e399c48..cfbe9e9 100644 (file)
@@ -55,37 +55,13 @@ Background:
       | grouping | group |
       | G1       | C2G1 |
       | G1       | C2G2 |
-    And I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Separate group forum |
-      | Forum type | Standard forum for general use |
-      | Description | Standard forum description |
-      | Group mode | Separate groups |
-    And I add a "Forum" to section "2" and I fill the form with:
-      | Forum name | Visible group forum |
-      | Forum type | Standard forum for general use |
-      | Description | Standard forum description |
-      | Group mode | Visible groups |
-    And I add a "Forum" to section "3" and I fill the form with:
-      | Forum name | No group forum |
-      | Forum type | Standard forum for general use |
-      | Description | Standard forum description |
-      | Group mode | No groups |
-    And I log out
-    And I log in as "teacher1"
-    And I follow "Course 2"
-    And I turn editing mode on
-    And I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Groupings forum |
-      | Forum type | Standard forum for general use |
-      | Description | Standard forum description |
-      | Group mode | Separate groups |
-      | Grouping | G1 |
-    And I log out
+    And the following "activities" exist:
+      | activity   | name                   | intro             | course | idnumber     | groupmode | grouping |
+      | forum      | No group forum         | Test forum name   | C1     | forum        | 0         |          |
+      | forum      | Separate group forum   | Test forum name   | C1     | forum        | 1         |          |
+      | forum      | Visible group forum    | Test forum name   | C1     | forum        | 2         |          |
+      | forum      | Groupings forum        | Test forum name   | C2     | forum        | 1         | G1       |
 
-  @javascript
   Scenario: Teacher is able to post a copy of a message to all groups in a separate group forum
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -109,7 +85,6 @@ Background:
     And I follow "Separate group forum"
     And I should see "Discussion 1"
 
-  @javascript
   Scenario: Teacher is able to post a copy of a message to all groups in a visible group forum
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -133,7 +108,6 @@ Background:
     And I follow "Visible group forum"
     And I should see "Discussion 1"
 
-  @javascript
   Scenario: Teacher is unable to post a copy of a message to all groups in a no group forum
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -141,7 +115,6 @@ Background:
     And I press "Add a new discussion topic"
     Then I should not see "Post a copy to all groups"
 
-  @javascript
   Scenario: Posts to all groups that have groupings should only display within the grouping and not to other groups
     Given I log in as "teacher1"
     And I follow "Course 2"
index 3810bdf..86d4e08 100644 (file)
@@ -56,7 +56,6 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
     And the "Group" select box should contain "Group B"
     And I should see "Post a copy to all groups"
 
-  @javascript
   Scenario: Teacher in all groups but without accessallgroups can only post in their groups
     And I log in as "admin"
     And I set the following system permissions of "Non-editing teacher" role:
@@ -71,7 +70,6 @@ Feature: Posting to all groups in a separate group discussion is restricted to u
     And the "Group" select box should contain "Group B"
     And I should see "Post a copy to all groups"
 
-  @javascript
   Scenario: Teacher in some groups and without accessallgroups can only post in their groups
     And I log in as "admin"
     And I set the following system permissions of "Non-editing teacher" role:
index 9699a30..7fda1f2 100644 (file)
@@ -66,7 +66,6 @@ Feature: Posting to groups in a separate group discussion when restricted to gro
     And the "Group" select box should contain "G2G1"
     And I should see "Post a copy to all groups"
 
-  @javascript
   Scenario: Teacher in all groups but without accessallgroups can post in either group but not to All Participants
     And I log in as "admin"
     And I set the following system permissions of "Non-editing teacher" role:
index cd6d196..ca4f906 100644 (file)
@@ -16,23 +16,18 @@ Feature: Single simple forum discussion type
       | user | course | role |
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
-    And I log in as "teacher1"
-    And I follow "Course 1"
-    And I turn editing mode on
-    And I add a "Forum" to section "1" and I fill the form with:
-      | Forum name | Single discussion forum name |
-      | Forum type | A single simple discussion |
-      | Description | Single discussion forum description |
+    And the following "activities" exist:
+      | activity   | name                         | intro                               | type    | course | idnumber     |
+      | forum      | Single discussion forum name | Single discussion forum description | single  | C1     | forum        |
 
-  @javascript
   Scenario: Teacher can start the single simple discussion
+    Given I log in as "teacher1"
+    And I follow "Course 1"
     When I follow "Single discussion forum name"
     Then I should see "Single discussion forum description" in the "div.firstpost.starter" "css_element"
     And I should not see "Add a new discussion topic"
 
-  @javascript
   Scenario: Student can not add more discussions
-    Given I log out
     And I log in as "student1"
     And I follow "Course 1"
     When I reply "Single discussion forum name" post from "Single discussion forum name" forum with:
index 965d061..422e4b8 100644 (file)
@@ -21,7 +21,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Course 1"
     And I turn editing mode on
 
-  @javascript
   Scenario: Tracking forum posts off
     Given I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
@@ -38,7 +37,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Test forum name"
     And I should not see "Track unread posts"
 
-  @javascript
   Scenario: Tracking forum posts optional with user tracking on
     Given I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
@@ -64,7 +62,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Course 1"
     And I should not see "1 unread post"
 
-  @javascript
   Scenario: Tracking forum posts optional with user tracking off
     Given I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
@@ -81,7 +78,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Test forum name"
     And I should not see "Track unread posts"
 
-  @javascript
   Scenario: Tracking forum posts forced with user tracking on
     Given the following config values are set as admin:
       | forum_allowforcedreadtracking | 1 |
@@ -105,7 +101,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Course 1"
     And I should not see "1 unread post"
 
-  @javascript
   Scenario: Tracking forum posts forced with user tracking off
     Given the following config values are set as admin:
       | forum_allowforcedreadtracking | 1 |
@@ -129,7 +124,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Course 1"
     And I should not see "1 unread post"
 
-  @javascript
   Scenario: Tracking forum posts forced (with force disabled) with user tracking on
     Given the following config values are set as admin:
       | forum_allowforcedreadtracking | 1 |
@@ -161,7 +155,6 @@ Feature: A teacher can set one of 3 possible options for tracking read forum pos
     And I follow "Course 1"
     And I should not see "1 unread post"
 
-  @javascript
   Scenario: Tracking forum posts forced (with force disabled) with user tracking off
     Given the following config values are set as admin:
       | forum_allowforcedreadtracking | 1 |
index d5dcb9b..65eeb3d 100644 (file)
@@ -34,7 +34,6 @@ Feature: Practice mode in a lesson activity
             | id_answer_editor_1 | False |
         And I press "Save page"
 
-    @javascript
     Scenario: Non-practice lesson records grades in the gradebook
         Given I follow "Test lesson name"
         And I navigate to "Edit settings" node in "Lesson administration"
@@ -55,7 +54,6 @@ Feature: Practice mode in a lesson activity
         And I follow "Course 1"
         And I should see "Non-practice lesson"
 
-    @javascript
     Scenario: Practice lesson doesn't record grades in the gradebook
         Given I follow "Test lesson name"
         And I navigate to "Edit settings" node in "Lesson administration"
@@ -76,7 +74,6 @@ Feature: Practice mode in a lesson activity
         And I follow "Course 1"
         And I should not see "Practice lesson"
 
-    @javascript
     Scenario: Practice lesson with scale doesn't record grades in the gradebook
         Given I follow "Test lesson name"
         And I navigate to "Edit settings" node in "Lesson administration"
diff --git a/mod/lti/grade.php b/mod/lti/grade.php
deleted file mode 100644 (file)
index 6eec41f..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-//
-// This file is part of BasicLTI4Moodle
-//
-// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
-// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
-// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
-// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
-// are already supporting or going to support BasicLTI. This project Implements the consumer
-// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
-// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
-// at the GESSI research group at UPC.
-// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
-// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
-// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
-//
-// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
-// of the Universitat Politecnica de Catalunya http://www.upc.edu
-// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
-
-/**
- * This file contains submissions-specific code for the lti module
- *
- * @package mod_lti
- * @copyright  2009 Marc Alier, Jordi Piguillem, Nikolas Galanis
- *  marc.alier@upc.edu
- * @copyright  2009 Universitat Politecnica de Catalunya http://www.upc.edu
- * @author     Marc Alier
- * @author     Jordi Piguillem
- * @author     Nikolas Galanis
- * @author     Chris Scribner
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @deprecated since 2.8
- */
-
-require_once(dirname(dirname(__DIR__)).'/config.php');
-
-$id = optional_param('id', 0, PARAM_INT);
-$l  = optional_param('l', 0, PARAM_INT);
-
-if ($l) {
-    $lti = $DB->get_record('lti', array('id' => $l), '*', MUST_EXIST);
-    $cm = get_coursemodule_from_instance('lti', $lti->id, $lti->course, false, MUST_EXIST);
-} else {
-    $cm = get_coursemodule_from_id('lti', $id, 0, false, MUST_EXIST);
-    $lti = $DB->get_record('lti', array('id' => $cm->instance), '*', MUST_EXIST);
-}
-
-$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
-
-require_login($course, false, $cm);
-require_capability('mod/lti:view', context_module::instance($cm->id));
-
-debugging('This file has been deprecated.  Links to this file should automatically '.
-    'fallback to /mod/lti/view.php once this file has been deleted.', DEBUG_DEVELOPER);
-
-redirect(new moodle_url('/mod/lti/view.php', array('l' => $lti->id)));
index 5e9c313..ae723ee 100644 (file)
@@ -489,20 +489,6 @@ function lti_grade_item_delete($basiclti) {
     return grade_update('mod/lti', $basiclti->course, 'mod', 'lti', $basiclti->id, 0, null, array('deleted' => 1));
 }
 
-function lti_extend_settings_navigation($settings, $parentnode) {
-    global $PAGE;
-
-    if (has_capability('mod/lti:manage', context_module::instance($PAGE->cm->id))) {
-        $keys = $parentnode->get_children_key_list();
-
-        $node = navigation_node::create('Submissions',
-            new moodle_url('/mod/lti/grade.php', array('id' => $PAGE->cm->id)),
-            navigation_node::TYPE_SETTING, null, 'mod_lti_submissions');
-
-        $parentnode->add_node($node, $keys[1]);
-    }
-}
-
 /**
  * Log post actions
  *
index 544959c..780086f 100644 (file)
@@ -73,6 +73,9 @@ if (!empty($id)) {
     }
 } else {
     $type = new stdClass();
+    // Assign a default empty value for the lti_icon.
+    $type->lti_icon = '';
+    $type->lti_secureicon = '';
 }
 
 $pageurl = new moodle_url('/mod/lti/typessettings.php');
index 7c137f5..c648195 100644 (file)
@@ -536,7 +536,7 @@ table.quizreviewsummary td.cell {
 #page-mod-quiz-edit .totalpoints {
     display: block;
     float: right;
-    margin: -2.5em 0 0;
+    margin: -2.85em 0 0;
     padding: .2em;
 }
 #page-mod-quiz-edit.dir-rtl .maxgrade,
@@ -1049,3 +1049,15 @@ table#categoryquestions {
         page-break-inside: avoid;
     }
 }
+/* Ajustments for mobile devices */
+@media only screen and (max-width:565px) {
+    #page-mod-quiz-edit .rpcontainerclass {
+        margin-top: 3em;
+    }
+    #page-mod-quiz-edit .maxgrade {
+        margin-top: 0.1em;
+    }
+    #page-mod-quiz-edit .statusbar {
+        padding: 0;
+    }
+}
index 7472137..f3ba525 100644 (file)
@@ -1303,7 +1303,7 @@ function scorm_get_attempt_count($userid, $scorm, $returnobjects = false, $ignor
  * @return boolean - debugging true/false
  */
 function scorm_debugging($scorm) {
-    global $CFG, $USER;
+    global $USER;
     $cfgscorm = get_config('scorm');
 
     if (!$cfgscorm->allowapidebug) {
@@ -1315,9 +1315,11 @@ function scorm_debugging($scorm) {
     if (!preg_match('/^[\w\s\*\.\?\+\:\_\\\]+$/', $test)) {
         return false;
     }
-    $res = false;
-    eval('$res = preg_match(\'/^'.$test.'/\', $identifier) ? true : false;');
-    return $res;
+
+    if (preg_match('/^'.$test.'/', $identifier)) {
+        return true;
+    }
+    return false;
 }
 
 /**
index 9fe500d..e553500 100644 (file)
@@ -1187,17 +1187,6 @@ function workshop_grade_item_category_update($workshop) {
                     $gradeitem->set_parent($workshop->gradinggradecategory);
                 }
             }
-            if (!empty($workshop->add)) {
-                $gradecategory = $gradeitem->get_parent_category();
-                if (grade_category::aggregation_uses_aggregationcoef($gradecategory->aggregation)) {
-                    if ($gradecategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
-                        $gradeitem->aggregationcoef = 1;
-                    } else {
-                        $gradeitem->aggregationcoef = 0;
-                    }
-                    $gradeitem->update();
-                }
-            }
         }
     }
 }
index 618bf8e..72c0105 100644 (file)
@@ -25,7 +25,6 @@ Feature: Restrict which blocks can be added to Dashboard
     And the "Add a block" select box should contain "HTML"
     And the "Add a block" select box should contain "Tags"
 
-  @javascript
   Scenario: Remove the ability to add the comments block to Dashboard
     When I log in as "admin"
     And I set the following system permissions of "Authenticated user" role:
index f5fc51f..6e1b95f 100644 (file)
@@ -13,7 +13,6 @@
         stopOnFailure="false"
         stopOnIncomplete="false"
         stopOnSkipped="false"
-        strict="false"
         printerClass="Hint_ResultPrinter"
         testSuiteLoaderClass="phpunit_autoloader"
         >
index 4ddff96..8bcada5 100644 (file)
@@ -198,7 +198,7 @@ class qformat_xml extends qformat_default {
      * @return object question object
      */
     public function import_headers($question) {
-        global $CFG, $USER;
+        global $USER;
 
         // This routine initialises the question object.
         $qo = $this->defaultquestion();
@@ -255,14 +255,7 @@ class qformat_xml extends qformat_default {
         }
 
         // Read the question tags.
-        if (!empty($CFG->usetags) && array_key_exists('tags', $question['#'])
-                && !empty($question['#']['tags'][0]['#']['tag'])) {
-            require_once($CFG->dirroot.'/tag/lib.php');
-            $qo->tags = array();
-            foreach ($question['#']['tags'][0]['#']['tag'] as $tagdata) {
-                $qo->tags[] = $this->getpath($tagdata, array('#', 'text', 0, '#'), '', true);
-            }
-        }
+        $this->import_question_tags($qo, $question);
 
         return $qo;
     }
@@ -380,6 +373,26 @@ class qformat_xml extends qformat_default {
         }
     }
 
+    /**
+     * Import all the question tags
+     *
+     * @param object $qo the question data that is being constructed.
+     * @param array $questionxml The xml representing the question.
+     * @return array of objects representing the tags in the file.
+     */
+    public function import_question_tags($qo, $questionxml) {
+        global $CFG;
+
+        if (!empty($CFG->usetags) && array_key_exists('tags', $questionxml['#'])
+                && !empty($questionxml['#']['tags'][0]['#']['tag'])) {
+            require_once($CFG->dirroot.'/tag/lib.php');
+            $qo->tags = array();
+            foreach ($questionxml['#']['tags'][0]['#']['tag'] as $tagdata) {
+                $qo->tags[] = $this->getpath($tagdata, array('#', 'text', 0, '#'), '', true);
+            }
+        }
+    }
+
     /**
      * Import files from a node in the XML.
      * @param array $xml an array of <file> nodes from the the parsed XML.
@@ -505,6 +518,7 @@ class qformat_xml extends qformat_default {
         }
 
         $this->import_hints($qo, $question, true, false, $this->get_format($qo->questiontextformat));
+        $this->import_question_tags($qo, $question);
 
         return $qo;
     }
index fbaa93d..acd73bb 100644 (file)
@@ -29,6 +29,7 @@ global $CFG;
 require_once($CFG->libdir . '/questionlib.php');
 require_once($CFG->dirroot . '/question/format/xml/format.php');
 require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+require_once($CFG->dirroot . '/tag/lib.php');
 
 
 /**
@@ -302,6 +303,10 @@ END;
     <defaultgrade>0</defaultgrade>
     <penalty>0</penalty>
     <hidden>0</hidden>
+    <tags>
+      <tag><text>tagDescription</text></tag>
+      <tag><text>tagTest</text></tag>
+    </tags>
   </question>';
         $xmldata = xmlize($xml);
 
@@ -317,6 +322,7 @@ END;
         $expectedq->defaultmark = 0;
         $expectedq->length = 0;
         $expectedq->penalty = 0;
+        $expectedq->tags = array('tagDescription', 'tagTest');
 
         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
     }
@@ -373,6 +379,11 @@ END;
     <defaultgrade>1</defaultgrade>
     <penalty>0</penalty>
     <hidden>0</hidden>
+    <tags>
+      <tag><text>tagEssay</text></tag>
+      <tag><text>tagEssay20</text></tag>
+      <tag><text>tagTest</text></tag>
+    </tags>
   </question>';
         $xmldata = xmlize($xml);
 
@@ -397,6 +408,7 @@ END;
         $expectedq->graderinfo['format'] = FORMAT_MOODLE;
         $expectedq->responsetemplate['text'] = '';
         $expectedq->responsetemplate['format'] = FORMAT_MOODLE;
+        $expectedq->tags = array('tagEssay', 'tagEssay20', 'tagTest');
 
         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
     }
@@ -426,6 +438,11 @@ END;
     <responsetemplate format="html">
         <text><![CDATA[<p>Here is something <b>really</b> interesting.</p>]]></text>
     </responsetemplate>
+    <tags>
+      <tag><text>tagEssay</text></tag>
+      <tag><text>tagEssay21</text></tag>
+      <tag><text>tagTest</text></tag>
+    </tags>
   </question>';
         $xmldata = xmlize($xml);
 
@@ -450,6 +467,7 @@ END;
         $expectedq->graderinfo['format'] = FORMAT_HTML;
         $expectedq->responsetemplate['text'] = '<p>Here is something <b>really</b> interesting.</p>';
         $expectedq->responsetemplate['format'] = FORMAT_HTML;
+        $expectedq->tags = array('tagEssay', 'tagEssay21', 'tagTest');
 
         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
     }
@@ -571,6 +589,10 @@ END;
       <shownumcorrect />
       <clearwrong />
     </hint>
+    <tags>
+      <tag><text>tagMatching</text></tag>
+      <tag><text>tagTest</text></tag>
+    </tags>
   </question>';
         $xmldata = xmlize($xml);
 
@@ -607,6 +629,7 @@ END;
         );
         $expectedq->hintshownumcorrect = array(true, true);
         $expectedq->hintclearwrong = array(false, true);
+        $expectedq->tags = array('tagMatching', 'tagTest');
 
         $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
     }
@@ -1343,6 +1366,10 @@ END;
     <hint format="html">
       <text>Hint 2</text>
     </hint>
+    <tags>
+      <tag><text>tagCloze</text></tag>
+      <tag><text>tagTest</text></tag>
+    </tags>
   </question>
 ';
         $xmldata = xmlize($xml);
@@ -1419,6 +1446,7 @@ END;
             1 => $sa,
             2 => $mc,
         );
+        $expectedqa->tags = array('tagCloze', 'tagTest');
 
         $this->assertEquals($expectedqa->hint, $q->hint);
         $this->assertEquals($expectedqa->options->questions[1], $q->options->questions[1]);
index 912945e..06026eb 100644 (file)
@@ -28,7 +28,6 @@ Feature: View the user page for the outline report
       | Description | URL description |
       | External URL | http://www.google.com |
 
-  @javascript
   Scenario: View the user page when only the legacy log reader is enabled
     Given I navigate to "Manage log stores" node in "Site administration > Plugins > Logging"
     And I click on "Enable" "link" in the "Legacy log" "table_row"
@@ -48,20 +47,17 @@ Feature: View the user page for the outline report
     And I follow "URL name"
     And I follow "URL name"
     And I follow "URL name"
-    And I click on "Student 1" "link"
-    And I follow "Profile" in the open menu
+    And I follow "Profile" in the user menu
     And I follow "Course 1"
     When I follow "Outline report"
     Then I should see "4 views" in the "Folder name" "table_row"
     And I should see "3 views" in the "URL name" "table_row"
-    And I click on "Student 1" "link"
-    And I follow "Profile" in the open menu
+    And I follow "Profile" in the user menu
     And I follow "Course 1"
-    When I follow "Complete report"
+    And I follow "Complete report"
     And I should see "4 views"
     And I should see "3 views"
 
-  @javascript
   Scenario: View the user page when only the standard log reader is enabled
     Given I navigate to "Manage log stores" node in "Site administration > Plugins > Logging"
     And "Enable" "link" should exist in the "Legacy log" "table_row"
@@ -90,7 +86,6 @@ Feature: View the user page for the outline report
     And I should see "4 views"
     And I should see "3 views"
 
-  @javascript
   Scenario: View the user page when both the standard and legacy log readers are enabled
     Given I navigate to "Manage log stores" node in "Site administration > Plugins > Logging"
     And I click on "Enable" "link" in the "Legacy log" "table_row"
index 5208c1f..238bef2 100644 (file)
@@ -1299,18 +1299,26 @@ M.core_filepicker.init = function(Y, options) {
             var client_id = this.options.client_id;
             var fpid = "filepicker-"+ client_id;
             var labelid = 'fp-dialog-label_'+ client_id;
+            var width = 873;
+            var draggable = true;
             this.fpnode = Y.Node.createWithFilesSkin(M.core_filepicker.templates.generallayout).
                 set('id', 'filepicker-'+client_id).set('aria-labelledby', labelid);
+
+            if (this.in_iframe()) {
+                width = Math.floor(window.innerWidth * 0.95);
+                draggable = false;
+            }
+
             this.mainui = new M.core.dialogue({
                 extraClasses : ['filepicker'],
-                draggable    : true,
+                draggable    : draggable,
                 bodyContent  : this.fpnode,
                 headerContent: '<h3 id="'+ labelid +'">'+ M.util.get_string('filepicker', 'repository') +'</h3>',
                 centered     : true,
                 modal        : true,
                 visible      : false,
-                width        : '873px',
-                responsiveWidth : 873,
+                width        : width+'px',
+                responsiveWidth : 768,
                 height       : '558px',
                 zIndex       : this.options.zIndex
             });
@@ -1947,6 +1955,10 @@ M.core_filepicker.init = function(Y, options) {
                 M.util.set_user_preference('filepicker_' + name, value);
                 this.options.userprefs[name] = value;
             }
+        },
+        in_iframe: function () {
+            // If we're not the top window then we're in an iFrame
+            return window.self !== window.top;
         }
     });
     var loading = Y.one('#filepicker-loading-'+options.client_id);
index 338f6d1..26c38fa 100644 (file)
 }
 
 /** Overide for RTL layout **/
+.dir-rtl .block .header h2 {
+    padding: .2em .2em 0 0;
+}
+
 .dir-rtl .block .header,
 .dir-rtl .block h2.header {text-align:right;}
 .dir-rtl .block .header .block_action { float: left; margin-left: 4px; margin-left: 0;}
index 670f3b0..ead5736 100644 (file)
@@ -461,9 +461,9 @@ a.ygtvspacer:hover {color:transparent;text-decoration:none;}
 /**
  * Responsive styles for the filepicker
  */
-@media (max-width:873px) {
+@media (max-width:767px) {
     .file-picker .fp-repo-area {width:100%;height:auto;max-height:220px;y-scroll:auto;float:none;border:0px;}
-    .file-picker .fp-repo-items {width:100%;float:none;}
+    .file-picker .fp-repo-items {width:100%;float:none;margin-left:0;}
     .file-picker .fp-login-form .fp-login-input .label {text-align:left;}
     .dir-rtl .file-picker .fp-login-form .fp-login-input .label {text-align:right;}
     .file-picker .fp-content form td {display:block;width:100%;text-align:left;}
index ef54613..def82db 100644 (file)
@@ -52,7 +52,7 @@
     }
 }
 
-@media (max-width: 873px) {
+@media (max-width: 767px) {
     .file-picker .fp-repo-area {
         width: 100%;
         height: auto;
@@ -64,6 +64,7 @@
     .file-picker .fp-repo-items {
         width: 100%;
         float: none;
+        margin-left: 0;
     }
     .file-picker .fp-login-form .fp-login-input label {
         text-align: left;
index e165ac4..09dc52c 100644 (file)
@@ -6,4 +6,4 @@
  * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Designed and built with all the love in the world @twitter by @mdo and @fat.
- */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}.img-responsive{width:auto\9;height:auto;max-width:100%;-ms-interpolation-mode:bicubic}img{vertical-align:middle;border:0}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#0070a8;text-decoration:none}a:hover,a:focus{color:#003d5c;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:180px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:200px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:180px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:200px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:200px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:200px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered&nb