Merge branch 'MDL-62342-master' of git://github.com/mihailges/moodle
authorDavid Monllao <davidm@moodle.com>
Tue, 4 Sep 2018 12:33:50 +0000 (14:33 +0200)
committerDavid Monllao <davidm@moodle.com>
Tue, 4 Sep 2018 12:33:50 +0000 (14:33 +0200)
89 files changed:
admin/environment.xml
blog/lib.php
calendar/classes/external/event_exporter_base.php
calendar/lib.php
calendar/templates/event_item.mustache
completion/classes/api.php
completion/tests/behat/completion_no_calendar_capabilities.feature [new file with mode: 0644]
completion/tests/capabilities_test.php [new file with mode: 0644]
course/classes/management_renderer.php
course/lib.php
course/tests/behat/course_category_management_listing.feature
course/tests/courselib_test.php
filter/algebra/algebradebug.php
filter/tex/lib.php
filter/tex/texdebug.php
lang/en/admin.php
lang/en/error.php
lib/amd/build/form-autocomplete.min.js
lib/amd/src/form-autocomplete.js
lib/db/upgrade.php
lib/editor/atto/lib.php
lib/messagelib.php
lib/outputrenderers.php
lib/phpunit/tests/advanced_test.php
lib/templates/form_autocomplete_input.mustache
lib/templates/paging_bar.mustache [moved from theme/boost/templates/core/paging_bar.mustache with 85% similarity]
lib/tests/messagelib_test.php
lib/tests/tablelib_test.php
lib/upgradelib.php
message/tests/externallib_test.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/tests/behat/assign_no_calendar_capabilities.feature [new file with mode: 0644]
mod/assign/tests/lib_test.php
mod/chat/lib.php
mod/chat/tests/behat/chat_no_calendar_capabilities.feature [new file with mode: 0644]
mod/chat/tests/lib_test.php
mod/choice/locallib.php
mod/choice/tests/behat/choice_no_calendar_capabilities.feature [new file with mode: 0644]
mod/choice/tests/lib_test.php
mod/data/edit.php
mod/data/export.php
mod/data/field.php
mod/data/locallib.php
mod/data/preset.php
mod/data/templates.php
mod/data/tests/behat/data_no_calendar_capabilities.feature [new file with mode: 0644]
mod/data/tests/lib_test.php
mod/feedback/lib.php
mod/feedback/tests/behat/feedback_no_calendar_capabilities.feature [new file with mode: 0644]
mod/feedback/tests/lib_test.php
mod/lesson/lib.php
mod/lesson/tests/behat/lesson_no_calendar_capabilities.feature [new file with mode: 0644]
mod/lesson/tests/lib_test.php
mod/quiz/lib.php
mod/quiz/tests/behat/quiz_no_calendar_capabilities.feature [new file with mode: 0644]
mod/quiz/tests/lib_test.php
mod/scorm/locallib.php
mod/scorm/tests/behat/scorm_no_calendar_capabilities.feature [new file with mode: 0644]
mod/scorm/tests/lib_test.php
mod/workshop/amd/build/modform.min.js [new file with mode: 0644]
mod/workshop/amd/src/modform.js [new file with mode: 0644]
mod/workshop/backup/moodle1/lib.php
mod/workshop/backup/moodle2/backup_workshop_stepslib.php
mod/workshop/backup/moodle2/restore_workshop_stepslib.php
mod/workshop/classes/external/workshop_summary_exporter.php
mod/workshop/db/install.xml
mod/workshop/db/upgrade.php
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/mod_form.php
mod/workshop/submission_form.php
mod/workshop/tests/behat/delete_submission.feature
mod/workshop/tests/behat/export_submission.feature
mod/workshop/tests/behat/file_type_restriction.feature
mod/workshop/tests/behat/submission_types.feature [new file with mode: 0644]
mod/workshop/tests/behat/workshop_assessment.feature
mod/workshop/tests/external_test.php
mod/workshop/upgrade.txt
mod/workshop/version.php
theme/boost/classes/output/core_renderer.php
theme/boost/scss/moodle/core.scss
theme/boost/style/moodle.css
theme/boost/templates/core/form_autocomplete_input.mustache
user/lib.php
version.php

index 2b8d4ef..fbda3f3 100644 (file)
       </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
+  <MOODLE version="3.6" requires="3.1">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mariadb" version="5.5.31" />
+      <VENDOR name="mysql" version="5.6" />
+      <VENDOR name="postgres" version="9.4" />
+      <VENDOR name="mssql" version="10.0" />
+      <VENDOR name="oracle" version="11.2" />
+    </DATABASE>
+    <PHP version="7.0.0" level="required">
+    </PHP>
+    <PCREUNICODE level="optional">
+      <FEEDBACK>
+        <ON_CHECK message="pcreunicodewarning" />
+      </FEEDBACK>
+    </PCREUNICODE>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="opensslrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zlib" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="gdrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlreader" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="intlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" level="required"/>
+      <PHP_EXTENSION name="fileinfo" level="required"/>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="96M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="opcache.enable" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opcacherecommended" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+    <CUSTOM_CHECKS>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_storage_engine" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbstorageengine" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="question/engine/upgrade/upgradelib.php" function="quiz_attempts_upgraded" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="quizattemptsupgradedmessage" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="slashargumentswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unsupporteddbtablerowformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_unoconv_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unoconvwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_libcurl_version" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="libcurlwarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_format" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbfileformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_file_per_table" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddbfilepertable" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_large_prefix" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="unsupporteddblargeprefix" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_is_https" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="ishttpswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_mysql_incomplete_unicode_support" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="incompleteunicodesupport" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_sixtyfour_bits" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="sixtyfourbitswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+    </CUSTOM_CHECKS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index 2e53d97..b81c645 100644 (file)
@@ -135,6 +135,25 @@ function blog_remove_associations_for_course($courseid) {
     $DB->delete_records('blog_association', array('contextid' => $context->id));
 }
 
