Merge branch 'MDL-57059-master' of git://github.com/lameze/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 23 Nov 2016 06:15:42 +0000 (14:15 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 23 Nov 2016 06:15:42 +0000 (14:15 +0800)
61 files changed:
admin/settings/grades.php
grade/export/txt/tests/behat/export.feature
grade/grading/form/guide/renderer.php
grade/report/grader/tests/behat/switch_views.feature
grade/report/singleview/tests/behat/singleview.feature
grade/report/user/tests/behat/view_usereport.feature
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_calculated_grade_items.feature
grade/tests/behat/grade_calculated_grade_items_20150627.feature
grade/tests/behat/grade_calculated_weights.feature
grade/tests/behat/grade_contribution_with_extra_credit.feature
grade/tests/behat/grade_hidden_items.feature
grade/tests/behat/grade_letter_boundary.feature
grade/tests/behat/grade_letter_boundary_20160518.feature
grade/tests/behat/grade_natural_exclude_empty.feature
grade/tests/behat/grade_natural_exclude_empty_20150619.feature
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/behat/grade_natural_normalisation_20150619.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/behat/grade_view.feature
lang/en/message.php
lib/blocklib.php
lib/db/install.xml [changed mode: 0644->0755]
lib/db/upgrade.php
lib/editor/atto/plugins/media/lang/en/atto_media.php
lib/editor/atto/plugins/media/lib.php
lib/editor/atto/plugins/media/styles.css [new file with mode: 0644]
lib/editor/atto/plugins/media/styles_clean.css [new file with mode: 0644]
lib/editor/atto/plugins/media/tests/behat/media.feature
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js
lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js
lib/editor/atto/plugins/media/yui/src/button/js/button.js
lib/editor/atto/tests/fixtures/moodle-logo.mp4 [new file with mode: 0755]
lib/editor/atto/tests/fixtures/pretty-good-en.vtt [new file with mode: 0755]
lib/editor/atto/tests/fixtures/pretty-good-sv.vtt [new file with mode: 0755]
lib/form/editor.php
lib/outputrenderers.php
lib/setuplib.php
lib/templates/popover_region.mustache
lib/tests/blocklib_test.php
message/classes/output/messagearea/user_search_results.php
message/externallib.php
message/output/popup/lib.php
message/templates/message_area_contact.mustache
message/templates/message_area_profile.mustache
message/tests/behat/behat_message.php
mod/assign/tests/behat/steps_blind_marking.feature
repository/filepicker.js
theme/boost/amd/build/drawer.min.js
theme/boost/amd/src/drawer.js
theme/boost/classes/autoprefixer.php
theme/boost/scss/moodle/modules.scss
theme/boost/templates/core/filemanager_modal_generallayout.mustache
theme/boost/templates/core/single_select.mustache
theme/boost/templates/gradingform_guide/comment_chooser.mustache [new file with mode: 0644]
theme/bootstrapbase/less/moodle/admin.less
theme/bootstrapbase/style/moodle.css
theme/upgrade.txt
version.php

index bd9ea98..8dfceea 100644 (file)
@@ -53,7 +53,8 @@ if (has_capability('moodle/grade:manage', $systemcontext)
                                                          '3' => '3',
                                                          '4' => '4',
                                                          '5' => '5')));