+/**
+ * Remove module associated blogs and blog tag instances.
+ *
+ * @param  int $modcontextid Module context ID.
+ */
+function blog_remove_associations_for_module($modcontextid) {
+    global $DB;
+
+    if (!empty($assocblogids = $DB->get_fieldset_select('blog_association', 'blogid',
+        'contextid = :contextid', ['contextid' => $modcontextid]))) {
+        list($sql, $params) = $DB->get_in_or_equal($assocblogids, SQL_PARAMS_NAMED);
+
+        $DB->delete_records_select('tag_instance', "itemid $sql", $params);
+        $DB->delete_records_select('post', "id $sql AND module = :module",
+            array_merge($params, ['module' => 'blog']));
+        $DB->delete_records('blog_association', ['contextid' => $modcontextid]);
+    }
+}
+
 /**
  * Given a record in the {blog_external} table, checks the blog's URL
  * for new entries not yet copied into Moodle.
index 770b5c7..d20cfb3 100644 (file)
@@ -255,6 +255,9 @@ class event_exporter_base extends exporter {
         $values['iscourseevent'] = false;
         $values['iscategoryevent'] = false;
         if ($moduleproxy = $event->get_course_module()) {
+            // We need a separate property to flag if an event is action event.
+            // That's required because canedit return true but action action events cannot be edited on the calendar UI.
+            // But they are considered editable because you can drag and drop the event on the month view.
             $values['isactionevent'] = true;
         } else if ($event->get_type() == 'course') {
             $values['iscourseevent'] = true;
index 3d8be58..2a0bc58 100644 (file)
@@ -426,11 +426,20 @@ class calendar_event {
      * Pass in a object containing the event properties and this function will
      * insert it into the database and deal with any associated files
      *
+     * Capability checking should be performed if the user is directly manipulating the event
+     * and no other capability has been tested. However if the event is not being manipulated
+     * directly by the user and another capability has been checked for them to do this then
+     * capabilites should not be checked.
+     *
+     * For example if a user is editing an event in the calendar the check should be true,
+     * but if you are updating an event in an activities settings are changed then the calendar
+     * capabilites should not be checked.
+     *
      * @see self::create()
      * @see self::update()
      *
      * @param \stdClass $data object of event
-     * @param bool $checkcapability if moodle should check calendar managing capability or not
+     * @param bool $checkcapability If Moodle should check the user can manage the calendar events for this call or not.
      * @return bool event updated
      */
     public function update($data, $checkcapability=true) {
@@ -914,10 +923,19 @@ class calendar_event {
     }
 
     /**
-     * Creates a new event and returns an event object
+     * Creates a new event and returns an event object.
+     *
+     * Capability checking should be performed if the user is directly creating the event
+     * and no other capability has been tested. However if the event is not being created
+     * directly by the user and another capability has been checked for them to do this then
+     * capabilites should not be checked.
+     *
+     * For example if a user is creating an event in the calendar the check should be true,
+     * but if you are creating an event in an activity when it is created then the calendar
+     * capabilites should not be checked.
      *
      * @param \stdClass|array $properties An object containing event properties
-     * @param bool $checkcapability Check caps or not
+     * @param bool $checkcapability If Moodle should check the user can manage the calendar events for this call or not.
      * @throws \coding_exception
      *
      * @return calendar_event|bool The event object or false if it failed
index 157a652..109d86c 100644 (file)
                             {{#pix}}t/delete, core, {{#str}}delete{{/str}}{{/pix}}
                         </a>
                     {{/candelete}}
-                    <a href="{{editurl}}" data-action="edit">
-                        {{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}}
-                    </a>
+                    {{^isactionevent}}
+                        <a href="{{editurl}}" data-action="edit">
+                            {{#pix}}t/edit, core, {{#str}}edit{{/str}}{{/pix}}
+                        </a>
+                    {{/isactionevent}}
                 {{/canedit}}
             </div>
             {{#icon}}<div class="d-inline-block mt-1 align-top">{{#pix}} {{key}}, {{component}}, {{alttext}} {{/pix}}</div>{{/icon}}
index f8040c5..b569e37 100644 (file)
@@ -94,7 +94,7 @@ class api {
                 $event->timeduration = 0;
 
                 $calendarevent = \calendar_event::load($event->id);
-                $calendarevent->update($event);
+                $calendarevent->update($event, false);
             } else {
                 // Calendar event is no longer needed.
                 $calendarevent = \calendar_event::load($event->id);
@@ -115,7 +115,7 @@ class api {
                 $event->visible = instance_is_visible($modulename, $instance);
                 $event->timeduration = 0;
 
-                \calendar_event::create($event);
+                \calendar_event::create($event, false);
             }
         }
 
diff --git a/completion/tests/behat/completion_no_calendar_capabilities.feature b/completion/tests/behat/completion_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..f690f76
--- /dev/null
@@ -0,0 +1,44 @@
+@core @core_completion
+Feature: Completion with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create activities with completion enabled without calendar capabilities
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode | enablecompletion |
+      | Course 1 | C1 | 0 | 1 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing completion date
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Forum" to section "1" and I fill the form with:
+      | Forum name | Test forum name |
+      | Description | Test forum description |
+      | Completion tracking | Show activity as complete when conditions are met |
+      | id_completionexpected_enabled | 1 |
+      | id_completionexpected_day | 1 |
+      | id_completionexpected_month | 1 |
+      | id_completionexpected_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test forum name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_completionexpected_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test forum name"
diff --git a/completion/tests/capabilities_test.php b/completion/tests/capabilities_test.php
new file mode 100644 (file)
index 0000000..5dd4239
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Tests that completion works without requiring unnecessary capabilities.
+ *
+ * @package    core_completion
+ * @copyright  2018 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tests that completion works without requiring unnecessary capabilities.
+ *
+ * @package    core_completion
+ * @copyright  2018 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_completion_capabilities_testcase extends advanced_testcase {
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create activities.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course(['enablecompletion' => 1]);
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_forum');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $params = array(
+            'course' => $course->id,
+            'completionexpected' => time() + 2000,
+        );
+        $generator->create_instance($params);
+    }
+}
index 300af2c..40bef75 100644 (file)
@@ -571,47 +571,14 @@ class core_course_management_renderer extends plugin_renderer_base {
             $html .= html_writer::div($str, 'listing-pagination-totals dimmed');
         }
 
-        if ($totalcourses <= $perpage) {
-            return $html;
-        }
-        $aside = 2;
-        $span = $aside * 2 + 1;
-        $start = max($page - $aside, 0);
-        $end = min($page + $aside, $totalpages - 1);
-        if (($end - $start) < $span) {
-            if ($start == 0) {
-                $end = min($totalpages - 1, $span - 1);
-            } else if ($end == ($totalpages - 1)) {
-                $start = max(0, $end - $span + 1);
-            }
-        }
-        $items = array();
         if ($viewmode !== 'default') {
             $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id,
                 'view' => $viewmode));
         } else {
             $baseurl = new moodle_url('/course/management.php', array('categoryid' => $category->id));
         }
-        if ($page > 0) {
-            $items[] = $this->action_button(new moodle_url($baseurl, array('page' => 0)), get_string('first'));
-            $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page - 1)), get_string('prev'));
-            $items[] = '...';
-        }
-        for ($i = $start; $i <= $end; $i++) {
-            $class = '';
-            if ($page == $i) {
-                $class = 'active-page';
-            }
-            $pageurl = new moodle_url($baseurl, array('page' => $i));
-            $items[] = $this->action_button($pageurl, $i + 1, null, $class, get_string('pagea', 'moodle', $i+1));
-        }
-        if ($page < ($totalpages - 1)) {
-            $items[] = '...';
-            $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $page + 1)), get_string('next'));
-            $items[] = $this->action_button(new moodle_url($baseurl, array('page' => $totalpages - 1)), get_string('last'));
-        }
 
-        $html .= html_writer::div(join('', $items), 'listing-pagination');
+        $html .= $this->output->paging_bar($totalcourses, $page, $perpage, $baseurl);
         return $html;
     }
 
@@ -1055,7 +1022,7 @@ class core_course_management_renderer extends plugin_renderer_base {
         }
 
         $menu = new action_menu;
-        $menu->attributes['class'] .= ' view-mode-selector vms';
+        $menu->attributes['class'] .= ' view-mode-selector vms ml-1';
 
         $selected = null;
         foreach ($modes as $mode => $modestr) {
@@ -1079,7 +1046,7 @@ class core_course_management_renderer extends plugin_renderer_base {
 
         $menu->set_menu_trigger($selected);
 
-        $html = html_writer::start_div('view-mode-selector vms');
+        $html = html_writer::start_div('view-mode-selector vms d-flex');
         $html .= get_string('viewing').' '.$this->render($menu);
         $html .= html_writer::end_div();
 
index bf54e66..aba5bdc 100644 (file)
@@ -1169,6 +1169,9 @@ function course_delete_module($cmid, $async = false) {
         }
     }
 
+    // Delete associated blogs and blog tag instances.
+    blog_remove_associations_for_module($modcontext->id);
+
     // Delete completion and availability data; it is better to do this even if the
     // features are not turned on, in case they were turned on previously (these will be
     // very quick on an empty table).
index f09cae2..5bef53f 100644 (file)
@@ -385,7 +385,7 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 9" before "Course 10"
     And I should see course listing "Course 10" before "Course 11"
     And I should see course listing "Course 11" before "Course 12"
-    And "#course-listing .listing-pagination" "css_element" should not exist
+    And "#course-listing .pagination" "css_element" should not exist
     And I click on "Per page: 20" "link" in the ".course-listing-actions" "css_element"
     And I should see "5" in the ".courses-per-page" "css_element"
     And I should see "10" in the ".courses-per-page" "css_element"
@@ -407,16 +407,15 @@ Feature: Course category management interface performs as expected
     And I should not see "Course 10"
     And I should not see "Course 11"
     And I should not see "Course 12"
-    And "#course-listing .listing-pagination" "css_element" should exist
+    And "#course-listing .pagination" "css_element" should exist
     And I should see "Showing courses 1 to 5 of 12 courses"
-    And I should not see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "2" "link" in the "#course-listing .listing-pagination" "css_element"
+    And I should not see "First" in the "#course-listing .pagination" "css_element"
+    And I should not see "Prev" in the "#course-listing .pagination" "css_element"
+    And I should see "1" in the "#course-listing .pagination" "css_element"
+    And I should see "2" in the "#course-listing .pagination" "css_element"
+    And I should see "3" in the "#course-listing .pagination" "css_element"
+    And I should see "Next" in the "#course-listing .pagination" "css_element"
+    And I click on "2" "link" in the "#course-listing .pagination" "css_element"
     And a new page should have loaded since I started watching
     And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
@@ -432,16 +431,14 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 9" before "Course 10"
     And I should not see "Course 11"
     And I should not see "Course 12"
-    And "#course-listing .listing-pagination" "css_element" should exist
+    And "#course-listing .pagination" "css_element" should exist
     And I should see "Showing courses 6 to 10 of 12 courses"
-    And I should see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "Next" "link" in the "#course-listing .listing-pagination" "css_element"
+    And I should see "Prev" in the "#course-listing .pagination" "css_element"
+    And I should see "1" in the "#course-listing .pagination" "css_element"
+    And I should see "2" in the "#course-listing .pagination" "css_element"
+    And I should see "3" in the "#course-listing .pagination" "css_element"
+    And I should see "Next" in the "#course-listing .pagination" "css_element"
+    And I click on "Next" "link" in the "#course-listing .pagination" "css_element"
     And a new page should have loaded since I started watching
     And I start watching to see if a new page loads
     And I should see the "Course categories and courses" management page
@@ -457,66 +454,14 @@ Feature: Course category management interface performs as expected
     And I should not see "Course 9" in the "#course-listing" "css_element"
     And I should not see "Course 10" in the "#course-listing" "css_element"
     And I should see course listing "Course 11" before "Course 12"
-    And "#course-listing .listing-pagination" "css_element" should exist
+    And "#course-listing .pagination" "css_element" should exist
     And I should see "Showing courses 11 to 12 of 12 courses"
-    And I should see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "First" "link" in the "#course-listing .listing-pagination" "css_element"
-    And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
-    And I should see the "Course categories and courses" management page
-    And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
-    And I should see course listing "Course 1" before "Course 2"
-    And I should see course listing "Course 2" before "Course 3"
-    And I should see course listing "Course 3" before "Course 4"
-    And I should see course listing "Course 4" before "Course 5"
-    And I should not see "Course 6" in the "#course-listing" "css_element"
-    And I should not see "Course 7" in the "#course-listing" "css_element"
-    And I should not see "Course 8" in the "#course-listing" "css_element"
-    And I should not see "Course 9" in the "#course-listing" "css_element"
-    And I should not see "Course 10" in the "#course-listing" "css_element"
-    And I should not see "Course 11" in the "#course-listing" "css_element"
-    And I should not see "Course 12" in the "#course-listing" "css_element"
-    And "#course-listing .listing-pagination" "css_element" should exist
-    And I should see "Showing courses 1 to 5 of 12 courses"
-    And I should not see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "Last" "link" in the "#course-listing .listing-pagination" "css_element"
-    And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
-    And I should see the "Course categories and courses" management page
-    And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
-    And I should see "Course 11" in the "#course-listing" "css_element"
-    And I should not see "Course 2" in the "#course-listing" "css_element"
-    And I should not see "Course 3" in the "#course-listing" "css_element"
-    And I should not see "Course 4" in the "#course-listing" "css_element"
-    And I should not see "Course 5" in the "#course-listing" "css_element"
-    And I should not see "Course 6" in the "#course-listing" "css_element"
-    And I should not see "Course 7" in the "#course-listing" "css_element"
-    And I should not see "Course 8" in the "#course-listing" "css_element"
-    And I should not see "Course 9" in the "#course-listing" "css_element"
-    And I should not see "Course 10" in the "#course-listing" "css_element"
-    And I should see course listing "Course 11" before "Course 12"
-    And "#course-listing .listing-pagination" "css_element" should exist
-    And I should see "Showing courses 11 to 12 of 12 courses"
-    And I should see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "Prev" "link" in the "#course-listing .listing-pagination" "css_element"
+    And I should see "Prev" in the "#course-listing .pagination" "css_element"
+    And I should see "1" in the "#course-listing .pagination" "css_element"
+    And I should see "2" in the "#course-listing .pagination" "css_element"
+    And I should see "3" in the "#course-listing .pagination" "css_element"
+    And I should not see "Next" in the "#course-listing .pagination" "css_element"
+    And I click on "Prev" "link" in the "#course-listing .pagination" "css_element"
     And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
@@ -531,15 +476,13 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 9" before "Course 10"
     And I should not see "Course 11"
     And I should not see "Course 12"
-    And "#course-listing .listing-pagination" "css_element" should exist
+    And "#course-listing .pagination" "css_element" should exist
     And I should see "Showing courses 6 to 10 of 12 courses"
-    And I should see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
+    And I should see "Prev" in the "#course-listing .pagination" "css_element"
+    And I should see "1" in the "#course-listing .pagination" "css_element"
+    And I should see "2" in the "#course-listing .pagination" "css_element"
+    And I should see "3" in the "#course-listing .pagination" "css_element"
+    And I should see "Next" in the "#course-listing .pagination" "css_element"
 
   Scenario: Test pagination is only shown when required
     Given the following "categories" exist:
@@ -566,7 +509,7 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 2" before "Course 3"
     And I should see course listing "Course 3" before "Course 4"
     And I should see course listing "Course 4" before "Course 5"
-    And "#course-listing .listing-pagination" "css_element" should not exist
+    And "#course-listing .pagination" "css_element" should not exist
     And I click on "5" "link" in the ".course-listing-actions" "css_element"
     # Redirect
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
@@ -574,7 +517,7 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 2" before "Course 3"
     And I should see course listing "Course 3" before "Course 4"
     And I should see course listing "Course 4" before "Course 5"
-    And "#course-listing .listing-pagination" "css_element" should not exist
+    And "#course-listing .pagination" "css_element" should not exist
 
   # We need at least 30 courses for this next test.
   @javascript
@@ -645,7 +588,7 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 19" before "Course 20"
     And I should see course listing "Course 21" before "Course 22"
     And I should see course listing "Course 31" before "Course 32"
-    And "#course-listing .listing-pagination" "css_element" should not exist
+    And "#course-listing .pagination" "css_element" should not exist
     And I click on "Per page: 100" "link" in the ".course-listing-actions" "css_element"
     And I click on "5" "link" in the ".courses-per-page" "css_element"
     And a new page should have loaded since I started watching
@@ -656,37 +599,9 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 4" before "Course 5"
     And I should not see "Course 6"
     And I should see "Showing courses 1 to 5 of 32 courses"
-    And I should not see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "4" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "5" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "6" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "7" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "Last" "link" in the "#course-listing .listing-pagination" "css_element"
-    And a new page should have loaded since I started watching
-    And I start watching to see if a new page loads
-    And I should see the "Course categories and courses" management page
-    And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
-    And I should not see "Course 30"
-    And I should see course listing "Course 31" before "Course 32"
-    And I should see "Showing courses 31 to 32 of 32 courses"
-    And I should see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "4" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "5" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "6" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "7" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "Last" in the "#course-listing .listing-pagination" "css_element"
-    And I click on "4" "link" in the "#course-listing .listing-pagination" "css_element"
+    And I should not see "Prev" in the "#course-listing .pagination" "css_element"
+    And I should see "Next" in the "#course-listing .pagination" "css_element"
+    And I click on "4" "link" in the "#course-listing .pagination" "css_element"
     And a new page should have loaded since I started watching
     And I should see the "Course categories and courses" management page
     And I should see "Per page: 5" in the ".course-listing-actions" "css_element"
@@ -697,17 +612,6 @@ Feature: Course category management interface performs as expected
     And I should see course listing "Course 19" before "Course 20"
     And I should not see "Course 21"
     And I should see "Showing courses 16 to 20 of 32 courses"
-    And I should see "First" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Prev" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "1" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "2" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "3" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "4" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "5" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "6" in the "#course-listing .listing-pagination" "css_element"
-    And I should not see "7" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Next" in the "#course-listing .listing-pagination" "css_element"
-    And I should see "Last" in the "#course-listing .listing-pagination" "css_element"
 
   Scenario: Test clicking to edit a course.
     Given the following "categories" exist:
index 249948d..ce18504 100644 (file)
@@ -303,6 +303,50 @@ class core_course_courselib_testcase extends advanced_testcase {
         return $moduleinfo;
     }
 
+    /**
+     * Create module associated blog and tags.
+     *
+     * @param object $course Course.
+     * @param object $modulecontext The context of the module.
+     */
+    private function create_module_asscociated_blog($course, $modulecontext) {
+        global $DB, $CFG;
+
+        // Create default group.
+        $group = new stdClass();
+        $group->courseid = $course->id;
+        $group->name = 'Group';
+        $group->id = $DB->insert_record('groups', $group);
+
+        // Create default user.
+        $user = $this->getDataGenerator()->create_user(array(
+            'username' => 'testuser',
+            'firstname' => 'Firsname',
+            'lastname' => 'Lastname'
+        ));
+
+        // Create default post.
+        $post = new stdClass();
+        $post->userid = $user->id;
+        $post->groupid = $group->id;
+        $post->content = 'test post content text';
+        $post->module = 'blog';
+        $post->id = $DB->insert_record('post', $post);
+
+        // Create default tag.
+        $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
+            'rawname' => 'Testtagname', 'isstandard' => 1));
+        // Apply the tag to the blog.
+        $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user',
+            'component' => 'core', 'itemid' => $post->id, 'ordering' => 0));
+
+        require_once($CFG->dirroot . '/blog/locallib.php');
+        $blog = new blog_entry($post->id);
+        $blog->add_association($modulecontext->id);
+
+        return $blog;
+    }
+
     /**
      * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
      */
@@ -1521,6 +1565,8 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Get the module context.
         $modcontext = context_module::instance($module->cmid);
 
+        $assocblog = $this->create_module_asscociated_blog($course, $modcontext);
+
         // Verify context exists.
         $this->assertInstanceOf('context_module', $modcontext);
 
@@ -1565,6 +1611,18 @@ class core_course_courselib_testcase extends advanced_testcase {
         $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
         $this->assertEmpty($cmcount);
 
+        // Verify the blog_association record has been deleted.
+        $this->assertCount(0, $DB->get_records('blog_association',
+                array('contextid' => $modcontext->id)));
+
+        // Verify the blog post record has been deleted.
+        $this->assertCount(0, $DB->get_records('post',
+                array('id' => $assocblog->id)));
+
+        // Verify the tag instance record has been deleted.
+        $this->assertCount(0, $DB->get_records('tag_instance',
+                array('itemid' => $assocblog->id)));
+
         // Test clean up of module specific messes.
         switch ($type) {
             case 'assign':
index 52cf068..cf5bf96 100644 (file)
@@ -338,17 +338,8 @@ Mathematics Tools</a> forum in the Using Moodle course on moodle.org.</li>
 running Unix, a likely cause is that the mimetex binary you are using is
 incompatible with your operating system. You can try compiling it from the
 C sources downloaded from <a href="http://www.forkosh.com/mimetex.zip">
-http://www.forkosh.com/mimetex.zip</a>, or looking for an appropriate
-binary at <a href="http://moodle.org/download/mimetex/">
-http://moodle.org/download/mimetex/</a>. You may then also need to
-edit your moodle/filter/algebra/pix.php file to add
-<br /><?php echo "case &quot;" . PHP_OS . "&quot;:" ;?><br ?> to the list of operating systems
-in the switch (PHP_OS) statement. Windows users may have a problem properly
-unzipping mimetex.exe. Make sure that mimetex.exe is is <b>PRECISELY</b>
-433152 bytes in size. If not, download fresh copy from
-<a href="http://moodle.org/download/mimetex/windows/mimetex.exe">
-http://moodle.org/download/mimetex/windows/mimetex.exe</a>. Lastly check
-the execute permissions on your mimetex binary, as outlined in item 2 above.</li>
+http://www.forkosh.com/mimetex.zip</a>. Lastly check the execute permissions
+on your mimetex binary, as outlined in item 2 above.</li>
 </ol>
 </body>
 </html>
index 4aad3ec..f579fa5 100644 (file)
@@ -29,15 +29,6 @@ defined('MOODLE_INTERNAL') || die();
 function filter_tex_get_executable($debug=false) {
     global $CFG;
 
-    $error_message1 = "Your system is not configured to run mimeTeX. You need to download the appropriate<br />"
-                     ."executable for you ".PHP_OS." platform from <a href=\"http://moodle.org/download/mimetex/\">"
-                     ."http://moodle.org/download/mimetex/</a>, or obtain the C source<br /> "
-                     ."from <a href=\"http://www.forkosh.com/mimetex.zip\">"
-                     ."http://www.forkosh.com/mimetex.zip</a>, compile it and "
-                     ."put the executable into your<br /> moodle/filter/tex/ directory.";
-
-    $error_message2 = "Custom mimetex is not executable!<br /><br />";
-
     if ((PHP_OS == "WINNT") || (PHP_OS == "WIN32") || (PHP_OS == "Windows")) {
         return "$CFG->dirroot/filter/tex/mimetex.exe";
     }
index e713d57..dadbe18 100644 (file)
@@ -377,16 +377,7 @@ If this fails or is not available, the Mimetex executable is tried. If this
 fails a likely cause is that the mimetex binary you are using is
 incompatible with your operating system. You can try compiling it from the
 C sources downloaded from <a href="http://www.forkosh.com/mimetex.zip">
-http://www.forkosh.com/mimetex.zip</a>, or looking for an appropriate
-binary at <a href="http://moodle.org/download/mimetex/">
-http://moodle.org/download/mimetex/</a>. You may then also need to
-edit your moodle/filter/tex/pix.php file to add
-<br /><?php echo "case &quot;" . PHP_OS . "&quot;:" ;?><br ?> to the list of operating systems
-in the switch (PHP_OS) statement. Windows users may have a problem properly
-unzipping mimetex.exe. Make sure that mimetex.exe is is <b>PRECISELY</b>
-433152 bytes in size. If not, download a fresh copy from
-<a href="http://moodle.org/download/mimetex/windows/mimetex.exe">
-http://moodle.org/download/mimetex/windows/mimetex.exe</a>.
+http://www.forkosh.com/mimetex.zip</a>.
 Another possible problem which may affect
 both Unix and Windows servers is that the web server doesn't have execute permission
 on the mimetex binary. In that case change permissions accordingly</li>
index 6c38f65..500b20e 100644 (file)
@@ -1080,6 +1080,7 @@ $string['sitepolicyhandlerplugin'] = '{$a->name} ({$a->component})';
 $string['sitepolicyguest'] = 'Site policy URL for guests';
 $string['sitepolicyguest_help'] = 'The URL of the site policy that all guests must see and agree to before accessing the site. Note that this setting will only have an effect if the site policy handler is set to default (core).';
 $string['sitesectionhelp'] = 'If selected, a topic section will be displayed on the site\'s front page.';
+$string['sixtyfourbitswarning'] = 'It has been detected that your site is not using a 64-bit PHP version. It is recommended that you upgrade your site to ensure future compatibility.';
 $string['slasharguments'] = 'Use slash arguments';
 $string['slashargumentswarning'] = 'It is recommended that the use of slash arguments is enabled. In future it will be required. For more details, see the documentation <a href="https://docs.moodle.org/en/admin/environment/slasharguments">Using slash arguments</a>.';
 $string['smartpix'] = 'Smart pix search';
index f15f401..95b9450 100644 (file)
@@ -383,7 +383,7 @@ $string['loginasonecourse'] = 'You cannot enter this course.<br /> You have to t
 $string['maxbytesfile'] = 'The file {$a->file} is too large. The maximum size you can upload is {$a->size}.';
 $string['maxareabytes'] = 'The file is larger than the space remaining in this area.';
 $string['messagingdisable'] = 'Messaging is disabled on this site';
-$string['mimetexisnotexist'] = 'Your system is not configured to run mimeTeX. You need to download the appropriate executable for you PHP_OS platform from <a href="http://moodle.org/download/mimetex/">http://moodle.org/download/mimetex/</a>, or obtain the C source from <a href="http://www.forkosh.com/mimetex.zip"> http://www.forkosh.com/mimetex.zip</a>, compile it and put the executable into your moodle/filter/tex/ directory.';
+$string['mimetexisnotexist'] = 'Your system is not configured to run mimeTeX. You need to obtain the C source from <a href="http://www.forkosh.com/mimetex.zip">http://www.forkosh.com/mimetex.zip</a>, compile it and put the executable into your moodle/filter/tex/ directory.';
 $string['mimetexnotexecutable'] = 'Custom mimetex is not executable!';
 $string['missingfield'] = 'Field "{$a}" is missing';
 $string['missingkeyinsql'] = 'ERROR: missing param "{$a}" in query';
index 7755fdc..0369c31 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index b4c0053..58a7fc2 100644 (file)
@@ -882,9 +882,23 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
             context.options = suggestions;
             context.items = [];
 
-            var renderInput = templates.render('core/form_autocomplete_input', context);
-            var renderDatalist = templates.render('core/form_autocomplete_suggestions', context);
-            var renderSelection = templates.render('core/form_autocomplete_selection', context);
+            // Collect rendered inline JS to be executed once the HTML is shown.
+            var collectedjs = '';
+
+            var renderInput = templates.render('core/form_autocomplete_input', context).then(function(html, js) {
+                collectedjs += js;
+                return html;
+            });
+
+            var renderDatalist = templates.render('core/form_autocomplete_suggestions', context).then(function(html, js) {
+                collectedjs += js;
+                return html;
+            });
+
+            var renderSelection = templates.render('core/form_autocomplete_selection', context).then(function(html, js) {
+                collectedjs += js;
+                return html;
+            });
 
             return $.when(renderInput, renderDatalist, renderSelection).then(function(input, suggestions, selection) {
                 originalSelect.hide();
@@ -892,6 +906,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                 originalSelect.after(input);
                 originalSelect.after(selection);
 
+                templates.runTemplateJS(collectedjs);
+
                 // Update the form label to point to the text input.
                 originalLabel.attr('for', state.inputId);
                 // Add the event handlers.
index 8fbb66a..80f33f8 100644 (file)
@@ -2301,5 +2301,32 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2018073000.00);
     }
 
+    if ($oldversion < 2018083100.01) {
+        // Remove module associated blog posts for non-existent (deleted) modules.
+        $sql = "SELECT ba.contextid as modcontextid
+                  FROM {blog_association} ba
+                  JOIN {post} p
+                       ON p.id = ba.blogid
+             LEFT JOIN {context} c
+                       ON c.id = ba.contextid
+                 WHERE p.module = :module
+                       AND c.contextlevel IS NULL
+              GROUP BY ba.contextid";
+        if ($deletedmodules = $DB->get_records_sql($sql, array('module' => 'blog'))) {
+            foreach ($deletedmodules as $module) {
+                $assocblogids = $DB->get_fieldset_select('blog_association', 'blogid',
+                    'contextid = :contextid', ['contextid' => $module->modcontextid]);
+                list($sql, $params) = $DB->get_in_or_equal($assocblogids, SQL_PARAMS_NAMED);
+
+                $DB->delete_records_select('tag_instance', "itemid $sql", $params);
+                $DB->delete_records_select('post', "id $sql AND module = :module",
+                    array_merge($params, ['module' => 'blog']));
+                $DB->delete_records('blog_association', ['contextid' => $module->modcontextid]);
+            }
+        }
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2018083100.01);
+    }
+
     return true;
 }
index 220d1ef..bcd5c38 100644 (file)
@@ -180,8 +180,8 @@ class atto_texteditor extends texteditor {
         }
         $contentcss     = $PAGE->theme->editor_css_url()->out(false);
 
-        // Autosave disabled for guests.
-        if (isguestuser()) {
+        // Autosave disabled for guests and not logged in users.
+        if (isguestuser() OR !isloggedin()) {
             $autosave = false;
         }
         // Note <> is a safe separator, because it will not appear in the output of s().
index 060e508..16ccefe 100644 (file)
@@ -63,7 +63,8 @@ function message_send(\core\message\message $eventdata) {
     // Fetch default (site) preferences
     $defaultpreferences = get_message_output_default_preferences();
     $preferencebase = $eventdata->component.'_'.$eventdata->name;
-    // If message provider is disabled then don't do any processing.
+
+    // If the message provider is disabled via preferences, then don't send the message.
     if (!empty($defaultpreferences->{$preferencebase.'_disable'})) {
         return $messageid;
     }
@@ -88,6 +89,20 @@ function message_send(\core\message\message $eventdata) {
         return false;
     }
 
+    // If the provider's component is disabled or the user can't receive messages from it, don't send the message.
+    $isproviderallowed = false;
+    foreach (message_get_providers_for_user($eventdata->userto->id) as $provider) {
+        if ($provider->component === $eventdata->component && $provider->name === $eventdata->name) {
+            $isproviderallowed = true;
+            break;
+        }
+    }
+    if (!$isproviderallowed) {
+        debugging('Attempt to send msg from a provider '.$eventdata->component.'/'.$eventdata->name.
+            ' that is inactive or not allowed for the user id='.$eventdata->userto->id, DEBUG_NORMAL);
+        return false;
+    }
+
     // Verify all necessary data fields are present.
     if (!isset($eventdata->userto->auth) or !isset($eventdata->userto->suspended)
             or !isset($eventdata->userto->deleted) or !isset($eventdata->userto->emailstop)) {
index 10b589d..454adf3 100644 (file)
@@ -3007,41 +3007,15 @@ EOD;
     }
 
     /**
-     * Internal implementation of paging bar rendering.
+     * Returns HTML to display the paging bar.
      *
      * @param paging_bar $pagingbar
-     * @return string
+     * @return string the HTML to output.
      */
     protected function render_paging_bar(paging_bar $pagingbar) {
-        $output = '';
-        $pagingbar = clone($pagingbar);
-        $pagingbar->prepare($this, $this->page, $this->target);
-
-        if ($pagingbar->totalcount > $pagingbar->perpage) {
-            $output .= get_string('page') . ':';
-
-            if (!empty($pagingbar->previouslink)) {
-                $output .= ' (' . $pagingbar->previouslink . ') ';
-            }
-
-            if (!empty($pagingbar->firstlink)) {
-                $output .= ' ' . $pagingbar->firstlink . ' ...';
-            }
-
-            foreach ($pagingbar->pagelinks as $link) {
-                $output .= "  $link";
-            }
-
-            if (!empty($pagingbar->lastlink)) {
-                $output .= ' ... ' . $pagingbar->lastlink . ' ';
-            }
-
-            if (!empty($pagingbar->nextlink)) {
-                $output .= '  (' . $pagingbar->nextlink . ')';
-            }
-        }
-
-        return html_writer::tag('div', $output, array('class' => 'paging'));
+        // Any more than 10 is not usable and causes weird wrapping of the pagination.
+        $pagingbar->maxdisplay = 10;
+        return $this->render_from_template('core/paging_bar', $pagingbar->export_for_template($this));
     }
 
     /**
index 2aaa254..39c7c16 100644 (file)
@@ -516,21 +516,16 @@ class core_phpunit_advanced_testcase extends advanced_testcase {
         $message3->smallmessage      = 'small message';
         $message3->notification      = 0;
 
-        try {
-            message_send($message3);
-            $this->fail('coding expcetion expected if invalid component specified');
-        } catch (moodle_exception $e) {
-            $this->assertInstanceOf('coding_exception', $e);
-        }
+        $this->assertFalse(message_send($message3));
+        $this->assertDebuggingCalled('Attempt to send msg from a provider xxxx_yyyyy/instantmessage '.
+            'that is inactive or not allowed for the user id='.$user1->id);
 
         $message3->component = 'moodle';
         $message3->name      = 'yyyyyy';
-        try {
-            message_send($message3);
-            $this->fail('coding expcetion expected if invalid name specified');
-        } catch (moodle_exception $e) {
-            $this->assertInstanceOf('coding_exception', $e);
-        }
+
+        $this->assertFalse(message_send($message3));
+        $this->assertDebuggingCalled('Attempt to send msg from a provider moodle/yyyyyy '.
+            'that is inactive or not allowed for the user id='.$user1->id);
 
         message_send($message1);
         $this->assertEquals(1, $sink->count());
index ad064f2..7bf5032 100644 (file)
 {{^showSuggestions}}
 <input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}"/>
 {{/showSuggestions}}
+
+{{#js}}
+require(['jquery'], function($) {
+    // Set the minimum width of the input so that the placeholder is whole displayed.
+    var inputElement = $(document.getElementById('{{inputId}}'));
+    if (inputElement.length) {
+        inputElement.css('min-width', inputElement.attr('placeholder').length + 'ch');
+    }
+});
+{{/js}}
similarity index 85%
rename from theme/boost/templates/core/paging_bar.mustache
rename to lib/templates/paging_bar.mustache
index 5dedc98..93ef0f8 100644 (file)
@@ -1,9 +1,9 @@
 {{#haspages}}
-    <nav aria-label="{{label}}">
-        <ul class="pagination mt-3">
+    <nav aria-label="{{label}}" class="pagination pagination-centered justify-content-center">
+        <ul class="m-t-1 pagination ">
             {{#previous}}
                 <li class="page-item">
-                    <a href="{{url}}" class="page-link">
+                    <a href="{{url}}" class="page-link" aria-label="Previous">
                         <span aria-hidden="true">&laquo;</span>
                         <span class="sr-only">{{#str}}previous{{/str}}</span>
                     </a>
@@ -37,7 +37,7 @@
             {{/last}}
             {{#next}}
                 <li class="page-item">
-                    <a href="{{url}}" class="page-link">
+                    <a href="{{url}}" class="page-link" aria-label="Next">
                         <span aria-hidden="true">&raquo;</span>
                         <span class="sr-only">{{#str}}next{{/str}}</span>
                     </a>
index ed6e8ab..679175c 100644 (file)
@@ -287,6 +287,8 @@ class core_messagelib_testcase extends advanced_testcase {
             $this->assertInstanceOf('coding_exception', $e);
         }
         $this->assertCount(0, $sink->get_messages());
+        $this->assertDebuggingCalled('Attempt to send msg from a provider xxxxx/instantmessage '.
+            'that is inactive or not allowed for the user id='.$user2->id);
 
         $message->component = 'moodle';
         $message->name = 'xxx';
@@ -297,6 +299,8 @@ class core_messagelib_testcase extends advanced_testcase {
             $this->assertInstanceOf('coding_exception', $e);
         }
         $this->assertCount(0, $sink->get_messages());
+        $this->assertDebuggingCalled('Attempt to send msg from a provider moodle/xxx '.
+            'that is inactive or not allowed for the user id='.$user2->id);
         $sink->close();
         $this->assertFalse($DB->record_exists('messages', array()));
 
@@ -430,6 +434,7 @@ class core_messagelib_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core\event\message_sent', $events[0]);
         $eventsink->clear();
 
+        // No messages are sent when the feature is disabled.
         $CFG->messaging = 0;
 
         $message = new \core\message\message();
@@ -446,20 +451,19 @@ class core_messagelib_testcase extends advanced_testcase {
         $message->notification      = '0';
 
         $messageid = message_send($message);
+        $this->assertFalse($messageid);
+        $this->assertDebuggingCalled('Attempt to send msg from a provider moodle/instantmessage '.
+            'that is inactive or not allowed for the user id='.$user2->id);
         $emails = $sink->get_messages();
         $this->assertCount(0, $emails);
-        $savedmessage = $DB->get_record('messages', array('id' => $messageid), '*', MUST_EXIST);
         $sink->clear();
-        $this->assertTrue($DB->record_exists('message_user_actions', array('userid' => $user2->id, 'messageid' => $messageid,
-            'action' => \core_message\api::MESSAGE_ACTION_READ)));
         $DB->delete_records('messages', array());
         $DB->delete_records('message_user_actions', array());
         $events = $eventsink->get_events();
-        $this->assertCount(2, $events);
-        $this->assertInstanceOf('\core\event\message_sent', $events[0]);
-        $this->assertInstanceOf('\core\event\message_viewed', $events[1]);
+        $this->assertCount(0, $events);
         $eventsink->clear();
 
+        // Example of a message that is sent and viewed.
         $CFG->messaging = 1;
 
         $message = new \core\message\message();
index 1e1fe7b..9346e05 100644 (file)
@@ -141,8 +141,8 @@ class core_tablelib_testcase extends basic_testcase {
         $columns = $this->generate_columns(2);
         $headers = $this->generate_headers(2);
 
-        // Search for pagination controls containing '1.*2</a>.*Next</a>'.
-        $this->expectOutputRegex('/1.*2<\/a>.*' . get_string('next') . '<\/a>/');
+        // Search for pagination controls containing 'page-link"\saria-label="Next"'.
+        $this->expectOutputRegex('/page-link"\saria-label="Next"/');
 
         $this->run_table_test(
             $columns,
index 6f35c36..f83ad6c 100644 (file)
@@ -2338,6 +2338,22 @@ function check_is_https(environment_results $result) {
     return null;
 }
 
+/**
+ * Check if the site is using 64 bits PHP.
+ *
+ * @param  environment_results $result
+ * @return environment_results|null updated results object, or null if the site is using 64 bits PHP.
+ */
+function check_sixtyfour_bits(environment_results $result) {
+
+    if (PHP_INT_SIZE === 4) {
+         $result->setInfo('php not 64 bits');
+         $result->setStatus(false);
+         return $result;
+    }
+    return null;
+}
+
 /**
  * Assert the upgrade key is provided, if it is defined.
  *
index 9ae8b7e..f78cc46 100644 (file)
@@ -534,9 +534,10 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         // Now, create some notifications...
         // We are creating fake notifications but based on real ones.
 
-        // This one omits notification = 1.
+        // This one comes from a disabled plugin's provider and therefore is not sent.
         $eventdata = new \core\message\message();
         $eventdata->courseid          = $course->id;
+        $eventdata->notification      = 1;
         $eventdata->modulename        = 'moodle';
         $eventdata->component         = 'enrol_paypal';
         $eventdata->name              = 'paypal_enrolment';
@@ -548,6 +549,24 @@ class core_message_externallib_testcase extends externallib_advanced_testcase {
         $eventdata->fullmessagehtml   = '';
         $eventdata->smallmessage      = '';
         message_send($eventdata);
+        $this->assertDebuggingCalled('Attempt to send msg from a provider enrol_paypal/paypal_enrolment '.
+            'that is inactive or not allowed for the user id='.$user1->id);
+
+        // This one omits notification = 1.
+        $message = new \core\message\message();
+        $message->courseid          = $course->id;
+        $message->component         = 'enrol_manual';
+        $message->name              = 'expiry_notification';
+        $message->userfrom          = $user2;
+        $message->userto            = $user1;
+        $message->subject           = 'Test: This is not a notification but otherwise is valid';
+        $message->fullmessage       = 'Test: Full message';
+        $message->fullmessageformat = FORMAT_MARKDOWN;
+        $message->fullmessagehtml   = markdown_to_html($message->fullmessage);
+        $message->smallmessage      = $message->subject;
+        $message->contexturlname    = $course->fullname;
+        $message->contexturl        = (string)new moodle_url('/course/view.php', array('id' => $course->id));
+        message_send($message);
 
         $message = new \core\message\message();
         $message->courseid          = $course->id;
index 06775e5..624e4d9 100644 (file)
@@ -134,8 +134,8 @@ $string['currentattemptof'] = 'This is attempt {$a->attemptnumber} ( {$a->maxatt
 $string['cutoffdate'] = 'Cut-off date';
 $string['cutoffdatecolon'] = 'Cut-off date: {$a}';
 $string['cutoffdate_help'] = 'If set, the assignment will not accept submissions after this date without an extension.';
-$string['cutoffdatevalidation'] = 'The cut-off date cannot be earlier than the due date.';
-$string['cutoffdatefromdatevalidation'] = 'Cut-off date must be after the allow submissions from date.';
+$string['cutoffdatevalidation'] = 'Cut-off date cannot be earlier than the due date.';
+$string['cutoffdatefromdatevalidation'] = 'Cut-off date cannot be earlier than the allow submissions from date.';
 $string['defaultlayout'] = 'Restore default layout';
 $string['defaultsettings'] = 'Default assignment settings';
 $string['defaultsettings_help'] = 'These settings define the defaults for all new assignments.';
@@ -157,7 +157,7 @@ $string['submissionempty'] = 'Nothing was submitted';
 $string['submissionmodified'] = 'You have existing submission data. Please leave this page and try again.';
 $string['submissionmodifiedgroup'] = 'The submission has been modified by somebody else. Please leave this page and try again.';
 $string['duedatereached'] = 'The due date for this assignment has now passed';
-$string['duedatevalidation'] = 'Due date must be after the allow submissions from date.';
+$string['duedatevalidation'] = 'Due date cannot be earlier than the allow submissions from date.';
 $string['editattemptfeedback'] = 'Edit the grade and feedback for attempt number {$a}.';
 $string['editonline'] = 'Edit online';
 $string['editingpreviousfeedbackwarning'] = 'You are editing the feedback for a previous attempt. This is attempt {$a->attemptnumber} out of {$a->totalattempts}.';
index ee78784..edc5c50 100644 (file)
@@ -345,7 +345,7 @@ function assign_update_events($assign, $override = null) {
                 unset($event->id);
             }
             $event->name      = $eventname.' ('.get_string('duedate', 'assign').')';
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 
index a607b2c..a2dd3e4 100644 (file)
@@ -274,7 +274,10 @@ class assign {
     public function get_return_params() {
         global $PAGE;
 
-        $params = $PAGE->url->params();
+        $params = array();
+        if (!WS_SERVER) {
+            $params = $PAGE->url->params();
+        }
         unset($params['id']);
         unset($params['action']);
         return $params;
@@ -1328,9 +1331,9 @@ class assign {
             // Now process the event.
             if ($event->id) {
                 $calendarevent = calendar_event::load($event->id);
-                $calendarevent->update($event);
+                $calendarevent->update($event, false);
             } else {
-                calendar_event::create($event);
+                calendar_event::create($event, false);
             }
         } else {
             $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id,
@@ -1349,9 +1352,9 @@ class assign {
             // Now process the event.
             if ($event->id) {
                 $calendarevent = calendar_event::load($event->id);
-                $calendarevent->update($event);
+                $calendarevent->update($event, false);
             } else {
-                calendar_event::create($event);
+                calendar_event::create($event, false);
             }
         } else {
             $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id,
index ffb9a15..4cfc941 100644 (file)
@@ -223,18 +223,18 @@ class mod_assign_mod_form extends moodleform_mod {
     public function validation($data, $files) {
         $errors = parent::validation($data, $files);
 
-        if ($data['allowsubmissionsfromdate'] && $data['duedate']) {
-            if ($data['allowsubmissionsfromdate'] > $data['duedate']) {
+        if (!empty($data['allowsubmissionsfromdate']) && !empty($data['duedate'])) {
+            if ($data['duedate'] < $data['allowsubmissionsfromdate']) {
                 $errors['duedate'] = get_string('duedatevalidation', 'assign');
             }
         }
-        if ($data['duedate'] && $data['cutoffdate']) {
-            if ($data['duedate'] > $data['cutoffdate']) {
+        if (!empty($data['cutoffdate']) && !empty($data['duedate'])) {
+            if ($data['cutoffdate'] < $data['duedate'] ) {
                 $errors['cutoffdate'] = get_string('cutoffdatevalidation', 'assign');
             }
         }
-        if ($data['allowsubmissionsfromdate'] && $data['cutoffdate']) {
-            if ($data['allowsubmissionsfromdate'] > $data['cutoffdate']) {
+        if (!empty($data['allowsubmissionsfromdate']) && !empty($data['cutoffdate'])) {
+            if ($data['cutoffdate'] < $data['allowsubmissionsfromdate']) {
                 $errors['cutoffdate'] = get_string('cutoffdatefromdatevalidation', 'assign');
             }
         }
diff --git a/mod/assign/tests/behat/assign_no_calendar_capabilities.feature b/mod/assign/tests/behat/assign_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..e97a77d
--- /dev/null
@@ -0,0 +1,58 @@
+@mod @mod_assign
+Feature: Assignment with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create assignments even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing an assignment
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assignment name |
+      | Description | Test assignment description |
+      | id_allowsubmissionsfromdate_enabled | 1 |
+      | id_allowsubmissionsfromdate_day | 1 |
+      | id_allowsubmissionsfromdate_month | 1 |
+      | id_allowsubmissionsfromdate_year | 2017 |
+      | id_duedate_enabled | 1 |
+      | id_duedate_day | 1 |
+      | id_duedate_month | 2 |
+      | id_duedate_year | 2017 |
+      | id_cutoffdate_enabled | 1 |
+      | id_cutoffdate_day | 2 |
+      | id_cutoffdate_month | 2 |
+      | id_cutoffdate_year | 2017 |
+      | id_gradingduedate_enabled | 1 |
+      | id_gradingduedate_day | 1 |
+      | id_gradingduedate_month | 3 |
+      | id_gradingduedate_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test assignment name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_allowsubmissionsfromdate_year | 2018 |
+      | id_duedate_year | 2018 |
+      | id_cutoffdate_year | 2018 |
+      | id_gradingduedate_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test assignment name"
index 54054b3..759f534 100644 (file)
@@ -1472,4 +1472,29 @@ class mod_assign_lib_testcase extends advanced_testcase {
         // is changed.
         $this->assertNotEmpty($moduleupdatedevents);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create an assignment.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_assign');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'allowsubmissionsfromdate' => $time,
+            'duedate' => $time + 500,
+            'cutoffdate' => $time + 600,
+            'gradingduedate' => $time + 700,
+        );
+        $generator->create_instance($params);
+    }
 }
index 7e2aaad..774ebee 100644 (file)
@@ -134,7 +134,7 @@ function chat_add_instance($chat) {
         $event->timesort    = $chat->chattime;
         $event->timeduration = 0;
 
-        calendar_event::create($event);
+        calendar_event::create($event, false);
     }
 
     if (!empty($chat->completionexpected)) {
@@ -174,7 +174,7 @@ function chat_update_instance($chat) {
             $event->timesort    = $chat->chattime;
 
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Do not publish this event, so delete it.
             $calendarevent = calendar_event::load($event->id);
@@ -197,7 +197,7 @@ function chat_update_instance($chat) {
             $event->timesort    = $chat->chattime;
             $event->timeduration = 0;
 
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 
@@ -501,7 +501,7 @@ function chat_prepare_update_events($chat, $cm = null) {
     if ($event->id = $DB->get_field('event', 'id', array('modulename' => 'chat', 'instance' => $chat->id,
             'eventtype' => CHAT_EVENT_TYPE_CHATTIME))) {
         $calendarevent = calendar_event::load($event->id);
-        $calendarevent->update($event);
+        $calendarevent->update($event, false);
     } else if ($chat->schedule > 0) {
         // The chat is scheduled and the event should be published.
         $event->courseid    = $chat->course;
@@ -512,7 +512,7 @@ function chat_prepare_update_events($chat, $cm = null) {
         $event->eventtype   = CHAT_EVENT_TYPE_CHATTIME;
         $event->timeduration = 0;
         $event->visible = $cm->visible;
-        calendar_event::create($event);
+        calendar_event::create($event, false);
     }
 }
 
diff --git a/mod/chat/tests/behat/chat_no_calendar_capabilities.feature b/mod/chat/tests/behat/chat_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..a990349
--- /dev/null
@@ -0,0 +1,43 @@
+@mod @mod_chat
+Feature: Chat with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create chats even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing a chat
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Chat" to section "1" and I fill the form with:
+      | Name of this chat room | Test chat name |
+      | Description | Test chat description |
+      | Repeat/publish session times | No repeats - publish the specified time only |
+      | id_chattime_day | 1 |
+      | id_chattime_month | 1 |
+      | id_chattime_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test chat name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_chattime_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test chat name"
index fda8b4a..9df1d41 100644 (file)
@@ -363,4 +363,25 @@ class mod_chat_lib_testcase extends advanced_testcase {
 
         return calendar_event::create($event);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create an chat.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_chat');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $params = array(
+            'course' => $course->id,
+            'chattime' => time() + 500,
+        );
+        $generator->create_instance($params);
+    }
 }
index ab33373..b5cb29b 100644 (file)
@@ -58,7 +58,7 @@ function choice_set_events($choice) {
             $event->visible      = instance_is_visible('choice', $choice);
             $event->timeduration = 0;
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Calendar event is on longer needed.
             $calendarevent = calendar_event::load($event->id);
@@ -78,7 +78,7 @@ function choice_set_events($choice) {
             $event->timesort     = $choice->timeopen;
             $event->visible      = instance_is_visible('choice', $choice);
             $event->timeduration = 0;
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 
@@ -97,7 +97,7 @@ function choice_set_events($choice) {
             $event->visible      = instance_is_visible('choice', $choice);
             $event->timeduration = 0;
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Calendar event is on longer needed.
             $calendarevent = calendar_event::load($event->id);
@@ -117,7 +117,7 @@ function choice_set_events($choice) {
             $event->timesort     = $choice->timeclose;
             $event->visible      = instance_is_visible('choice', $choice);
             $event->timeduration = 0;
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 }
diff --git a/mod/choice/tests/behat/choice_no_calendar_capabilities.feature b/mod/choice/tests/behat/choice_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..0f4554c
--- /dev/null
@@ -0,0 +1,50 @@
+@mod @mod_choice
+Feature: Choice with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create choices even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing a choice
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Choice" to section "1" and I fill the form with:
+      | Choice name | Test choice name |
+      | Description | Test choice description |
+      | option[0] | Option 1 |
+      | option[1] | Option 2 |
+      | id_timeopen_enabled | 1 |
+      | id_timeopen_day | 1 |
+      | id_timeopen_month | 1 |
+      | id_timeopen_year | 2017 |
+      | id_timeclose_enabled | 1 |
+      | id_timeclose_day | 1 |
+      | id_timeclose_month | 2 |
+      | id_timeclose_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test choice name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_timeopen_year | 2018 |
+      | id_timeclose_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test choice name"
index eb452e1..72a32fe 100644 (file)
@@ -979,4 +979,27 @@ class mod_choice_lib_testcase extends externallib_advanced_testcase {
         $this->expectException('moodle_exception');
         choice_user_submit_response($optionids[1], $choicewithoptions, $user2->id, $course, $cm);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create an choice.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_choice');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'timeopen' => $time + 200,
+            'timeclose' => $time + 500,
+        );
+        $generator->create_instance($params);
+    }
 }
index 5c6a6bf..d7ec1db 100644 (file)
@@ -156,6 +156,7 @@ if ($rid) {
 
 $PAGE->set_title($data->name);
 $PAGE->set_heading($course->fullname);
+$PAGE->force_settings_menu(true);
 
 // Process incoming data for adding/updating records.
 
index b2444f3..e6d6e50 100644 (file)
@@ -84,6 +84,7 @@ if($mform->is_cancelled()) {
     // build header to match the rest of the UI
     $PAGE->set_title($data->name);
     $PAGE->set_heading($course->fullname);
+    $PAGE->force_settings_menu(true);
     echo $OUTPUT->header();
     echo $OUTPUT->heading(format_string($data->name), 2);
     echo $OUTPUT->box(format_module_intro('data', $data, $cm->id), 'generalbox', 'intro');
index d5040af..f014bb1 100644 (file)
@@ -241,6 +241,7 @@ foreach ($plugins as $plugin=>$fulldir){
 asort($menufield);    //sort in alphabetical order
 $PAGE->set_title(get_string('course') . ': ' . $course->fullname);
 $PAGE->set_heading($course->fullname);
+$PAGE->force_settings_menu(true);
 
 $PAGE->set_pagetype('mod-data-field-' . $newtype);
 if (($mode == 'new') && (!empty($newtype)) && confirm_sesskey()) {          ///  Adding a new field
index e2f6442..db8a779 100644 (file)
@@ -613,7 +613,7 @@ function data_set_events($data) {
             $event->visible      = instance_is_visible('data', $data);
             $event->timeduration = 0;
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Calendar event is on longer needed.
             $calendarevent = calendar_event::load($event->id);
@@ -633,7 +633,7 @@ function data_set_events($data) {
             $event->timesort     = $data->timeavailablefrom;
             $event->visible      = instance_is_visible('data', $data);
             $event->timeduration = 0;
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 
@@ -652,7 +652,7 @@ function data_set_events($data) {
             $event->visible      = instance_is_visible('data', $data);
             $event->timeduration = 0;
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Calendar event is on longer needed.
             $calendarevent = calendar_event::load($event->id);
@@ -672,7 +672,7 @@ function data_set_events($data) {
             $event->timesort     = $data->timeavailableto;
             $event->visible      = instance_is_visible('data', $data);
             $event->timeduration = 0;
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 }
index 5cb45e9..9a166b2 100644 (file)
@@ -51,6 +51,7 @@ require_capability('mod/data:managetemplates', $context);
 $PAGE->set_url(new moodle_url('/mod/data/preset.php', array('d'=>$data->id)));
 $PAGE->set_title(get_string('course') . ': ' . $course->fullname);
 $PAGE->set_heading($course->fullname);
+$PAGE->force_settings_menu(true);
 
 // fill in missing properties needed for updating of instance
 $data->course     = $cm->course;
index 8fa48c5..ce5af6c 100644 (file)
@@ -103,6 +103,7 @@ $PAGE->requires->js('/mod/data/data.js');
 $PAGE->set_title($data->name);
 $PAGE->set_heading($course->fullname);
 $PAGE->set_pagelayout('admin');
+$PAGE->force_settings_menu(true);
 echo $OUTPUT->header();
 echo $OUTPUT->heading(format_string($data->name), 2);
 echo $OUTPUT->box(format_module_intro('data', $data, $cm->id), 'generalbox', 'intro');
diff --git a/mod/data/tests/behat/data_no_calendar_capabilities.feature b/mod/data/tests/behat/data_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..98d7d49
--- /dev/null
@@ -0,0 +1,58 @@
+@mod @mod_data
+Feature: Database with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create databases even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing a database
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Database" to section "1" and I fill the form with:
+      | Name | Test database name |
+      | Description | Test database description |
+      | id_timeavailablefrom_enabled | 1 |
+      | id_timeavailablefrom_day | 1 |
+      | id_timeavailablefrom_month | 1 |
+      | id_timeavailablefrom_year | 2017 |
+      | id_timeavailableto_enabled | 1 |
+      | id_timeavailableto_day | 1 |
+      | id_timeavailableto_month | 4 |
+      | id_timeavailableto_year | 2017 |
+      | id_timeviewfrom_enabled | 1 |
+      | id_timeviewfrom_day | 1 |
+      | id_timeviewfrom_month | 3 |
+      | id_timeviewfrom_year | 2017 |
+      | id_timeviewto_enabled | 1 |
+      | id_timeviewto_day | 1 |
+      | id_timeviewto_month | 4 |
+      | id_timeviewto_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test database name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_timeavailablefrom_year | 2018 |
+      | id_timeavailableto_year | 2018 |
+      | id_timeviewfrom_year | 2018 |
+      | id_timeviewto_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test database name"
index 69e93f6..876550d 100644 (file)
@@ -1772,4 +1772,29 @@ class mod_data_lib_testcase extends advanced_testcase {
         $this->assertNull($min);
         $this->assertNull($max);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create an database.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_data');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'timeavailablefrom' => $time + 200,
+            'timeavailableto' => $time + 2000,
+            'timeviewfrom' => $time + 400,
+            'timeviewto' => $time + 2000,
+        );
+        $generator->create_instance($params);
+    }
 }
index 0b2d3e5..191602a 100644 (file)
@@ -826,7 +826,7 @@ function feedback_set_events($feedback) {
             // Calendar event exists so update it.
             $event->id = $eventid;
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Event doesn't exist so create one.
             $event->courseid     = $feedback->course;
@@ -835,7 +835,7 @@ function feedback_set_events($feedback) {
             $event->modulename   = 'feedback';
             $event->instance     = $feedback->id;
             $event->eventtype    = FEEDBACK_EVENT_TYPE_OPEN;
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     } else if ($eventid) {
         // Calendar event is on longer needed.
@@ -861,7 +861,7 @@ function feedback_set_events($feedback) {
             // Calendar event exists so update it.
             $event->id = $eventid;
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Event doesn't exist so create one.
             $event->courseid     = $feedback->course;
@@ -869,7 +869,7 @@ function feedback_set_events($feedback) {
             $event->userid       = 0;
             $event->modulename   = 'feedback';
             $event->instance     = $feedback->id;
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     } else if ($eventid) {
         // Calendar event is on longer needed.
diff --git a/mod/feedback/tests/behat/feedback_no_calendar_capabilities.feature b/mod/feedback/tests/behat/feedback_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..bf348ad
--- /dev/null
@@ -0,0 +1,48 @@
+@mod @mod_feedback
+Feature: Feedback with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create feedbacks even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing a feedback
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Feedback" to section "1" and I fill the form with:
+      | Name | Test feedback name |
+      | Description | Test feedback description |
+      | id_timeopen_enabled | 1 |
+      | id_timeopen_day | 1 |
+      | id_timeopen_month | 1 |
+      | id_timeopen_year | 2017 |
+      | id_timeclose_enabled | 1 |
+      | id_timeclose_day | 1 |
+      | id_timeclose_month | 2 |
+      | id_timeclose_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test feedback name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_timeopen_year | 2018 |
+      | id_timeclose_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test feedback name"
index 37cbf30..0990f40 100644 (file)
@@ -852,4 +852,27 @@ class mod_feedback_lib_testcase extends advanced_testcase {
         // was successfully modified.
         $this->assertNotEmpty($moduleupdatedevents);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create an feedback.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_feedback');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'timeopen' => $time + 200,
+            'timeclose' => $time + 2000,
+        );
+        $generator->create_instance($params);
+    }
 }
index fb55485..885e8ae 100644 (file)
@@ -217,7 +217,7 @@ function lesson_update_events($lesson, $override = null) {
                 }
                 $event->name = get_string('lessoneventopens', 'lesson', $eventname);
                 // The method calendar_event::create will reuse a db record if the id field is set.
-                calendar_event::create($event);
+                calendar_event::create($event, false);
             }
             if ($deadline && $addclose) {
                 if ($oldevent = array_shift($oldevents)) {
@@ -236,7 +236,7 @@ function lesson_update_events($lesson, $override = null) {
                         $event->priority = $closepriorities[$deadline];
                     }
                 }
-                calendar_event::create($event);
+                calendar_event::create($event, false);
             }
         }
     }
diff --git a/mod/lesson/tests/behat/lesson_no_calendar_capabilities.feature b/mod/lesson/tests/behat/lesson_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..80e5cf8
--- /dev/null
@@ -0,0 +1,48 @@
+@mod @mod_lesson
+Feature: Lesson with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create lessons even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing a lesson
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Lesson" to section "1" and I fill the form with:
+      | Name | Test lesson name |
+      | Description | Test lesson description |
+      | id_available_enabled | 1 |
+      | id_available_day | 1 |
+      | id_available_month | 1 |
+      | id_available_year | 2017 |
+      | id_deadline_enabled | 1 |
+      | id_deadline_day | 1 |
+      | id_deadline_month | 2 |
+      | id_deadline_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test lesson name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_available_year | 2018 |
+      | id_deadline_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test lesson name"
index a122c0f..049724f 100644 (file)
@@ -723,4 +723,27 @@ class mod_lesson_lib_testcase extends advanced_testcase {
         $this->assertNull($min);
         $this->assertNull($max);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create an lesson.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_lesson');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'available' => $time + 200,
+            'deadline' => $time + 2000,
+        );
+        $generator->create_instance($params);
+    }
 }
index a8caba4..9fd12e9 100644 (file)
@@ -1340,7 +1340,7 @@ function quiz_update_events($quiz, $override = null) {
                 }
                 $event->name = get_string('quizeventopens', 'quiz', $eventname);
                 // The method calendar_event::create will reuse a db record if the id field is set.
-                calendar_event::create($event);
+                calendar_event::create($event, false);
             }
             if ($timeclose && $addclose) {
                 if ($oldevent = array_shift($oldevents)) {
@@ -1359,7 +1359,7 @@ function quiz_update_events($quiz, $override = null) {
                         $event->priority = $closepriorities[$timeclose];
                     }
                 }
-                calendar_event::create($event);
+                calendar_event::create($event, false);
             }
         }
     }
diff --git a/mod/quiz/tests/behat/quiz_no_calendar_capabilities.feature b/mod/quiz/tests/behat/quiz_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..52b1f21
--- /dev/null
@@ -0,0 +1,48 @@
+@mod @mod_quiz
+Feature: Quiz with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create quiz even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  Scenario: Editing a quiz
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "Quiz" to section "1" and I fill the form with:
+      | Name | Test quiz name |
+      | Description | Test quiz description |
+      | id_timeopen_enabled | 1 |
+      | id_timeopen_day | 1 |
+      | id_timeopen_month | 1 |
+      | id_timeopen_year | 2017 |
+      | id_timeclose_enabled | 1 |
+      | id_timeclose_day | 1 |
+      | id_timeclose_month | 2 |
+      | id_timeclose_year | 2017 |
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test quiz name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_timeopen_year | 2018 |
+      | id_timeclose_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test quiz name"
index 8a54c36..65d8b56 100644 (file)
@@ -736,4 +736,27 @@ class mod_quiz_lib_testcase extends advanced_testcase {
         $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions);
         $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions(new stdClass()), []);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create a quiz.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_quiz');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'timeopen' => $time + 200,
+            'timeclose' => $time + 2000,
+        );
+        $generator->create_instance($params);
+    }
 }
index 27e4b94..cf61bb0 100644 (file)
@@ -2397,7 +2397,7 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
             $event->timeduration = 0;
 
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Calendar event is on longer needed.
             $calendarevent = calendar_event::load($event->id);
@@ -2418,7 +2418,7 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
             $event->visible = instance_is_visible('scorm', $scorm);
             $event->timeduration = 0;
 
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 
@@ -2438,7 +2438,7 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
             $event->timeduration = 0;
 
             $calendarevent = calendar_event::load($event->id);
-            $calendarevent->update($event);
+            $calendarevent->update($event, false);
         } else {
             // Calendar event is on longer needed.
             $calendarevent = calendar_event::load($event->id);
@@ -2459,7 +2459,7 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
             $event->visible = instance_is_visible('scorm', $scorm);
             $event->timeduration = 0;
 
-            calendar_event::create($event);
+            calendar_event::create($event, false);
         }
     }
 
diff --git a/mod/scorm/tests/behat/scorm_no_calendar_capabilities.feature b/mod/scorm/tests/behat/scorm_no_calendar_capabilities.feature
new file mode 100644 (file)
index 0000000..77cd7a0
--- /dev/null
@@ -0,0 +1,52 @@
+@mod @mod_scorm
+Feature: Scorm with no calendar capabilites
+  In order to allow work effectively
+  As a teacher
+  I need to be able to create SCORM activities even when I cannot edit calendar events
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I navigate to "Users > Permissions" in current page administration
+    And I override the system permissions of "Teacher" role with:
+      | capability | permission |
+      | moodle/calendar:manageentries | Prohibit |
+    And I log out
+
+  @javascript @_file_upload @_switch_iframe
+  Scenario: Editing a chat
+    Given I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I add a "SCORM package" to section "1"
+    And I set the following fields to these values:
+      | Name | Test scorm name |
+      | Description | Test scorm description |
+      | id_timeopen_enabled | 1 |
+      | id_timeopen_day | 1 |
+      | id_timeopen_month | 1 |
+      | id_timeopen_year | 2017 |
+      | id_timeclose_enabled | 1 |
+      | id_timeclose_day | 1 |
+      | id_timeclose_month | 2 |
+      | id_timeclose_year | 2017 |
+    And I upload "mod/scorm/tests/packages/singlesco_scorm12.zip" file to "Package file" filemanager
+    And I click on "Save and display" "button"
+    And I log out
+    When I log in as "teacher1"
+    And I am on "Course 1" course homepage with editing mode on
+    And I follow "Test scorm name"
+    And I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | id_timeopen_year | 2018 |
+      | id_timeclose_year | 2018 |
+    And I press "Save and return to course"
+    Then I should see "Test scorm name"
index 0e98e3b..173e879 100644 (file)
@@ -736,4 +736,27 @@ class mod_scorm_lib_testcase extends externallib_advanced_testcase {
         $this->assertNull($min);
         $this->assertNull($max);
     }
+
+    /**
+     * A user who does not have capabilities to add events to the calendar should be able to create a SCORM.
+     */
+    public function test_creation_with_no_calendar_capabilities() {
+        $this->resetAfterTest();
+        $course = self::getDataGenerator()->create_course();
+        $context = context_course::instance($course->id);
+        $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher');
+        $roleid = self::getDataGenerator()->create_role();
+        self::getDataGenerator()->role_assign($roleid, $user->id, $context->id);
+        assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true);
+        $generator = self::getDataGenerator()->get_plugin_generator('mod_scorm');
+        // Create an instance as a user without the calendar capabilities.
+        $this->setUser($user);
+        $time = time();
+        $params = array(
+            'course' => $course->id,
+            'timeopen' => $time + 200,
+            'timeclose' => $time + 2000,
+        );
+        $generator->create_instance($params);
+    }
 }
diff --git a/mod/workshop/amd/build/modform.min.js b/mod/workshop/amd/build/modform.min.js
new file mode 100644 (file)
index 0000000..860dc01
Binary files /dev/null and b/mod/workshop/amd/build/modform.min.js differ
diff --git a/mod/workshop/amd/src/modform.js b/mod/workshop/amd/src/modform.js
new file mode 100644 (file)
index 0000000..2352370
--- /dev/null
@@ -0,0 +1,99 @@
+// 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/>.
+
+/**
+ * Additional javascript for the Workshop module form.
+ *
+ * @module      mod_workshop/modform
+ * @copyright   The Open University 2018
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery'], function($) {
+
+    var submissionTypes = {
+        text: {
+            available: null,
+            required: null,
+            requiredHidden: null
+        },
+        file: {
+            available: null,
+            required: null,
+            requiredHidden: null
+        }
+    };
+
+    /**
+     * Determine whether one of the submission types has been marked as not available.
+     *
+     * If it has been marked not available, clear and disable its required checkbox.  Then determine if the other submission
+     * type is available, and if it is, check and disable its required checkbox.
+     *
+     * @param {Object} checkUnavailable
+     * @param {Object} checkAvailable
+     */
+    function checkAvailability(checkUnavailable, checkAvailable) {
+        if (!checkUnavailable.available.prop('checked')) {
+            checkUnavailable.required.prop('disabled', true);
+            checkUnavailable.required.prop('checked', false);
+            if (checkAvailable.available.prop('checked')) {
+                checkAvailable.required.prop('disabled', true);
+                checkAvailable.required.prop('checked', true);
+                // Also set the checkbox's hidden field to 1 so a 'required' value is submitted for the submission type.
+                checkAvailable.requiredHidden.val(1);
+            }
+        }
+    }
+
+    /**
+     * Enable the submission type's required checkbox and uncheck it.
+     *
+     * @param {Object} submissionType
+     */
+    function enableRequired(submissionType) {
+        submissionType.required.prop('disabled', false);
+        submissionType.required.prop('checked', false);
+        submissionType.requiredHidden.val(0);
+    }
+
+    /**
+     * Check which submission types have been marked as available, and disable required checkboxes as necessary.
+     */
+    function submissionTypeChanged() {
+        checkAvailability(submissionTypes.file, submissionTypes.text);
+        checkAvailability(submissionTypes.text, submissionTypes.file);
+        if (submissionTypes.text.available.prop('checked') && submissionTypes.file.available.prop('checked')) {
+            enableRequired(submissionTypes.text);
+            enableRequired(submissionTypes.file);
+        }
+    }
+
+    return /** @alias module:mod_workshop/modform */ {
+        /**
+         * Find all the required fields, set up event listeners, and set the initial state of required checkboxes.
+         */
+        init: function() {
+            submissionTypes.text.available = $('#id_submissiontypetextavailable');
+            submissionTypes.text.required = $('#id_submissiontypetextrequired');
+            submissionTypes.text.requiredHidden = $('input[name="submissiontypetextrequired"][type="hidden"]');
+            submissionTypes.file.available = $('#id_submissiontypefileavailable');
+            submissionTypes.file.required = $('#id_submissiontypefilerequired');
+            submissionTypes.file.requiredHidden = $('input[name="submissiontypefilerequired"][type="hidden"]');
+            submissionTypes.text.available.on('change', submissionTypeChanged);
+            submissionTypes.file.available.on('change', submissionTypeChanged);
+            submissionTypeChanged();
+        }
+    };
+});
index ddc4a01..587d276 100644 (file)
@@ -369,7 +369,14 @@ function workshop_upgrade_transform_instance(stdClass $old) {
     $new->name          = $old->name;
     $new->intro         = $old->description;
     $new->introformat   = $old->format;
-    $new->nattachments  = $old->nattachments;
+    if ($old->nattachments == 0) {
+        // Convert to the new method for disabling file submissions.
+        $new->submissiontypefile = WORKSHOP_SUBMISSION_TYPE_DISABLED;
+        $new->submissiontypetext = WORKSHOP_SUBMISSION_TYPE_REQUIRED;
+        $new->nattachments = 1;
+    } else {
+        $new->nattachments  = $old->nattachments;
+    }
     $new->maxbytes      = $old->maxbytes;
     $new->grade         = $old->grade;
     $new->gradinggrade  = $old->gradinggrade;
@@ -409,4 +416,4 @@ function workshop_upgrade_transform_instance(stdClass $old) {
     }
 
     return $new;
-}
\ No newline at end of file
+}
index 3e9d8d1..d357331 100644 (file)
@@ -53,8 +53,8 @@ class backup_workshop_activity_structure_step extends backup_activity_structure_
             'instructauthorsformat', 'instructreviewers',
             'instructreviewersformat', 'timemodified', 'phase', 'useexamples',
             'usepeerassessment', 'useselfassessment', 'grade', 'gradinggrade',