-        $temp->add(new admin_setting_configselect('grade_navmethod', new lang_string('navmethod', 'grades'), null, 0,
+        $temp->add(new admin_setting_configselect('grade_navmethod', new lang_string('navmethod', 'grades'), null,
+                                                  GRADE_NAVMETHOD_TABS,
                                                   array(GRADE_NAVMETHOD_DROPDOWN => new lang_string('dropdown', 'grades'),
                                                         GRADE_NAVMETHOD_TABS => new lang_string('tabs', 'grades'),
                                                         GRADE_NAVMETHOD_COMBO => new lang_string('combo', 'grades'))));
index db6d0e9..f749219 100644 (file)
@@ -29,7 +29,8 @@ Feature: I need to export grades as text
 
   @javascript
   Scenario: Export grades as text
-    When I set the field "Grade report" to "Plain text file"
+    When I follow "Export"
+    And I follow "Plain text file"
     And I expand all fieldsets
     And I click on "Course total" "checkbox"
     And I set the field "Grade export decimal points" to "1"
@@ -41,7 +42,8 @@ Feature: I need to export grades as text
 
   @javascript
   Scenario: Export grades as text using real
-    When I set the field "Grade report" to "Plain text file"
+    When I follow "Export"
+    And I follow "Plain text file"
     And I expand all fieldsets
     And  I set the following fields to these values:
       | Real        | 1                        |
@@ -52,7 +54,8 @@ Feature: I need to export grades as text
 
   @javascript
   Scenario: Export grades as text using percentages and letters
-    When I set the field "Grade report" to "Plain text file"
+    When I follow "Export"
+    And I follow "Plain text file"
     And  I set the following fields to these values:
       | Percentage   | 1                        |
       | Letter       | 1                        |
@@ -65,7 +68,8 @@ Feature: I need to export grades as text
 
   @javascript
   Scenario: Export grades as text using real, percentages and letters
-    When I set the field "Grade report" to "Plain text file"
+    When I follow "Export"
+    And I follow "Plain text file"
     And  I set the following fields to these values:
       | Real         | 1                        |
       | Percentage   | 1                        |
index 53ce81e..7332912 100644 (file)
@@ -228,7 +228,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
             $remarkparams = array(
                 'name' => '{NAME}[criteria][{CRITERION-id}][remark]',
                 'id' => $remarkid,
-                'cols' => '65', 'rows' => '5', 'class' => 'markingguideremark',
+                'cols' => '65', 'rows' => '5', 'class' => 'markingguideremark form-control',
                 'aria-labelledby' => '{NAME}-remarklabel{CRITERION-id}'
             );
 
@@ -239,7 +239,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
             if (!empty($comments)) {
                 // Frequently used comments chooser.
                 $chooserbuttonid = 'criteria-' . $criterion['id'] . '-commentchooser';
-                $commentchooserparams = array('id' => $chooserbuttonid, 'class' => 'commentchooser');
+                $commentchooserparams = array('id' => $chooserbuttonid, 'class' => 'commentchooser btn btn-secondary');
                 $commentchooser = html_writer::tag('button', get_string('insertcomment', 'gradingform_guide'),
                     $commentchooserparams);
 
@@ -273,7 +273,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
             $scoreinputparams = array(
                 'type' => 'text',
                 'name' => '{NAME}[criteria][{CRITERION-id}][score]',
-                'class' => $scoreclass,
+                'class' => $scoreclass . ' form-control',
                 'id' => '{NAME}-criteria-{CRITERION-id}-score',
                 'size' => '3',
                 'value' => $currentscore,
@@ -534,7 +534,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
         }
         $html = html_writer::start_tag('div', array('class' => 'options'));
         $html .= html_writer::tag('div', get_string('guideoptions', 'gradingform_guide'), array('class' => 'optionsheading'));
-        $attrs = array('type' => 'hidden', 'name' => '{NAME}[options][optionsset]', 'value' => 1);
+        $attrs = array('type' => 'hidden', 'name' => '{NAME}[options][optionsset]', 'value' => 1, 'class' => 'form-control');
         $html .= html_writer::empty_tag('input', $attrs);
         foreach ($options as $option => $value) {
             $html .= html_writer::start_tag('div', array('class' => 'option '.$option));
index 09ecf62..185781f 100644 (file)
@@ -61,7 +61,6 @@ Feature: We can change what we are viewing on the grader report
     And I click on "Hide" "link" in the "Test assignment name 2" activity
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I select "Grader report" from the "Grade report" singleselect
     And I should see "Test assignment name 1"
     And I should see "Test assignment name 2"
     And I should see "Course total"
@@ -98,7 +97,6 @@ Feature: We can change what we are viewing on the grader report
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I select "Grader report" from the "Grade report" singleselect
     And I should see "Test assignment name 1"
     And I should see "Test assignment name 2"
     And I should see "Course total"
index f2158da..1da08bb 100644 (file)
@@ -56,7 +56,7 @@ Feature: We can use Single view
 
   @javascript
   Scenario: I can update grades, add feedback and exclude grades.
-    Given I select "Single view" from the "Grade report" singleselect
+    Given I follow "Single view"
     And I select "Student 4" from the "Select user..." singleselect
     And I set the field "Override for Test assignment one" to "1"
     When I set the following fields to these values:
@@ -98,7 +98,7 @@ Feature: We can use Single view
     And I log in as "teacher2"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I click on "Single view" "option"
+    And I follow "Single view"
     And I click on "Student 4" "option"
     And the "Exclude for Test assignment one" "checkbox" should be disabled
     And the "Override for Test assignment one" "checkbox" should be enabled
index 7f7b25e..2fe4888 100644 (file)
@@ -13,6 +13,6 @@ Feature: We can use the user report
       And I am on site homepage
       And I follow "Course 1"
       And I click on "Grades" "link" in the "Navigation" "block"
-      And I select "User report" from the "Grade report" singleselect
+      And I follow "User report"
       And I select "All users (0)" from the "Select all or one user" singleselect
       Then I should see "No students enrolled in this course yet"
index dcbbd5c..4f06a42 100644 (file)
@@ -242,7 +242,7 @@ Feature: We can use calculated grade totals
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 1       |
       | Exclude empty grades            | 0       |
-    And I follow "Grader report"
+    And I follow "View"
     And I give the grade "Excellent" to the user "Student 1" for the grade item "Test outcome item one"
     And I press "Save changes"
     And I navigate to "Course grade settings" node in "Grade administration > Setup"
@@ -307,7 +307,7 @@ Feature: We can use calculated grade totals
     And I set the following settings for grade item "Test outcome item one":
      | Weight adjusted  | 1   |
      | aggregationcoef2 | 100 |
-    And I follow "Grader report"
+    And I follow "View"
     And I give the grade "Excellent" to the user "Student 1" for the grade item "Test outcome item one"
     And I press "Save changes"
     And I navigate to "Course grade settings" node in "Grade administration > Setup"
@@ -344,7 +344,7 @@ 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 select "User report" from the "Grade report" singleselect
+    And I follow "User report"
     And I select "Myself" from the "View report as" singleselect
     And I select "Student 1" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
@@ -412,7 +412,7 @@ Feature: We can use calculated grade totals
       | Item name | Manual item 3 |
       | Grade category | Sub category 3 |
     And I press "Save changes"
-    And I follow "Grader report"
+    And I follow "View"
     And I give the grade "60.00" to the user "Student 1" for the grade item "Manual item 1"
     And I give the grade "20.00" to the user "Student 1" for the grade item "Manual item 2"
     And I give the grade "40.00" to the user "Student 1" for the grade item "Manual item 3"
@@ -452,12 +452,12 @@ Feature: We can use calculated grade totals
       | Category name | Sub sub category 1 |
       | Parent category | Sub category 3 |
     And I press "Save changes"
-    And I follow "Grader report"
+    And I follow "View"
     And I should see "270.00 (24.77 %)" in the ".course" "css_element"
 
   @javascript
   Scenario: Natural aggregation from the setup screen
-    And I select "Gradebook setup" from the "Grade report" singleselect
+    And I follow "Setup"
     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":
@@ -519,7 +519,7 @@ Feature: We can use calculated grade totals
       | Aggregation          | Natural |
       | Exclude empty grades | 0       |
     And I turn editing mode off
-    And I select "Gradebook setup" from the "Grade report" singleselect
+    And I follow "Setup"
     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"
@@ -534,7 +534,7 @@ 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 select "User report" from the "Grade report" singleselect
+    And I follow "User report"
     And I select "Myself" from the "View report as" singleselect
     And I select "Student 1" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
index d39b632..5740af3 100644 (file)
@@ -39,7 +39,7 @@ Feature: Calculated grade items can be used in the gradebook
       | grade item 1 | gi1 |
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 50 |
-    And I follow "Grader report"
+    And I follow "View"
     And I turn editing mode on
     And I give the grade "75.00" to the user "Student 1" for the grade item "grade item 1"
     And I press "Save changes"
@@ -66,8 +66,8 @@ Feature: Calculated grade items can be used in the gradebook
       | grade item 1 | gi1 |
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 50 |
-    And I follow "Grader report"
-    And I turn editing mode on
+    And I follow "View"
+    And I press "Turn editing on"
     And I give the grade "75.00" to the user "Student 1" for the grade item "grade item 1"
     And I press "Save changes"
     And I follow "User report"
@@ -80,6 +80,7 @@ Feature: Calculated grade items can be used in the gradebook
     And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 40 |
+    And I follow "View"
     And I follow "Grader report"
     And I give the grade "65.00" to the user "Student 2" for the grade item "grade item 1"
     And I press "Save changes"
@@ -132,8 +133,7 @@ Feature: Calculated grade items can be used in the gradebook
     And I set the following fields to these values:
       | Min and max grades used in calculation | Initial min and max grades |
     And I press "Save changes"
-    And I follow "Grader report"
-    And I turn editing mode on
+    And I press "Turn editing on"
     And I give the grade "75.00" to the user "Student 1" for the grade item "grade item 1"
     And I press "Save changes"
     And I follow "User report"
@@ -147,6 +147,7 @@ Feature: Calculated grade items can be used in the gradebook
     And I set the following settings for grade item "calc item":
       | Rescale existing grades | No |
       | Maximum grade | 40 |
+    And I follow "View"
     And I follow "Grader report"
     And I give the grade "65.00" to the user "Student 2" for the grade item "grade item 1"
     And I press "Save changes"
index 6271c9f..1f7d217 100644 (file)
@@ -40,7 +40,6 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | grade item 1 | gi1 |
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 50 |
-    And I follow "Grader report"
     And I turn editing mode on
     And I give the grade "75.00" to the user "Student 1" for the grade item "grade item 1"
     And I press "Save changes"
@@ -67,8 +66,9 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | grade item 1 | gi1 |
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 50 |
+    And I follow "View"
     And I follow "Grader report"
-    And I turn editing mode on
+    And I press "Turn editing on"
     And I give the grade "75.00" to the user "Student 1" for the grade item "grade item 1"
     And I press "Save changes"
     And I follow "User report"
@@ -81,6 +81,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
     And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "Calc cat":
       | Maximum grade | 40 |
+    And I follow "View"
     And I follow "Grader report"
     And I give the grade "65.00" to the user "Student 2" for the grade item "grade item 1"
     And I press "Save changes"
@@ -134,7 +135,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | Min and max grades used in calculation | Initial min and max grades |
     And I press "Save changes"
     And I follow "Grader report"
-    And I turn editing mode on
+    And I press "Turn editing on"
     And I give the grade "75.00" to the user "Student 1" for the grade item "grade item 1"
     And I press "Save changes"
     And I follow "User report"
@@ -148,6 +149,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
     And I set the following settings for grade item "calc item":
       | Rescale existing grades | No |
       | Maximum grade | 40 |
+    And I follow "View"
     And I follow "Grader report"
     And I give the grade "65.00" to the user "Student 2" for the grade item "grade item 1"
     And I press "Save changes"
index 97c4fff..d0e7f98 100644 (file)
@@ -43,7 +43,7 @@ Feature: We can understand the gradebook user report
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I set the field "Grade report" to "Gradebook setup"
+    And I follow "Setup"
     And I press "Add category"
     And I set the field "Category name" to "Sub category"
     And I press "Save changes"
@@ -59,7 +59,7 @@ Feature: We can understand the gradebook user report
   Scenario: Mean of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Mean of grades |
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -84,7 +84,7 @@ Feature: We can understand the gradebook user report
       | Item weight | 1.0 |
     And I set the following settings for grade item "Sub category":
       | Item weight | 1.0 |
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -105,7 +105,8 @@ Feature: We can understand the gradebook user report
       | Aggregation | Simple weighted mean of grades |
     And I set the following settings for grade item "Test assignment three":
       | Extra credit | 1 |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -124,7 +125,8 @@ Feature: We can understand the gradebook user report
       | Aggregation | Mean of grades (with extra credits) |
     And I set the following settings for grade item "Test assignment three":
       | Extra credit weight | 1.0 |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -141,7 +143,8 @@ Feature: We can understand the gradebook user report
   Scenario: Median of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Median of grades |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -158,7 +161,8 @@ Feature: We can understand the gradebook user report
   Scenario: Lowest grade aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Lowest grade |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -175,7 +179,8 @@ Feature: We can understand the gradebook user report
   Scenario: Highest grade aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Highest grade |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -192,7 +197,8 @@ Feature: We can understand the gradebook user report
   Scenario: Mode of grades aggregation
     And I set the following settings for grade item "Course 1":
       | Aggregation | Mode of grades |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -213,7 +219,8 @@ Feature: We can understand the gradebook user report
       | Aggregation | Weighted mean of grades |
     And I set the following settings for grade item "Test assignment three":
       | Extra credit | 1 |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
@@ -232,7 +239,8 @@ Feature: We can understand the gradebook user report
   Scenario: View user report with natural aggregation
     And I set the following settings for grade item "Test assignment three":
       | Extra credit | 1 |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
 
     # Check the values in the weights column.
index e59d032..45a2c98 100644 (file)
@@ -56,7 +56,7 @@ Feature: Extra credit contributions are normalised when going out of bounds
     And I press "Save changes"
 
   Scenario Outline: The contribution of extra credit items is normalised
-    Given I set the field "Grade report" to "Gradebook setup"
+    Given I follow "Setup"
     When I set the following settings for grade item "Course 1":
       | Aggregation | <aggregation> |
     And I set the following settings for grade item "Manual item 2":
@@ -65,7 +65,8 @@ Feature: Extra credit contributions are normalised when going out of bounds
       | Extra credit | 1 |
     And I set the following settings for grade item "Manual item 4":
       | Extra credit | 1 |
-    And I set the field "Grade report" to "User report"
+    And I follow "View"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item    | Calculated weight | Grade  | Contribution to course total |
index 519e750..c74957e 100644 (file)
@@ -45,7 +45,7 @@ Feature: Student and teacher's view of aggregated grade items is consistent when
     And I press "Save changes"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I select "User report" from the "Grade report" singleselect
+    And I follow "User report"
     And I select "Myself" from the "View report as" singleselect
     And I select "Student 1" from the "Select all or one user" singleselect
     Then the following should exist in the "user-grade" table:
index 868a2ce..381b3a6 100644 (file)
@@ -23,7 +23,8 @@ Feature: We can customise the letter boundary of a course.
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I select "Course grade settings" from the "Grade report" singleselect
+    And I follow "Setup"
+    And I follow "Course grade settings"
     And I set the following fields to these values:
       | Grade display type | Letter |
     And I press "Save changes"
@@ -33,7 +34,7 @@ Feature: We can customise the letter boundary of a course.
       | id_override | 1 |
       | id_gradeboundary10 | 57 |
     And I press "Save changes"
-    And I select "Grader report" from the "Grade report" singleselect
+    And I follow "View"
     And I press "Turn editing on"
     And I give the grade "57" to the user "Student 1" for the grade item "Test assignment one"
     And I press "Save changes"
index bd81919..97fd330 100644 (file)
@@ -24,7 +24,8 @@ Feature: We can customise the letter boundary of a course.
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I select "Course grade settings" from the "Grade report" singleselect
+    And I follow "Setup"
+    And I follow "Course grade settings"
     And I set the following fields to these values:
       | Grade display type | Letter |
     And I press "Save changes"
@@ -34,7 +35,7 @@ Feature: We can customise the letter boundary of a course.
       | id_override | 1 |
       | id_gradeboundary10 | 57 |
     And I press "Save changes"
-    And I select "Grader report" from the "Grade report" singleselect
+    And I follow "View"
     And I press "Turn editing on"
     And I give the grade "57" to the user "Student 1" for the grade item "Test assignment one"
     And I press "Save changes"
index 4710299..3d7ecc9 100644 (file)
@@ -26,7 +26,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I set the field "Grade report" to "Gradebook setup"
+    And I follow "Setup"
     And I set the following settings for grade item "Test assignment four (extra)":
       | Extra credit | 1 |
     And I set the following settings for grade item "Test assignment five (extra)":
@@ -42,7 +42,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade  | Range | Percentage | Contribution to course total |
@@ -63,7 +63,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -82,7 +82,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
@@ -109,7 +109,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
@@ -134,7 +134,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade  | Range | Percentage | Contribution to course total |
@@ -158,7 +158,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -180,7 +180,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
@@ -207,7 +207,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -233,7 +233,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -257,7 +257,7 @@ Feature: Weights in natural aggregation are adjusted if the items are excluded f
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
index 81bab50..c439d17 100644 (file)
@@ -27,7 +27,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I set the field "Grade report" to "Gradebook setup"
+    And I follow "Setup"
     And I set the following settings for grade item "Test assignment four (extra)":
       | Extra credit | 1 |
     And I set the following settings for grade item "Test assignment five (extra)":
@@ -43,7 +43,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade  | Range | Percentage | Contribution to course total |
@@ -64,7 +64,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -83,7 +83,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
@@ -110,7 +110,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
@@ -135,7 +135,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade  | Range | Percentage | Contribution to course total |
@@ -160,7 +160,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -183,7 +183,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
@@ -210,7 +210,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -237,7 +237,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight       | Grade  | Range | Percentage | Contribution to course total |
@@ -262,7 +262,7 @@ Feature: Gradebook calculations for extra credit items before the fix 20150619
     And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four (extra)"
     And I give the grade "8.00" to the user "Student 1" for the grade item "Test assignment five (extra)"
     And I press "Save changes"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item                   | Calculated weight      | Grade | Range | Percentage | Contribution to course total |
index 3bcc6b6..93c5996 100644 (file)
@@ -33,7 +33,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I set the field "Grade report" to "Gradebook setup"
+    And I follow "Setup"
 
   @javascript
   Scenario: Setting all weights in a category to exactly one hundred in total.
index 55ef48f..a940acc 100644 (file)
@@ -34,7 +34,7 @@ Feature: Gradebook calculations for natural weights normalisation before the fix
     And I log in as "teacher1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I set the field "Grade report" to "Gradebook setup"
+    And I follow "Setup"
 
   @javascript
   Scenario: Grade items weights are normalised when all grade item weights are overridden (sum exactly 100). Extra credit is set to zero (before the fix 20150619).
index b89b67c..1817f7d 100644 (file)
@@ -76,7 +76,6 @@ Feature: View gradebook when scales are used
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I follow "Grader report"
     And I turn editing mode on
 
   Scenario: Test displaying scales in gradebook in aggregation method Natural
@@ -99,7 +98,7 @@ Feature: View gradebook when scales are used
       | 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 select "Gradebook setup" from the "Grade report" singleselect
+    And I follow "Setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 5.00      |
@@ -145,7 +144,7 @@ Feature: View gradebook when scales are used
       | 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 select "Gradebook setup" from the "Grade report" singleselect
+    And I follow "Setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | A (5)     |
index 76a2b34..2c7eba7 100644 (file)
@@ -54,7 +54,6 @@ Feature: View gradebook when single item scales are used
     And I set the field "Show weightings" to "Show"
     And I set the field "Show contribution to course total" to "Show"
     And I press "Save changes"
-    And I follow "Grader report"
     And I turn editing mode on
 
   Scenario: Test displaying single item scales in gradebook in aggregation method Natural
@@ -79,7 +78,7 @@ Feature: View gradebook when single item scales are used
       | Test assignment one | -     | Ace!–Ace! | -                            |
       | Sub category 1 total| -     | 0–1       | -                            |
       | Course total        | -     | 0–1       | -                            |
-    And I select "Gradebook setup" from the "jump" singleselect
+    And I follow "Setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                | Max grade |
       | Test assignment one | 1.00      |
@@ -111,7 +110,7 @@ Feature: View gradebook when single item scales are used
       | Test assignment one                              | Ace!           | Ace!–Ace!   | <contrib1>                   |
       | Sub category (<aggregation>) total<aggregation>. | <cattotal1>    | 0–100       | -                            |
       | Course total<aggregation>.                       | <coursetotal1> | 0–100       | -                            |
-    And I select "Gradebook setup" from the "jump" singleselect
+    And I follow "Setup"
     And the following should exist in the "grade_edit_tree_table" table:
       | Name                                             | Max grade |
       | Test assignment one                              | Ace! (1)  |
index 73c1995..9ea6c96 100644 (file)
@@ -58,10 +58,10 @@ Feature: We can enter in grades and view reports from the gradebook
     And I press "Save changes"
 
   Scenario: Grade a grade item and ensure the results display correctly in the gradebook
-    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"
+    When I follow "User report"
+    And "Grader report" "link" should exist
+    And "Outcomes report" "link" should exist
+    And "Grader report" "link" should exist
     And the "Select all or one user" select box should contain "All users (1)"
     And I log out
     And I log in as "student1"
@@ -80,12 +80,13 @@ Feature: We can enter in grades and view reports from the gradebook
     And "Course 1" row "Grade" column of "overview-grade" table should not contain "90.00"
 
   Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
-    When I select "Gradebook setup" from the "Grade report" singleselect
+    When I follow "Setup"
     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 select "User report" from the "Grade report" singleselect
+    And I follow "View"
+    And I follow "User report"
     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 49bd897..159aea2 100644 (file)
@@ -123,7 +123,6 @@ $string['unreadnewmessage'] = 'New message from {$a}';
 $string['userisblockingyou'] = 'This user has blocked you from sending messages to them';
 $string['userisblockingyounoncontact'] = '{$a} only accepts messages from their contacts.';
 $string['viewinganotherusersmessagearea'] = 'You are viewing another user\'s message area.';
-$string['viewmessages'] = 'View messages';
 $string['viewmessageswith'] = 'View messages with {$a}';
 $string['viewnotificationresource'] = 'Go to: {$a}';
 $string['viewunreadmessageswith'] = 'View unread messages with {$a}';
index 0cd4c46..5a159c7 100644 (file)
@@ -234,12 +234,18 @@ class block_manager {
             return false;
         }
 
+        $undeletableblocks = self::get_undeletable_block_types();
         foreach ($this->blockinstances as $region) {
             foreach ($region as $instance) {
                 if (empty($instance->instance->blockname)) {
                     continue;
                 }
                 if ($instance->instance->blockname == $blockname) {
+                    if ($instance->instance->requiredbytheme) {
+                        if (!in_array($block->name, $undeletableblocks)) {
+                            continue;
+                        }
+                    }
                     return true;
                 }
             }
@@ -579,6 +585,20 @@ class block_manager {
             return;
         }
 
+        // Exclude auto created blocks if they are not undeletable in this theme.
+        $undeletable = $this->get_undeletable_block_types();
+        $undeletablecheck = '';
+        $undeletableparams = array();
+        $undeletablenotparams = array();
+        if (!empty($undeletable)) {
+            list($testsql, $undeletableparams) = $DB->get_in_or_equal($undeletable, SQL_PARAMS_NAMED, 'undeletable');
+            list($testnotsql, $undeletablenotparams) = $DB->get_in_or_equal($undeletable, SQL_PARAMS_NAMED, 'deletable', false);
+            $undeletablecheck = 'AND ((bi.blockname ' . $testsql . ' AND bi.requiredbytheme = 1) OR ' .
+                                ' (bi.blockname ' . $testnotsql . ' AND bi.requiredbytheme = 0))';
+        } else {
+            $undeletablecheck = 'AND (bi.requiredbytheme = 0)';
+        }
+
         if (is_null($includeinvisible)) {
             $includeinvisible = $this->page->user_is_editing();
         }
@@ -626,6 +646,7 @@ class block_manager {
                     bi.parentcontextid,
                     bi.showinsubcontexts,
                     bi.pagetypepattern,
+                    bi.requiredbytheme,
                     bi.subpagepattern,
                     bi.defaultregion,
                     bi.defaultweight,
@@ -649,12 +670,15 @@ class block_manager {
                 AND (bi.subpagepattern IS NULL OR bi.subpagepattern = :subpage2)
                 $visiblecheck
                 AND b.visible = 1
+                $undeletablecheck
 
                 ORDER BY
                     COALESCE(bp.region, bi.defaultregion),
                     COALESCE(bp.weight, bi.defaultweight),
                     bi.id";
-        $blockinstances = $DB->get_recordset_sql($sql, $params + $parentcontextparams + $pagetypepatternparams);
+
+        $allparams = $params + $parentcontextparams + $pagetypepatternparams + $undeletableparams + $undeletablenotparams;
+        $blockinstances = $DB->get_recordset_sql($sql, $allparams);
 
         $this->birecordsbyregion = $this->prepare_per_region_arrays();
         $unknown = array();
@@ -957,6 +981,10 @@ class block_manager {
      * load_blocks. This is used, for example, to ensure that all blocks get a
      * chance to initialise themselves via the {@link block_base::specialize()}
      * method, before any output is done.
+     *
+     * It is also used to create any blocks that are "undeletable" by the current theme.
+     * These blocks that are auto-created have requiredbytheme set on the block instance
+     * so they are only visible on themes that require them.
      */
     public function create_all_block_instances() {
         global $PAGE;
@@ -964,21 +992,23 @@ class block_manager {
 
         // If there are any un-removable blocks that were not created - force them.
         $undeletable = $this->get_undeletable_block_types();
-        foreach ($undeletable as $forced) {
-            if (empty($forced)) {
-                continue;
-            }
-            $found = false;
-            foreach ($this->get_regions() as $region) {
-                foreach($this->birecordsbyregion[$region] as $instance) {
-                    if ($instance->blockname == $forced) {
-                        $found = true;
+        if (!$this->fakeblocksonly) {
+            foreach ($undeletable as $forced) {
+                if (empty($forced)) {
+                    continue;
+                }
+                $found = false;
+                foreach ($this->get_regions() as $region) {
+                    foreach($this->birecordsbyregion[$region] as $instance) {
+                        if ($instance->blockname == $forced) {
+                            $found = true;
+                        }
                     }
                 }
-            }
-            if (!$found) {
-                $this->add_block_at_end_of_default_region($forced);
-                $missing = true;
+                if (!$found) {
+                    $this->add_block_required_by_theme($forced);
+                    $missing = true;
+                }
             }
         }
 
@@ -993,6 +1023,50 @@ class block_manager {
 
     }
 
+    /**
+     * Add a block that is required by the current theme but has not been
+     * created yet. This is a special type of block that only shows in themes that
+     * require it (by listing it in undeletable_block_types).
+     *
+     * @param string $blockname the name of the block type.
+     */
+    protected function add_block_required_by_theme($blockname) {
+        global $DB;
+
+        if (empty($this->birecordsbyregion)) {
+            // No blocks or block regions exist yet.
+            return;
+        }
+
+        // Never auto create blocks when we are showing fake blocks only.
+        if ($this->fakeblocksonly) {
+            return;
+        }
+
+        $systemcontext = context_system::instance();
+        $defaultregion = $this->get_default_region();
+        // Add a special system wide block instance only for themes that require it.
+        $blockinstance = new stdClass;
+        $blockinstance->blockname = $blockname;
+        $blockinstance->parentcontextid = $systemcontext->id;
+        $blockinstance->showinsubcontexts = true;
+        $blockinstance->requiredbytheme = true;
+        $blockinstance->pagetypepattern = '*';
+        $blockinstance->subpagepattern = null;
+        $blockinstance->defaultregion = $defaultregion;
+        $blockinstance->defaultweight = 0;
+        $blockinstance->configdata = '';
+        $blockinstance->id = $DB->insert_record('block_instances', $blockinstance);
+
+        // Ensure the block context is created.
+        context_block::instance($blockinstance->id);
+
+        // If the new instance was created, allow it to do additional setup.
+        if ($block = block_instance($blockname, $blockinstance)) {
+            $block->instance_create();
+        }
+    }
+
     /**
      * Return an array of content objects from a set of block instances
      *
old mode 100644 (file)
new mode 100755 (executable)
index 39bbd58..a26be81
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20160804" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20161119" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="blockname" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false" COMMENT="The type of block this is. Foreign key, references block.name."/>
         <FIELD NAME="parentcontextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The context within which this block appears. Foreign key, references context.id."/>
         <FIELD NAME="showinsubcontexts" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false" COMMENT="If 1, this block appears on all matching pages in subcontexts of parentcontextid, as well in pages belonging to parentcontextid."/>
+        <FIELD NAME="requiredbytheme" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If 1, this block was created because it was required by the theme and did not exist."/>
         <FIELD NAME="pagetypepattern" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false" COMMENT="The types of page this block appears on. Either an exact page type like mod-quiz-view, or a pattern like mod-quiz-* or course-view-*. Note that course-view-* will match course-view."/>
         <FIELD NAME="subpagepattern" TYPE="char" LENGTH="16" NOTNULL="false" SEQUENCE="false" COMMENT="Further restrictions on where this block appears. In some places, e.g. during a quiz or lesson attempt, different pages have different subpage ids. If this field is not null, the block only appears on that particular subpage."/>
         <FIELD NAME="defaultregion" TYPE="char" LENGTH="16" NOTNULL="true" SEQUENCE="false" COMMENT="Which block region this block should appear in on each page, in the absence of a specific position in the block_positions table."/>
index 9d68867..cd9f0f0 100644 (file)
@@ -2406,5 +2406,32 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2016110600.00);
     }
 
+    if ($oldversion < 2016112200.01) {
+
+        // Define field requiredbytheme to be added to block_instances.
+        $table = new xmldb_table('block_instances');
+        $field = new xmldb_field('requiredbytheme', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'showinsubcontexts');
+
+        // Conditionally launch add field requiredbytheme.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2016112200.01);
+    }
+    if ($oldversion < 2016112200.02) {
+
+        // Change the existing site level admin and settings blocks to be requiredbytheme which means they won't show in boost.
+        $context = context_system::instance();
+        $params = array('blockname' => 'settings', 'parentcontextid' => $context->id);
+        $DB->set_field('block_instances', 'requiredbytheme', 1, $params);
+
+        $params = array('blockname' => 'navigation', 'parentcontextid' => $context->id);
+        $DB->set_field('block_instances', 'requiredbytheme', 1, $params);
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2016112200.02);
+    }
+
     return true;
 }
index 58b8cab..6c57e13 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['add'] = 'Add';
+$string['addcaptionstrack'] = 'Add caption track';
+$string['addchapterstrack'] = 'Add chapter track';
+$string['adddescriptionstrack'] = 'Add description track';
+$string['addmetadatatrack'] = 'Add metadata track';
+$string['addsource'] = 'Add alternative source';
+$string['addsource_help'] = 'It is recommended that an alternative media source is provided, since desktop and mobile browsers vary in which file formats they support.';
+$string['addsubtitlestrack'] = 'Add subtitle track';
+$string['addtrack'] = 'Add track';
+$string['advancedsettings'] = 'Advanced settings';
+$string['audio'] = 'Audio';
+$string['audiosourcelabel'] = 'Audio source URL';
+$string['autoplay'] = 'Play automatically';
 $string['browserepositories'] = 'Browse repositories...';
+$string['captions'] = 'Captions';
+$string['captions_help'] = 'Captions may be used to describe everything happening in the track, including non-verbal sounds such as a phone ringing.';
+$string['captionssourcelabel'] = 'Caption track URL';
+$string['chapters'] = 'Chapters';
+$string['chapters_help'] = 'Chapter titles may be provided for use in navigating the media resource.';
+$string['chapterssourcelabel'] = 'Chapter track URL';
+$string['controls'] = 'Show controls';
 $string['createmedia'] = 'Insert media';
+$string['default'] = 'Default';
+$string['descriptions'] = 'Descriptions';
+$string['descriptions_help'] = 'Audio descriptions may be used to provide a narration which explains visual details not apparent from the audio alone.';
+$string['descriptionssourcelabel'] = 'Description track URL';
+$string['displayoptions'] = 'Display options';
 $string['entername'] = 'Enter name';
+$string['entersource'] = 'Source URL';
 $string['enterurl'] = 'Enter URL';
 $string['height'] = 'Height';
+$string['kind'] = 'Type';
+$string['label'] = 'Label';
+$string['languagesavailable'] = 'Languages available';
+$string['languagesinstalled'] = 'Languages installed';
+$string['link'] = 'Link';
+$string['loop'] = 'Loop';
+$string['metadata'] = 'Metadata';
+$string['metadata_help'] = 'Metadata tracks, for use from a script, may be used only if the player supports metadata';
+$string['metadatasourcelabel'] = 'Metadata track URL';
+$string['mute'] = 'Muted';
 $string['pluginname'] = 'Media';
+$string['poster'] = 'Thumbnail URL';
+$string['remove'] = 'Remove';
+$string['size'] = 'Size';
+$string['srclang'] = 'Language';
+$string['subtitles'] = 'Subtitles';
+$string['subtitles_help'] = 'Subtitles may be used to provide a transcription or translation of the dialogue.';
+$string['subtitlessourcelabel'] = 'Subtitle track URL';
+$string['track'] = 'Track URL';
+$string['tracks'] = 'Subtitles and captions';
+$string['tracks_help'] = 'Subtitles, captions, chapters and descriptions can be added via a WebVTT (Web Video Text Tracks) format file. Track labels will be shown in the selection dropdown menu. For each type of track, any track set as default will be pre-selected at the start of the video.';
+$string['video'] = 'Video';
+$string['videoheight'] = 'Video height';
+$string['videosourcelabel'] = 'Video source URL';
+$string['videowidth'] = 'Video width';
 $string['width'] = 'Width';
+
index 62e0dd4..86a9d37 100644 (file)
 function atto_media_strings_for_js() {
     global $PAGE;
 
-    $PAGE->requires->strings_for_js(array('createmedia',
-                                          'enterurl',
+    $PAGE->requires->strings_for_js(array('add',
+                                          'addcaptionstrack',
+                                          'addchapterstrack',
+                                          'adddescriptionstrack',
+                                          'addmetadatatrack',
+                                          'addsource',
+                                          'addsubtitlestrack',
+                                          'addtrack',
+                                          'advancedsettings',
+                                          'audio',
+                                          'audiosourcelabel',
+                                          'autoplay',
+                                          'browserepositories',
+                                          'browserepositories',
+                                          'captions',
+                                          'captionssourcelabel',
+                                          'chapters',
+                                          'chapterssourcelabel',
+                                          'controls',
+                                          'createmedia',
+                                          'default',
+                                          'descriptions',
+                                          'descriptionssourcelabel',
+                                          'displayoptions',
+                                          'entername',
                                           'entername',
-                                          'browserepositories'),
+                                          'entersource',
+                                          'enterurl',
+                                          'height',
+                                          'kind',
+                                          'label',
+                                          'languagesavailable',
+                                          'languagesinstalled',
+                                          'link',
+                                          'loop',
+                                          'metadata',
+                                          'metadatasourcelabel',
+                                          'mute',
+                                          'poster',
+                                          'remove',
+                                          'size',
+                                          'srclang',
+                                          'subtitles',
+                                          'subtitlessourcelabel',
+                                          'track',
+                                          'tracks',
+                                          'video',
+                                          'videoheight',
+                                          'videosourcelabel',
+                                          'videowidth',
+                                          'width'),
                                     'atto_media');
 }
+
+/**
+ * Sends the parameters to the JS module.
+ *
+ * @return array
+ */
+function atto_media_params_for_js() {
+    global $OUTPUT;
+    global $PAGE;
+    $currentlang = current_language();
+    $langsinstalled = get_string_manager()->get_list_of_translations(true);
+    $langsavailable = get_string_manager()->get_list_of_languages();
+    $params = [
+        'langs' => ['installed' => [], 'available' => []],
+        'help' => []
+    ];
+
+    foreach ($langsinstalled as $code => $name) {
+        $params['langs']['installed'][] = [
+            'lang' => $name,
+            'code' => $code,
+            'default' => $currentlang == $code
+        ];
+    }
+
+    foreach ($langsavailable as $code => $name) {
+        // See MDL-50829 for an explanation of this lrm thing.
+        $lrm = json_decode('"\u200E"');
+        $params['langs']['available'][] = [
+            'lang' => $name . ' ' . $lrm . '(' . $code . ')' . $lrm, 'code' => $code];
+    }
+
+    $params['help'] = [
+        'addsource' => $OUTPUT->help_icon('addsource', 'atto_media'),
+        'tracks' => $OUTPUT->help_icon('tracks', 'atto_media'),
+        'subtitles' => $OUTPUT->help_icon('subtitles', 'atto_media'),
+        'captions' => $OUTPUT->help_icon('captions', 'atto_media'),
+        'descriptions' => $OUTPUT->help_icon('descriptions', 'atto_media'),
+        'chapters' => $OUTPUT->help_icon('chapters', 'atto_media'),
+        'metadata' => $OUTPUT->help_icon('metadata', 'atto_media')
+    ];
+
+    return $params;
+}
diff --git a/lib/editor/atto/plugins/media/styles.css b/lib/editor/atto/plugins/media/styles.css
new file mode 100644 (file)
index 0000000..149ad94
--- /dev/null
@@ -0,0 +1,106 @@
+.atto_form.atto_media #video input,
+.atto_form.atto_media #audio input,
+.atto_form.atto_media #link input {
+    box-sizing: border-box;
+    height: inherit;
+}
+
+.atto_form.atto_media > .tab-content {
+    max-height: 60vh;
+    overflow-x: hidden;
+    padding-left: 20px;
+    padding-right: 20px;
+    margin-left: -20px;
+    margin-right: -21px;
+}
+
+.atto_form.atto_media [id$="-advanced-settings"] label {
+    margin-right: 10px;
+}
+
+.atto_form.atto_media label {
+    display: inline-block;
+}
+
+.atto_form.atto_media label > span {
+    display: inline-block;
+    min-width: 6em;
+}
+
+.atto_form.atto_media .atto_media_track_lang_entry,
+.atto_form.atto_media .atto_media_track_label_entry {
+    width: 168px;
+}
+
+.atto_form.atto_media .atto_media_track_source {
+    margin-bottom: 10px;
+}
+
+.atto_form.atto_media select {
+    margin-right: 10px;
+}
+
+.atto_form.atto_media [id$="-tracks"] input[type=checkbox] {
+    margin-left: 10px;
+}
+
+.atto_form.atto_media .atto_media_track ~ .atto_media_track {
+    margin-top: 5px;
+    padding-top: 10px;
+    border-top: 1px solid #e5e5e5;
+}
+
+.atto_form.atto_media label.fullwidth {
+    width: 100%;
+}
+
+.atto_media_postersize {
+    display: inline-block;
+}
+
+.atto_media_postersize input[type=text] {
+    width: 3em;
+}
+
+input[size].atto_media_url_entry {
+    width: calc(100% - 15px);
+}
+
+.openmediabrowser {
+    margin-top: -4px;
+}
+
+.addcomponent,
+.removecomponent {
+    font-weight: bold;
+    margin-right: 10px;
+}
+
+.trackhelp {
+    text-align: right;
+}
+
+.atto_form.atto_media .atto_media_source > label {
+    width: calc(100% - 153px);
+}
+
+.atto_form.atto_media .atto_media_track_lang_entry,
+.atto_form.atto_media .atto_media_track_label_entry {
+    width: 116px;
+}
+
+.langlabel {
+    width: 42%;
+}
+
+.labellabel {
+    width: 44%;
+}
+
+.defaultlabel {
+    width: 14%;
+}
+
+[data-medium-type=link] label {
+    width: 100%;
+}
diff --git a/lib/editor/atto/plugins/media/styles_clean.css b/lib/editor/atto/plugins/media/styles_clean.css
new file mode 100644 (file)
index 0000000..e947eab
--- /dev/null
@@ -0,0 +1,28 @@
+.nav-tabs > .nav-item a.active {
+    color: #555;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    border-bottom-color: transparent;
+    cursor: default;
+}
+
+.atto_form.atto_media .atto_media_track_lang_entry,
+.atto_form.atto_media .atto_media_track_label_entry {
+    width: 124px;
+}
+
+.atto_form.atto_media .atto_media_source > label {
+    width: calc(100% - 168px);
+}
+
+.langlabel {
+    width: 42%;
+}
+
+.labellabel {
+    width: 44%;
+}
+
+.defaultlabel {
+    width: 14%;
+}
index 5c1326a..aa6aef0 100644 (file)
 Feature: Add media to Atto
   To write rich text - I need to add media.
 
+  Background:
+  Given I log in as "admin"
+  And I follow "Manage private files..."
+  And I upload "lib/editor/atto/tests/fixtures/moodle-logo.webm" file to "Files" filemanager
+  And I upload "lib/editor/atto/tests/fixtures/moodle-logo.mp4" file to "Files" filemanager
+  And I upload "lib/editor/atto/tests/fixtures/moodle-logo.png" file to "Files" filemanager
+  And I upload "lib/editor/atto/tests/fixtures/pretty-good-en.vtt" file to "Files" filemanager
+  And I upload "lib/editor/atto/tests/fixtures/pretty-good-sv.vtt" file to "Files" filemanager
+  And I click on "Save changes" "button"
+  And I follow "Profile" in the user menu
+  And I follow "Blog entries"
+  And I follow "Add a new entry"
+  And I set the field "Blog entry body" to "<p>Media test</p>"
+  And I select the text in the "Blog entry body" Atto editor
+  And I set the field "Entry title" to "The best video in the entire world (not really)"
+  And I click on "Media" "button"
+
+  @javascript
+  Scenario: Insert some media as a link
+  Given I click on "Browse repositories..." "button" in the "#id_summary_editor_link .atto_media_source.atto_media_link_source" "css_element"
+  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+  And I click on "moodle-logo.webm" "link"
+  And I click on "Select this file" "button"
+  And the field "Enter name" matches value "moodle-logo.webm"
+  And I wait until the page is ready
+  And I click on "Insert media" "button"
+  When I click on "Save changes" "button"
+  Then "//a[. = 'moodle-logo.webm']" "xpath_element" should exist
+
+  @javascript
+  Scenario: Insert some media as a plain video
+  Given I click on "Video" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+  And I click on "moodle-logo.webm" "link"
+  And I click on "Select this file" "button"
+  And I click on "Add alternative source" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source:nth-of-type(2)" "css_element"
+  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+  And I click on "moodle-logo.mp4" "link"
+  And I click on "Select this file" "button"
+  When I click on "Insert media" "button"
+  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][descendant::source[contains(@src, 'moodle-logo.mp4')]]" "xpath_element" should exist
+
+  @javascript
+  Scenario: Insert some media as a video with display settings
+  Given I click on "Video" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+  And I click on "moodle-logo.webm" "link"
+  And I click on "Select this file" "button"
+  And I click on "Display options" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_poster_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "moodle-logo.png" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I set the field with xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_width_entry ')]" to "420"
+  And I set the field with xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_height_entry ')]" to "69"
+  And I click on "Display options" "link"
+  When I click on "Insert media" "button"
+  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][contains(@poster, 'moodle-logo.png')][@width=420][@height=69]" "xpath_element" should exist
+
   @javascript
-  Scenario: Insert some media
-    Given I log in as "admin"
-    And I follow "Manage private files..."
-    And I upload "lib/editor/atto/tests/fixtures/moodle-logo.webm" file to "Files" filemanager
-    And I click on "Save changes" "button"
-    When I follow "Profile" in the user menu
-    And I follow "Blog entries"
-    And I follow "Add a new entry"
-    And I set the field "Blog entry body" to "<p>Media test</p>"
-    And I select the text in the "Blog entry body" Atto editor
-    And I set the field "Entry title" to "The best video in the entire world (not really)"
-    And I click on "Media" "button"
-    And I click on "Browse repositories..." "button"
-    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-    And I click on "moodle-logo.webm" "link"
-    And I click on "Select this file" "button"
-    And I set the field "Enter name" to "It's the logo"
-    And I click on "Insert media" "button"
-    And I click on "Save changes" "button"
-    Then "video" "css_element" should be visible
+  Scenario: Insert some media as a video with advanced settings
+  Given I click on "Video" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+  And I click on "moodle-logo.webm" "link"
+  And I click on "Select this file" "button"
+  And I click on "Advanced settings" "link"
+  And the field "Show controls" matches value "1"
+  And I set the field "Play automatically" to "1"
+  And I set the field "Muted" to "1"
+  And I set the field "Loop" to "1"
+  When I click on "Insert media" "button"
+  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][@controls='true'][@loop='true'][@autoplay='true'][@autoplay='true']" "xpath_element" should exist
 
+  @javascript
+  Scenario: Insert some media as a video with tracks
+  Given I click on "Video" "link"
+  And I change window size to "large"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+  And I click on "moodle-logo.webm" "link"
+  And I click on "Select this file" "button"
+  And I click on "Subtitles and captions" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_subtitles .atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-sv.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And the field "Label" matches value "Swedish"
+  And the field "Language" matches value "sv"
+  And I click on "Add subtitle track" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_subtitles .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-en.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[2]" matches value "English"
+  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[1]" to "1"
+  And I click on "Captions" "link" in the ".nav-item[data-track-kind='captions']" "css_element"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_captions .atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-sv.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[3]" matches value "Swedish"
+  And I click on "Add caption track" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_captions .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-en.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[4]" matches value "English"
+  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[4]" to "1"
+  And I click on "Descriptions" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_descriptions .atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And  I click on "pretty-good-sv.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[5]" matches value "Swedish"
+  And I click on "Add description track" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_descriptions .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-en.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[6]" matches value "English"
+  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[5]" to "1"
+  And I click on "Chapters" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_chapters .atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And  I click on "pretty-good-sv.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[7]" matches value "Swedish"
+  And I click on "Add chapter track" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_chapters .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-en.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[8]" matches value "English"
+  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[8]" to "1"
+  And I click on "Metadata" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_metadata .atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And  I click on "pretty-good-sv.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[9]" matches value "Swedish"
+  And I click on "Add metadata track" "link"
+  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_metadata .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+  And I click on "pretty-good-en.vtt" "link"
+  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+  And I click on "Overwrite" "button"
+  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[10]" matches value "English"
+  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[9]" to "1"
+  When I click on "Insert media" "button"
+  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='subtitles'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='subtitles'][@label='English'][@srclang='en'][not(@default)]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='captions'][@label='Swedish'][@srclang='sv'][not(@default)]][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='captions'][@label='English'][@srclang='en'][@default='true']][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='descriptions'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='descriptions'][@label='English'][@srclang='en'][not(@default)]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='chapters'][@label='Swedish'][@srclang='sv'][not(@default)]][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='chapters'][@label='English'][@srclang='en'][@default='true']][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='metadata'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='metadata'][@label='English'][@srclang='en'][not(@default)]]" "xpath_element" should exist
index ed3048a..2d4acc6 100644 (file)
Binary files a/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js and b/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-debug.js differ
index d84c7a9..77af4ce 100644 (file)
Binary files a/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js and b/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button-min.js differ
index ed3048a..2d4acc6 100644 (file)
Binary files a/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js and b/lib/editor/atto/plugins/media/yui/build/moodle-atto_media-button/moodle-atto_media-button.js differ
index b9f43b0..c6073f3 100644 (file)
  */
 
 var COMPONENTNAME = 'atto_media',
+    MEDIA_TYPES = {LINK: 'LINK', VIDEO: 'VIDEO', AUDIO: 'AUDIO'},
+    TRACK_KINDS = {
+        SUBTITLES: 'SUBTITLES',
+        CAPTIONS: 'CAPTIONS',
+        DESCRIPTIONS: 'DESCRIPTIONS',
+        CHAPTERS: 'CHAPTERS',
+        METADATA: 'METADATA'
+    },
     CSS = {
-        URLINPUT: 'atto_media_urlentry',
-        NAMEINPUT: 'atto_media_nameentry'
+        SOURCE: 'atto_media_source',
+        TRACK: 'atto_media_track',
+        MEDIA_SOURCE: 'atto_media_media_source',
+        LINK_SOURCE: 'atto_media_link_source',
+        POSTER_SOURCE: 'atto_media_poster_source',
+        TRACK_SOURCE: 'atto_media_track_source',
+        DISPLAY_OPTIONS: 'atto_media_display_options',
+        NAME_INPUT: 'atto_media_name_entry',
+        URL_INPUT: 'atto_media_url_entry',
+        POSTER_SIZE: 'atto_media_poster_size',
+        LINK_SIZE: 'atto_media_link_size',
+        WIDTH_INPUT: 'atto_media_width_entry',
+        HEIGHT_INPUT: 'atto_media_height_entry',
+        TRACK_KIND_INPUT: 'atto_media_track_kind_entry',
+        TRACK_LABEL_INPUT: 'atto_media_track_label_entry',
+        TRACK_LANG_INPUT: 'atto_media_track_lang_entry',
+        TRACK_DEFAULT_SELECT: 'atto_media_track_default',
+        MEDIA_CONTROLS_TOGGLE: 'atto_media_controls',
+        MEDIA_AUTOPLAY_TOGGLE: 'atto_media_autoplay',
+        MEDIA_MUTE_TOGGLE: 'atto_media_mute',
+        MEDIA_LOOP_TOGGLE: 'atto_media_loop',
+        ADVANCED_SETTINGS: 'atto_media_advancedsettings',
+        LINK: MEDIA_TYPES.LINK.toLowerCase(),
+        VIDEO: MEDIA_TYPES.VIDEO.toLowerCase(),
+        AUDIO: MEDIA_TYPES.AUDIO.toLowerCase(),
+        TRACK_SUBTITLES: TRACK_KINDS.SUBTITLES.toLowerCase(),
+        TRACK_CAPTIONS: TRACK_KINDS.CAPTIONS.toLowerCase(),
+        TRACK_DESCRIPTIONS: TRACK_KINDS.DESCRIPTIONS.toLowerCase(),
+        TRACK_CHAPTERS: TRACK_KINDS.CHAPTERS.toLowerCase(),
+        TRACK_METADATA: TRACK_KINDS.METADATA.toLowerCase()
     },
     SELECTORS = {
-        URLINPUT: '.' + CSS.URLINPUT,
-        NAMEINPUT: '.' + CSS.NAMEINPUT
-    },
-    TEMPLATE = '' +
-        '<form class="atto_form">' +
-            '<label for="{{elementid}}_atto_media_urlentry">{{get_string "enterurl" component}}</label>' +
-            '<input class="fullwidth {{CSS.URLINPUT}}" type="url" id="{{elementid}}_atto_media_urlentry" size="32"/><br/>' +
-            '<button class="openmediabrowser" type="button">{{get_string "browserepositories" component}}</button>' +
-            '<label for="{{elementid}}_atto_media_nameentry">{{get_string "entername" component}}</label>' +
-            '<input class="fullwidth {{CSS.NAMEINPUT}}" type="text" id="{{elementid}}_atto_media_nameentry"' +
-                    'size="32" required="true"/>' +
-            '<div class="mdl-align">' +
-                '<br/>' +
-                '<button class="submit" type="submit">{{get_string "createmedia" component}}</button>' +
-            '</div>' +
-        '</form>';
+        SOURCE: '.' + CSS.SOURCE,
+        TRACK: '.' + CSS.TRACK,
+        MEDIA_SOURCE: '.' + CSS.MEDIA_SOURCE,
+        POSTER_SOURCE: '.' + CSS.POSTER_SOURCE,
+        TRACK_SOURCE: '.' + CSS.TRACK_SOURCE,
+        DISPLAY_OPTIONS: '.' + CSS.DISPLAY_OPTIONS,
+        NAME_INPUT: '.' + CSS.NAME_INPUT,
+        URL_INPUT: '.' + CSS.URL_INPUT,
+        POSTER_SIZE: '.' + CSS.POSTER_SIZE,
+        LINK_SIZE: '.' + CSS.LINK_SIZE,
+        WIDTH_INPUT: '.' + CSS.WIDTH_INPUT,
+        HEIGHT_INPUT: '.' + CSS.HEIGHT_INPUT,
+        TRACK_KIND_INPUT: '.' + CSS.TRACK_KIND_INPUT,
+        TRACK_LABEL_INPUT: '.' + CSS.TRACK_LABEL_INPUT,
+        TRACK_LANG_INPUT: '.' + CSS.TRACK_LANG_INPUT,
+        TRACK_DEFAULT_SELECT: '.' + CSS.TRACK_DEFAULT_SELECT,
+        MEDIA_CONTROLS_TOGGLE: '.' + CSS.MEDIA_CONTROLS_TOGGLE,
+        MEDIA_AUTOPLAY_TOGGLE: '.' + CSS.MEDIA_AUTOPLAY_TOGGLE,
+        MEDIA_MUTE_TOGGLE: '.' + CSS.MEDIA_MUTE_TOGGLE,
+        MEDIA_LOOP_TOGGLE: '.' + CSS.MEDIA_LOOP_TOGGLE,
+        ADVANCED_SETTINGS: '.' + CSS.ADVANCED_SETTINGS,
+        LINK_TAB: 'li[data-medium-type="' + CSS.LINK + '"]',
+        LINK_PANE: '.tab-pane[data-medium-type="' + CSS.LINK + '"]',
+        VIDEO_TAB: 'li[data-medium-type="' + CSS.VIDEO + '"]',
+        VIDEO_PANE: '.tab-pane[data-medium-type="' + CSS.VIDEO + '"]',
+        AUDIO_TAB: 'li[data-medium-type="' + CSS.AUDIO + '"]',
+        AUDIO_PANE: '.tab-pane[data-medium-type="' + CSS.AUDIO + '"]',
+        TRACK_SUBTITLES_TAB: 'li[data-track-kind="' + CSS.TRACK_SUBTITLES + '"]',
+        TRACK_SUBTITLES_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_SUBTITLES + '"]',
+        TRACK_CAPTIONS_TAB: 'li[data-track-kind="' + CSS.TRACK_CAPTIONS + '"]',
+        TRACK_CAPTIONS_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_CAPTIONS + '"]',
+        TRACK_DESCRIPTIONS_TAB: 'li[data-track-kind="' + CSS.TRACK_DESCRIPTIONS + '"]',
+        TRACK_DESCRIPTIONS_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_DESCRIPTIONS + '"]',
+        TRACK_CHAPTERS_TAB: 'li[data-track-kind="' + CSS.TRACK_CHAPTERS + '"]',
+        TRACK_CHAPTERS_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_CHAPTERS + '"]',
+        TRACK_METADATA_TAB: 'li[data-track-kind="' + CSS.TRACK_METADATA + '"]',
+        TRACK_METADATA_PANE: '.tab-pane[data-track-kind="' + CSS.TRACK_METADATA + '"]'
+    },
+    TEMPLATES = {
+        ROOT: '' +
+            '<form class="mform atto_form atto_media" id="{{elementid}}_atto_media_form">' +
+                '<ul class="root nav nav-tabs" role="tablist">' +
+                    '<li data-medium-type="{{CSS.LINK}}" class="nav-item">' +
+                        '<a class="nav-link active" href="#{{elementid}}_{{CSS.LINK}}" role="tab" data-toggle="tab">' +
+                            '{{get_string "link" component}}' +
+                        '</a>' +
+                    '</li>' +
+                    '<li data-medium-type="{{CSS.VIDEO}}" class="nav-item">' +
+                        '<a class="nav-link" href="#{{elementid}}_{{CSS.VIDEO}}" role="tab" data-toggle="tab">' +
+                            '{{get_string "video" component}}' +
+                        '</a>' +
+                    '</li>' +
+                    '<li data-medium-type="{{CSS.AUDIO}}" class="nav-item">' +
+                        '<a class="nav-link" href="#{{elementid}}_{{CSS.AUDIO}}" role="tab" data-toggle="tab">' +
+                            '{{get_string "audio" component}}' +
+                        '</a>' +
+                    '</li>' +
+                '</ul>' +
+                '<div class="root tab-content">' +
+                    '<div data-medium-type="{{CSS.LINK}}" class="tab-pane active" id="{{elementid}}_{{CSS.LINK}}">' +
+                        '{{> tab_panes.link}}' +
+                    '</div>' +
+                    '<div data-medium-type="{{CSS.VIDEO}}" class="tab-pane" id="{{elementid}}_{{CSS.VIDEO}}">' +
+                        '{{> tab_panes.video}}' +
+                    '</div>' +
+                    '<div data-medium-type="{{CSS.AUDIO}}" class="tab-pane" id="{{elementid}}_{{CSS.AUDIO}}">' +
+                        '{{> tab_panes.audio}}' +
+                    '</div>' +
+                '</div>' +
+                '<div class="mdl-align">' +
+                    '<br/>' +
+                    '<button class="submit" type="submit">{{get_string "createmedia" component}}</button>' +
+                '</div>' +
+            '</form>',
+        TAB_PANES: {
+            LINK: '' +
+                '{{renderPartial "form_components.source" context=this id=CSS.LINK_SOURCE}}' +
+                '<label>' +
+                    'Enter name' +
+                    '<input class="fullwidth {{CSS.NAME_INPUT}}" type="text" id="{{elementid}}_link_nameentry"' +
+                        'size="32" required="true"/>' +
+                '</label>',
+            VIDEO: '' +
+                '{{renderPartial "form_components.source" context=this id=CSS.MEDIA_SOURCE entersourcelabel="videosourcelabel"' +
+                    ' addcomponentlabel="addsource" multisource="true" addsourcehelp=helpStrings.addsource}}' +
+                '<fieldset class="collapsible collapsed" id="{{elementid}}_video-display-options">' +
+                    '<input name="mform_isexpanded_{{elementid}}_video-display-options" type="hidden">' +
+                    '<legend class="ftoggler">{{get_string "displayoptions" component}}</legend>' +
+                    '<div class="fcontainer">' +
+                        '{{> form_components.display_options}}' +
+                    '</div>' +
+                '</fieldset>' +
+                '<fieldset class="collapsible collapsed" id="{{elementid}}_video-advanced-settings">' +
+                    '<input name="mform_isexpanded_{{elementid}}_video-advanced-settings" type="hidden">' +
+                    '<legend class="ftoggler">{{get_string "advancedsettings" component}}</legend>' +
+                    '<div class="fcontainer">' +
+                        '{{> form_components.advanced_settings}}' +
+                    '</div>' +
+                '</fieldset>' +
+                '<fieldset class="collapsible collapsed" id="{{elementid}}_video-tracks">' +
+                    '<input name="mform_isexpanded_{{elementid}}_video-tracks" type="hidden">' +
+                    '<legend class="ftoggler">{{get_string "tracks" component}} {{{helpStrings.tracks}}}</legend>' +
+                    '<div class="fcontainer">' +
+                        '{{renderPartial "form_components.track_tabs" context=this id=CSS.VIDEO}}' +
+                    '</div>' +
+                '</fieldset>',
+            AUDIO: '' +
+                '{{renderPartial "form_components.source" context=this id=CSS.MEDIA_SOURCE entersourcelabel="audiosourcelabel"' +
+                    ' addcomponentlabel="addsource" multisource="true" addsourcehelp=helpStrings.addsource}}' +
+                '<fieldset class="collapsible collapsed" id="{{elementid}}_audio-advanced-settings">' +
+                    '<input name="mform_isexpanded_{{elementid}}_audio-advanced-settings" type="hidden">' +
+                    '<legend class="ftoggler">{{get_string "advancedsettings" component}}</legend>' +
+                    '<div class="fcontainer">' +
+                        '{{> form_components.advanced_settings}}' +
+                    '</div>' +
+                '</fieldset>' +
+                '<fieldset class="collapsible collapsed" id="{{elementid}}_audio-tracks">' +
+                    '<input name="mform_isexpanded_{{elementid}}_audio-tracks" type="hidden">' +
+                    '<legend class="ftoggler">{{get_string "tracks" component}} {{{helpStrings.tracks}}}</legend>' +
+                    '<div class="fcontainer">' +
+                        '{{renderPartial "form_components.track_tabs" context=this id=CSS.AUDIO}}' +
+                    '</div>' +
+                '</fieldset>'
+        },
+        FORM_COMPONENTS: {
+            SOURCE: '' +
+                '<div class="{{CSS.SOURCE}} {{id}}">' +
+                    '<label>' +
+                        '{{#entersourcelabel}}{{get_string ../entersourcelabel ../component}}{{/entersourcelabel}}' +
+                        '{{^entersourcelabel}}{{get_string "entersource" ../component}}{{/entersourcelabel}}</a>' +
+                        '<br/>' +
+                        '<input class="{{CSS.URL_INPUT}}" type="url" size="32"/>' +
+                    '</label>' +
+                    '<button class="openmediabrowser" type="button">{{get_string "browserepositories" component}}</button>' +
+                    '{{#multisource}}' +
+                        '{{renderPartial "form_components.add_component" context=../this label=../addcomponentlabel ' +
+                            ' help=../addsourcehelp}}' +
+                    '{{/multisource}}' +
+                '</div>',
+            ADD_COMPONENT: '' +
+                '<div>' +
+                    '<a href="#" class="addcomponent">' +
+                        '{{#label}}{{get_string ../label ../component}}{{/label}}' +
+                        '{{^label}}{{get_string "add" ../component}}{{/label}}' +
+                    '</a>' +
+                    '{{#help}}{{{../help}}}{{/help}}' +
+                '</div>',
+            REMOVE_COMPONENT: '' +
+                '<div>' +
+                    '<a href="#" class="removecomponent">' +
+                        '{{#label}}{{get_string ../label ../component}}{{/label}}' +
+                        '{{^label}}{{get_string "remove" ../component}}{{/label}}' +
+                    '</a>' +
+                '</div>',
+            DISPLAY_OPTIONS: '' +
+                '<div class="{{CSS.DISPLAY_OPTIONS}}">' +
+                    '<label>' +
+                        '{{get_string "size" component}}' +
+                        '<div class={{CSS.POSTER_SIZE}}>' +
+                            '<label>' +
+                                '<span class="accesshide">{{get_string "videowidth" component}}</span>' +
+                                '<input type="text" class="{{CSS.WIDTH_INPUT}} input-mini" size="4"/>' +
+                            '</label>' +
+                            ' x ' +
+                            '<label>' +
+                                '<span class="accesshide">{{get_string "videoheight" component}}</span>' +
+                                '<input type="text" class="{{CSS.HEIGHT_INPUT}} input-mini" size="4"/>' +
+                            '</label>' +
+                        '</div>' +
+                    '</label>' +
+                    '<div class="clearfix"></div>' +
+                    '{{renderPartial "form_components.source" context=this id=CSS.POSTER_SOURCE entersourcelabel="poster"}}' +
+                '<div>',
+            ADVANCED_SETTINGS: '' +
+                '<div class="{{CSS.ADVANCED_SETTINGS}}">' +
+                    '<label>' +
+                        '<input type="checkbox" checked="true" class="{{CSS.MEDIA_CONTROLS_TOGGLE}}"/>' +
+                        '{{get_string "controls" component}}' +
+                    '</label>' +
+                    '<label>' +
+                        '<input type="checkbox" class="{{CSS.MEDIA_AUTOPLAY_TOGGLE}}"/>' +
+                        '{{get_string "autoplay" component}}' +
+                    '</label>' +
+                    '<label>' +
+                        '<input type="checkbox" class="{{CSS.MEDIA_MUTE_TOGGLE}}"/>' +
+                        '{{get_string "mute" component}}' +
+                    '</label>' +
+                    '<label>' +
+                        '<input type="checkbox" class="{{CSS.MEDIA_LOOP_TOGGLE}}"/>' +
+                        '{{get_string "loop" component}}' +
+                    '</label>' +
+                '</div>',
+            TRACK_TABS: '' +
+                '<ul class="nav nav-tabs">' +
+                    '<li data-track-kind="{{CSS.TRACK_SUBTITLES}}" class="nav-item">' +
+                        '<a class="nav-link active" href="#{{elementid}}_{{id}}_{{CSS.TRACK_SUBTITLES}}"' +
+                            ' role="tab" data-toggle="tab">' +
+                            '{{get_string "subtitles" component}}' +
+                        '</a>' +
+                    '</li>' +
+                    '<li data-track-kind="{{CSS.TRACK_CAPTIONS}}" class="nav-item">' +
+                        '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_CAPTIONS}}" role="tab" data-toggle="tab">' +
+                            '{{get_string "captions" component}}' +
+                        '</a>' +
+                    '</li>' +
+                    '<li data-track-kind="{{CSS.TRACK_DESCRIPTIONS}}"  class="nav-item">' +
+                        '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_DESCRIPTIONS}}"' +
+                            ' role="tab" data-toggle="tab">' +
+                            '{{get_string "descriptions" component}}' +
+                        '</a>' +
+                    '</li>' +
+                    '<li data-track-kind="{{CSS.TRACK_CHAPTERS}}" class="nav-item">' +
+                        '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_CHAPTERS}}" role="tab" data-toggle="tab">' +
+                            '{{get_string "chapters" component}}' +
+                        '</a>' +
+                    '</li>' +
+                    '<li data-track-kind="{{CSS.TRACK_METADATA}}" class="nav-item">' +
+                        '<a class="nav-link" href="#{{elementid}}_{{id}}_{{CSS.TRACK_METADATA}}" role="tab" data-toggle="tab">' +
+                            '{{get_string "metadata" component}}' +
+                        '</a>' +
+                    '</li>' +
+                '</ul>' +
+                '<div class="tab-content">' +
+                    '<div data-track-kind="{{CSS.TRACK_SUBTITLES}}" class="tab-pane active"' +
+                        ' id="{{elementid}}_{{id}}_{{CSS.TRACK_SUBTITLES}}">' +
+                        '<div class="trackhelp">{{{helpStrings.subtitles}}}</div>' +
+                        '{{renderPartial "form_components.track" context=this sourcelabel="subtitlessourcelabel"' +
+                            ' addcomponentlabel="addsubtitlestrack"}}' +
+                    '</div>' +
+                    '<div data-track-kind="{{CSS.TRACK_CAPTIONS}}" class="tab-pane"' +
+                        ' id="{{elementid}}_{{id}}_{{CSS.TRACK_CAPTIONS}}">' +
+                        '<div class="trackhelp">{{{helpStrings.captions}}}</div>' +
+                        '{{renderPartial "form_components.track" context=this sourcelabel="captionssourcelabel"' +
+                            ' addcomponentlabel="addcaptionstrack"}}' +
+                    '</div>' +
+                    '<div data-track-kind="{{CSS.TRACK_DESCRIPTIONS}}" class="tab-pane"' +
+                        ' id="{{elementid}}_{{id}}_{{CSS.TRACK_DESCRIPTIONS}}">' +
+                        '<div class="trackhelp">{{{helpStrings.descriptions}}}</div>' +
+                        '{{renderPartial "form_components.track" context=this sourcelabel="descriptionssourcelabel"' +
+                            ' addcomponentlabel="adddescriptionstrack"}}' +
+                    '</div>' +
+                    '<div data-track-kind="{{CSS.TRACK_CHAPTERS}}" class="tab-pane"' +
+                        ' id="{{elementid}}_{{id}}_{{CSS.TRACK_CHAPTERS}}">' +
+                        '<div class="trackhelp">{{{helpStrings.chapters}}}</div>' +
+                        '{{renderPartial "form_components.track" context=this sourcelabel="chapterssourcelabel"' +
+                            ' addcomponentlabel="addchapterstrack"}}' +
+                    '</div>' +
+                    '<div data-track-kind="{{CSS.TRACK_METADATA}}" class="tab-pane"' +
+                        ' id="{{elementid}}_{{id}}_{{CSS.TRACK_METADATA}}">' +
+                        '<div class="trackhelp">{{{helpStrings.metadata}}}</div>' +
+                        '{{renderPartial "form_components.track" context=this sourcelabel="metadatasourcelabel"' +
+                            ' addcomponentlabel="addmetadatatrack"}}' +
+                    '</div>' +
+                '</div>',
+            TRACK: '' +
+                '<div class="{{CSS.TRACK}}">' +
+                    '{{renderPartial "form_components.source" context=this id=CSS.TRACK_SOURCE entersourcelabel=sourcelabel}}' +
+                    '<label class="langlabel">' +
+                        '<span>{{get_string "srclang" component}}</span>' +
+                        '<select class="{{CSS.TRACK_LANG_INPUT}}">' +
+                            '<optgroup label="{{get_string "languagesinstalled" component}}">' +
+                                '{{#langsinstalled}}' +
+                                    '<option value="{{code}}" {{#default}}selected="selected"{{/default}}>{{lang}}</option>' +
+                                '{{/langsinstalled}}' +
+                            '</optgroup>' +
+                            '<optgroup label="{{get_string "languagesavailable" component}} ">' +
+                                '{{#langsavailable}}<option value="{{code}}">{{lang}}</option>{{/langsavailable}}' +
+                            '</optgroup>' +
+                        '</select>' +
+                    '</label>' +
+                    '<label class="labellabel">' +
+                        '<span>{{get_string "label" component}}</span>' +
+                        '<input class="{{CSS.TRACK_LABEL_INPUT}}" type="text"/>' +
+                    '</label>' +
+                    '<label class="defaultlabel">' +
+                        '<input type="checkbox" class="{{CSS.TRACK_DEFAULT_SELECT}}"/>' +
+                        '{{get_string "default" component}}' +
+                    '</label>' +
+                    '{{renderPartial "form_components.add_component" context=this label=addcomponentlabel}}' +
+                '</div>'
+        },
+        HTML_MEDIA: {
+            VIDEO: '' +
+                '&nbsp;<video ' +
+                    '{{#width}}width="{{../width}}" {{/width}}' +
+                    '{{#height}}height="{{../height}}" {{/height}}' +
+                    '{{#poster}}poster="{{../poster}}" {{/poster}}' +
+                    '{{#showControls}}controls="true" {{/showControls}}' +
+                    '{{#loop}}loop="true" {{/loop}}' +
+                    '{{#muted}}muted="true" {{/muted}}' +
+                    '{{#autoplay}}autoplay="true" {{/autoplay}}' +
+                '>' +
+                    '{{#sources}}<source src="{{source}}">{{/sources}}' +
+                    '{{#tracks}}' +
+                        '<track src="{{track}}" kind="{{kind}}" srclang="{{srclang}}" label="{{label}}"' +
+                            ' {{#defaultTrack}}default="true"{{/defaultTrack}}>' +
+                    '{{/tracks}}' +
+                    '{{#description}}{{../description}}{{/description}}' +
+                '</video>&nbsp',
+            AUDIO: '' +
+                '&nbsp;<audio ' +
+                    '{{#showControls}}controls="true" {{/showControls}}' +
+                    '{{#loop}}loop="true" {{/loop}}' +
+                    '{{#muted}}muted="true" {{/muted}}' +
+                    '{{#autoplay}}autoplay="true" {{/autoplay}}' +
+                '>' +
+                    '{{#sources}}<source src="{{source}}">{{/sources}}' +
+                    '{{#tracks}}' +
+                        '<track src="{{track}}" kind="{{kind}}" srclang="{{srclang}}" label="{{label}}"' +
+                            ' {{#defaultTrack}}default="true"{{/defaultTrack}}>' +
+                    '{{/tracks}}' +
+                    '{{#description}}{{../description}}{{/description}}' +
+                '</audio>&nbsp',
+            LINK: '' +
+                '<a href="{{url}}" ' +
+                    '{{#width}}data-width="{{../width}}" {{/width}}' +
+                    '{{#height}}data-height="{{../height}}"{{/height}}' +
+                '>{{#name}}{{../name}}{{/name}}{{^name}}{{../url}}{{/name}}</a>'
+         }
+    };
 
 Y.namespace('M.atto_media').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
 
+    initializer: function() {
+        if (this.get('host').canShowFilepicker('media')) {
+            this.editor.delegate('dblclick', this._displayDialogue, 'video', this);
+            this.editor.delegate('click', this._handleClick, 'video', this);
+
+            this.addButton({
+                icon: 'e/insert_edit_video',
+                callback: this._displayDialogue,
+                tags: 'video, audio',
+                tagMatchRequiresAll: false
+            });
+        }
+    },
+
     /**
-     * A reference to the current selection at the time that the dialogue
-     * was opened.
+     * Gets the root context for all templates, with extra supplied context.
      *
-     * @property _currentSelection
-     * @type Range
+     * @method _getContext
+     * @param  {Object} extra The extra context to add
+     * @return {Object}
      * @private
      */
-    _currentSelection: null,
+    _getContext: function(extra) {
+        return Y.merge({
+            elementid: this.get('host').get('elementid'),
+            component: COMPONENTNAME,
+            langsinstalled: this.get('langs').installed,
+            langsavailable: this.get('langs').available,
+            helpStrings: this.get('help'),
+            CSS: CSS
+        }, extra);
+    },
 
     /**
-     * A reference to the dialogue content.
+     * Handles a click on a media element.
      *
-     * @property _content
-     * @type Node
+     * @method _handleClick
+     * @param  {EventFacade} e
      * @private
      */
-    _content: null,
+    _handleClick: function(e) {
+        var medium = e.target;
 
-    initializer: function() {
-        if (this.get('host').canShowFilepicker('media')) {
-            this.addButton({
-                icon: 'e/insert_edit_video',
-                callback: this._displayDialogue
-            });
+        var selection = this.get('host').getSelectionFromNode(medium);
+        if (this.get('host').getSelection() !== selection) {
+            this.get('host').setSelection(selection);
         }
     },
 
@@ -91,90 +452,502 @@ Y.namespace('M.atto_media').Button = Y.Base.create('button', Y.M.editor_atto.Edi
      * @private
      */
     _displayDialogue: function() {
-        // Store the current selection.
-        this._currentSelection = this.get('host').getSelection();
-        if (this._currentSelection === false) {
+        if (this.get('host').getSelection() === false) {
             return;
         }
 
+        if (!('renderPartial' in Y.Handlebars.helpers)) {
+            (function smashPartials(chain, obj) {
+                Y.each(obj, function(value, index) {
+                    chain.push(index);
+                    if (typeof value !== "object") {
+                        Y.Handlebars.registerPartial(chain.join('.').toLowerCase(), value);
+                    } else {
+                        smashPartials(chain, value);
+                    }
+                    chain.pop();
+                });
+            })([], TEMPLATES);
+
+            Y.Handlebars.registerHelper('renderPartial', function(partialName, options) {
+                if (!partialName) {
+                    return '';
+                }
+
+                var partial = Y.Handlebars.partials[partialName];
+                var parentContext = options.hash.context ? Y.clone(options.hash.context) : {};
+                var context = Y.merge(parentContext, options.hash);
+                delete context.context;
+
+                if (!partial) {
+                    return '';
+                }
+                return new Y.Handlebars.SafeString(Y.Handlebars.compile(partial)(context));
+            });
+        }
+
         var dialogue = this.getDialogue({
             headerContent: M.util.get_string('createmedia', COMPONENTNAME),
             focusAfterHide: true,
-            focusOnShowSelector: SELECTORS.URLINPUT
+            width: 660,
+            focusOnShowSelector: SELECTORS.URL_INPUT
         });
 
         // Set the dialogue content, and then show the dialogue.
-        dialogue.set('bodyContent', this._getDialogueContent())
-                .show();
+        dialogue.set('bodyContent', this._getDialogueContent(this.get('host').getSelection())).show();
+        M.form.shortforms({formid: this.get('host').get('elementid') + '_atto_media_form'});
     },
 
     /**
-     * Return the dialogue content for the tool, attaching any required
-     * events.
+     * Returns the dialogue content for the tool.
      *
      * @method _getDialogueContent
-     * @return {Node} The content to place in the dialogue.
+     * @param  {WrappedRange[]} selection Current editor selection
+     * @return {Y.Node}
      * @private
      */
-    _getDialogueContent: function() {
-        var template = Y.Handlebars.compile(TEMPLATE);
+    _getDialogueContent: function(selection) {
+        var content = Y.Node.create(
+            Y.Handlebars.compile(TEMPLATES.ROOT)(this._getContext())
+        );
 
-        this._content = Y.Node.create(template({
-            component: COMPONENTNAME,
-            elementid: this.get('host').get('elementid'),
-            CSS: CSS
-        }));
+        var medium = this.get('host').getSelectedNodes().filter('video,audio').shift();
+        var mediumProperties = medium ? this._getMediumProperties(medium) : false;
+        return this._attachEvents(this._applyMediumProperties(content, mediumProperties), selection);
+    },
+
+    /**
+     * Attaches required events to the content node.
+     *
+     * @method _attachEvents
+     * @param  {Y.Node}         content The content to which events will be attached
+     * @param  {WrappedRange[]} selection Current editor selection
+     * @return {Y.Node}
+     * @private
+     */
+    _attachEvents: function(content, selection) {
+        // Delegate add component link for media source fields.
+        content.delegate('click', function(e) {
+            e.preventDefault();
+            this._addMediaSourceComponent(e.currentTarget);
+        }, SELECTORS.MEDIA_SOURCE + ' .addcomponent', this);
 
-        this._content.one('.submit').on('click', this._setMedia, this);
-        this._content.one('.openmediabrowser').on('click', function(e) {
+        // Delegate add component link for track fields.
+        content.delegate('click', function(e) {
             e.preventDefault();
-            this.get('host').showFilepicker('media', this._filepickerCallback, this);
+            this._addTrackComponent(e.currentTarget);
+        }, SELECTORS.TRACK + ' .addcomponent', this);
+
+        // Only allow one track per tab to be selected as "default".
+        content.delegate('click', function(e) {
+            var element = e.currentTarget;
+            if (element.get('checked')) {
+                var getKind = function(el) {
+                    return this._getTrackTypeFromTabPane(el.ancestor('.tab-pane'));
+                }.bind(this);
+
+                element.ancestor('.root.tab-content').all(SELECTORS.TRACK_DEFAULT_SELECT).each(function(select) {
+                    if (select !== element && getKind(element) === getKind(select)) {
+                        select.set('checked', false);
+                    }
+                });
+            }
+        }, SELECTORS.TRACK_DEFAULT_SELECT, this);
+
+        // Set up filepicker click event.
+        content.delegate('click', function(e) {
+            var element = e.currentTarget;
+            var fptype = (element.ancestor(SELECTORS.POSTER_SOURCE) && 'image') ||
+                    (element.ancestor(SELECTORS.TRACK_SOURCE) && 'subtitle') ||
+                    'media';
+            e.preventDefault();
+            this.get('host').showFilepicker(fptype, this._getFilepickerCallback(element, fptype), this);
+        }, '.openmediabrowser', this);
+
+        // This is a nasty hack. Basically we are using BS4 markup for the tabs
+        // but it isn't completely backwards compatible with BS2. The main problem is
+        // that the "active" class goes on a different node. So the idea is to put it
+        // the node for BS4, and then use CSS to make it look right in BS2. However,
+        // once another tab is clicked, everything sorts itself out, more or less. Except
+        // that the original "active" tab hasn't had the BS4 "active" class removed
+        // (so the styles will still apply to it). So we need to remove the "active"
+        // class on the BS4 node so that BS2 is happy.
+        //
+        // This doesn't upset BS4 since it removes this class anyway when clicking on
+        // another tab.
+        content.all('.nav-item').on('click', function(elem) {
+            elem.currentTarget.get('parentNode').all('.active').removeClass('active');
+        });
+
+        content.one('.submit').on('click', function(e) {
+            e.preventDefault();
+            var mediaHTML = this._getMediaHTML(e.currentTarget.ancestor('.atto_form')),
+                host = this.get('host');
+            this.getDialogue({
+                focusAfterHide: null
+            }).hide();
+            if (mediaHTML) {
+                host.setSelection(selection);
+                host.insertContentAtFocusPoint(mediaHTML);
+                this.markUpdated();
+            }
         }, this);
 
-        return this._content;
+        return content;
     },
 
     /**
-     * Update the dialogue after an media was selected in the File Picker.
+     * Applies medium properties to the content node.
      *
-     * @method _filepickerCallback
-     * @param {object} params The parameters provided by the filepicker
-     * containing information about the image.
+     * @method _applyMediumProperties
+     * @param  {Y.Node} content The content to apply the properties to
+     * @param  {object} properties The medium properties to apply
+     * @return {Y.Node}
      * @private
      */
-    _filepickerCallback: function(params) {
-        if (params.url !== '') {
-            this._content.one(SELECTORS.URLINPUT)
-                    .set('value', params.url);
-            this._content.one(SELECTORS.NAMEINPUT)
-                    .set('value', params.file);
+    _applyMediumProperties: function(content, properties) {
+        if (!properties) {
+            return content;
         }
+
+        var applyTrackProperties = function(track, properties) {
+            track.one(SELECTORS.TRACK_SOURCE + ' ' + SELECTORS.URL_INPUT).set('value', properties.src);
+            track.one(SELECTORS.TRACK_LANG_INPUT).set('value', properties.srclang);
+            track.one(SELECTORS.TRACK_LABEL_INPUT).set('value', properties.label);
+            track.one(SELECTORS.TRACK_DEFAULT_SELECT).set('checked', properties.defaultTrack);
+        };
+
+        var tabPane = content.one('.root.tab-content > .tab-pane#' + this.get('host').get('elementid') +
+                              '_' + properties.type.toLowerCase());
+
+        // Populate sources.
+        tabPane.one(SELECTORS.MEDIA_SOURCE + ' ' + SELECTORS.URL_INPUT).set('value', properties.sources[0]);
+        Y.Array.each(properties.sources.slice(1), function(source) {
+            this._addMediaSourceComponent(tabPane.one(SELECTORS.MEDIA_SOURCE + ' .addcomponent'), function(newComponent) {
+                newComponent.one(SELECTORS.URL_INPUT).set('value', source);
+            });
+        }, this);
+
+        // Populate tracks.
+        Y.Object.each(properties.tracks, function(value, key) {
+            var trackData = value.length ? value : [{src: '', srclang: '', label: '', defaultTrack: false}];
+            var paneSelector = SELECTORS['TRACK_' + key.toUpperCase() + '_PANE'];
+
+            applyTrackProperties(tabPane.one(paneSelector + ' ' + SELECTORS.TRACK), trackData[0]);
+            Y.Array.each(trackData.slice(1), function(track) {
+                this._addTrackComponent(
+                    tabPane.one(paneSelector + ' ' + SELECTORS.TRACK + ' .addcomponent'), function(newComponent) {
+                    applyTrackProperties(newComponent, track);
+                });
+            }, this);
+        }, this);
+
+        // Populate values.
+        tabPane.one(SELECTORS.POSTER_SOURCE + ' ' + SELECTORS.URL_INPUT).setAttribute('value', properties.poster);
+        tabPane.one(SELECTORS.WIDTH_INPUT).set('value', properties.width);
+        tabPane.one(SELECTORS.HEIGHT_INPUT).set('value', properties.height);
+        tabPane.one(SELECTORS.MEDIA_CONTROLS_TOGGLE).set('checked', properties.controls);
+        tabPane.one(SELECTORS.MEDIA_AUTOPLAY_TOGGLE).set('checked', properties.autoplay);
+        tabPane.one(SELECTORS.MEDIA_MUTE_TOGGLE).set('checked', properties.muted);
+        tabPane.one(SELECTORS.MEDIA_LOOP_TOGGLE).set('checked', properties.loop);
+
+        // Switch to the correct tab.
+        var mediumType = this._getMediumTypeFromTabPane(tabPane);
+
+        // Remove active class from all tabs + tab panes.
+        tabPane.siblings('.active').removeClass('active');
+        content.all('.root.nav-tabs .nav-item a').removeClass('active');
+
+        // Add active class to the desired tab and tab pane.
+        tabPane.addClass('active');
+        content.one(SELECTORS[mediumType.toUpperCase() + '_TAB'] + ' a').addClass('active');
+
+        return content;
+    },
+
+    /**
+     * Extracts medium properties.
+     *
+     * @method _getMediumProperties
+     * @param  {Y.Node} medium The medium node from which to extract
+     * @return {Object}
+     * @private
+     */
+    _getMediumProperties: function(medium) {
+        var boolAttr = function(elem, attr) {
+            return elem.getAttribute(attr) ? true : false;
+        };
+
+        var tracks = {
+            subtitles: [],
+            captions: [],
+            descriptions: [],
+            chapters: [],
+            metadata: []
+        };
+
+        medium.all('track').each(function(track) {
+            tracks[track.getAttribute('kind')].push({
+                src: track.getAttribute('src'),
+                srclang: track.getAttribute('srclang'),
+                label: track.getAttribute('label'),
+                defaultTrack: boolAttr(track, 'default')
+            });
+        });
+
+        return {
+            type: medium.test('video') ? MEDIA_TYPES.VIDEO : MEDIA_TYPES.AUDIO,
+            sources: medium.all('source').get('src'),
+            poster: medium.getAttribute('poster'),
+            width: medium.getAttribute('width'),
+            height: medium.getAttribute('height'),
+            autoplay: boolAttr(medium, 'autoplay'),
+            loop: boolAttr(medium, 'loop'),
+            muted: boolAttr(medium, 'muted'),
+            controls: boolAttr(medium, 'controls'),
+            tracks: tracks
+        };
+    },
+
+    /**
+     * Adds a track form component.
+     *
+     * @method _addTrackComponent
+     * @param  {Y.Node}   element    The element which was used to trigger this function
+     * @param  {Function} [callback] Function to be called when the new component is added
+     *     @param {Y.Node}    callback.newComponent The compiled component
+     * @private
+     */
+    _addTrackComponent: function(element, callback) {
+        var trackType = this._getTrackTypeFromTabPane(element.ancestor('.tab-pane'));
+        var context = this._getContext({
+            sourcelabel: trackType + 'sourcelabel',
+            addcomponentlabel: 'add' + trackType + 'track'
+        });
+
+        this._addComponent(element, TEMPLATES.FORM_COMPONENTS.TRACK, SELECTORS.TRACK, context, callback);
+    },
+
+    /**
+     * Adds a media source form component.
+     *
+     * @method _addMediaSourceComponent
+     * @param  {Y.Node}   element    The element which was used to trigger this function
+     * @param  {Function} [callback] Function to be called when the new component is added
+     *     @param {Y.Node}    callback.newComponent The compiled component
+     * @private
+     */
+    _addMediaSourceComponent: function(element, callback) {
+        var mediumType = this._getMediumTypeFromTabPane(element.ancestor('.tab-pane'));
+        var context = this._getContext({
+            multisource: true,
+            id: CSS.MEDIA_SOURCE,
+            entersourcelabel: mediumType + 'sourcelabel',
+            addcomponentlabel: 'addsource'
+        });
+        this._addComponent(element, TEMPLATES.FORM_COMPONENTS.SOURCE, SELECTORS.MEDIA_SOURCE, context, callback);
     },
 
     /**
-     * Update the media in the contenteditable.
+     * Adds an arbitrary form component.
+     *
+     * This function Compiles and adds the provided component in the supplied 'ancestor' container.
+     * It will also add links to add/remove the relevant components, attaching the
+     * necessary events.
      *
-     * @method setMedia
-     * @param {EventFacade} e
+     * @method _addComponent
+     * @param  {Y.Node}   element    The element which was used to trigger this function
+     * @param  {String}   component  The component to compile and add
+     * @param  {String}   ancestor   A selector used to find an ancestor of 'component', to which
+     *                               the compiled component will be appended
+     * @param  {Object}   context    The context with which to render the component
+     * @param  {Function} [callback] Function to be called when the new component is added
+     *     @param {Y.Node}    callback.newComponent The compiled component
      * @private
      */
-    _setMedia: function(e) {
-        e.preventDefault();
-        this.getDialogue({
-            focusAfterHide: null
-        }).hide();
+    _addComponent: function(element, component, ancestor, context, callback) {
+        var currentComponent = element.ancestor(ancestor),
+            newComponent = Y.Node.create(Y.Handlebars.compile(component)(context)),
+            removeNodeContext = this._getContext(context);
 
-        var form = e.currentTarget.ancestor('.atto_form'),
-            url = form.one(SELECTORS.URLINPUT).get('value'),
-            name = form.one(SELECTORS.NAMEINPUT).get('value'),
-            host = this.get('host');
+        removeNodeContext.label = "remove";
+        var removeNode = Y.Node.create(Y.Handlebars.compile(TEMPLATES.FORM_COMPONENTS.REMOVE_COMPONENT)(removeNodeContext));
 
-        if (url !== '' && name !== '') {
-            host.setSelection(this._currentSelection);
-            var mediahtml = '<a href="' + Y.Escape.html(url) + '">' + name + '</a>';
+        removeNode.one('.removecomponent').on('click', function(e) {
+            e.preventDefault();
+            currentComponent.remove(true);
+        });
 
-            host.insertContentAtFocusPoint(mediahtml);
-            this.markUpdated();
+        currentComponent.insert(newComponent, 'after');
+        element.ancestor().insert(removeNode, 'after');
+        element.ancestor().remove(true);
+
+        if (callback) {
+            callback.call(this, newComponent);
         }
+    },
+
+    /**
+     * Returns the callback for the file picker to call after a file has been selected.
+     *
+     * @method _getFilepickerCallback
+     * @param  {Y.Node} element The element which triggered the callback
+     * @param  {String} fptype  The file pickertype (as would be passed to `showFilePicker`)
+     * @return {Function} The function to be used as a callback when the file picker returns the file
+     * @private
+     */
+    _getFilepickerCallback: function(element, fptype) {
+        return function(params) {
+            if (params.url !== '') {
+                var tabPane = element.ancestor('.tab-pane');
+                element.ancestor(SELECTORS.SOURCE).one(SELECTORS.URL_INPUT).set('value', params.url);
+
+                // Links (and only links) have a name field.
+                if (tabPane.get('id') === this.get('host').get('elementid') + '_' + CSS.LINK) {
+                    tabPane.one(SELECTORS.NAME_INPUT).set('value', params.file);
+                }
+
+                if (fptype === 'subtitle') {
+                    var subtitleLang = params.file.split('.vtt')[0].split('-').slice(-1)[0];
+                    var langObj = this.get('langs').available.reduce(function(carry, lang) {
+                        return lang.code === subtitleLang ? lang : carry;
+                    }, false);
+                    if (langObj) {
+                        element.ancestor(SELECTORS.TRACK).one(SELECTORS.TRACK_LABEL_INPUT).set('value',
+                                langObj.lang.substr(0, langObj.lang.lastIndexOf(' ')));
+                        element.ancestor(SELECTORS.TRACK).one(SELECTORS.TRACK_LANG_INPUT).set('value', langObj.code);
+                    }
+                }
+            }
+        };
+    },
+
+    /**
+     * Given a "medium" tab pane, returns what kind of medium it contains.
+     *
+     * @method _getMediumTypeFromTabPane
+     * @param  {Y.Node} tabPane The tab pane
+     * @return {String} The type of medium in the pane
+     */
+    _getMediumTypeFromTabPane: function(tabPane) {
+        return tabPane.getAttribute('data-medium-type');
+    },
+
+    /**
+     * Given a "track" tab pane, returns what kind of track it contains.
+     *
+     * @method _getTrackTypeFromTabPane
+     * @param  {Y.Node} tabPane The tab pane
+     * @return {String} The type of track in the pane
+     */
+    _getTrackTypeFromTabPane: function(tabPane) {
+        return tabPane.getAttribute('data-track-kind');
+    },
+
+    /**
+     * Returns the HTML to be inserted to the text area.
+     *
+     * @method _getMediaHTML
+     * @param  {Y.Node} form The form from which to extract data
+     * @return {String} The compiled markup
+     * @private
+     */
+    _getMediaHTML: function(form) {
+        var mediumType = this._getMediumTypeFromTabPane(form.one('.root.tab-content > .tab-pane.active'));
+        var tabContent = form.one(SELECTORS[mediumType.toUpperCase() + '_PANE']);
+
+        return this['_getMediaHTML' + mediumType[0].toUpperCase() + mediumType.substr(1)](tabContent);
+    },
+
+    /**
+     * Returns the HTML to be inserted to the text area for the link tab.
+     *
+     * @method _getMediaHTMLLink
+     * @param  {Y.Node} tab The tab from which to extract data
+     * @return {String} The compiled markup
+     * @private
+     */
+    _getMediaHTMLLink: function(tab) {
+        var context = {
+            url: tab.one(SELECTORS.URL_INPUT).get('value'),
+            name: tab.one(SELECTORS.NAME_INPUT).get('value') || false
+        };
+
+        return context.url ? Y.Handlebars.compile(TEMPLATES.HTML_MEDIA.LINK)(context) : '';
+    },
+
+    /**
+     * Returns the HTML to be inserted to the text area for the video tab.
+     *
+     * @method _getMediaHTMLVideo
+     * @param  {Y.Node} tab The tab from which to extract data
+     * @return {String} The compiled markup
+     * @private
+     */
+    _getMediaHTMLVideo: function(tab) {
+        var context = this._getContextForMediaHTML(tab);
+        context.width = tab.one(SELECTORS.WIDTH_INPUT).get('value') || false;
+        context.height = tab.one(SELECTORS.HEIGHT_INPUT).get('value') || false;
+        context.poster = tab.one(SELECTORS.POSTER_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value') || false;
+
+        return context.sources.length ? Y.Handlebars.compile(TEMPLATES.HTML_MEDIA.VIDEO)(context) : '';
+    },
+
+    /**
+     * Returns the HTML to be inserted to the text area for the audio tab.
+     *
+     * @method _getMediaHTMLAudio
+     * @param  {Y.Node} tab The tab from which to extract data
+     * @return {String} The compiled markup
+     * @private
+     */
+    _getMediaHTMLAudio: function(tab) {
+        var context = this._getContextForMediaHTML(tab);
+
+        return context.sources.length ? Y.Handlebars.compile(TEMPLATES.HTML_MEDIA.AUDIO)(context) : '';
+    },
+
+    /**
+     * Returns the context with which to render a media template.
+     *
+     * @method _getContextForMediaHTML
+     * @param  {Y.Node} tab The tab from which to extract data
+     * @return {Object}
+     * @private
+     */
+    _getContextForMediaHTML: function(tab) {
+        var tracks = [];
+
+        tab.all(SELECTORS.TRACK).each(function(track) {
+            tracks.push({
+                track: track.one(SELECTORS.TRACK_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value'),
+                kind: this._getTrackTypeFromTabPane(track.ancestor('.tab-pane')),
+                label: track.one(SELECTORS.TRACK_LABEL_INPUT).get('value') ||
+                    track.one(SELECTORS.TRACK_LANG_INPUT).get('value'),
+                srclang: track.one(SELECTORS.TRACK_LANG_INPUT).get('value'),
+                defaultTrack: track.one(SELECTORS.TRACK_DEFAULT_SELECT).get('checked') ? "true" : null
+            });
+        }, this);
+
+        return {
+            sources: tab.all(SELECTORS.MEDIA_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value').filter(function(source) {
+                return !!source;
+            }).map(function(source) {
+                return {source: source};
+            }),
+            description: tab.one(SELECTORS.MEDIA_SOURCE + ' ' + SELECTORS.URL_INPUT).get('value') || false,
+            tracks: tracks.filter(function(track) {
+                return !!track.track;
+            }),
+            showControls: tab.one(SELECTORS.MEDIA_CONTROLS_TOGGLE).get('checked'),
+            autoplay: tab.one(SELECTORS.MEDIA_AUTOPLAY_TOGGLE).get('checked'),
+            muted: tab.one(SELECTORS.MEDIA_MUTE_TOGGLE).get('checked'),
+            loop: tab.one(SELECTORS.MEDIA_LOOP_TOGGLE).get('checked')
+        };
+    }
+}, {
+    ATTRS: {
+        langs: {},
+        help: {}
     }
 });
diff --git a/lib/editor/atto/tests/fixtures/moodle-logo.mp4 b/lib/editor/atto/tests/fixtures/moodle-logo.mp4
new file mode 100755 (executable)
index 0000000..db47591
Binary files /dev/null and b/lib/editor/atto/tests/fixtures/moodle-logo.mp4 differ
diff --git a/lib/editor/atto/tests/fixtures/pretty-good-en.vtt b/lib/editor/atto/tests/fixtures/pretty-good-en.vtt
new file mode 100755 (executable)
index 0000000..6898d3e
--- /dev/null
@@ -0,0 +1,54 @@
+WEBVTT\r
+\r
+1\r
+00:00:00.530 --> 00:00:00.620\r
+Hey!\r
+\r
+2\r
+00:00:00.620 --> 00:00:00.710\r
+Heey!\r
+\r
+3\r
+00:00:00.710 --> 00:00:00.800\r
+Heeey!\r
+\r
+4\r
+00:00:00.800 --> 00:00:00.890\r
+Heeeey!\r
+\r
+5\r
+00:00:00.890 --> 00:00:00.980\r
+Heeeeey!\r
+\r
+6\r
+00:00:00.980 --> 00:00:01.070\r
+Heeeeeey!\r
+\r
+7\r
+00:00:01.070 --> 00:00:01.160\r
+Heeeeeeey!\r
+\r
+8\r
+00:00:01.160 --> 00:00:01.250\r
+Heeeeeeeey!\r
+\r
+9\r
+00:00:01.250 --> 00:00:01.340\r
+Heeeeeeeey!\r
+\r
+1\r
+00:00:01.340 --> 00:00:01.430\r
+Heeeeeeeeey!\r
+\r
+1\r
+00:00:01.430 --> 00:00:01.510\r
+That's\r
+\r
+1\r
+00:00:01.510 --> 00:00:01.770\r
+Pretty\r
+\r
+1\r
+00:00:01.770 --> 00:00:03.970\r
+Good!\r
+\r
diff --git a/lib/editor/atto/tests/fixtures/pretty-good-sv.vtt b/lib/editor/atto/tests/fixtures/pretty-good-sv.vtt
new file mode 100755 (executable)
index 0000000..32c8f36
--- /dev/null
@@ -0,0 +1,54 @@
+WEBVTT\r
+\r
+1\r
+00:00:00.530 --> 00:00:00.620\r
+Hej!\r
+\r
+2\r
+00:00:00.620 --> 00:00:00.710\r
+Heej!\r
+\r
+3\r
+00:00:00.710 --> 00:00:00.800\r
+Heeej!\r
+\r
+4\r
+00:00:00.800 --> 00:00:00.890\r
+Heeeej!\r
+\r
+5\r
+00:00:00.890 --> 00:00:00.980\r
+Heeeeej!\r
+\r
+6\r
+00:00:00.980 --> 00:00:01.070\r
+Heeeeeej!\r
+\r
+7\r
+00:00:01.070 --> 00:00:01.160\r
+Heeeeeeej!\r
+\r
+8\r
+00:00:01.160 --> 00:00:01.250\r
+Heeeeeeeej!\r
+\r
+9\r
+00:00:01.250 --> 00:00:01.340\r
+Heeeeeeeej!\r
+\r
+1\r
+00:00:01.340 --> 00:00:01.430\r
+Heeeeeeeeej!\r
+\r
+1\r
+00:00:01.430 --> 00:00:01.510\r
+Det är\r
+\r
+1\r
+00:00:01.510 --> 00:00:01.770\r
+Ganska\r
+\r
+1\r
+00:00:01.770 --> 00:00:03.970\r
+Bra!\r
+\r
index 2409966..551e306 100644 (file)
@@ -381,9 +381,19 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element implements templatab
             $link_options->env = 'editor';
             $link_options->itemid = $draftitemid;
 
+            $args->accepted_types = array('.vtt');
+            $subtitle_options = initialise_filepicker($args);
+            $subtitle_options->context = $ctx;
+            $subtitle_options->client_id = uniqid();
+            $subtitle_options->maxbytes  = $this->_options['maxbytes'];
+            $subtitle_options->areamaxbytes  = $this->_options['areamaxbytes'];
+            $subtitle_options->env = 'editor';
+            $subtitle_options->itemid = $draftitemid;
+
             $fpoptions['image'] = $image_options;
             $fpoptions['media'] = $media_options;
             $fpoptions['link'] = $link_options;
+            $fpoptions['subtitle'] = $subtitle_options;
         }
 
         //If editor is required and tinymce, then set required_tinymce option to initalize tinymce validation.
index 4e18268..ec3aa46 100644 (file)
@@ -4922,6 +4922,47 @@ class core_renderer_maintenance extends core_renderer {
 
     }
 
+    /**
+     * Overridden confirm message for upgrades.
+     *
+     * @param string $message The question to ask the user
+     * @param single_button|moodle_url|string $continue The single_button component representing the Continue answer.
+     * @param single_button|moodle_url|string $cancel The single_button component representing the Cancel answer.
+     * @return string HTML fragment
+     */
+    public function confirm($message, $continue, $cancel) {
+        // We need plain styling of confirm boxes on upgrade because we don't know which stylesheet we have (it could be
+        // from any previous version of Moodle).
+        if ($continue instanceof single_button) {
+            $continue->primary = true;
+        } else if (is_string($continue)) {
+            $continue = new single_button(new moodle_url($continue), get_string('continue'), 'post', true);
+        } else if ($continue instanceof moodle_url) {
+            $continue = new single_button($continue, get_string('continue'), 'post', true);
+        } else {
+            throw new coding_exception('The continue param to $OUTPUT->confirm() must be either a URL' .
+                                       ' (string/moodle_url) or a single_button instance.');
+        }
+
+        if ($cancel instanceof single_button) {
+            $output = '';
+        } else if (is_string($cancel)) {
+            $cancel = new single_button(new moodle_url($cancel), get_string('cancel'), 'get');
+        } else if ($cancel instanceof moodle_url) {
+            $cancel = new single_button($cancel, get_string('cancel'), 'get');
+        } else {
+            throw new coding_exception('The cancel param to $OUTPUT->confirm() must be either a URL' .
+                                       ' (string/moodle_url) or a single_button instance.');
+        }
+
+        $output = $this->box_start('generalbox', 'notice');
+        $output .= html_writer::tag('h4', get_string('confirm'));
+        $output .= html_writer::tag('p', $message);
+        $output .= html_writer::tag('div', $this->render($continue) . $this->render($cancel), array('class' => 'buttons'));
+        $output .= $this->box_end();
+        return $output;
+    }
+
     /**
      * Does nothing. The maintenance renderer does not support JS.
      *
index 852d68a..9829426 100644 (file)
@@ -1391,7 +1391,7 @@ function disable_output_buffering() {
  */
 function redirect_if_major_upgrade_required() {
     global $CFG;
-    $lastmajordbchanges = 2016110600.00;
+    $lastmajordbchanges = 2016112200.03;
     if (empty($CFG->version) or (float)$CFG->version < $lastmajordbchanges or
             during_initial_install() or !empty($CFG->adminsetuppending)) {
         try {
index 18bd954..753ab80 100644 (file)
             {{> core/loading }}
         </div>
         {{$anchor}}
-            <a class="see-all-link"
-                href="{{{urls.seeall}}}">
-                <div class="popover-region-footer-container">
-                    <div class="popover-region-seeall-text">{{#str}} seeall, message {{/str}}</div>
-                </div>
-            </a>
+            {{#urls.seeall}}
+                <a class="see-all-link"
+                    href="{{{.}}}">
+                    <div class="popover-region-footer-container">
+                        <div class="popover-region-seeall-text">{{#str}} seeall, message {{/str}}</div>
+                    </div>
+                </a>
+            {{/urls.seeall}}
         {{/anchor}}
     </div>
 </div>
index 533eeaa..b396373 100644 (file)
@@ -542,6 +542,70 @@ class core_blocklib_testcase extends advanced_testcase {
         context_block::instance($tokeep);   // Would throw an exception if it was deleted.
     }
 
+    public function test_create_all_block_instances() {
+        global $CFG, $PAGE, $DB;
+
+        $this->resetAfterTest();
+        $regionname = 'side-pre';
+        $context = context_system::instance();
+
+        $PAGE->reset_theme_and_output();
+        $CFG->theme = 'boost';
+
+        list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname),
+            $context, 'page-type');
+        $blockmanager->load_blocks();
+        $blockmanager->create_all_block_instances();
+        $blocks = $blockmanager->get_blocks_for_region($regionname);
+        $this->assertEmpty($blocks);
+        // There should be no blocks in the DB.
+
+        $PAGE->reset_theme_and_output();
+        // Change to a theme with undeletable blocks.
+        $CFG->theme = 'clean';
+
+        list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname),
+            $context, 'page-type');
+
+        $blockmanager->show_only_fake_blocks(true);
+        $blockmanager->load_blocks();
+        $blockmanager->create_all_block_instances();
+        $blocks = $blockmanager->get_blocks_for_region($regionname);
+        $this->assertEmpty($blocks);
+
+        $PAGE->reset_theme_and_output();
+        list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname),
+            $context, 'page-type');
+
+        $blockmanager->show_only_fake_blocks(false);
+        $blockmanager->load_blocks();
+        $blockmanager->create_all_block_instances();
+        $blocks = $blockmanager->get_blocks_for_region($regionname);
+        $this->assertCount(2, $blocks);
+
+        $undeletable = block_manager::get_undeletable_block_types();
+        foreach ($undeletable as $blockname) {
+            $instance = $DB->get_record('block_instances', array('blockname' => $blockname));
+            $this->assertEquals(1, $instance->requiredbytheme);
+        }
+
+        // Switch back and those auto blocks should not be returned.
+        $PAGE->reset_theme_and_output();
+        $CFG->theme = 'boost';
+
+        list($page, $blockmanager) = $this->get_a_page_and_block_manager(array($regionname),
+            $context, 'page-type');
+        $blockmanager->load_blocks();
+        $blockmanager->create_all_block_instances();
+        $blocks = $blockmanager->get_blocks_for_region($regionname);
+        $this->assertEmpty($blocks);
+        // But they should exist in the DB.
+        foreach ($undeletable as $blockname) {
+            $count = $DB->count_records('block_instances', array('blockname' => $blockname));
+            $this->assertEquals(1, $count);
+        }
+    }
+
 }
 
 /**
index c4fbdad..622c629 100644 (file)
@@ -56,9 +56,9 @@ class user_search_results implements templatable, renderable {
     /**
      * Constructor.
      *
-     * @param array $contacts
-     * @param array $courses
-     * @param array $noncontacts
+     * @param array $contacts list of contacts.
+     * @param array $courses list of courses.
+     * @param array $noncontacts list of nonconcat users.
      */
     public function __construct($contacts, $courses = array(), $noncontacts = array()) {
         $this->contacts = $contacts;
@@ -88,7 +88,13 @@ class user_search_results implements templatable, renderable {
         // Check if there are any courses.
         if (!empty($this->courses)) {
             $data->hascourses = true;
-            $data->courses = $this->courses;
+            $data->courses = [];
+            foreach ($this->courses as $course) {
+                $coursecontext = \context_course::instance($course->id);
+                $course->shortname = external_format_string($course->shortname, $coursecontext->id, true);
+                $course->fullname = external_format_string($course->fullname, $coursecontext->id, true);
+                $data->courses[] = $course;
+            }
         }
 
         // Check if there are any non-contacts.
index 8ec40ad..9f3e6e5 100644 (file)
@@ -670,8 +670,8 @@ class core_message_external extends external_api {
                     new external_single_structure(
                         array(
                             'id' => new external_value(PARAM_INT, 'The course id'),
-                            'shortname' => new external_value(PARAM_NOTAGS, 'The course shortname'),
-                            'fullname' => new external_value(PARAM_NOTAGS, 'The course fullname'),
+                            'shortname' => new external_value(PARAM_TEXT, 'The course shortname'),
+                            'fullname' => new external_value(PARAM_TEXT, 'The course fullname'),
                         )
                     )
                 ),
index a051f93..0993084 100644 (file)
@@ -31,12 +31,12 @@ defined('MOODLE_INTERNAL') || die();
  * @return string The HTML
  */
 function message_popup_render_navbar_output(\renderer_base $renderer) {
-    global $USER, $DB, $CFG;
+    global $USER, $CFG;
 
     // Early bail out conditions.
     if (!isloggedin() || isguestuser() || user_not_fully_set_up($USER) ||
         get_user_preferences('auth_forcepasswordchange') ||
-        (!$USER->policyagreed && $CFG->sitepolicy)) {
+        ($CFG->sitepolicy && !$USER->policyagreed && !is_siteadmin())) {
         return '';
     }
 
@@ -61,6 +61,7 @@ function message_popup_render_navbar_output(\renderer_base $renderer) {
         $context = [
             'userid' => $USER->id,
             'urls' => [
+                'seeall' => (new moodle_url('/message/output/popup/notifications.php'))->out(),
                 'preferences' => (new moodle_url('/message/notificationpreferences.php', ['userid' => $USER->id]))->out(),
             ],
         ];
index 205b557..b517439 100644 (file)
@@ -15,7 +15,7 @@
     along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 }}
 <div class="contact {{#selected}}selected{{/selected}} {{#lastmessage}}{{^isread}}unread{{/isread}}{{/lastmessage}}"
-     data-action="{{#ismessaging}}view-contact-msg{{/ismessaging}}{{^ismessaging}}view-contact-profile{{/ismessaging}}"
+     data-action="view-contact-msg"
      data-userid="{{userid}}" data-messageid="{{#messageid}}{{.}}{{isread}}{{/messageid}}" data-region="contact" tabindex="0">
     <div class="picture">
         <img src="{{profileimageurl}}" alt="" />
index 13250a3..14903fa 100644 (file)
 </div>
 <div class="profile" data-userid="{{userid}}" data-region="profile">
     <div class="user-container">
-        <img class="profile-picture" src="{{profileimageurl}}" alt="" />
+        <a data-action="profile-view" href="#">
+            <img class="profile-picture" src="{{profileimageurl}}"
+                 alt="{{#str}}pictureof, moodle, {{fullname}}{{/str}}"
+                 title="{{#str}}pictureof, moodle, {{fullname}}{{/str}}" />
+        </a>
         <div class="name-container">
             <div class="name">{{fullname}}</div>
             <div class="status {{#isonline}}online{{/isonline}}">
     </div>
     <div class="actions">
         <div class="separator">
-            <a data-action="profile-view" href="#">{{#str}}viewprofile{{/str}}</a>
-        </div>
-        <div class="separator">
-            <a data-action="profile-send-message" href="#">{{#str}}viewmessages, message{{/str}}</a>
+            <a data-action="profile-send-message" href="#">{{#str}}sendmessage, message{{/str}}</a>
         </div>
         <div class="separator">
             {{#isblocked}}
index 7aff12b..c8f2a8c 100644 (file)
@@ -44,6 +44,30 @@ class behat_message extends behat_base {
      * @param string $userfullname
      */
     public function i_view_contact_in_messages($userfullname) {
+        // Visit home page and follow messages.
+        $this->i_select_user_in_messaging($userfullname);
+
+        $this->execute('behat_general::i_click_on_in_the',
+            array(
+                "//button[@data-action='view-contact-profile']
+                [contains(normalize-space(.), '" . $this->escape($userfullname) . "')]",
+                'xpath_element',
+                ".messages-header",
+                "css_element",
+            )
+        );
+
+        $this->execute('behat_general::wait_until_the_page_is_ready');
+    }
+
+    /**
+     * Select a user in the messaging UI.
+     *
+     * @Given /^I select "(?P<user_full_name_string>(?:[^"]|\\")*)" user in messaging$/
+     * @param string $userfullname
+     */
+    public function i_select_user_in_messaging($userfullname) {
+
         // Visit home page and follow messages.
         $this->execute("behat_general::i_am_on_homepage");
 
@@ -63,7 +87,7 @@ class behat_message extends behat_base {
         // can occur in two separate divs on the page.
         $this->execute('behat_general::i_click_on_in_the',
             array(
-                "//div[@data-action='view-contact-profile']
+                "//div[@data-action='view-contact-msg']
                 [./div[contains(normalize-space(.), '" . $this->escape($userfullname) . "')]]",
                 'xpath_element',
                 "[data-region='messaging-area'] [data-region='search-results-area']",
@@ -74,6 +98,7 @@ class behat_message extends behat_base {
         $this->execute('behat_general::wait_until_the_page_is_ready');
     }
 
+
     /**
      * Sends a message to the specified user from the logged user. The user full name should contain the first and last names.
      *
@@ -82,11 +107,7 @@ class behat_message extends behat_base {
      * @param string $userfullname
      */
     public function i_send_message_to_user($messagecontent, $userfullname) {
-        $this->i_view_contact_in_messages($userfullname);
-
-        $this->execute("behat_general::i_click_on", array("[data-action='profile-send-message']", 'css_element'));
-
-        $this->execute('behat_general::wait_until_the_page_is_ready');
+        $this->i_select_user_in_messaging($userfullname);
 
         $this->execute('behat_forms::i_set_the_field_with_xpath_to',
             array("//textarea[@data-region='send-message-txt']", $this->escape($messagecontent))
index a6aa695..6098672 100644 (file)
@@ -81,7 +81,7 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I log in as "student1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     Then I should see "50"
     And I should see "Great job! Lol, not really."
 
@@ -109,6 +109,6 @@ Feature: Assignments correctly add feedback to the grade report when workflow an
     And I log in as "student1"
     And I follow "Course 1"
     And I click on "Grades" "link" in the "Navigation" "block"
-    And I set the field "Grade report" to "User report"
+    And I follow "User report"
     Then I should see "50"
     And I should see "Great job! Lol, not really."
index 43d1a22..386a6ab 100644 (file)
@@ -498,6 +498,7 @@ M.core_filepicker.show = function(Y, options) {
     if (!M.core_filepicker.instances[options.client_id]) {
         M.core_filepicker.init(Y, options);
     }
+    M.core_filepicker.instances[options.client_id].options.formcallback = options.formcallback;
     M.core_filepicker.instances[options.client_id].show();
 };
 
index 919a930..7797ed0 100644 (file)
Binary files a/theme/boost/amd/build/drawer.min.js and b/theme/boost/amd/build/drawer.min.js differ
index ce208ba..2096135 100644 (file)
@@ -63,6 +63,10 @@ define(['jquery', 'core/custom_interaction_events', 'core/log'],
         }.bind(this));
 
         this.registerEventListeners();
+        var small = $(document).width() < 768;
+        if (small) {
+            this.closeAll();
+        }
     };
 
     Drawer.prototype.closeAll = function() {
@@ -99,7 +103,7 @@ define(['jquery', 'core/custom_interaction_events', 'core/log'],
         body.addClass('drawer-ease');
         var open = trigger.attr('aria-expanded') == 'true';
         if (!open) {
-            var small = $(document).width() < 512;
+            var small = $(document).width() < 768;
             if (small) {
                 this.closeAll();
             }
index 09bc67c..59f1989 100644 (file)
 /**
  * Autoprefixer.
  *
+ * This autoprefixer has been developed to satisfy the basic needs of the
+ * theme Boost when working with Bootstrap 4 alpha. We do not recommend
+ * that this tool is shared, nor used outside of this theme.
+ *
  * @package    theme_boost
  * @copyright  2016 Frédéric Massart - FMCorz.net
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
index 91dcd52..90fe242 100644 (file)
@@ -593,6 +593,20 @@ $popout-header-height: 4rem;
     padding: 0;
 }
 
+.path-mod-assign [data-region="grade-panel"] .gradingform_guide .remark .commentchooser {
+    float: none;
+}
+.path-mod-assign [data-region="grade-panel"] .gradingform_guide .markingguideremark {
+    width: 100%;
+}
+.path-mod-assign [data-region="grade-panel"] .mform .fitem .felement[data-fieldtype="grading"] {
+    padding-left: $spacer;
+    padding-right: $spacer;
+}
+.path-mod-assign [data-region="grade-panel"] .showmarkerdesc,
+.path-mod-assign [data-region="grade-panel"] .showstudentdesc {
+    background-color: $card-bg;
+}
 
 /**
  * Mod LTI.
index 6eca870..e7f40ba 100644 (file)
@@ -1,4 +1,4 @@
-<div tabindex="0" class="file-picker fp-generallayout container row" role="dialog" aria-live="assertive">
+<div tabindex="0" class="file-picker fp-generallayout container-fluid row" role="dialog" aria-live="assertive">
     <div class="fp-repo-area col-md-3 nav nav-pills nav-stacked">
             <div class="fp-repo nav-item">
                 <a href="#" class="nav-link"><img class="fp-repo-icon" alt=" " width="16" height="16" />&nbsp;<span class="fp-repo-name"></span></a>
@@ -58,4 +58,4 @@
             <div class="fp-content card"></div>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
index 465ea4d..95e8614 100644 (file)
@@ -1,3 +1,91 @@
+{{!
+    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/>.
+}}
+{{!
+    @template core/single_select
+
+    Moodle template for a single select submit form.
+
+    Context variables required for this template:
+    * name - Element name.
+    * method - get or post.
+    * action - the action url to submit to.
+    * classes - Element classes.
+    * label - Element label.
+    * disabled - true if this element is disabled.
+    * title - Element title.
+    * formid - optional id value for the form.
+    * id - id for the element.
+    * params - array of params with name and value attributes.
+    * hasnothing - true if element has no options.
+    * nothingkey - nothing key to be set.
+    * options - Array of options for the select with value, name , slected and optgroup properites.
+    * labelattributes - Label attributes.
+    * helpicon - Help icon.
+
+    Example context (json):
+    {
+        "name": "lang",
+        "method": "get",
+        "action": "http://localhost/stable_master/mod/scorm/player.php",
+        "classes": "autosubmit langmenu",
+        "label": "Zombies are coming...",
+        "disabled": false,
+        "title": null,
+        "formid": "randomid",
+        "id": "single_select5833dd4f4b08d108",
+        "params": [
+            {
+                "name": "scoid",
+                "value": "12"
+            },
+            {
+                "name": "cm",
+                "value": "15"
+            },
+            {
+                "name": "mode",
+                "value": "review"
+            },
+            {
+                "name": "currentorg",
+                "value": "eXeMapADrive4823c6301cf72b22b72"
+            }
+        ],
+        "hasnothing": false,
+        "nothingkey": false,
+        "options": [
+            {
+                "value": "en",
+                "name": "English ‎(en)‎",
+                "selected": true,
+                "optgroup": false
+            },
+            {
+                "value": "ar",
+                "name": "Muhaaaa..",
+                "selected": false,
+                "optgroup": false
+            }
+        ],
+        "labelattributes": [],
+        "helpicon": false
+    }
+}}
+
 <div class="{{classes}} d-inline-block">
     <form method="{{method}}" action="{{action}}" class="form-inline" id="{{formid}}">
         {{#params}}
                 {{#optgroup}}
                     <optgroup label="{{name}}">
                     {{#options}}
-                    <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+                    <option value="{{value}}" {{#selected}}selected{{/selected}}>{{{name}}}</option>
                     {{/options}}
                     </optgroup>
                 {{/optgroup}}
                 {{^optgroup}}
-                    <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
+                    <option value="{{value}}" {{#selected}}selected{{/selected}}>{{{name}}}</option>
                 {{/optgroup}}
             {{/options}}
         </select>
diff --git a/theme/boost/templates/gradingform_guide/comment_chooser.mustache b/theme/boost/templates/gradingform_guide/comment_chooser.mustache
new file mode 100644 (file)
index 0000000..1199a1e
--- /dev/null
@@ -0,0 +1,57 @@
+{{!
+    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/>.
+}}
+{{!
+    @template gradingform_guide/comment_chooser
+
+    Moodle comment chooser template for marking guide.
+
+    The purpose of this template is to render a list of frequently used comments that can be used by the comment chooser dialog.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * criterionId The criterion ID this chooser template is being generated for.
+    * comments Array of id / description pairs.
+
+    Example context (json):
+    {
+        "criterionId": "1",
+        "comments": [
+            {
+                "id": "1",
+                "description": "Test comment description 1"
+            },
+            {
+                "id": "2",
+                "description": "Test comment description 2"
+            }
+        ]
+    }
+}}
+<div class="gradingform_guide_comment_chooser" id="comment_chooser">
+    <div class="list-group">
+        {{#comments}}
+            <button class="list-group-item list-group-item-action" id="comment-option-{{criterionId}}-{{id}}" tabindex="0">
+                {{description}}
+            </button>
+        {{/comments}}
+    </div>
+</div>
index 3fe8f59..0b31b18 100644 (file)
@@ -204,7 +204,21 @@ img.iconsmall {
 .modal.modal-in-page {
     position: static;
     z-index: 0;
-    margin: 0;
+    margin: 0 auto 0 auto;
+}
+
+// We need to keep this rule because confirm messages during upgrade use it. We can never change it because we don't
+// serve new stylesheets until after upgrade.
+#page-admin-index #notice {
+    width: 60%;
+    min-width: 220px;
+    margin: auto;
+
+    .buttons {
+        padding-left: 0;
+        padding-right: 0;
+        text-align: center;
+    }
 }
 
 #page-admin-index .releasenoteslink,
index 0bcaad4..23db289 100644 (file)
@@ -2789,7 +2789,17 @@ img.iconsmall {
 .modal.modal-in-page {
   position: static;
   z-index: 0;
-  margin: 0;
+  margin: 0 auto 0 auto;
+}
+#page-admin-index #notice {
+  width: 60%;
+  min-width: 220px;
+  margin: auto;
+}
+#page-admin-index #notice .buttons {
+  padding-left: 0;
+  padding-right: 0;
+  text-align: center;
 }
 #page-admin-index .releasenoteslink,
 #page-admin-index .adminwarning,
index 9a17dd6..7efedcb 100644 (file)
@@ -3,39 +3,39 @@ information provided here is intended especially for theme designer.
 
 === 3.2 ===
 
-* mod_chat will not display the 'course theme' option for all themes (previously it was only displayed on
-  bootstrap2 based themes).
-* A new theme config 'undeletableblocktypes' allows a theme to define which blocks are deletable.
-* A new core setting now enables admins to upload the logos of their site. Using the
-  following methods, themers can instantly support branding logos without the need
-  to implement specific theme settings:
-  * $OUTPUT->get_logo_url($maxwidth, $maxheight);
-  * $OUTPUT->get_compact_logo_url($maxwidth, $maxheight);
-Removed themes:
- * base, canvas
-   During the upgrade process the themes will be uninstalled and all their settings will be deleted.
-   If you wish to keep your theme and its settings, download it from moodle.org and put it back in
-   the theme/ directory BEFORE UPGRADING.
- * CLI svgtool.php has moved from theme/base/cli to admin/cli and paths should be relative to the new location.
+* Removed themes: base, canvas
+  During the upgrade process the themes will be uninstalled and all their settings will be deleted.
+  If you wish to keep your theme and its settings, download it from moodle.org and put it back in
+  the theme/ directory BEFORE UPGRADING.
 * Bootstrap 4 was added as part of a the new theme 'boost'.
-* Themes can now automatically compile SCSS on the fly. This works the same way as it
-  does compiling LESS on the fly, effecitvely adding $THEME->scss to your config.
-* Two new callbacks allow themes to inject SCSS code before and after the content provided
-  by the SCSS file $THEME->scss. See $THEME->prescsscallback and $THEME->extrascsscallback.
-* $THEME->scss can also be a Closure which will return the main SCSS content.
+* Some backwards and forwards compatibility has been added for different bootstrap versions.
+  This is to allow the same markup to work in "clean" and "boost" themes a lot of the time.
+  It is also to allow user text with bootstrap classes to keep working in the new theme.
 * Using .dir-rtl for RTL styling is deprecated and should not be used any more. From now
   the styles are automatically flipped when the language is right-to-left. However,
   as this is not always perfect, you can define exceptions. Please refer to the documentation
   of the tool RTLCSS-PHP for more information: https://github.com/moodlehq/rtlcss-php
+* Themes can now automatically compile SCSS on the fly. This works the same way as it
+  does compiling LESS on the fly, effecitvely adding $THEME->scss to your config. The
+  latter can either be the name a SCSS file (without extension) in your theme's scss/ folder,
+  or a Closure which will return the main SCSS content.
+* Two new callbacks allow themes to inject SCSS code before and after the content provided
+  by the SCSS file $THEME->scss. See $THEME->prescsscallback and $THEME->extrascsscallback.
+* A new callback can be defined to post process the CSS using an object representation
+  of the CSS tree ($THEME->csstreepostprocess). This gives a lot more flexibility than a
+  simple find and replace. Refer to 'boost' for an example, and to PHP-CSS-Parser.
+  (https://github.com/sabberworm/PHP-CSS-Parser) for the API.
+* A new core setting now enables admins to upload the logos of their site. Using the
+  following methods, themers can instantly support branding logos without the need
+  to implement specific theme settings:
+  * $OUTPUT->get_logo_url($maxwidth, $maxheight);
+  * $OUTPUT->get_compact_logo_url($maxwidth, $maxheight);
 * The class .dir-ltr should not be used any more. To force LTR styles use the directive
   to remove the rule when the language is RTL. See RTLCSS-PHP for more information.
 * A new class .text-ltr may be used to force the direction to LTR. This is especially useful
   for forms fields (numbers, emails, URLs must not be RTL'd), and for displaying code
   snippets or configuration samples.
-* A new callback can be defined to post process the CSS using an object representation
-  of the CSS tree ($THEME->csstreepostprocess). This gives a lot more flexibility than a
-  simplel find and replace. Refer to 'boost' for an example, and to PHP-CSS-Parser
-  (https://github.com/sabberworm/PHP-CSS-Parser) for the API.
+* A new theme config 'undeletableblocktypes' allows a theme to define which blocks are deletable.
 * You may no longer override the following core_course_renderer methods.
   See course/upgrade.txt for more information:
   - course_modchooser_module_types
@@ -54,6 +54,9 @@ Removed themes:
   - css_is_width
   - css_sort_by_count
   - class css_optimiser no longer does anything.
+* CLI svgtool.php has moved from theme/base/cli to admin/cli and paths should be relative to the new location.
+* mod_chat will now display the 'course theme' option for all themes (previously it was only displayed on
+  bootstrap2 based themes).
 
 === 3.1 ===
 
index 17b2534..adc3726 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2016112200.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2016112200.03;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.