-            'strategy', 'evaluation', 'gradedecimals', 'nattachments', 'submissionfiletypes',
-            'latesubmissions', 'maxbytes', 'examplesmode', 'submissionstart',
+            'strategy', 'evaluation', 'gradedecimals', 'submissiontypetext', 'submissiontypefile', 'nattachments',
+            'submissionfiletypes', 'latesubmissions', 'maxbytes', 'examplesmode', 'submissionstart',
             'submissionend', 'assessmentstart', 'assessmentend',
             'conclusion', 'conclusionformat', 'overallfeedbackmode',
             'overallfeedbackfiles', 'overallfeedbackfiletypes', 'overallfeedbackmaxbytes'));
index 1812f03..7a2a5ae 100644 (file)
@@ -111,6 +111,13 @@ class restore_workshop_activity_structure_step extends restore_activity_structur
         $data->assessmentstart = $this->apply_date_offset($data->assessmentstart);
         $data->assessmentend = $this->apply_date_offset($data->assessmentend);
 
+        if ($data->nattachments == 0) {
+            // Convert to the new method for disabling file submissions.
+            $data->submissiontypefile = WORKSHOP_SUBMISSION_TYPE_DISABLED;
+            $data->submissiontypetext = WORKSHOP_SUBMISSION_TYPE_REQUIRED;
+            $data->nattachments = 1;
+        }
+
         // insert the workshop record
         $newitemid = $DB->insert_record('workshop', $data);
         // immediately after inserting "activity" record, call this
index bbacd5c..0b2de7f 100644 (file)
@@ -145,10 +145,24 @@ class workshop_summary_exporter extends exporter {
                 'description' => 'Number of digits that should be shown after the decimal point when displaying grades.',
                 'optional' => true,
             ),
+            'submissiontypetext' => array (
+                'type' => PARAM_INT,
+                'default' => 1,
+                'description' => 'Indicates whether text is required as part of each submission. ' .
+                        '0 for no, 1 for optional, 2 for required.',
+                'optional' => true
+            ),
+            'submissiontypefile' => array (
+                'type' => PARAM_INT,
+                'default' => 2,
+                'description' => 'Indicates whether a file upload is required as part of each submission. ' .
+                        '0 for no, 1 for optional, 2 for required.',
+                'optional' => true
+            ),
             'nattachments' => array(
                 'type' => PARAM_INT,
-                'default' => 0,
-                'description' => 'Number of required submission attachments.',
+                'default' => 1,
+                'description' => 'Maximum number of submission attachments.',
                 'optional' => true,
             ),
             'submissionfiletypes' => array(
index fb7e7ab..d3ffcff 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/workshop/db" VERSION="20180427" COMMENT="XMLDB file for Moodle mod/workshop"
+<XMLDB PATH="mod/workshop/db" VERSION="20180626" COMMENT="XMLDB file for Moodle mod/workshop"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
@@ -25,7 +25,9 @@
         <FIELD NAME="strategy" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" COMMENT="The type of the current grading strategy used in this workshop"/>
         <FIELD NAME="evaluation" TYPE="char" LENGTH="30" NOTNULL="true" SEQUENCE="false" COMMENT="The recently used grading evaluation method"/>
         <FIELD NAME="gradedecimals" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Number of digits that should be shown after the decimal point when displaying grades"/>
-        <FIELD NAME="nattachments" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Number of required submission attachments"/>
+        <FIELD NAME="submissiontypetext" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Can students enter text for their submissions? 0 for no, 1 for optional, 2 for required."/>
+        <FIELD NAME="submissiontypefile" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="Can students attach files for their submissions? 0 for no, 1 for optional, 2 for required."/>
+        <FIELD NAME="nattachments" TYPE="int" LENGTH="3" NOTNULL="false" DEFAULT="1" SEQUENCE="false" COMMENT="Maximum number of submission attachments"/>
         <FIELD NAME="submissionfiletypes" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="comma separated list of file extensions"/>
         <FIELD NAME="latesubmissions" TYPE="int" LENGTH="2" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Allow submitting the work after the deadline"/>
         <FIELD NAME="maxbytes" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="100000" SEQUENCE="false" COMMENT="Maximum size of the one attached file"/>
       </KEYS>
     </TABLE>
   </TABLES>
-</XMLDB>
\ No newline at end of file
+</XMLDB>
index 6c6e2c3..0019f2b 100644 (file)
@@ -68,5 +68,46 @@ function xmldb_workshop_upgrade($oldversion) {
     // Automatically generated Moodle v3.5.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2018062600) {
+
+        // Define field submissiontypetext to be added to workshop.
+        $table = new xmldb_table('workshop');
+        $field = new xmldb_field('submissiontypetext', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'gradedecimals');
+
+        // Conditionally launch add field submissiontypetext.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        $field = new xmldb_field('submissiontypefile', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '2',
+                'submissiontypetext');
+
+        // Conditionally launch add field submissiontypefile.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Convert existing workshops with attachments disabled to use the new settings.
+        $workshops = $DB->get_records('workshop', ['nattachments' => 0]);
+        foreach ($workshops as $workshop) {
+            $update = (object) [
+                'id' => $workshop->id,
+                'submissiontypefile' => 0,
+                'submissiontypetext' => 2,
+                'nattachments' => 1
+            ];
+            $DB->update_record('workshop', $update);
+        }
+
+        // Changing the default of field nattachments on table workshop to 1.
+        $field = new xmldb_field('nattachments', XMLDB_TYPE_INTEGER, '3', null, null, null, '1', 'submissiontypefile');
+
+        // Launch change of default for field nattachments.
+        $dbman->change_field_default($table, $field);
+
+        // Workshop savepoint reached.
+        upgrade_mod_savepoint(true, 2018062600, 'workshop');
+    }
+
     return true;
 }
index f1f69aa..bf559e1 100644 (file)
@@ -204,6 +204,7 @@ $string['mysubmission'] = 'My submission';
 $string['nattachments'] = 'Maximum number of submission attachments';
 $string['noexamples'] = 'No examples yet in this workshop';
 $string['noexamplesformready'] = 'You must define the assessment form before providing example submissions';
+$string['nosubmissiontype'] = 'At least one submission type must be available';
 $string['nogradeyet'] = 'No grade yet';
 $string['nosubmissionfound'] = 'No submission found for this user';
 $string['nosubmissions'] = 'No submissions yet in this workshop';
@@ -333,6 +334,12 @@ $string['submissionstart'] = 'Open for submissions from';
 $string['submissionstartevent'] = '{$a} opens for submissions';
 $string['submissionstartdatetime'] = 'Open for submissions from {$a->daydatetime} ({$a->distanceday})';
 $string['submissiontitle'] = 'Title';
+$string['submissiontypefileavailable'] = 'File attachment<span class="accesshide"> available</span>';
+$string['submissiontypefilerequired'] = '<span class="accesshide">File attachment </span>Required';
+$string['submissiontypetextavailable'] = 'Online text<span class="accesshide"> available</span>';
+$string['submissiontypetextrequired'] = '<span class="accesshide">Online text </span>Required';
+$string['submissiontypedisabled'] = 'This submission type is disabled for this workshop.';
+$string['submissiontypes'] = 'Submission types';
 $string['submissionsreport'] = 'Workshop submissions report';
 $string['submittednotsubmitted'] = 'Submitted ({$a->submitted}) / not submitted ({$a->notsubmitted})';
 $string['subplugintype_workshopallocation'] = 'Submissions allocation method';
index 3ef9612..b4e3a41 100644 (file)
@@ -34,6 +34,9 @@ define('WORKSHOP_EVENT_TYPE_SUBMISSION_OPEN',   'opensubmission');
 define('WORKSHOP_EVENT_TYPE_SUBMISSION_CLOSE',  'closesubmission');
 define('WORKSHOP_EVENT_TYPE_ASSESSMENT_OPEN',   'openassessment');
 define('WORKSHOP_EVENT_TYPE_ASSESSMENT_CLOSE',  'closeassessment');
+define('WORKSHOP_SUBMISSION_TYPE_DISABLED', 0);
+define('WORKSHOP_SUBMISSION_TYPE_AVAILABLE', 1);
+define('WORKSHOP_SUBMISSION_TYPE_REQUIRED', 2);
 
 ////////////////////////////////////////////////////////////////////////////////
 // Moodle core API                                                            //
index 681eede..e928b3d 100644 (file)
@@ -171,6 +171,12 @@ class workshop {
     /** @var int maximum size of one file attached to the overall feedback */
     public $overallfeedbackmaxbytes;
 
+    /** @var int Should the submission form show the text field? */
+    public $submissiontypetext;
+
+    /** @var int Should the submission form show the file attachment field? */
+    public $submissiontypefile;
+
     /**
      * @var workshop_strategy grading strategy instance
      * Do not use directly, get the instance using {@link workshop::grading_strategy_instance()}
@@ -2870,9 +2876,39 @@ class workshop {
                 $errors['title'] = get_string('err_multiplesubmissions', 'mod_workshop');
             }
         }
+        // Get the workshop record by id or cmid, depending on whether we're creating or editing a submission.
+        if (empty($data['workshopid'])) {
+            $workshop = $DB->get_record_select('workshop', 'id = (SELECT instance FROM {course_modules} WHERE id = ?)',
+                    [$data['cmid']]);
+        } else {
+            $workshop = $DB->get_record('workshop', ['id' => $data['workshopid']]);
+        }
+
+        if (isset($data['attachment_filemanager'])) {
+            $getfiles = file_get_drafarea_files($data['attachment_filemanager']);
+            $attachments = $getfiles->list;
+        } else {
+            $attachments = array();
+        }
 
-        $getfiles = file_get_drafarea_files($data['attachment_filemanager']);
-        if (empty($getfiles->list) and html_is_blank($data['content_editor']['text'])) {
+        if ($workshop->submissiontypefile == WORKSHOP_SUBMISSION_TYPE_REQUIRED) {
+            if (empty($attachments)) {
+                $errors['attachment_filemanager'] = get_string('err_required', 'form');
+            }
+        } else if ($workshop->submissiontypefile == WORKSHOP_SUBMISSION_TYPE_DISABLED && !empty($data['attachment_filemanager'])) {
+            $errors['attachment_filemanager'] = get_string('submissiontypedisabled', 'mod_workshop');
+        }
+
+        if ($workshop->submissiontypetext == WORKSHOP_SUBMISSION_TYPE_REQUIRED && html_is_blank($data['content_editor']['text'])) {
+            $errors['content_editor'] = get_string('err_required', 'form');
+        } else if ($workshop->submissiontypetext == WORKSHOP_SUBMISSION_TYPE_DISABLED && !empty($data['content_editor']['text'])) {
+            $errors['content_editor'] = get_string('submissiontypedisabled', 'mod_workshop');
+        }
+
+        // If neither type is explicitly required, one or the other must be submitted.
+        if ($workshop->submissiontypetext != WORKSHOP_SUBMISSION_TYPE_REQUIRED
+                && $workshop->submissiontypefile != WORKSHOP_SUBMISSION_TYPE_REQUIRED
+                && empty($attachments) && html_is_blank($data['content_editor']['text'])) {
             $errors['content_editor'] = get_string('submissionrequiredcontent', 'mod_workshop');
             $errors['attachment_filemanager'] = get_string('submissionrequiredfile', 'mod_workshop');
         }
@@ -2939,8 +2975,10 @@ class workshop {
         $params['objectid'] = $submission->id;
 
         // Save and relink embedded images and save attachments.
-        $submission = file_postupdate_standard_editor($submission, 'content', $this->submission_content_options(),
-            $this->context, 'mod_workshop', 'submission_content', $submission->id);
+        if ($this->submissiontypetext != WORKSHOP_SUBMISSION_TYPE_DISABLED) {
+            $submission = file_postupdate_standard_editor($submission, 'content', $this->submission_content_options(),
+                    $this->context, 'mod_workshop', 'submission_content', $submission->id);
+        }
 
         $submission = file_postupdate_standard_filemanager($submission, 'attachment', $this->submission_attachment_options(),
             $this->context, 'mod_workshop', 'submission_attachment', $submission->id);
index a23e44a..12d0b67 100644 (file)
@@ -54,7 +54,7 @@ class mod_workshop_mod_form extends moodleform_mod {
      * @return void
      */
     public function definition() {
-        global $CFG;
+        global $CFG, $PAGE;
 
         $workshopconfig = get_config('workshop');
         $mform = $this->_form;
@@ -129,23 +129,39 @@ class mod_workshop_mod_form extends moodleform_mod {
         $mform->addElement('editor', 'instructauthorseditor', $label, null,
                             workshop::instruction_editors_options($this->context));
 
+        $typeelements = [];
+        foreach (['submissiontypetext', 'submissiontypefile'] as $type) {
+            $available = $type . 'available';
+            $required = $type . 'required';
+            $availablelabel = get_string($available, 'workshop');
+            $requiredlabel = get_string($required, 'workshop');
+            $typeelements[] = $mform->createElement('advcheckbox', $available, '', $availablelabel);
+            $typeelements[] = $mform->createElement('advcheckbox', $required, '', $requiredlabel);
+            $mform->setDefault($available, 1);
+        }
+        // We can't use <br> as the separator as it does not work well in this case with the Boost theme.
+        // Instead, separate both tuples with a full-width empty div.
+        $mform->addGroup($typeelements, 'submissiontypes', get_string('submissiontypes', 'workshop'),
+            array(' ', '<div style="width:100%"></div>'), false);
+
         $options = array();
-        for ($i = 7; $i >= 0; $i--) {
+        for ($i = 7; $i >= 1; $i--) {
             $options[$i] = $i;
         }
         $label = get_string('nattachments', 'workshop');
         $mform->addElement('select', 'nattachments', $label, $options);
         $mform->setDefault('nattachments', 1);
+        $mform->disabledIf('nattachments', 'submissiontypefileavailable');
 
         $label = get_string('allowedfiletypesforsubmission', 'workshop');
         $mform->addElement('filetypes', 'submissionfiletypes', $label);
         $mform->addHelpButton('submissionfiletypes', 'allowedfiletypesforsubmission', 'workshop');
-        $mform->disabledIf('submissionfiletypes', 'nattachments', 'eq', 0);
+        $mform->disabledIf('submissionfiletypes', 'submissiontypefileavailable');
 
         $options = get_max_upload_sizes($CFG->maxbytes, $this->course->maxbytes, 0, $workshopconfig->maxbytes);
         $mform->addElement('select', 'maxbytes', get_string('maxbytes', 'workshop'), $options);
         $mform->setDefault('maxbytes', $workshopconfig->maxbytes);
-        $mform->disabledIf('maxbytes', 'nattachments', 'eq', 0);
+        $mform->disabledIf('maxbytes', 'submissiontypefileavailable');
 
         $label = get_string('latesubmissions', 'workshop');
         $text = get_string('latesubmissions_desc', 'workshop');
@@ -243,6 +259,8 @@ class mod_workshop_mod_form extends moodleform_mod {
 
         // Standard buttons, common to all modules ------------------------------------
         $this->add_action_buttons();
+
+        $PAGE->requires->js_call_amd('mod_workshop/modform', 'init');
     }
 
     /**
@@ -280,6 +298,16 @@ class mod_workshop_mod_form extends moodleform_mod {
                                 $data['conclusion']);
             $data['conclusioneditor']['format'] = $data['conclusionformat'];
             $data['conclusioneditor']['itemid'] = $draftitemid;
+            // Set submission type checkboxes.
+            foreach (['submissiontypetext', 'submissiontypefile'] as $type) {
+                $data[$type . 'available'] = 1;
+                $data[$type . 'required'] = 0;
+                if ($data[$type] == WORKSHOP_SUBMISSION_TYPE_DISABLED) {
+                    $data[$type . 'available'] = 0;
+                } else if ($data[$type] == WORKSHOP_SUBMISSION_TYPE_REQUIRED) {
+                    $data[$type . 'required'] = 1;
+                }
+            }
         } else {
             // adding a new workshop instance
             $draftitemid = file_get_submitted_draft_itemid('instructauthors');
@@ -296,6 +324,30 @@ class mod_workshop_mod_form extends moodleform_mod {
         }
     }
 
+    /**
+     * Combine submission type checkboxes into integer values for the database.
+     *
+     * @param stdClass $data The submitted form data.
+     */
+    public function data_postprocessing($data) {
+        parent::data_postprocessing($data);
+
+        foreach (['text', 'file'] as $type) {
+            $field = 'submissiontype' . $type;
+            $available = $field . 'available';
+            $required = $field . 'required';
+            if ($data->$required) {
+                $data->$field = WORKSHOP_SUBMISSION_TYPE_REQUIRED;
+            } else if ($data->$available) {
+                $data->$field = WORKSHOP_SUBMISSION_TYPE_AVAILABLE;
+            } else {
+                $data->$field = WORKSHOP_SUBMISSION_TYPE_DISABLED;
+            }
+            unset($data->$available);
+            unset($data->$required);
+        }
+    }
+
     /**
      * Set the grade item categories when editing an instance
      */
@@ -341,6 +393,13 @@ class mod_workshop_mod_form extends moodleform_mod {
                 }
             }
         }
+        $typevalues = $mform->getElementValue('submissiontypes');
+        foreach (['submissiontypetext', 'submissiontypefile'] as $type) {
+            // Don't leave a disabled "required" checkbox checked.
+            if (!$typevalues[$type . 'available']) {
+                $mform->setDefault($type . 'required', 0);
+            }
+        }
 
         parent::definition_after_data();
     }
@@ -404,6 +463,11 @@ class mod_workshop_mod_form extends moodleform_mod {
             }
         }
 
+        if (!$data['submissiontypetextavailable'] && !$data['submissiontypefileavailable']) {
+            // One submission type must be available.
+            $errors['submissiontypes'] = get_string('nosubmissiontype', 'workshop');
+        }
+
         return $errors;
     }
 }
index f8088a6..a2cbb84 100644 (file)
@@ -44,13 +44,21 @@ class workshop_submission_form extends moodleform {
         $mform->addRule('title', null, 'required', null, 'client');
         $mform->addRule('title', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
 
-        $mform->addElement('editor', 'content_editor', get_string('submissioncontent', 'workshop'), null, $contentopts);
-        $mform->setType('content', PARAM_RAW);
+        if ($workshop->submissiontypetext != WORKSHOP_SUBMISSION_TYPE_DISABLED) {
+            $mform->addElement('editor', 'content_editor', get_string('submissioncontent', 'workshop'), null, $contentopts);
+            $mform->setType('content_editor', PARAM_RAW);
+            if ($workshop->submissiontypetext == WORKSHOP_SUBMISSION_TYPE_REQUIRED) {
+                $mform->addRule('content_editor', null, 'required', null, 'client');
+            }
+        }
 
-        if ($workshop->nattachments > 0) {
+        if ($workshop->submissiontypefile != WORKSHOP_SUBMISSION_TYPE_DISABLED) {
             $mform->addElement('static', 'filemanagerinfo', get_string('nattachments', 'workshop'), $workshop->nattachments);
             $mform->addElement('filemanager', 'attachment_filemanager', get_string('submissionattachment', 'workshop'),
                                 null, $attachmentopts);
+            if ($workshop->submissiontypefile == WORKSHOP_SUBMISSION_TYPE_REQUIRED) {
+                $mform->addRule('attachment_filemanager', null, 'required', null, 'client');
+            }
         }
 
         $mform->addElement('hidden', 'id', $current->id);
index 269a59a..92be14c 100644 (file)
@@ -21,8 +21,8 @@ Feature: Workshop submission removal
       | student3 | c1     | student        |
       | teacher1 | c1     | editingteacher |
     And the following "activities" exist:
-      | activity | name         | intro                     | course | idnumber  |
-      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 |
+      | activity | name         | intro                     | course | idnumber  | submissiontypefile |
+      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 | 1                  |
     # Teacher sets up assessment form and changes the phase to submission.
     And I log in as "teacher1"
     And I am on "Course1" course homepage
index 852e542..861ef2d 100644 (file)
@@ -19,8 +19,8 @@ Feature: Exporting workshop submissions and assessments to a portfolio
       | student2 | c1     | student        |
       | teacher1 | c1     | editingteacher |
     And the following "activities" exist:
-      | activity | name         | intro                     | course | idnumber  |
-      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 |
+      | activity | name         | intro                     | course | idnumber  | submissiontypefile |
+      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 | 1                  |
     # Admin needs to enable portfolio API and set a portfolio instance first.
     And I log in as "admin"
     And the following config values are set as admin:
index d709d7b..5ceedd9 100644 (file)
@@ -19,8 +19,8 @@ Feature: File types of the submission and feedback attachments can be limitted
       | student1 | c1     | student        |
       | student2 | c1     | student        |
     And the following "activities" exist:
-      | activity | name         | intro                     | course | idnumber  |
-      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 |
+      | activity | name         | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 | 2                  | 1                  |
 
   @_file_upload @javascript
   Scenario: Student submission attachments obey the list of allowed file types
diff --git a/mod/workshop/tests/behat/submission_types.feature b/mod/workshop/tests/behat/submission_types.feature
new file mode 100644 (file)
index 0000000..d5970f1
--- /dev/null
@@ -0,0 +1,198 @@
+@mod @mod_workshop
+Feature: Submission types
+  In order to Submit the correct type of materials
+  As a student
+  I want have a clear indication of which fields are accepted and required on the submission form
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname |
+      | Test     | TEST      |
+    And I log in as "admin"
+
+  @javascript
+  Scenario: Test workshop settings validation
+    Given I am on "Test" course homepage with editing mode on
+    And I add a "Workshop" to section "0"
+    When I set the following fields to these values:
+      | Workshop name               | Test workshop |
+      | submissiontypetextavailable | 0             |
+      | submissiontypefileavailable | 0             |
+    And I press "Save and display"
+    Then I should see "At least one submission type must be available"
+    When I set the following fields to these values:
+      | submissiontypetextavailable | 1 |
+    Then the "submissiontypetextrequired" "field" should be disabled
+    Then the "submissiontypefilerequired" "field" should be disabled
+    And the field "submissiontypetextrequired" matches value "1"
+    And the field "submissiontypefilerequired" matches value "0"
+    When I set the following fields to these values:
+      | submissiontypetextavailable | 0 |
+      | submissiontypefileavailable | 1 |
+    Then the "submissiontypetextrequired" "field" should be disabled
+    Then the "submissiontypefilerequired" "field" should be disabled
+    And the field "submissiontypetextrequired" matches value "0"
+    And the field "submissiontypefilerequired" matches value "1"
+    When I set the following fields to these values:
+      | submissiontypetextavailable | 1 |
+      | submissiontypetextrequired  | 1 |
+      | submissiontypefileavailable | 1 |
+      | submissiontypefilerequired  | 1 |
+    And I press "Save and display"
+    Then I should see "Setup phase" in the "h3#mod_workshop-userplanheading" "css_element"
+    When I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | submissiontypetextrequired | 0 |
+    And I press "Save and display"
+    Then I should see "Setup phase" in the "h3#mod_workshop-userplanheading" "css_element"
+    When I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | submissiontypetextrequired | 1 |
+      | submissiontypefilerequired | 0 |
+    And I press "Save and display"
+    Then I should see "Setup phase" in the "h3#mod_workshop-userplanheading" "css_element"
+    When I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | submissiontypefileavailable | 0 |
+    And I press "Save and display"
+    Then I should see "Setup phase" in the "h3#mod_workshop-userplanheading" "css_element"
+    When I navigate to "Edit settings" in current page administration
+    And I set the following fields to these values:
+      | submissiontypefileavailable | 1 |
+      | submissiontypefilerequired  | 1 |
+      | submissiontypetextavailable | 0 |
+    And I press "Save and display"
+    Then I should see "Setup phase" in the "h3#mod_workshop-userplanheading" "css_element"
+
+  @javascript @_file_upload
+  Scenario: All submission fields required
+    Given the following "activities" exist:
+      | activity | name         | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | All required | Test workshop description | TEST   | workshop1 | 2                  | 2                  |
+    And I am on "Test" course homepage
+    And I follow "All required"
+    And I follow "Switch to the submission phase"
+    And I press "Continue"
+    And I press "Start preparing your submission"
+    And I set the field "Title" to "Test submission"
+    When I press "Save changes"
+    Then I should see "You must supply a value here." in the "Submission content" "form_row"
+    And I set the field "Submission content" to "Lorem ipsum dolor"
+    And I press "Save changes"
+    And I should see "You must supply a value here." in the "Attachment" "form_row"
+    And I set the following fields to these values:
+      | Attachment         | mod/workshop/tests/fixtures/moodlelogo.png |
+    And I press "Save changes"
+    And I should not see "You must supply a value here."
+    And I should see "My submission"
+    And "Edit submission" "button" should exist
+
+  Scenario: Online text required, file attachment optional
+    Given the following "activities" exist:
+      | activity | name          | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | Optional file | Test workshop description | TEST   | workshop1 | 2                  | 1                  |
+    And I am on "Test" course homepage
+    And I follow "Optional file"
+    And I follow "Switch to the submission phase"
+    And I press "Continue"
+    And I press "Start preparing your submission"
+    And I set the field "Title" to "Test submission"
+    When I press "Save changes"
+    Then I should see "You must supply a value here." in the "Submission content" "form_row"
+    And I set the following fields to these values:
+      | Submission content | Lorem ipsum dolor                          |
+    And I press "Save changes"
+    And I should not see "You must supply a value here."
+    And I should see "My submission"
+    And "Edit submission" "button" should exist
+
+  @javascript @_file_upload
+  Scenario: Online text optional, file attachment required
+    Given the following "activities" exist:
+      | activity | name          | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | Optional text | Test workshop description | TEST   | workshop1 | 1                  | 2                  |
+    And I am on "Test" course homepage
+    And I follow "Optional text"
+    And I follow "Switch to the submission phase"
+    And I press "Continue"
+    And I press "Start preparing your submission"
+    And I set the field "Title" to "Test submission"
+    When I press "Save changes"
+    Then I should see "You must supply a value here." in the "Attachment" "form_row"
+    And I set the following fields to these values:
+      | Attachment         | mod/workshop/tests/fixtures/moodlelogo.png |
+    And I press "Save changes"
+    And I should not see "You must supply a value here."
+    And I should see "My submission"
+    And "Edit submission" "button" should exist
+
+  Scenario: Online text only
+    Given the following "activities" exist:
+      | activity | name      | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | Only text | Test workshop description | TEST   | workshop1 | 2                  | 0                  |
+    And I am on "Test" course homepage
+    And I follow "Only text"
+    And I follow "Switch to the submission phase"
+    And I press "Continue"
+    When I press "Start preparing your submission"
+    Then "Attachment" "field" should not exist
+    And I set the field "Title" to "Test submission"
+    And I press "Save changes"
+    And I should see "You must supply a value here." in the "Submission content" "form_row"
+    And I set the following fields to these values:
+      | Submission content | Lorem ipsum dolor                          |
+    And I press "Save changes"
+    And I should not see "You must supply a value here."
+    And I should see "My submission"
+    And "Edit submission" "button" should exist
+
+  @javascript @_file_upload
+  Scenario: File attachment only
+    Given the following "activities" exist:
+      | activity | name      | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | Only file | Test workshop description | TEST   | workshop1 | 0                  | 2                  |
+    And I am on "Test" course homepage
+    And I follow "Only file"
+    And I follow "Switch to the submission phase"
+    And I press "Continue"
+    When I press "Start preparing your submission"
+    Then "Submission content" "field" should not exist
+    And I set the field "Title" to "Test submission"
+    And I press "Save changes"
+    And I should see "You must supply a value here." in the "Attachment" "form_row"
+    And "Submission content" "form_row" should not exist
+    And I set the following fields to these values:
+      | Attachment         | mod/workshop/tests/fixtures/moodlelogo.png |
+    And I press "Save changes"
+    And I should not see "You must supply a value here."
+    And I should see "My submission"
+    And "Edit submission" "button" should exist
+
+  @javascript @_file_upload
+  Scenario: Neither submission type explicitly required
+    Given the following "activities" exist:
+      | activity | name             | intro                     | course | idnumber  |
+      | workshop | Neither required | Test workshop description | TEST   | workshop1 |
+    And I am on "Test" course homepage
+    And I follow "Neither required"
+    And I follow "Switch to the submission phase"
+    And I press "Continue"
+    And I press "Start preparing your submission"
+    And I set the field "Title" to "Test submission"
+    When I press "Save changes"
+    Then I should see "You need to add a file or enter some text." in the "Attachment" "form_row"
+    And I should see "You need to enter some text or add a file." in the "Submission content" "form_row"
+    And I set the following fields to these values:
+      | Submission content | Lorem ipsum dolor                          |
+    And I press "Save changes"
+    And I should not see "You need to add a file or enter some text."
+    And I should not see "You need to enter some text or add a file."
+    And I should see "My submission"
+    And "Edit submission" "button" should exist
+    And I press "Edit submission"
+    And I set the following fields to these values:
+      | Submission content |                                            |
+      | Attachment         | mod/workshop/tests/fixtures/moodlelogo.png |
+    And I press "Save changes"
+    And I should not see "You need to add a file or enter some text."
+    And I should not see "You need to enter some text or add a file."
index d434c9d..d835dc1 100644 (file)
@@ -23,8 +23,8 @@ Feature: Workshop submission and assessment
       | student4 | c1     | student        |
       | teacher1 | c1     | editingteacher |
     And the following "activities" exist:
-      | activity | name         | intro                     | course | idnumber  |
-      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 |
+      | activity | name         | intro                     | course | idnumber  | submissiontypetext | submissiontypefile |
+      | workshop | TestWorkshop | Test workshop description | c1     | workshop1 | 2                  | 1                  |
 # teacher1 sets up assessment form and changes the phase to submission
     When I log in as "teacher1"
     And I am on "Course1" course homepage
index e8e9f5d..afb9a27 100644 (file)
@@ -162,6 +162,8 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase {
         $workshop1->instructreviewersformat = 1;
         $workshop1->conclusionfiles = [];
         $workshop1->conclusionformat = 1;
+        $workshop1->submissiontypetext = 1;
+        $workshop1->submissiontypefile = 1;
 
         $workshop2->coursemodule = $workshop2->cmid;
         $workshop2->introformat = 1;
@@ -172,6 +174,8 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase {
         $workshop2->instructreviewersformat = 1;
         $workshop2->conclusionfiles = [];
         $workshop2->conclusionformat = 1;
+        $workshop2->submissiontypetext = 1;
+        $workshop2->submissiontypefile = 1;
 
         foreach ($expectedfields as $field) {
             if (!empty($properties[$field]) && $properties[$field]['type'] == PARAM_BOOL) {
@@ -551,24 +555,35 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase {
     public function test_add_submission_already_added() {
         $this->setUser($this->student);
 
+        $usercontext = context_user::instance($this->student->id);
+        $fs = get_file_storage();
+        $draftidattach = file_get_unused_draft_itemid();
+        $filerecordattach = [
+            'contextid' => $usercontext->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $draftidattach,
+            'filepath'  => '/',
+            'filename'  => 'attachement.txt'
+        ];
+        $fs->create_file_from_string($filerecordattach, 'simple text attachment');
+
         // Switch to submission phase.
         $workshop = new workshop($this->workshop, $this->cm, $this->course);
         $workshop->switch_phase(workshop::PHASE_SUBMISSION);
 
         // Create the submission.
-        $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
+        $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission', '', FORMAT_MOODLE, 0, $draftidattach);
         $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
 
         // Try to create it again.
-        $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission');
+        $result = mod_workshop_external::add_submission($this->workshop->id, 'My submission', '', FORMAT_MOODLE, 0, $draftidattach);
         $result = external_api::clean_returnvalue(mod_workshop_external::add_submission_returns(), $result);
         $this->assertFalse($result['status']);
         $this->assertArrayNotHasKey('submissionid', $result);
-        $this->assertCount(2, $result['warnings']);
+        $this->assertCount(1, $result['warnings']);
         $this->assertEquals('fielderror', $result['warnings'][0]['warningcode']);
-        $this->assertEquals('content_editor', $result['warnings'][0]['item']);
-        $this->assertEquals('fielderror', $result['warnings'][1]['warningcode']);
-        $this->assertEquals('attachment_filemanager', $result['warnings'][1]['item']);
+        $this->assertEquals('title', $result['warnings'][0]['item']);
     }
 
     /**
@@ -587,7 +602,7 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase {
         // Create a file in a draft area for inline attachments.
         $fs = get_file_storage();
         $draftidinlineattach = file_get_unused_draft_itemid();
-        $usercontext = context_user::instance($this->student->id);
+        $usercontext = context_user::instance($user->id);
         $filenameimg = 'shouldbeanimage.txt';
         $filerecordinline = array(
             'contextid' => $usercontext->id,
@@ -832,7 +847,7 @@ class mod_workshop_external_testcase extends externallib_advanced_testcase {
 
         // Create a couple of submissions with files.
         $firstsubmissionid = $this->create_test_submission($this->student);  // Create submission with files.
-        $secondsubmissionid = $this->create_test_submission($this->anotherstudentg1->id);
+        $secondsubmissionid = $this->create_test_submission($this->anotherstudentg1);
 
         $this->setUser($this->student);
         $result = mod_workshop_external::get_submissions($this->workshop->id);
index 4032b08..8ccc0e6 100644 (file)
@@ -1,6 +1,11 @@
 This files describes API changes in /mod/workshop - activity modules,
 information provided here is intended especially for developers.
 
+=== 3.6 ===
+
+* The external function get_workshops_by_courses now returns 2 additional fields: submissiontypetext and
+  submissiontypefile, indicating which submission types are available and required.
+
 === 3.4 ===
 
 * workshop_strategy::get_dimensions_info now returns also the scale items (if scales are being used).
index 593d9aa..61d88c6 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2018051400;        // The current module version (YYYYMMDDXX)
+$plugin->version   = 2018062600;        // The current module version (YYYYMMDDXX)
 $plugin->requires  = 2018050800;        // Requires this Moodle version.
 $plugin->component = 'mod_workshop';
 $plugin->cron      = 60;                // Give as a chance every minute.
index 8c850c1..15b4231 100644 (file)
@@ -31,7 +31,6 @@ use preferences_groups;
 use action_menu;
 use help_icon;
 use single_button;
-use paging_bar;
 use context_course;
 use pix_icon;
 
@@ -417,18 +416,6 @@ class core_renderer extends \core_renderer {
         return $this->render_from_template('core/single_button', $button->export_for_template($this));
     }
 
-    /**
-     * Renders a paging bar.
-     *
-     * @param paging_bar $pagingbar The object.
-     * @return string HTML
-     */
-    protected function render_paging_bar(paging_bar $pagingbar) {
-        // Any more than 10 is not usable and causes wierd wrapping of the pagination in this theme.
-        $pagingbar->maxdisplay = 10;
-        return $this->render_from_template('core/paging_bar', $pagingbar->export_for_template($this));
-    }
-
     /**
      * Renders the login form.
      *
index af836a3..02806c6 100644 (file)
@@ -10,6 +10,7 @@ $bg-inverse-link-color: #fff !default;
     overflow-y: visible;
     border: $card-border-width solid $card-border-color;
     padding: $card-spacer-x;
+    background-color: $body-bg;
 }
 
 .context-header-settings-menu,
index 808ef33..f28c297 100644 (file)
@@ -8794,7 +8794,8 @@ input[disabled] {
   overflow-x: auto;
   overflow-y: visible;
   border: 1px solid rgba(0, 0, 0, 0.125);
-  padding: 1.25rem; }
+  padding: 1.25rem;
+  background-color: #fff; }
 
 .context-header-settings-menu,
 .region-main-settings-menu {
index f8d9293..0d2b966 100644 (file)
 {{^showSuggestions}}
 <input type="text" id="{{inputId}}" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}"/>
 {{/showSuggestions}}
+
+{{#js}}
+require(['jquery'], function($) {
+    // Set the minimum width of the input so that the placeholder is whole displayed.
+    var inputElement = $(document.getElementById('{{inputId}}'));
+    if (inputElement.length) {
+        inputElement.css('min-width', inputElement.attr('placeholder').length + 'ch');
+    }
+});
+{{/js}}
index f7008b5..e42ae41 100644 (file)
@@ -1505,7 +1505,7 @@ function user_get_course_lastaccess_sql($accesssince = null, $tableprefix = 'ul'
     if ($accesssince == -1) { // Never.
         return $tableprefix . '.timeaccess = 0';
     } else {
-        return $tableprefix . '.timeaccess != 0 AND ul.timeaccess < ' . $accesssince;
+        return $tableprefix . '.timeaccess != 0 AND ' . $tableprefix . '.timeaccess < ' . $accesssince;
     }
 }
 
@@ -1524,7 +1524,7 @@ function user_get_user_lastaccess_sql($accesssince = null, $tableprefix = 'u') {
     if ($accesssince == -1) { // Never.
         return $tableprefix . '.lastaccess = 0';
     } else {
-        return $tableprefix . '.lastaccess != 0 AND u.lastaccess < ' . $accesssince;
+        return $tableprefix . '.lastaccess != 0 AND ' . $tableprefix . '.lastaccess < ' . $accesssince;
     }
 }
 
index 6bfb060..4a22036 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2018083100.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2018083100.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.