Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install
authorIlya Tregubov <ilya@moodle.com>
Thu, 23 Sep 2021 16:21:47 +0000 (18:21 +0200)
committerIlya Tregubov <ilya@moodle.com>
Thu, 23 Sep 2021 16:21:47 +0000 (18:21 +0200)
107 files changed:
admin/settings/users.php
course/amd/build/actions.min.js
course/amd/build/actions.min.js.map
course/amd/src/actions.js
course/edit.php
course/format/amd/build/local/content.min.js
course/format/amd/build/local/content.min.js.map
course/format/amd/build/local/content/section.min.js [new file with mode: 0644]
course/format/amd/build/local/content/section.min.js.map [new file with mode: 0644]
course/format/amd/build/local/content/section/cmitem.min.js [new file with mode: 0644]
course/format/amd/build/local/content/section/cmitem.min.js.map [new file with mode: 0644]
course/format/amd/build/local/content/section/header.min.js [new file with mode: 0644]
course/format/amd/build/local/content/section/header.min.js.map [new file with mode: 0644]
course/format/amd/src/local/content.js
course/format/amd/src/local/content/section.js [new file with mode: 0644]
course/format/amd/src/local/content/section/cmitem.js [new file with mode: 0644]
course/format/amd/src/local/content/section/header.js [new file with mode: 0644]
course/format/classes/output/local/content/section.php
course/format/classes/output/local/content/section/header.php
course/format/classes/output/local/content/sectionnavigation.php
course/format/classes/output/local/content/sectionselector.php
course/format/classes/output/section_renderer.php
course/format/templates/local/content/section.mustache
course/format/templates/local/content/section/header.mustache
course/lib.php
course/tests/behat/course_collapse_sections.feature [new file with mode: 0644]
course/tests/behat/course_creation.feature
course/tests/behat/section_visibility.feature
enrol/manual/amd/build/quickenrolment.min.js
enrol/manual/amd/build/quickenrolment.min.js.map
enrol/manual/amd/src/quickenrolment.js
filter/mediaplugin/dev/perftest.php
filter/mediaplugin/filter.php
filter/mediaplugin/tests/filter_test.php
grade/report/grader/lib.php
grade/report/grader/styles.css
grade/report/lib.php
lang/en/admin.php
lang/en/deprecated.txt
lang/en/grades.php
lang/en/moodle.php
lang/en/user.php
lib/adminlib.php
lib/behat/behat_field_manager.php
lib/behat/core_behat_file_helper.php
lib/classes/check/manager.php
lib/classes/check/security/mediafilterswf.php [deleted file]
lib/classes/filetypes.php
lib/classes/navigation/views/primary.php
lib/classes/output/icon_system_fontawesome.php
lib/classes/plugin_manager.php
lib/classes/user.php
lib/completionlib.php
lib/db/install.php
lib/db/upgrade.php
lib/googleapi.php
lib/outputrenderers.php
lib/templates/full_header.mustache
lib/templates/welcome.mustache [new file with mode: 0644]
lib/tests/medialib_test.php
lib/tests/navigation/views/primary_test.php
lib/upgrade.txt
login/lib.php
media/classes/manager.php
media/player/swf/classes/plugin.php [deleted file]
media/player/swf/classes/privacy/provider.php [deleted file]
media/player/swf/lang/en/media_swf.php [deleted file]
media/player/swf/pix/icon.png [deleted file]
media/player/swf/tests/player_test.php [deleted file]
media/player/swf/version.php [deleted file]
mod/feedback/tests/behat/export_import.feature
mod/url/locallib.php
my/tests/behat/add_blocks.feature
my/tests/behat/welcome.feature [new file with mode: 0644]
portfolio/googledocs/lang/en/portfolio_googledocs.php
portfolio/picasa/classes/privacy/provider.php [deleted file]
portfolio/picasa/db/upgrade.php [deleted file]
portfolio/picasa/lang/en/portfolio_picasa.php [deleted file]
portfolio/picasa/lib.php [deleted file]
portfolio/picasa/tests/privacy_provider_test.php [deleted file]
portfolio/picasa/version.php [deleted file]
portfolio/upgrade.txt
report/security/lang/en/deprecated.txt [new file with mode: 0644]
report/security/lang/en/report_security.php
repository/picasa/classes/privacy/provider.php [deleted file]
repository/picasa/db/access.php [deleted file]
repository/picasa/db/upgrade.php [deleted file]
repository/picasa/lang/en/repository_picasa.php [deleted file]
repository/picasa/lib.php [deleted file]
repository/picasa/pix/icon.png [deleted file]
repository/picasa/tests/generator/lib.php [deleted file]
repository/picasa/version.php [deleted file]
repository/tests/generator_test.php
repository/upgrade.txt
repository/upload/tests/behat/upload_file.feature
theme/boost/scss/moodle/course.scss
theme/boost/scss/moodle/grade.scss
theme/boost/scss/moodle/icons.scss
theme/boost/scss/moodle/popover-region.scss
theme/boost/style/moodle.css
theme/classic/style/moodle.css
theme/classic/templates/core/full_header.mustache
theme/classic/tests/behat/blacklist.json
user/classes/privacy/provider.php
user/tests/behat/enrol_cohort_list.feature
user/tests/behat/set_default_homepage.feature
version.php

index aa60229..961bc36 100644 (file)
@@ -186,6 +186,9 @@ if ($hassiteconfig
             unset($restorersnewrole);
         }
 
+        $temp->add(new admin_setting_configcheckbox('enroladminnewcourse', new lang_string('enroladminnewcourse', 'admin'),
+            new lang_string('enroladminnewcourse_help', 'admin'), 1));
+
         $temp->add(new admin_setting_configcheckbox('autologinguests', new lang_string('autologinguests', 'admin'), new lang_string('configautologinguests', 'admin'), 0));
 
         $temp->add(new admin_setting_configmultiselect('hiddenuserfields', new lang_string('hiddenuserfields', 'admin'),
index 1fca150..36d59df 100644 (file)
Binary files a/course/amd/build/actions.min.js and b/course/amd/build/actions.min.js differ
index d435aed..66080cb 100644 (file)
Binary files a/course/amd/build/actions.min.js.map and b/course/amd/build/actions.min.js.map differ
index f7c62fa..109bac7 100644 (file)
@@ -57,9 +57,15 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
          * @returns {Integer}
          */
         var getModuleId = function(element) {
-            var id;
+            // Check if we have a data-id first.
+            const item = element.get(0);
+            if (item.dataset.id) {
+                return item.dataset.id;
+            }
+            // Use YUI way if data-id is not present.
+            let id;
             Y.use('moodle-course-util', function(Y) {
-                id = Y.Moodle.core_course.util.cm.getId(Y.Node(element.get(0)));
+                id = Y.Moodle.core_course.util.cm.getId(Y.Node(item));
             });
             return id;
         };
@@ -75,6 +81,12 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
             Y.use('moodle-course-util', function(Y) {
                 name = Y.Moodle.core_course.util.cm.getName(Y.Node(element.get(0)));
             });
+            // Check if we have the name in the course state.
+            const state = courseeditor.state;
+            const cmid = getModuleId(element);
+            if (!name && state && cmid) {
+                name = state.cm.get(cmid)?.name;
+            }
             return name;
         };
 
@@ -282,6 +294,7 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
          * @param {JQuery|Element} element
          * @param {Number} cmid
          * @param {Number} sectionreturn
+         * @return {Promise} the refresh promise
          */
         var refreshModule = function(element, cmid, sectionreturn) {
             const activityElement = $(element);
@@ -291,13 +304,17 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
                 args: {id: cmid, sectionreturn: sectionreturn}
             }], true);
 
-            $.when.apply($, promises)
-                .done(function(data) {
-                    removeSpinner(activityElement, spinner, 400);
-                    replaceActivityHtmlWith(data);
-                }).fail(function() {
-                    removeSpinner(activityElement, spinner);
-                });
+            return new Promise((resolve, reject) => {
+                $.when.apply($, promises)
+                    .done(function(data) {
+                        removeSpinner(activityElement, spinner, 400);
+                        replaceActivityHtmlWith(data);
+                        resolve(data);
+                    }).fail(function() {
+                        removeSpinner(activityElement, spinner);
+                        reject();
+                    });
+            });
         };
 
         /**
index 976ddc3..70537bb 100644 (file)
@@ -164,7 +164,15 @@ if ($editform->is_cancelled()) {
         // Get the context of the newly created course.
         $context = context_course::instance($course->id, MUST_EXIST);
 
-        if (!empty($CFG->creatornewroleid) and !is_viewing($context, NULL, 'moodle/role:assign') and !is_enrolled($context, NULL, 'moodle/role:assign')) {
+        // Admins have all capabilities, so is_viewing is returning true for admins.
+        // We are checking 'enroladminnewcourse' setting to decide to enrol them or not.
+        if (is_siteadmin($USER->id)) {
+            $enroluser = $CFG->enroladminnewcourse;
+        } else {
+            $enroluser = !is_viewing($context, null, 'moodle/role:assign');
+        }
+
+        if (!empty($CFG->creatornewroleid) and $enroluser and !is_enrolled($context, null, 'moodle/role:assign')) {
             // Deal with course creators - enrol them internally with default role.
             // Note: This does not respect capabilities, the creator will be assigned the default role.
             // This is an expected behaviour. See MDL-66683 for further details.
index 0433933..57cbdf6 100644 (file)
Binary files a/course/format/amd/build/local/content.min.js and b/course/format/amd/build/local/content.min.js differ
index 4aaa1c8..9ea4db4 100644 (file)
Binary files a/course/format/amd/build/local/content.min.js.map and b/course/format/amd/build/local/content.min.js.map differ
diff --git a/course/format/amd/build/local/content/section.min.js b/course/format/amd/build/local/content/section.min.js
new file mode 100644 (file)
index 0000000..7b7a8ee
Binary files /dev/null and b/course/format/amd/build/local/content/section.min.js differ
diff --git a/course/format/amd/build/local/content/section.min.js.map b/course/format/amd/build/local/content/section.min.js.map
new file mode 100644 (file)
index 0000000..cdff0ec
Binary files /dev/null and b/course/format/amd/build/local/content/section.min.js.map differ
diff --git a/course/format/amd/build/local/content/section/cmitem.min.js b/course/format/amd/build/local/content/section/cmitem.min.js
new file mode 100644 (file)
index 0000000..3e2eb52
Binary files /dev/null and b/course/format/amd/build/local/content/section/cmitem.min.js differ
diff --git a/course/format/amd/build/local/content/section/cmitem.min.js.map b/course/format/amd/build/local/content/section/cmitem.min.js.map
new file mode 100644 (file)
index 0000000..ef34a5c
Binary files /dev/null and b/course/format/amd/build/local/content/section/cmitem.min.js.map differ
diff --git a/course/format/amd/build/local/content/section/header.min.js b/course/format/amd/build/local/content/section/header.min.js
new file mode 100644 (file)
index 0000000..ef855de
Binary files /dev/null and b/course/format/amd/build/local/content/section/header.min.js differ
diff --git a/course/format/amd/build/local/content/section/header.min.js.map b/course/format/amd/build/local/content/section/header.min.js.map
new file mode 100644 (file)
index 0000000..1de4f4a
Binary files /dev/null and b/course/format/amd/build/local/content/section/header.min.js.map differ
index 1a25150..d2f3690 100644 (file)
@@ -25,6 +25,8 @@
 import {BaseComponent} from 'core/reactive';
 import {getCurrentCourseEditor} from 'core_courseformat/courseeditor';
 import inplaceeditable from 'core/inplace_editable';
+import Section from 'core_courseformat/local/content/section';
+import CmItem from 'core_courseformat/local/content/section/cmitem';
 // Course actions is needed for actions that are not migrated to components.
 import courseActions from 'core_course/actions';
 
@@ -39,8 +41,7 @@ export default class Component extends BaseComponent {
         // Default query selectors.
         this.selectors = {
             SECTION: `[data-for='section']`,
-            SECTION_ITEM: `[data-for='section_item']`,
-            SECTION_TITLE: `[data-for='section_title']`,
+            SECTION_ITEM: `[data-for='section_title']`,
             SECTION_CMLIST: `[data-for='cmlist']`,
             COURSE_SECTIONLIST: `[data-for='course_sectionlist']`,
             CM: `[data-for='cmitem']`,
@@ -48,6 +49,9 @@ export default class Component extends BaseComponent {
         // Array to save dettached elements during element resorting.
         this.dettachedCms = {};
         this.dettachedSections = {};
+        // Index of sections and cms components.
+        this.sections = {};
+        this.cms = {};
     }
 
     /**
@@ -65,6 +69,19 @@ export default class Component extends BaseComponent {
         });
     }
 
+    /**
+     * Initial state ready method.
+     *
+     * Course content elements could not provide JS Components because the elements HTML is applied
+     * directly from the course actions. To keep internal components updated this module keeps
+     * a list of the active components and mark them as "indexed". This way when any action replace
+     * the HTML this component will recreate the components an add any necessary event listener.
+     *
+     */
+    stateReady() {
+        this._indexContents();
+    }
+
     /**
      * Return the component watchers.
      *
@@ -84,6 +101,11 @@ export default class Component extends BaseComponent {
             {watch: `transaction:start`, handler: this._startProcessing},
             {watch: `course.sectionlist:updated`, handler: this._refreshCourseSectionlist},
             {watch: `section.cmlist:updated`, handler: this._refreshSectionCmlist},
+            // Reindex sections and cms.
+            {watch: `state:updated`, handler: this._indexContents},
+            // State changes thaty require to reload course modules.
+            {watch: `cm.visible:updated`, handler: this._reloadCm},
+            {watch: `cm.sectionid:updated`, handler: this._reloadCm},
         ];
     }
 
@@ -144,7 +166,7 @@ export default class Component extends BaseComponent {
         target.dataset.number = element.number;
 
         // Update title and title inplace editable, if any.
-        const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_TITLE));
+        const inplace = inplaceeditable.getInplaceEditable(target.querySelector(this.selectors.SECTION_ITEM));
         if (inplace) {
             // The course content HTML can be modified at any moment, so the function need to do some checkings
             // to make sure the inplace editable still represents the same itemid.
@@ -187,6 +209,77 @@ export default class Component extends BaseComponent {
         }
     }
 
+    /**
+     * Regenerate content indexes.
+     *
+     * This method is used when a legacy action refresh some content element.
+     */
+    _indexContents() {
+        // Find unindexed sections.
+        this._scanIndex(
+            this.selectors.SECTION,
+            this.sections,
+            (item) => {
+                return new Section(item);
+            }
+        );
+
+        // Find unindexed cms.
+        this._scanIndex(
+            this.selectors.CM,
+            this.cms,
+            (item) => {
+                return new CmItem(item);
+            }
+        );
+    }
+
+    /**
+     * Reindex a content (section or cm) of the course content.
+     *
+     * This method is used internally by _indexContents.
+     *
+     * @param {string} selector the DOM selector to scan
+     * @param {*} index the index attribute to update
+     * @param {*} creationhandler method to create a new indexed element
+     */
+    _scanIndex(selector, index, creationhandler) {
+        const items = this.getElements(`${selector}:not([data-indexed])`);
+        items.forEach((item) => {
+            if (!item?.dataset?.id) {
+                return;
+            }
+            // Delete previous item component.
+            if (index[item.dataset.id] !== undefined) {
+                index[item.dataset.id].unregister();
+            }
+            // Create the new component.
+            index[item.dataset.id] = creationhandler({
+                ...this,
+                element: item,
+            });
+            // Mark as indexed.
+            item.dataset.indexed = true;
+        });
+    }
+
+    /**
+     * Reload a course module contents.
+     *
+     * @param {details} param0 the watcher details
+     * @property {object} param0.element the state object
+     */
+    _reloadCm({element}) {
+        const cmitem = this.getElement(this.selectors.CM, element.id);
+        if (cmitem) {
+            const promise = courseActions.refreshModule(cmitem, element.id);
+            promise.then(() => {
+                this._indexContents();
+                return;
+            }).catch();
+        }
+    }
+
     /**
      * Fix/reorder the section or cms order.
      *
@@ -220,11 +313,23 @@ export default class Component extends BaseComponent {
                 container.insertBefore(item, currentitem);
             }
         });
+
+        // Dndupload add a fake element we need to keep.
+        let dndFakeActivity;
+
         // Remove the remaining elements.
         while (container.children.length > neworder.length) {
             const lastchild = container.lastChild;
-            dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;
+            if (lastchild.classList.contains('dndupload-preview')) {
+                dndFakeActivity = lastchild;
+            } else {
+                dettachedelements[lastchild?.dataset?.id ?? 0] = lastchild;
+            }
             container.removeChild(lastchild);
         }
+        // Restore dndupload fake element.
+        if (dndFakeActivity) {
+            container.append(dndFakeActivity);
+        }
     }
 }
diff --git a/course/format/amd/src/local/content/section.js b/course/format/amd/src/local/content/section.js
new file mode 100644 (file)
index 0000000..929dfbf
--- /dev/null
@@ -0,0 +1,79 @@
+// 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/>.
+
+/**
+ * Course section format component.
+ *
+ * @module     core_courseformat/local/content/section
+ * @class      core_courseformat/local/content/section
+ * @copyright  2021 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import Header from 'core_courseformat/local/content/section/header';
+import DndSection from 'core_courseformat/local/courseeditor/dndsection';
+
+export default class extends DndSection {
+
+    /**
+     * Constructor hook.
+     */
+    create() {
+        // Optional component name for debugging.
+        this.name = 'content_section';
+        // Default query selectors.
+        this.selectors = {
+            SECTION_ITEM: `[data-for='section_title']`,
+            CM: `[data-for="cmitem"]`,
+        };
+    }
+
+    /**
+     * Initial state ready method.
+     *
+     * @param {Object} state the initial state
+     */
+    stateReady(state) {
+        this.configState(state);
+        // Drag and drop is only available for components compatible course formats.
+        if (this.reactive.isEditing && this.reactive.supportComponents) {
+            // Section zero and other formats sections may not have a title to drag.
+            const sectionItem = this.getElement(this.selectors.SECTION_ITEM);
+            if (sectionItem) {
+                // Init the inner dragable element.
+                const headerComponent = new Header({
+                    ...this,
+                    element: sectionItem,
+                    fullregion: this.element,
+                });
+                this.configDragDrop(headerComponent);
+            }
+        }
+    }
+
+    /**
+     * Get the last CM element of that section.
+     *
+     * @returns {element|null}
+     */
+    getLastCm() {
+        const cms = this.getElements(this.selectors.CM);
+        // DndUpload may add extra elements so :last-child selector cannot be used.
+        if (!cms || cms.length === 0) {
+            return null;
+        }
+        return cms[cms.length - 1];
+    }
+}
\ No newline at end of file
diff --git a/course/format/amd/src/local/content/section/cmitem.js b/course/format/amd/src/local/content/section/cmitem.js
new file mode 100644 (file)
index 0000000..4bcfe9f
--- /dev/null
@@ -0,0 +1,63 @@
+// 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/>.
+
+/**
+ * Course course module item component.
+ *
+ * This component is used to control specific course modules interactions like drag and drop.
+ *
+ * @module     core_courseformat/local/content/section/cmitem
+ * @class      core_courseformat/local/content/section/cmitem
+ * @copyright  2021 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import DndCmItem from 'core_courseformat/local/courseeditor/dndcmitem';
+
+export default class extends DndCmItem {
+
+    /**
+     * Constructor hook.
+     */
+    create() {
+        // Optional component name for debugging.
+        this.name = 'content_section_cmitem';
+        // Default query selectors.
+        this.selectors = {
+            DRAGICON: `.editing_move`,
+        };
+        // We need our id to watch specific events.
+        this.id = this.element.dataset.id;
+    }
+
+    /**
+     * Initial state ready method.
+     */
+    stateReady() {
+        this.configDragDrop(this.id);
+        this.getElement(this.selectors.DRAGICON)?.classList.add(this.classes.DRAGICON);
+    }
+
+    /**
+     * Component watchers.
+     *
+     * @returns {Array} of watchers
+     */
+    getWatchers() {
+        return [
+            {watch: `cm[${this.id}]:deleted`, handler: this.unregister},
+        ];
+    }
+}
\ No newline at end of file
diff --git a/course/format/amd/src/local/content/section/header.js b/course/format/amd/src/local/content/section/header.js
new file mode 100644 (file)
index 0000000..e5130ec
--- /dev/null
@@ -0,0 +1,56 @@
+// 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/>.
+
+/**
+ * Course section header component.
+ *
+ * This component is used to control specific course section interactions like drag and drop.
+ *
+ * @module     core_courseformat/local/content/section/header
+ * @class      core_courseformat/local/content/section/header
+ * @copyright  2021 Ferran Recio <ferran@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import DndSectionItem from 'core_courseformat/local/courseeditor/dndsectionitem';
+
+export default class extends DndSectionItem {
+
+    /**
+     * Constructor hook.
+     *
+     * @param {Object} descriptor
+     */
+    create(descriptor) {
+        // Optional component name for debugging.
+        this.name = 'content_section_header';
+        // We need our id to watch specific events.
+
+        // Get main info from the descriptor.
+        this.id = descriptor.id;
+        this.section = descriptor.section;
+        this.course = descriptor.course;
+        this.fullregion = descriptor.fullregion;
+    }
+
+    /**
+     * Initial state ready method.
+     *
+     * @param {Object} state the initial state
+     */
+    stateReady(state) {
+        this.configDragDrop(this.id, state, this.fullregion);
+    }
+}
\ No newline at end of file
index 1545d21..6d3940b 100644 (file)
@@ -152,6 +152,18 @@ class section implements renderable, templatable {
             }
         }
 
+        $coursedisplay = $course->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE;
+        if ($coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+            $data->iscoursedisplaymultipage = true;
+        }
+
+        if ($course->id == SITEID) {
+            $data->sitehome = true;
+        }
+
+        // For now sections are always expanded. User preferences will be done in MDL-71211.
+        $data->isactive = true;
+
         if ($thissection->section == 0) {
             // Section zero is always visible only as a cmlist.
             $cmlist = new $this->cmlistclass($format, $thissection);
index 595632e..e063b47 100644 (file)
@@ -80,9 +80,12 @@ class header implements renderable, templatable {
             // Regular section title.
             $data->title = $output->section_title_without_link($section, $course);
             $data->issinglesection = true;
-        } else {
+        } else if ($section->uservisible) {
             // Regular section title.
             $data->title = $output->section_title($section, $course);
+        } else {
+            // Regular section title without link.
+            $data->title = $output->section_title_without_link($section, $course);
         }
 
         if (!$section->visible) {
@@ -91,10 +94,16 @@ class header implements renderable, templatable {
 
         $coursedisplay = $course->coursedisplay ?? COURSE_DISPLAY_SINGLEPAGE;
 
+        if ($course->id == SITEID) {
+            $data->sitehome = true;
+        }
+
         if (!$format->show_editor() && $coursedisplay == COURSE_DISPLAY_MULTIPAGE && empty($data->issinglesection)) {
-            $data->url = course_get_url($course, $section->section);
-            $data->name = get_section_name($course, $section);
+            if ($section->uservisible) {
+                $data->url = course_get_url($course, $section->section);
+            }
         }
+        $data->name = get_section_name($course, $section);
 
         return $data;
     }
index 3066c74..9b9e150 100644 (file)
@@ -80,7 +80,7 @@ class sectionnavigation implements renderable, templatable {
         $sections = $modinfo->get_section_info_all();
 
         // FIXME: This is really evil and should by using the navigation API.
-        $canviewhidden = has_capability('moodle/course:viewhiddensections', $context, $USER) || !$course->hiddensections;
+        $canviewhidden = has_capability('moodle/course:viewhiddensections', $context, $USER);
 
         $data = (object)[
             'previousurl' => '',
index 5523439..07a7ec3 100644 (file)
@@ -82,9 +82,8 @@ class sectionselector implements renderable, templatable {
         $numsections = $format->get_last_section_number();
         while ($section <= $numsections) {
             $thissection = $modinfo->get_section_info($section);
-            $showsection = $thissection->uservisible || !$course->hiddensections;
             $url = course_get_url($course, $section);
-            if ($showsection && $url && $section != $data->currentsection) {
+            if ($thissection->uservisible && $url && $section != $data->currentsection) {
                 $sectionmenu[$url->out(false)] = get_section_name($course, $section);
             }
             $section++;
index 790cd05..a61dc2c 100644 (file)
@@ -801,8 +801,7 @@ abstract class section_renderer extends core_course_renderer {
         $numsections = course_get_format($course)->get_last_section_number();
         while ($section <= $numsections) {
             $thissection = $modinfo->get_section_info($section);
-            $showsection = $thissection->uservisible or !$course->hiddensections;
-            if (($showsection) && ($section != $displaysection) && ($url = course_get_url($course, $section))) {
+            if (($thissection->uservisible) && ($section != $displaysection) && ($url = course_get_url($course, $section))) {
                 $sectionmenu[$url->out(false)] = get_section_name($course, $section);
             }
             $section++;
index 0e51f35..e0d7459 100644 (file)
             "menu": "<a href=\"#\" class=\"d-inline-block dropdown-toggle icon-no-margin\">Edit<b class=\"caret\"></b></a>",
             "hasmenu": true
         },
-        "cmcontrols": "[Add an activity or resource]"
+        "cmcontrols": "[Add an activity or resource]",
+        "iscoursedisplaymultipage": true,
+        "sectionreturnid": 0,
+        "isactive": true,
+        "sitehome": false
     }
 }}
 <li id="section-{{num}}"
     class="section main {{#onlysummary}} section-summary {{/onlysummary}} clearfix
             {{#ishidden}} hidden {{/ishidden}} {{#iscurrent}} current {{/iscurrent}}
             {{#isstealth}} orphaned {{/isstealth}}"
-    role="region"
-    aria-labelledby="sectionid-{{id}}-title"
     data-sectionid="{{num}}"
     data-sectionreturnid="{{sectionreturnid}}"
     data-for="section"
             {{> core_courseformat/local/content/section/controlmenu }}
     {{/controlmenu}}
     </div>
-    <div class="content">
-        {{#header}} {{> core_courseformat/local/content/section/header }} {{/header}}
+    {{#header}} {{> core_courseformat/local/content/section/header }} {{/header}}
+    <div id="coursecontentcollapse{{num}}"
+         class="content {{^iscoursedisplaymultipage}}
+             {{^sitehome}}course-content-item-content collapse {{#isactive}}show{{/isactive}}{{/sitehome}}
+         {{/iscoursedisplaymultipage}}">
         {{#availability}}
             {{> core_courseformat/local/content/section/availability }}
         {{/availability}}
index 7359a29..357a82b 100644 (file)
 
     Example context (json):
     {
+        "id": 123,
         "name": "Section title",
+        "title": "<a href=\"http://moodle/course/view.php?id=5#section-0\">Section title</a>",
         "url": "#",
-        "ishidden": true
+        "iscoursedisplaymultipage": true
     }
 }}
 
-<h3 class="sectionid-{{id}}-title sectionname" data-for="section_title">
-    {{#url}}
-    <a href="{{{url}}}" class="{{#ishidden}} dimmed_text {{/ishidden}}">{{name}}</a>
-    {{/url}}
-    {{^url}}
-    <span>{{{title}}}</span>
-    {{/url}}
-</h3>
+{{#iscoursedisplaymultipage}}
+    <h3 class="sectionid-{{id}}-title sectionname"
+        data-for="section_title" data-id="{{id}}" data-number="{{num}}">
+        {{#url}}
+            <a href="{{{url}}}" class="{{#ishidden}} dimmed_text {{/ishidden}}">{{name}}</a>
+        {{/url}}
+        {{^url}}
+            <span>{{{title}}}</span>
+        {{/url}}
+    </h3>
+{{/iscoursedisplaymultipage}}
+{{^iscoursedisplaymultipage}}
+    {{#sitehome}}
+        <h2 class="sectionid-{{id}}-title sectionname"
+            data-for="section_title" data-id="{{id}}" data-number="{{num}}">
+            {{#url}}
+                <a href="{{{url}}}" class="{{#ishidden}} dimmed_text {{/ishidden}}">{{name}}</a>
+            {{/url}}
+            {{^url}}
+                <span>{{{title}}}</span>
+            {{/url}}
+        </h2>
+    {{/sitehome}}
+    {{^sitehome}}
+        <div class="d-flex">
+            <a role="button" data-toggle="collapse"
+               href="#coursecontentcollapse{{num}}"
+               id="collapssesection{{num}}"
+               aria-expanded="{{#isactive}}true{{/isactive}}{{^isactive}}false{{/isactive}}"
+               aria-controls="coursecontentcollapse{{num}}"
+               class="btn btn-icon mr-1 icons-collapse-expand {{^isactive}}collapsed{{/isactive}}"
+               aria-label="{{name}}">
+            <span class="expanded-icon icon-no-margin p-2" data-toggle="tooltip" title="{{#str}} collapse, core {{/str}}">
+                {{#pix}} t/expandedchevron, core {{/pix}}
+            </span>
+                <span class="collapsed-icon icon-no-margin p-2" data-toggle="tooltip" title="{{#str}} expand, core {{/str}}">
+                    {{#pix}} t/collapsedchevron, core {{/pix}}
+                </span>
+            </a>
+            <h3 class="sectionid-{{id}}-title sectionname course-content-item {{^visible}}dimmed{{/visible}}"
+                id="coursecontentsection{{num}}" data-for="section_title" data-id="{{id}}" data-number="{{num}}">
+                {{{title}}}
+            </h3>
+        </div>
+    {{/sitehome}}
+{{/iscoursedisplaymultipage}}
index 5b9bd4f..693cea7 100644 (file)
@@ -3200,32 +3200,37 @@ function course_ajax_enabled($course) {
 function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
     global $CFG, $PAGE, $SITE;
 
+    // Init the course editor module to support UI components.
+    $format = course_get_format($course);
+    include_course_editor($format);
+
     // Ensure that ajax should be included
     if (!course_ajax_enabled($course)) {
         return false;
     }
 
-    if (!$config) {
-        $config = new stdClass();
-    }
+    // Component based formats don't use YUI drag and drop anymore.
+    if (!$format->supports_components() && course_format_uses_sections($course->format)) {
 
-    // The URL to use for resource changes
-    if (!isset($config->resourceurl)) {
-        $config->resourceurl = '/course/rest.php';
-    }
+        if (!$config) {
+            $config = new stdClass();
+        }
 
-    // The URL to use for section changes
-    if (!isset($config->sectionurl)) {
-        $config->sectionurl = '/course/rest.php';
-    }
+        // The URL to use for resource changes
+        if (!isset($config->resourceurl)) {
+            $config->resourceurl = '/course/rest.php';
+        }
 
-    // Any additional parameters which need to be included on page submission
-    if (!isset($config->pageparams)) {
-        $config->pageparams = array();
-    }
+        // The URL to use for section changes
+        if (!isset($config->sectionurl)) {
+            $config->sectionurl = '/course/rest.php';
+        }
+
+        // Any additional parameters which need to be included on page submission
+        if (!isset($config->pageparams)) {
+            $config->pageparams = array();
+        }
 
-    // Include course dragdrop
-    if (course_format_uses_sections($course->format)) {
         $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
             array(array(
                 'courseid' => $course->id,
@@ -3856,7 +3861,7 @@ function core_course_core_calendar_get_valid_event_timestart_range(\calendar_eve
 }
 
 /**
- * Render the main course drawer to be included in the left part of the page.
+ * Render the message drawer to be included in the top of the body of each page.
  *
  * @return string HTML
  */
diff --git a/course/tests/behat/course_collapse_sections.feature b/course/tests/behat/course_collapse_sections.feature
new file mode 100644 (file)
index 0000000..b4955aa
--- /dev/null
@@ -0,0 +1,192 @@
+@core @core_course
+Feature: Collapse course sections
+  In order to quickly access the course structure
+  As a user
+  I need to collapse/extend sections for Topics/Weeks formats.
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email                |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+      | student1 | Student   | 1        | student1@example.com |
+    And the following "course" exists:
+      | fullname         | Course 1  |
+      | shortname        | C1        |
+      | category         | 0         |
+      | enablecompletion | 1         |
+      | numsections      | 5         |
+      | startdate        | 957139200 |
+      | enablecompletion | 1         |
+    And the following "activities" exist:
+      | activity | name         | intro                        | course | idnumber | section | completion |
+      | assign   | Assignment 1 | Test assignment description1 | C1     | assign1  | 1       | 1          |
+      | assign   | Assignment 2 | Test assignment description2 | C1     | assign2  | 2       | 1          |
+      | book     | Book 2       | Test book description2       | C1     | book2    | 2       | 1          |
+      | book     | Book 3       | Test book description3       | C1     | book3    | 3       | 1          |
+      | forum    | Forum 4      | Test forum description4      | C1     | forum4   | 4       | 1          |
+      | forum    | Forum 5      | Test forum description5      | C1     | forum5   | 5       | 1          |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | student1 | C1     | student        |
+      | teacher1 | C1     | editingteacher |
+    And I log in as "admin"
+    And I am on "Course 1" course homepage with editing mode on
+    When I edit the section "4"
+    And I expand all fieldsets
+    And I press "Add restriction..."
+    And I click on "Date" "button" in the "Add restriction..." "dialogue"
+    And I set the field "direction" to "until"
+    And I set the field "x[year]" to "2013"
+    And I press "Save changes"
+    And I hide section "5"
+    And I log out
+
+  @javascript
+  Scenario: No chevron on site home
+    Given I log in as "admin"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add a "Forum" to section "1" and I fill the form with:
+      | Forum name  | Test forum post backup name        |
+      | Description | Test forum post backup description |
+    And I click on "Edit summary" "link" in the "region-main" "region"
+    And I click on "Custom" "checkbox"
+    And I set the field "New value for Section name" to "New section name"
+    When I press "Save changes"
+    Then "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region"
+
+  @javascript
+  Scenario: Expand/collapse sections for Topics format.
+    Given I log in as "student1"
+    And I am on "Course 1" course homepage
+    And "[data-toggle=collapse]" "css_element" should exist in the "region-main" "region"
+    And I should see "Assignment 1" in the "region-main" "region"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And I should see "Book 2" in the "region-main" "region"
+    And I should see "Book 3" in the "region-main" "region"
+    And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "2013" in the "#section-4 .availabilityinfo" "css_element"
+    And I should not see "Forum 4"
+    And I should see "Not available" in the "#section-5 .availabilityinfo" "css_element"
+    And I should not see "Forum 5"
+    When I click on "#collapssesection3" "css_element"
+    And I should see "Assignment 1" in the "region-main" "region"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And I should see "Book 2" in the "region-main" "region"
+    And I should not see "Book 3" in the "region-main" "region"
+    And I click on "#collapssesection1" "css_element"
+    And I click on "#collapssesection2" "css_element"
+    And I click on "#collapssesection4" "css_element"
+    And I click on "#collapssesection5" "css_element"
+    Then I should not see "Assignment 1" in the "region-main" "region"
+    And I should not see "Assignment 2" in the "region-main" "region"
+    And I should not see "Book 2" in the "region-main" "region"
+    And I should not see "Book 3" in the "region-main" "region"
+    And I should not see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should not see "Not available" in the "#section-5 .availabilityinfo" "css_element"
+    And I click on "#collapssesection1" "css_element"
+    And I click on "#collapssesection2" "css_element"
+    And I click on "#collapssesection3" "css_element"
+    And I click on "#collapssesection4" "css_element"
+    And I click on "#collapssesection5" "css_element"
+    And I should see "Assignment 1" in the "region-main" "region"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And I should see "Book 2" in the "region-main" "region"
+    And I should see "Book 3" in the "region-main" "region"
+    And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "Not available" in the "#section-5 .availabilityinfo" "css_element"
+
+  @javascript
+  Scenario: Expand/collapse sections for Weeks format.
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to "Settings" in current page administration
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Format      | Weekly format     |
+    And I press "Save and display"
+    And I should see "Assignment 1" in the "region-main" "region"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And I should see "Book 2" in the "region-main" "region"
+    And I should see "Book 3" in the "region-main" "region"
+    And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "2013" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "Forum 4"
+    And I should see "Hidden from students" in the "#section-5 .availabilityinfo" "css_element"
+    And I should see "Forum 5"
+    When I click on "#collapssesection3" "css_element"
+    And I should see "Assignment 1" in the "region-main" "region"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And I should see "Book 2" in the "region-main" "region"
+    And I should not see "Book 3" in the "region-main" "region"
+    And I click on "#collapssesection1" "css_element"
+    And I click on "#collapssesection2" "css_element"
+    And I click on "#collapssesection4" "css_element"
+    And I click on "#collapssesection5" "css_element"
+    Then I should not see "Assignment 1" in the "region-main" "region"
+    And I should not see "Assignment 2" in the "region-main" "region"
+    And I should not see "Book 2" in the "region-main" "region"
+    And I should not see "Book 3" in the "region-main" "region"
+    And I should not see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should not see "Not available" in the "#section-5 .availabilityinfo" "css_element"
+    And I click on "#collapssesection1" "css_element"
+    And I click on "#collapssesection2" "css_element"
+    And I click on "#collapssesection3" "css_element"
+    And I click on "#collapssesection4" "css_element"
+    And I click on "#collapssesection5" "css_element"
+    And I should see "Assignment 1" in the "region-main" "region"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And I should see "Book 2" in the "region-main" "region"
+    And I should see "Book 3" in the "region-main" "region"
+    And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "2013" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "Forum 4"
+    And I should see "Hidden from students" in the "#section-5 .availabilityinfo" "css_element"
+    And I should see "Forum 5"
+
+  @javascript
+  Scenario: Users don't see chevron on one section per page for Topics format
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to "Settings" in current page administration
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Course layout | Show one section per page |
+    And I press "Save and display"
+    And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region"
+    And I follow "Topic 2"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region"
+    Then "Topic 1" "section" should not exist
+    And "Topic 3" "section" should not exist
+    And I am on "Course 1" course homepage with editing mode on
+    And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "2013" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "Forum 4"
+    And I should see "Hidden from students" in the "#section-5 .availabilityinfo" "css_element"
+    And I should see "Forum 5"
+
+  @javascript
+  Scenario: Users don't see chevron on one section per page for Weeks format
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    When I navigate to "Settings" in current page administration
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Format      | Weekly format     |
+      | Course layout | Show one section per page |
+    And I press "Save and display"
+    And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region"
+    And I follow "8 May - 14 May"
+    And I should see "Assignment 2" in the "region-main" "region"
+    And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region"
+    Then "1 May - 7 May" "section" should not exist
+    And "15 May - 21 May" "section" should not exist
+    And I log out
+    And I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element"
+    And I should see "2013" in the "#section-4 .availabilityinfo" "css_element"
+    And I should not see "Forum 4"
+    And I should see "Not available" in the "#section-5 .availabilityinfo" "css_element"
+    And I should not see "Forum 5"
index 23d20c8..73ec853 100644 (file)
@@ -114,3 +114,27 @@ Feature: Managers can create courses
     And I press "Save and display"
     And I click on "Participants" "link"
     Then I should see "Non-editing teacher" in the "Kevin the" "table_row"
+
+  @javascript
+  Scenario: Create a course as admin
+    Given I log in as "admin"
+    And the following config values are set as admin:
+      | enroladminnewcourse | 0 |
+    And I navigate to "Courses > Add a new course" in site administration
+    And I set the following fields to these values:
+      | Course full name  | My first course |
+      | Course short name | myfirstcourse |
+    And I press "Save and display"
+    And I navigate to course participants
+    Then I should not see "Teacher"
+    And I should see "Nothing to display"
+    And the following config values are set as admin:
+      | enroladminnewcourse | 1 |
+    And I navigate to "Courses > Add a new course" in site administration
+    And I set the following fields to these values:
+      | Course full name  | My second course |
+      | Course short name | mysecondcourse |
+    And I press "Save and display"
+    And I navigate to course participants
+    And I should see "Teacher"
+    And I should not see "Nothing to display"
index 7bba43f..49677fc 100644 (file)
@@ -4,8 +4,7 @@ Feature: Show/hide course sections
   As a teacher
   I need to show or hide sections
 
-  @javascript
-  Scenario: Show / hide section icon functions correctly
+  Background:
     Given the following "users" exist:
       | username | firstname | lastname | email |
       | teacher1 | Teacher | 1 | teacher1@example.com |
@@ -43,7 +42,10 @@ Feature: Show/hide course sections
       | Forum name | Test hidden forum 32 name |
       | Description | Test hidden forum 32 description |
       | Availability | Show on course page |
-    And I am on "Course 1" course homepage
+
+  @javascript
+  Scenario: Show / hide section icon functions correctly
+    Given I am on "Course 1" course homepage
     When I hide section "1"
     Then section "1" should be hidden
     And section "2" should be visible
@@ -70,3 +72,60 @@ Feature: Show/hide course sections
     And section "2" should be visible
     And section "3" should be hidden
     And all activities in section "1" should be hidden
+
+  @javascript
+  Scenario: Students can not navigate to hidden sections
+    Given I am on "Course 1" course homepage
+    And I hide section "2"
+    Given I navigate to "Settings" in current page administration
+    And I set the following fields to these values:
+      | Course layout | Show one section per page |
+    And I press "Save and display"
+    When I click on "Topic 1" "link" in the "region-main" "region"
+    Then I should see "Topic 2" in the "region-main" "region"
+    And I click on "Topic 2" "link" in the "region-main" "region"
+    And I should see "Topic 1" in the "region-main" "region"
+    And I should see "Topic 3" in the "region-main" "region"
+    And I log out
+    And I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I click on "Topic 1" "link" in the "region-main" "region"
+    And I should not see "Topic 2" in the "region-main" "region"
+    And I should see "Topic 3" in the "region-main" "region"
+    And I click on "Topic 3" "link" in the "region-main" "region"
+    And I should not see "Topic 2" in the "region-main" "region"
+    And I should see "Topic 1" in the "region-main" "region"
+
+  @javascript
+  Scenario: Students can not navigate to restricted sections
+    Given I am on "Course 1" course homepage
+    Given I navigate to "Settings" in current page administration
+    And I set the following fields to these values:
+      | Course layout | Show one section per page |
+      | Enable completion tracking | Yes |
+    And I press "Save and display"
+    And I add a "Label" to section "1" and I fill the form with:
+      | Label text | Test label |
+      | Completion tracking | Students can manually mark the activity as completed |
+    And I edit the section "2"
+    And I expand all fieldsets
+    And I click on "Add restriction..." "button"
+    And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
+    And I set the following fields to these values:
+      | cm | Test label |
+      | Required completion status | must be marked complete |
+    And I press "Save changes"
+    When I click on "Topic 1" "link" in the "region-main" "region"
+    Then I should see "Topic 2" in the "region-main" "region"
+    And I click on "Topic 2" "link" in the "region-main" "region"
+    And I should see "Topic 1" in the "region-main" "region"
+    And I should see "Topic 3" in the "region-main" "region"
+    And I log out
+    And I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I click on "Topic 1" "link" in the "region-main" "region"
+    And I should not see "Topic 2" in the "region-main" "region"
+    And I should see "Topic 3" in the "region-main" "region"
+    And I click on "Topic 3" "link" in the "region-main" "region"
+    And I should not see "Topic 2" in the "region-main" "region"
+    And I should see "Topic 1" in the "region-main" "region"
index 02411ec..413250c 100644 (file)
Binary files a/enrol/manual/amd/build/quickenrolment.min.js and b/enrol/manual/amd/build/quickenrolment.min.js differ
index d587cfe..826f59c 100644 (file)
Binary files a/enrol/manual/amd/build/quickenrolment.min.js.map and b/enrol/manual/amd/build/quickenrolment.min.js.map differ
index 1034c2b..a919112 100644 (file)
@@ -93,6 +93,9 @@ const showModal = (dynamicTable, contextId) => {
         large: true,
         title: Str.get_string('enrolusers', 'enrol_manual'),
         body: getBodyForContext(contextId),
+        buttons: {
+            save: Str.get_string('enrolusers', 'enrol_manual'),
+        }
     })
     .then(modal => {
         modal.getRoot().on(ModalEvents.save, e => {
@@ -114,25 +117,19 @@ const showModal = (dynamicTable, contextId) => {
             modal.destroy();
         });
 
+        modal.show();
+
         return modal;
     })
-    .then(modal => {
-        modal.show();
+    .then(modal => Promise.all([modal, modal.getBodyPromise()]))
+    .then(([modal, body]) => {
+        if (body.get(0).querySelector(Selectors.cohortSelector)) {
+            return modal.setSaveButtonText(Str.get_string('enroluserscohorts', 'enrol_manual')).then(() => modal);
+        }
 
         return modal;
     })
     .then(modal => {
-        modal.setSaveButtonText(Str.get_string('enrolusers', 'enrol_manual'));
-
-        modal.getBodyPromise().then(body => {
-            if (body.get(0).querySelector(Selectors.cohortSelector)) {
-                modal.setSaveButtonText(Str.get_string('enroluserscohorts', 'enrol_manual'));
-            }
-
-            return body;
-        })
-        .catch();
-
         pendingPromise.resolve();
 
         return modal;
index 2d09c50..2ad68a6 100644 (file)
@@ -42,7 +42,7 @@ print $OUTPUT->header();
 
 // Enable all players.
 $enabledmediaplugins = \core\plugininfo\media::get_enabled_plugins();
-\core\plugininfo\media::set_enabled_plugins('vimeo,youtube,videojs,html5audio,html5video,swf');
+\core\plugininfo\media::set_enabled_plugins('vimeo,youtube,videojs,html5audio,html5video');
 
 // Create plugin.
 $filterplugin = new filter_mediaplugin(null, array());
@@ -153,4 +153,4 @@ filter_mediaplugin_perf_stop('One link (mp3)');
 
 // End page.
 echo html_writer::end_tag('ul');
-print $OUTPUT->footer();
\ No newline at end of file
+print $OUTPUT->footer();
index 726c1d1..7d70f83 100644 (file)
@@ -74,7 +74,7 @@ class filter_mediaplugin extends moodle_text_filter {
             return $text;
         }
 
-        // Check SWF permissions.
+        // Check permissions.
         $this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
 
         // Looking for tags.
@@ -177,7 +177,7 @@ class filter_mediaplugin extends moodle_text_filter {
      */
     protected function embed_alternatives($urls, $name, $width, $height, $options) {
 
-        // Allow SWF (or not).
+        // Allow trusted content (or not).
         if ($this->trusted) {
             $options[core_media_manager::OPTION_TRUSTED] = true;
         }
index 861e4f0..de09814 100644 (file)
@@ -34,8 +34,8 @@ class filter_mediaplugin_testcase extends advanced_testcase {
     function test_filter_mediaplugin_link() {
         $this->resetAfterTest(true);
 
-        // we need to enable the plugins somehow and the flash fallback.
-        \core\plugininfo\media::set_enabled_plugins('vimeo,youtube,videojs,html5video,swf,html5audio');
+        // We need to enable the media plugins.
+        \core\plugininfo\media::set_enabled_plugins('vimeo,youtube,videojs,html5video,html5audio');
         set_config('useflash', true, 'media_videojs');
 
         $filterplugin = new filter_mediaplugin(null, array());
index 874af23..5762430 100644 (file)
@@ -369,19 +369,11 @@ class grade_report_grader extends grade_report {
 
         if ($this->sortitemid) {
             if (!isset($SESSION->gradeuserreport->sort)) {
-                if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') {
-                    $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
-                } else {
-                    $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
-                }
+                $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
             } else {
                 // this is the first sort, i.e. by last name
                 if (!isset($SESSION->gradeuserreport->sortitemid)) {
-                    if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') {
-                        $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
-                    } else {
-                        $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
-                    }
+                    $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
                 } else if ($SESSION->gradeuserreport->sortitemid == $this->sortitemid) {
                     // same as last sort
                     if ($SESSION->gradeuserreport->sort == 'ASC') {
@@ -390,11 +382,7 @@ class grade_report_grader extends grade_report {
                         $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
                     }
                 } else {
-                    if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') {
-                        $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
-                    } else {
-                        $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
-                    }
+                    $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
                 }
             }
             $SESSION->gradeuserreport->sortitemid = $this->sortitemid;
@@ -1090,10 +1078,13 @@ class grade_report_grader extends grade_report {
                 }
 
                 $gradepass = ' gradefail ';
+                $gradepassicon = $OUTPUT->pix_icon('i/invalid', get_string('fail', 'grades'));
                 if ($grade->is_passed($item)) {
                     $gradepass = ' gradepass ';
+                    $gradepassicon = $OUTPUT->pix_icon('i/valid', get_string('pass', 'grades'));
                 } else if (is_null($grade->is_passed($item))) {
                     $gradepass = '';
+                    $gradepassicon = '';
                 }
 
                 // if in editing mode, we need to print either a text box
@@ -1145,10 +1136,12 @@ class grade_report_grader extends grade_report {
 
                             // invalid grade if gradeval < 1
                             if ($gradeval < 1) {
-                                $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>-</span>";
+                                $itemcell->text .= $gradepassicon .
+                                    "<span class='gradevalue{$hidden}{$gradepass}'>-</span>";
                             } else {
                                 $gradeval = $grade->grade_item->bounded_grade($gradeval); //just in case somebody changes scale
-                                $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>{$scales[$gradeval - 1]}</span>";
+                                $itemcell->text .= $gradepassicon .
+                                    "<span class='gradevalue{$hidden}{$gradepass}'>{$scales[$gradeval - 1]}</span>";
                             }
                         }
 
@@ -1162,7 +1155,7 @@ class grade_report_grader extends grade_report {
                                           . '" type="text" class="text" title="'. $strgrade .'" name="grade['
                                           .$userid.'][' .$item->id.']" id="grade_'.$userid.'_'.$item->id.'" value="'.$value.'" />';
                         } else {
-                            $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>" .
+                            $itemcell->text .= $gradepassicon . "<span class='gradevalue{$hidden}{$gradepass}'>" .
                                     format_float($gradeval, $decimalpoints) . "</span>";
                         }
                     }
@@ -1196,7 +1189,7 @@ class grade_report_grader extends grade_report {
                     }
 
                     if ($item->needsupdate) {
-                        $itemcell->text .= "<span class='gradingerror{$hidden}{$gradepass}'>" . $error . "</span>";
+                        $itemcell->text .= $gradepassicon . "<span class='gradingerror{$hidden}{$gradepass}'>" . $error . "</span>";
                     } else {
                         // The max and min for an aggregation may be different to the grade_item.
                         if (!is_null($gradeval)) {
@@ -1204,7 +1197,7 @@ class grade_report_grader extends grade_report {
                             $item->grademin = $grade->get_grade_min();
                         }
 
-                        $itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>" .
+                        $itemcell->text .= $gradepassicon . "<span class='gradevalue{$hidden}{$gradepass}'>" .
                                 grade_format_gradevalue($gradeval, $item, true, $gradedisplaytype, null) . "</span>";
                         if ($showanalysisicon) {
                             $itemcell->text .= $this->gtree->get_grade_analysis_icon($grade);
index e3a6280..8e59f6b 100644 (file)
     display: inline-block;
 }
 
-.path-grade-report-grader span.gradepass {
-    color: #298721;
-}
-
-.path-grade-report-grader span.gradefail {
-    color: #890d0d;
-}
-
 .path-grade-report-grader .gradeparent tr:nth-child(n) td.overridden:nth-child(n) {
     /* Made very specific to override the default stripped style of the table. */
     background-color: #efd9a4;
index 98b46a0..6fa0512 100644 (file)
@@ -417,7 +417,7 @@ abstract class grade_report {
     protected function get_sort_arrow($direction='move', $sortlink=null) {
         global $OUTPUT;
         $pix = array('up' => 't/sort_desc', 'down' => 't/sort_asc', 'move' => 't/sort');
-        $matrix = array('up' => 'desc', 'down' => 'asc', 'move' => 'desc');
+        $matrix = array('up' => 'desc', 'down' => 'asc', 'move' => 'asc');
         $strsort = $this->get_lang_string('sort' . $matrix[$direction]);
 
         $arrow = $OUTPUT->pix_icon($pix[$direction], '', '', ['class' => 'sorticon']);
index 5723e87..e7f472c 100644 (file)
@@ -576,6 +576,8 @@ $string['enablewebservices'] = 'Enable web services';
 $string['enablewsdocumentation'] = 'Web services documentation';
 $string['encryptedpassword_set'] = '(Set and encrypted)';
 $string['encryptedpassword_edit'] = 'Enter new value';
+$string['enroladminnewcourse'] = 'Auto-enrol admin in new courses';
+$string['enroladminnewcourse_help'] = 'When an admin adds a new course, should they be automatically enrolled and assigned the creators\' role in new courses?';
 $string['enrolinstancedefaults'] = 'Enrolment instance defaults';
 $string['enrolinstancedefaults_desc'] = 'Default enrolment settings in new courses.';
 $string['enrolmultipleusers'] = 'Enrol the users';
@@ -826,8 +828,6 @@ $string['mediapluginogv'] = 'Enable .ogv filter';
 $string['mediapluginram'] = 'Enable .ram filter';
 $string['mediapluginrm'] = 'Enable .rm filter';
 $string['mediapluginrpm'] = 'Enable .rpm filter';
-$string['mediapluginswf'] = 'Enable .swf filter';
-$string['mediapluginswfnote'] = 'As a default security measure, normal users should not be allowed to embed swf flash files.';
 $string['mediapluginwmv'] = 'Enable .wmv filter';
 $string['mediapluginyoutube'] = 'Enable YouTube links filter';
 $string['messaging'] = 'Enable messaging system';
@@ -1552,3 +1552,6 @@ $string['modchooserdefault'] = 'Activity chooser default';
 
 // Deprecated since Moodle 4.0.
 $string['coursepage'] = 'Course page';
+$string['mediapluginswf'] = 'Enable .swf filter';
+$string['mediapluginswfnote'] = 'As a default security measure, normal users should not be allowed to embed swf flash files.';
+
index 82c1378..cb6a296 100644 (file)
@@ -159,3 +159,5 @@ importfrominstructions,core_calendar
 proceedtocourse,core_enrol
 coursepage,core_admin
 invalidpersistenterror,core_competency
+mediapluginswf,core_admin
+mediapluginswfnote,core_admin
index 15de361..8cd9000 100644 (file)
@@ -226,6 +226,7 @@ $string['externalurl'] = 'External URL';
 $string['externalurl_desc'] = 'If an external gradebook is used, the URL should be specified here.';
 $string['extracreditvalue'] = 'Extra credit value for {$a}';
 $string['extracreditwarning'] = 'Note: Setting all items for a category to extra credit will effectively remove them from the grade calculation. Since there will be no point total';
+$string['fail'] = 'Fail';
 $string['feedback'] = 'Feedback';
 $string['feedback_help'] = 'This box enables any comments about the grade to be added.';
 $string['feedbackadd'] = 'Add feedback';
@@ -616,6 +617,7 @@ $string['overridesitedefaultgradedisplaytype_help'] = 'If ticked, grade letters
 $string['overrideweightofa'] = 'Override weight of {$a}';
 $string['parentcategory'] = 'Parent category';
 $string['pctoftotalgrade'] = '% of total grade';
+$string['pass'] = 'Pass';
 $string['percent'] = 'Percent';
 $string['percentage'] = 'Percentage';
 $string['percentageletter'] = 'Percentage (letter)';
index 40ff7e9..577fd36 100644 (file)
@@ -2266,6 +2266,8 @@ $string['weeks'] = 'weeks';
 $string['weekhide'] = 'Hide this week from {$a}';
 $string['weeklyoutline'] = 'Weekly outline';
 $string['weekshow'] = 'Show this week to {$a}';
+$string['welcomeback'] = 'Welcome back, {$a}! 👋';
+$string['welcometosite'] = 'Welcome, {$a}! 👋';
 $string['welcometocourse'] = 'Welcome to {$a}';
 $string['welcometocoursetext'] = 'Welcome to {$a->coursename}!
 
index 83c4e01..a47dacd 100644 (file)
@@ -110,6 +110,7 @@ $string['privacy:metadata:reason'] = 'The reason for requesting this course.';
 $string['privacy:metadata:requester'] = 'The ID of the user who requested the course';
 $string['privacy:metadata:requestsummary'] = 'Stores information about requests for courses that users make.';
 $string['privacy:metadata:suspended'] = 'A flag to show if the user has been suspended on this system.';
+$string['privacy:metadata:user_preference:core_user_welcome'] = 'Timestamp logged for when the welcome message was shown to the user for the first time.';
 $string['privacy:metadata:user_preferences'] = 'Preferences associated with the given user';
 $string['privacy:metadata:user_preferences:name'] = 'Preference name';
 $string['privacy:metadata:user_preferences:userid'] = 'The user ID';
index 06e1aad..d25dbfd 100644 (file)
@@ -3923,6 +3923,9 @@ class admin_setting_configduration extends admin_setting {
         if ($this->validatefunction) {
             return call_user_func($this->validatefunction, $data);
         } else {
+            if ($data < 0) {
+                return get_string('errorsetting', 'admin');
+            }
             return '';
         }
     }
@@ -4008,9 +4011,6 @@ class admin_setting_configduration extends admin_setting {
         }
 
         $seconds = (int)($data['v']*$data['u']);
-        if ($seconds < 0) {
-            return get_string('errorsetting', 'admin');
-        }
 
         // Validate the new setting.
         $error = $this->validate_setting($seconds);
index d2d0da7..0f386b7 100644 (file)
@@ -54,7 +54,8 @@ class behat_field_manager {
         $fieldnode = $context->find_field($label);
 
         // The behat field manager.
-        return self::get_form_field($fieldnode, $context->getSession());
+        $field = self::get_form_field($fieldnode, $context->getSession());
+        return $field;
     }
 
     /**
@@ -74,12 +75,7 @@ class behat_field_manager {
 
         // Get the field type if is part of a moodleform.
         if (self::is_moodleform_field($fieldnode)) {
-            // This might go out of scope, finding element beyond the dom and fail. So fallback to guessing type.
-            try {
-                $type = self::get_field_node_type($fieldnode, $session);
-            } catch (WebDriver\Exception\InvalidSelector $e) {
-                $type = 'field';
-            }
+            $type = self::get_field_node_type($fieldnode, $session);
         }
 
         // If is not a moodleforms field use the base field type.
@@ -105,8 +101,7 @@ class behat_field_manager {
 
         // If the field is not part of a moodleform, we should still try to find out
         // which field type are we dealing with.
-        if ($type == 'field' &&
-                $guessedtype = self::guess_field_type($fieldnode, $session)) {
+        if ($type == 'field' && $guessedtype = self::guess_field_type($fieldnode, $session)) {
             $type = $guessedtype;
         }
 
@@ -135,26 +130,31 @@ class behat_field_manager {
      * @return string|bool The field type or false.
      */
     public static function guess_field_type(NodeElement $fieldnode, Session $session) {
+        [
+            'document' => $document,
+            'node' => $node,
+        ] = self::get_dom_elements_for_node($fieldnode, $session);
 
         // If the type is explicitly set on the element pointed to by the label - use it.
-        if ($fieldtype = $fieldnode->getAttribute('data-fieldtype')) {
+        if ($fieldtype = $node->getAttribute('data-fieldtype')) {
             return self::normalise_fieldtype($fieldtype);
         }
 
         // Textareas are considered text based elements.
-        $tagname = strtolower($fieldnode->getTagName());
+        $tagname = strtolower($node->nodeName);
         if ($tagname == 'textarea') {
+            $xpath = new \DOMXPath($document);
 
             // If there is an iframe with $id + _ifr there a TinyMCE editor loaded.
-            $xpath = '//div[@id="' . $fieldnode->getAttribute('id') . 'editable"]';
-            if ($session->getPage()->find('xpath', $xpath)) {
+            if ($xpath->query('//div[@id="' . $node->getAttribute('id') . 'editable"]')->count() !== 0) {
                 return 'editor';
             }
             return 'textarea';
 
-        } else if ($tagname == 'input') {
-            $type = $fieldnode->getAttribute('type');
-            switch ($type) {
+        }
+
+        if ($tagname == 'input') {
+            switch ($node->getAttribute('type')) {
                 case 'text':
                 case 'password':
                 case 'email':
@@ -172,11 +172,15 @@ class behat_field_manager {
                     return false;
             }
 
-        } else if ($tagname == 'select') {
+        }
+
+        if ($tagname == 'select') {
             // Select tag.
             return 'select';
-        } else if ($tagname == 'span') {
-            if ($fieldnode->hasAttribute('data-inplaceeditable') && $fieldnode->getAttribute('data-inplaceeditable')) {
+        }
+
+        if ($tagname == 'span') {
+            if ($node->hasAttribute('data-inplaceeditable') && $node->getAttribute('data-inplaceeditable')) {
                 return 'inplaceeditable';
             }
         }
@@ -206,6 +210,32 @@ class behat_field_manager {
         return ($parentformfound != false);
     }
 
+    /**
+     * Get the DOMDocument and DOMElement for a NodeElement.
+     *
+     * @param NodeElement $fieldnode
+     * @param Session $session
+     * @return array
+     */
+    protected static function get_dom_elements_for_node(NodeElement $fieldnode, Session $session): array {
+        $html = $session->getPage()->getContent();
+
+        $document = new \DOMDocument();
+
+        $previousinternalerrors = libxml_use_internal_errors(true);
+        $document->loadHTML($html, LIBXML_HTML_NODEFDTD | LIBXML_BIGLINES);
+        libxml_clear_errors();
+        libxml_use_internal_errors($previousinternalerrors);
+
+        $xpath = new \DOMXPath($document);
+        $node = $xpath->query($fieldnode->getXpath())->item(0);
+
+        return [
+            'document' => $document,
+            'node' => $node,
+        ];
+    }
+
     /**
      * Recursive method to find the field type.
      *
@@ -214,31 +244,54 @@ class behat_field_manager {
      *
      * @param NodeElement $fieldnode The current node.
      * @param Session $session The behat browser session
-     * @return mixed A NodeElement if we continue looking for the element type and String or false when we are done.
+     * @return null|string A text description of the node type, or null if one could not be accurately determined
      */
-    protected static function get_field_node_type(NodeElement $fieldnode, Session $session) {
+    protected static function get_field_node_type(NodeElement $fieldnode, Session $session): ?string {
+        [
+            'document' => $document,
+            'node' => $node,
+        ] = self::get_dom_elements_for_node($fieldnode, $session);
+
+        return self::get_field_type($document, $node, $session);
+    }
 
-        // Special handling for availability field which requires custom JavaScript.
-        if ($fieldnode->getAttribute('name') === 'availabilityconditionsjson') {
+    /**
+     * Get the field type from the specified DOMElement.
+     *
+     * @param \DOMDocument $document
+     * @param \DOMElement $node
+     * @param Session $session
+     * @return null|string
+     */
+    protected static function get_field_type(\DOMDocument $document, \DOMElement $node, Session $session): ?string {
+        $xpath = new \DOMXPath($document);
+
+        if ($node->getAttribute('name') === 'availabilityconditionsjson') {
+            // Special handling for availability field which requires custom JavaScript.
             return 'availability';
         }
 
-        if ($fieldnode->getTagName() == 'html') {
-            return false;
+        if ($node->nodeName == 'html') {
+            // The top of the document has been reached.
+            return null;
         }
 
         // If the type is explictly set on the element pointed to by the label - use it.
-        $fieldtype = $fieldnode->getAttribute('data-fieldtype');
+        $fieldtype = $node->getAttribute('data-fieldtype');
         if ($fieldtype) {
             return self::normalise_fieldtype($fieldtype);
         }
 
-        if (!empty($fieldnode->find('xpath', '/ancestor::*[@data-passwordunmaskid]'))) {
+        if ($xpath->query('/ancestor::*[@data-passwordunmaskid]', $node)->count() !== 0) {
+            // This element has a passwordunmaskid as a parent.
             return 'passwordunmask';
         }
 
         // Fetch the parentnode only once.
-        $parentnode = $fieldnode->getParent();
+        $parentnode = $node->parentNode;
+        if ($parentnode instanceof \DOMDocument) {
+            return null;
+        }
 
         // Check the parent fieldtype before we check classes.
         $fieldtype = $parentnode->getAttribute('data-fieldtype');
@@ -255,11 +308,12 @@ class behat_field_manager {
 
             // Stop propagation through the DOM, if it does not have a felement is not part of a moodle form.
             if (strstr($class, 'fcontainer') != false) {
-                return false;
+                return null;
             }
         }
 
-        return self::get_field_node_type($parentnode, $session);
+        // Move up the tree.
+        return self::get_field_type($document, $parentnode, $session);
     }
 
     /**
index b4064ef..17285ed 100644 (file)
@@ -177,20 +177,14 @@ trait core_behat_file_helper {
      * @return void
      */
     protected function open_add_file_window($filemanagernode, $repositoryname) {
-
         $exception = new ExpectationException('No files can be added to the specified filemanager', $this->getSession());
 
         // We should deal with single-file and multiple-file filemanagers,
         // catching the exception thrown by behat_base::find() in case is not multiple
-        try {
-            // Looking for the add button inside the specified filemanager.
-            $add = $this->find('css', 'div.fp-btn-add a', $exception, $filemanagernode);
-        } catch (Exception $e) {
-            // Otherwise should be a single-file filepicker form element.
-            $add = $this->find('css', 'input.fp-btn-choose', $exception, $filemanagernode);
-        }
-        $this->ensure_node_is_visible($add);
-        $add->click();
+        $this->execute('behat_general::i_click_on_in_the', [
+            'div.fp-btn-add a, input.fp-btn-choose', 'css_element',
+            $filemanagernode, 'NodeElement'
+        ]);
 
         // Wait for the default repository (if any) to load. This checks that
         // the relevant div exists and that it does not include the loading image.
@@ -220,7 +214,7 @@ trait core_behat_file_helper {
         if (!$repositorylink->getParent()->getParent()->hasClass('active')) {
             // If the repository link is active, then the repository is already loaded.
             // Clicking it while it's active causes issues, so only click it when it isn't (see MDL-51014).
-            $repositorylink->click();
+            $this->execute('behat_general::i_click_on', [$repositorylink, 'NodeElement']);
         }
     }
 
index d32386f..b8b5224 100644 (file)
@@ -126,7 +126,6 @@ class manager {
             new environment\publicpaths(),
             new environment\configrw(),
             new environment\preventexecpath(),
-            new security\mediafilterswf(),
             new security\embed(),
             new security\openprofiles(),
             new security\crawlers(),
@@ -156,4 +155,3 @@ class manager {
         return $checks;
     }
 }
-
diff --git a/lib/classes/check/security/mediafilterswf.php b/lib/classes/check/security/mediafilterswf.php
deleted file mode 100644 (file)
index 78632b9..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Verifies sloppy swf embedding - this should have been removed long ago!!
- *
- * @package    core
- * @category   check
- * @copyright  2020 Brendan Heywood <brendan@catalyst-au.net>
- * @copyright  2008 petr Skoda
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace core\check\security;
-
-defined('MOODLE_INTERNAL') || die();
-
-use core\check\check;
-use core\check\result;
-
-/**
- * Verifies sloppy swf embedding - this should have been removed long ago!!
- *
- * @copyright  2020 Brendan Heywood <brendan@catalyst-au.net>
- * @copyright  2008 petr Skoda
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class mediafilterswf extends check {
-
-    /**
-     * Get the short check name
-     *
-     * @return string
-     */
-    public function get_name(): string {
-        return get_string('check_mediafilterswf_name', 'report_security');
-    }
-
-    /**
-     * A link to a place to action this
-     *
-     * @return action_link|null
-     */
-    public function get_action_link(): ?\action_link {
-        return new \action_link(
-            new \moodle_url('/admin/settings.php?section=managemediaplayers'),
-            get_string('managemediaplayers', 'media'));
-    }
-
-    /**
-     * Return result
-     * @return result
-     */
-    public function get_result(): result {
-        $details = get_string('check_mediafilterswf_details', 'report_security');
-
-        $activefilters = filter_get_globally_enabled();
-
-        $enabledmediaplayers = \core\plugininfo\media::get_enabled_plugins();
-        if (array_search('mediaplugin', $activefilters) !== false and array_key_exists('swf', $enabledmediaplayers)) {
-            $status = result::CRITICAL;
-            $summary = get_string('check_mediafilterswf_error', 'report_security');
-        } else {
-            $status = result::OK;
-            $summary = get_string('check_mediafilterswf_ok', 'report_security');
-        }
-        return new result($status, $summary, $details);
-    }
-}
-
index 8d32b35..cea82ed 100644 (file)
@@ -260,8 +260,8 @@ abstract class core_filetypes {
             'svgz' => array('type' => 'image/svg+xml', 'icon' => 'image',
                     'groups' => array('image', 'web_image'), 'string' => 'image'),
             'swa' => array('type' => 'application/x-director', 'icon' => 'flash'),
-            'swf' => array('type' => 'application/x-shockwave-flash', 'icon' => 'flash', 'groups' => array('video', 'web_video')),
-            'swfl' => array('type' => 'application/x-shockwave-flash', 'icon' => 'flash', 'groups' => array('video', 'web_video')),
+            'swf' => array('type' => 'application/x-shockwave-flash', 'icon' => 'flash'),
+            'swfl' => array('type' => 'application/x-shockwave-flash', 'icon' => 'flash'),
 
             'sxw' => array('type' => 'application/vnd.sun.xml.writer', 'icon' => 'writer'),
             'stw' => array('type' => 'application/vnd.sun.xml.writer.template', 'icon' => 'writer'),
index c30b678..c6373a4 100644 (file)
@@ -35,15 +35,19 @@ class primary extends view {
             return;
         }
         $this->id = 'primary_navigation';
-        if (get_home_page() == HOMEPAGE_SITE && isloggedin() && !isguestuser()) {
-            $this->add(get_string('home'), new \moodle_url('/'), self::TYPE_SYSTEM,
-                null, 'home', new \pix_icon('i/home', ''));
-        }
-
-        // Add the dashboard link.
-        if (isloggedin() && !isguestuser()) {  // Makes no sense if you aren't logged in.
-            $this->rootnodes['home'] = $this->add(get_string('myhome'), new \moodle_url('/my/'),
-                self::TYPE_SETTING, null, 'myhome', new \pix_icon('i/dashboard', ''));
+        if (isloggedin() && !isguestuser()) {
+            $homepage = get_home_page();
+            if ($homepage === HOMEPAGE_SITE) {
+                $this->add(get_string('home'), new \moodle_url('/'), self::TYPE_SYSTEM,
+                        null, 'home', new \pix_icon('i/home', ''));
+                $this->rootnodes['home'] = $this->add(get_string('myhome'), new \moodle_url('/my/'),
+                        self::TYPE_SETTING, null, 'myhome', new \pix_icon('i/dashboard', ''));
+            } else if ($homepage === HOMEPAGE_MY) {
+                $this->add(get_string('myhome'), new \moodle_url('/my/'), self::TYPE_SYSTEM,
+                        null, 'home', new \pix_icon('i/home', ''));
+                $this->rootnodes['home'] = $this->add(get_string('sitehome'), new \moodle_url('/'),
+                        self::TYPE_SETTING, null, 'myhome', new \pix_icon('i/dashboard', ''));
+            }
         }
 
         // Add a dummy mycourse link to a mycourses page.
index 29d7f95..9074c20 100644 (file)
@@ -366,6 +366,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:t/collapsed_rtl' => 'fa-caret-left',
             'core:t/collapsed' => 'fa-caret-right',
             'core:t/collapsedcaret' => 'fa-caret-right',
+            'core:t/collapsedchevron' => 'fa-chevron-right',
             'core:t/contextmenu' => 'fa-cog',
             'core:t/copy' => 'fa-copy',
             'core:t/delete' => 'fa-trash',
@@ -385,6 +386,7 @@ class icon_system_fontawesome extends icon_system_font {
             'core:t/emptystar' => 'fa-star-o',
             'core:t/enrolusers' => 'fa-user-plus',
             'core:t/expanded' => 'fa-caret-down',
+            'core:t/expandedchevron' => 'fa-chevron-down',
             'core:t/go' => 'fa-play',
             'core:t/grades' => 'fa-table',
             'core:t/groupn' => 'fa-user',
@@ -487,4 +489,3 @@ class icon_system_fontawesome extends icon_system_font {
     }
 
 }
-
index be2741a..2a83273 100644 (file)
@@ -1725,11 +1725,13 @@ class core_plugin_manager {
             'block' => array('course_overview', 'messages', 'community', 'participants'),
             'cachestore' => array('memcache'),
             'enrol' => array('authorize'),
+            'portfolio' => array('picasa'),
+            'media' => array('swf'),
             'qformat' => array('webct'),
             'message' => array('jabber'),
             'quizaccess' => array('safebrowser'),
             'report' => array('search'),
-            'repository' => array('alfresco'),
+            'repository' => array('alfresco', 'picasa'),
             'tinymce' => array('dragmath'),
             'tool' => array('bloglevelupgrade', 'qeupgradehelper', 'timezoneimport', 'assignmentupgrade'),
             'theme' => array('bootstrapbase', 'clean', 'more', 'afterburner', 'anomaly', 'arialist', 'base',
@@ -1908,7 +1910,7 @@ class core_plugin_manager {
             ),
 
             'media' => array(
-                'html5audio', 'html5video', 'swf', 'videojs', 'vimeo', 'youtube'
+                'html5audio', 'html5video', 'videojs', 'vimeo', 'youtube'
             ),
 
             'message' => array(
@@ -1933,7 +1935,7 @@ class core_plugin_manager {
             ),
 
             'portfolio' => array(
-                'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
+                'boxnet', 'download', 'flickr', 'googledocs', 'mahara'
             ),
 
             'profilefield' => array(
@@ -1994,7 +1996,7 @@ class core_plugin_manager {
             'repository' => array(
                 'areafiles', 'boxnet', 'contentbank', 'coursefiles', 'dropbox', 'equella', 'filesystem',
                 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot', 'nextcloud',
-                'onedrive', 'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav',
+                'onedrive', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav',
                 'wikimedia', 'youtube'
             ),
 
index 774d5a8..c1e81c6 100644 (file)
@@ -1169,4 +1169,28 @@ class core_user {
 
         return false;
     }
+
+    /**
+     * Get welcome message.
+     *
+     * @return lang_string welcome message
+     */
+    public static function welcome_message(): ?lang_string {
+        global $USER;
+
+        $isloggedinas = \core\session\manager::is_loggedinas();
+        if (!isloggedin() || isguestuser() || $isloggedinas) {
+            return null;
+        }
+        if (empty($USER->core_welcome_message)) {
+            $USER->core_welcome_message = true;
+            $messagekey = 'welcomeback';
+            if (empty(get_user_preferences('core_user_welcome', null))) {
+                $messagekey = 'welcometosite';
+                set_user_preference('core_user_welcome', time());
+            }
+            return new lang_string($messagekey, 'core', $USER->firstname);
+        };
+        return null;
+    }
 }
index 7575d1d..6270113 100644 (file)
@@ -1070,20 +1070,25 @@ class completion_info {
                                               INNER JOIN {modules} m ON m.id = cm.module
                                                    WHERE m.visible = 1 AND cm.course = ?", [$userid, $this->course->id]);
 
+            $cminfos = get_fast_modinfo($cm->course, $userid)->get_cms();
+
             // Reindex by course module id.
             foreach ($alldatabycmc as $data) {
+
+                // Filter acitivites with no cm_info (missing plugins or other causes).
+                if (!isset($cminfos[$data->cmid])) {
+                    continue;
+                }
+
                 if (empty($data->coursemoduleid)) {
                     $cacheddata[$data->cmid] = $defaultdata;
                     $cacheddata[$data->cmid]['coursemoduleid'] = $data->cmid;
                 } else {
                     $cacheddata[$data->cmid] = (array) $data;
                 }
-                // Make sure we're working on a cm_info object.
-                $cmstd = new stdClass();
-                $cmstd->id = $data->cmid;
-                $cmstd->course = $this->course->id;
-                $othercminfo = cm_info::create($cmstd, $userid);
+
                 // Add the other completion data for this user in this module instance.
+                $othercminfo = $cminfos[$data->cmid];
                 $cacheddata[$othercminfo->id] += $this->get_other_cm_completion_data($othercminfo, $userid);
             }
 
index c67a582..e1ba215 100644 (file)
@@ -131,7 +131,7 @@ function xmldb_main_install() {
         'filterall'             => 0, // setting page, so have to be initialised here.
         'texteditors'           => 'atto,tinymce,textarea',
         'antiviruses'           => '',
-        'media_plugins_sortorder' => 'videojs,youtube,swf',
+        'media_plugins_sortorder' => 'videojs,youtube',
         'upgrade_extracreditweightsstepignored' => 1, // New installs should not run this upgrade step.
         'upgrade_calculatedgradeitemsignored' => 1, // New installs should not run this upgrade step.
         'upgrade_letterboundarycourses' => 1, // New installs should not run this upgrade step.
index 96e0dea..3aeda86 100644 (file)
@@ -2778,5 +2778,69 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2021091100.02);
     }
 
+    if ($oldversion < 2021091700.01) {
+        // Default 'off' for existing sites as this is the behaviour they had earlier.
+        set_config('enroladminnewcourse', false);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2021091700.01);
+    }
+
+    if ($oldversion < 2021091700.02) {
+        // If portfolio_picasa is no longer present, remove it.
+        if (!file_exists($CFG->dirroot . '/portfolio/picasa/version.php')) {
+            $instance = $DB->get_record('portfolio_instance', ['plugin' => 'picasa']);
+            if (!empty($instance)) {
+                // Remove all records from portfolio_instance_config.
+                $DB->delete_records('portfolio_instance_config', ['instance' => $instance->id]);
+                // Remove all records from portfolio_instance_user.
+                $DB->delete_records('portfolio_instance_user', ['instance' => $instance->id]);
+                // Remove all records from portfolio_log.
+                $DB->delete_records('portfolio_log', ['portfolio' => $instance->id]);
+                // Remove all records from portfolio_tempdata.
+                $DB->delete_records('portfolio_tempdata', ['instance' => $instance->id]);
+                // Remove the record from the portfolio_instance table.
+                $DB->delete_records('portfolio_instance', ['id' => $instance->id]);
+            }
+
+            // Clean config.
+            unset_all_config_for_plugin('portfolio_picasa');
+        }
+
+        upgrade_main_savepoint(true, 2021091700.02);
+    }
+
+    if ($oldversion < 2021091700.03) {
+        // If repository_picasa is no longer present, remove it.
+        if (!file_exists($CFG->dirroot . '/repository/picasa/version.php')) {
+            $instance = $DB->get_record('repository', ['type' => 'picasa']);
+            if (!empty($instance)) {
+                // Remove all records from repository_instance_config table.
+                $DB->delete_records('repository_instance_config', ['instanceid' => $instance->id]);
+                // Remove all records from repository_instances table.
+                $DB->delete_records('repository_instances', ['typeid' => $instance->id]);
+                // Remove the record from the repository table.
+                $DB->delete_records('repository', ['id' => $instance->id]);
+            }
+
+            // Clean config.
+            unset_all_config_for_plugin('picasa');
+
+            // Remove orphaned files.
+            upgrade_delete_orphaned_file_records();
+        }
+
+        upgrade_main_savepoint(true, 2021091700.03);
+    }
+
+    if ($oldversion < 2021091700.04) {
+        // Remove media_swf (unless it has manually been added back).
+        if (!file_exists($CFG->dirroot . '/media/player/swf/classes/plugin.php')) {
+            unset_all_config_for_plugin('media_swf');
+        }
+
+        upgrade_main_savepoint(true, 2021091700.04);
+    }
+
     return true;
 }
index 5c198cc..9d03ccf 100644 (file)
@@ -211,212 +211,6 @@ class google_docs {
     }
 }
 
-/**
- * Class for manipulating picasa through the google data api.
- *
- * Docs for this can be found here:
- * {@link http://code.google.com/apis/picasaweb/developers_guide_protocol.html}
- *
- * @package   core
- * @copyright Dan Poltawski <talktodan@gmail.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class google_picasa {
-    /** @var string Realm for authentication */
-    const REALM             = 'http://picasaweb.google.com/data/';
-    /** @var string Upload url */
-    const UPLOAD_LOCATION   = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/default';
-    /** @var string photo list url */
-    const ALBUM_PHOTO_LIST  = 'https://picasaweb.google.com/data/feed/api/user/default/albumid/';
-    /** @var string search url */
-    const PHOTO_SEARCH_URL  = 'https://picasaweb.google.com/data/feed/api/user/default?kind=photo&q=';
-    /** @var string album list url */
-    const LIST_ALBUMS_URL   = 'https://picasaweb.google.com/data/feed/api/user/default';
-    /** @var string manage files url */
-    const MANAGE_URL        = 'http://picasaweb.google.com/';
-
-    /** @var google_oauth oauth curl class for making authenticated requests */
-    private $googleoauth = null;
-    /** @var string Last album name retrievied */
-    private $lastalbumname = null;
-
-    /**
-     * Constructor.
-     *
-     * @param google_oauth $googleoauth oauth curl class for making authenticated requests
-     */
-    public function __construct(google_oauth $googleoauth) {
-        $this->googleoauth = $googleoauth;
-        $this->googleoauth->setHeader('GData-Version: 2');
-    }
-
-    /**
-     * Sends a file object to picasaweb
-     *
-     * @param object $file File object
-     * @return boolean True on success
-     */
-    public function send_file($file) {
-        $this->googleoauth->setHeader("Content-Length: ". $file->get_filesize());
-        $this->googleoauth->setHeader("Content-Type: ". $file->get_mimetype());
-        $this->googleoauth->setHeader("Slug: ". $file->get_filename());
-
-        $this->googleoauth->post(self::UPLOAD_LOCATION, $file->get_content());
-
-        if ($this->googleoauth->info['http_code'] === 201) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Returns list of photos for file picker.
-     * If top level then returns list of albums, otherwise
-     * photos within an album.
-     *
-     * @param string $path The path to files (assumed to be albumid)
-     * @return mixed $files A list of files for the file picker
-     */
-    public function get_file_list($path = '') {
-        if (!$path) {
-            return $this->get_albums();
-        } else {
-            return $this->get_album_photos($path);
-        }
-    }
-
-    /**
-     * Returns list of photos in album specified
-     *
-     * @param int $albumid Photo album to list photos from
-     * @return mixed $files A list of files for the file picker
-     */
-    public function get_album_photos($albumid) {
-        $albumcontent = $this->googleoauth->get(self::ALBUM_PHOTO_LIST.$albumid);
-
-        return $this->get_photo_details($albumcontent);
-    }
-
-    /**
-     * Returns the name of the album for which get_photo_details was called last time.
-     *
-     * @return string
-     */
-    public function get_last_album_name() {
-        return $this->lastalbumname;
-    }
-
-    /**
-     * Does text search on the users photos and returns
-     * matches in format for picasa api
-     *
-     * @param string $query Search terms
-     * @return mixed $files A list of files for the file picker
-     */
-    public function do_photo_search($query) {
-        $content = $this->googleoauth->get(self::PHOTO_SEARCH_URL.htmlentities($query));
-
-        return $this->get_photo_details($content);
-    }
-
-    /**
-     * Gets all the users albums and returns them as a list of folders
-     * for the file picker
-     *
-     * @return mixes $files Array in the format get_listing uses for folders
-     */
-    public function get_albums() {
-        $files = array();
-        $content = $this->googleoauth->get(self::LIST_ALBUMS_URL);
-
-        try {
-            if (strpos($content, '<?xml') !== 0) {
-                throw new moodle_exception('invalidxmlresponse');
-            }
-            $xml = new SimpleXMLElement($content);
-        } catch (Exception $e) {
-            // An error occured while trying to parse the XML, let's just return nothing. SimpleXML does not
-            // return a more specific Exception, that's why the global Exception class is caught here.
-            return $files;
-        }
-
-        foreach ($xml->entry as $album) {
-            $gphoto = $album->children('http://schemas.google.com/photos/2007');
-
-            $mediainfo = $album->children('http://search.yahoo.com/mrss/');
-            // Hacky...
-            $thumbnailinfo = $mediainfo->group->thumbnail[0]->attributes();
-
-            $files[] = array( 'title' => (string) $album->title,
-                'date'  => userdate($gphoto->timestamp),
-                'size'  => (int) $gphoto->bytesUsed,
-                'path'  => (string) $gphoto->id,
-                'thumbnail' => (string) $thumbnailinfo['url'],
-                'thumbnail_width' => 160,  // 160 is the native maximum dimension.
-                'thumbnail_height' => 160,
-                'children' => array(),
-            );
-        }
-
-        return $files;
-    }
-
-    /**
-     * Recieves XML from a picasa list of photos and returns
-     * array in format for file picker.
-     *
-     * @param string $rawxml XML from picasa api
-     * @return mixed $files A list of files for the file picker
-     */
-    public function get_photo_details($rawxml) {
-        $files = array();
-
-        try {
-            if (strpos($rawxml, '<?xml') !== 0) {
-                throw new moodle_exception('invalidxmlresponse');
-            }
-            $xml = new SimpleXMLElement($rawxml);
-        } catch (Exception $e) {
-            // An error occured while trying to parse the XML, let's just return nothing. SimpleXML does not
-            // return a more specific Exception, that's why the global Exception class is caught here.
-            return $files;
-        }
-        $this->lastalbumname = (string)$xml->title;
-
-        foreach ($xml->entry as $photo) {
-            $gphoto = $photo->children('http://schemas.google.com/photos/2007');
-
-            $mediainfo = $photo->children('http://search.yahoo.com/mrss/');
-            $fullinfo = $mediainfo->group->content->attributes();
-            // Hacky...
-            $thumbnailinfo = $mediainfo->group->thumbnail[0]->attributes();
-
-            // Derive the nicest file name we can.
-            if (!empty($mediainfo->group->description)) {
-                $title = shorten_text((string)$mediainfo->group->description, 20, false, '');
-                $title = clean_filename($title).'.jpg';
-            } else {
-                $title = (string)$mediainfo->group->title;
-            }
-
-            $files[] = array(
-                'title' => $title,
-                'date'  => userdate($gphoto->timestamp),
-                'size' => (int) $gphoto->size,
-                'path' => $gphoto->albumid.'/'.$gphoto->id,
-                'thumbnail' => (string) $thumbnailinfo['url'],
-                'thumbnail_width' => 72,  // 72 is the native maximum dimension.
-                'thumbnail_height' => 72,
-                'source' => (string) $fullinfo['url'],
-                'url' => (string) $fullinfo['url']
-            );
-        }
-
-        return $files;
-    }
-}
-
 /**
  * OAuth 2.0 client for Google Services
  *
index 8deb104..39b3533 100644 (file)
@@ -4326,7 +4326,14 @@ EOD;
      * @return string HTML to display the main header.
      */
     public function full_header() {
-
+        $pagetype = $this->page->pagetype;
+        $homepage = get_home_page();
+        $homepagetype = null;
+        if ($homepage == HOMEPAGE_MY) {
+            $homepagetype = 'my-index';
+        } else if ($homepage == HOMEPAGE_SITE) {
+            $homepagetype = 'site-index';
+        }
         if ($this->page->include_region_main_settings_in_header_actions() &&
                 !$this->page->blocks->is_block_present('settings')) {
             // Only include the region main settings if the page has requested it and it doesn't already have
@@ -4347,6 +4354,9 @@ EOD;
         $header->pageheadingbutton = $this->page_heading_button();
         $header->courseheader = $this->course_header();
         $header->headeractions = $this->page->get_header_actions();
+        if (!empty($pagetype) && !empty($homepagetype) && $pagetype == $homepagetype) {
+            $header->welcomemessage = \core_user::welcome_message();
+        }
         return $this->render_from_template('core/full_header', $header);
     }
 
index f4fc8d6..37ff595 100644 (file)
@@ -25,7 +25,8 @@
         "settingsmenu": "settings_html",
         "hasnavbar": false,
         "navbar": "navbar_if_available",
-        "courseheader": "course_header_html"
+        "courseheader": "course_header_html",
+        "welcomemessage": "welcomemessage"
     }
 }}
 <header id="page-header" class="row">
@@ -65,5 +66,6 @@
                 </div>
             </div>
         </div>
+        {{> core/welcome }}
     </div>
 </header>
diff --git a/lib/templates/welcome.mustache b/lib/templates/welcome.mustache
new file mode 100644 (file)
index 0000000..60b02db
--- /dev/null
@@ -0,0 +1,31 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core/welcome
+
+    This template renders welcome message.
+
+    Example context (json):
+    {
+        "welcomemessage": "welcomemessage"
+    }
+}}
+{{#welcomemessage}}
+<h2 class="mb-3 mt-3">
+    {{.}}
+</h2>
+{{/welcomemessage}}
index bd3ed75..833fadb 100644 (file)
@@ -142,10 +142,10 @@ class core_medialib_testcase extends advanced_testcase {
         $manager = core_media_manager::instance();
         $this->assertSame('youtube, html5audio', $this->get_players_test($manager));
 
-        // Test SWF and HTML5 media order.
-        \core\plugininfo\media::set_enabled_plugins('html5video,html5audio,swf');
+        // Test HTML5 media order.
+        \core\plugininfo\media::set_enabled_plugins('html5video,html5audio');
         $manager = core_media_manager::instance();
-        $this->assertSame('html5video, html5audio, swf', $this->get_players_test($manager));
+        $this->assertSame('html5video, html5audio', $this->get_players_test($manager));
 
         // Make sure that our test plugin is considered installed.
         \core\plugininfo\media::set_enabled_plugins('test,html5video');
@@ -181,11 +181,6 @@ class core_medialib_testcase extends advanced_testcase {
         \core\plugininfo\media::set_enabled_plugins('html5video');
         $manager = core_media_manager::instance();
         $this->assertTrue($manager->can_embed_url($url));
-
-        // Only SWF.
-        \core\plugininfo\media::set_enabled_plugins('swf');
-        $manager = core_media_manager::instance();
-        $this->assertFalse($manager->can_embed_url($url));
     }
 
     /**
@@ -195,7 +190,6 @@ class core_medialib_testcase extends advanced_testcase {
     public function test_embed_url_fallbacks() {
 
         // Key strings in the embed code that identify with the media formats being tested.
-        $swf = '</object>';
         $html5video = '</video>';
         $html5audio = '</audio>';
         $link = 'mediafallbacklink';
@@ -218,7 +212,7 @@ class core_medialib_testcase extends advanced_testcase {
         $this->assertStringContainsString($link, $t);
 
         // Enable media players that can play the same media formats. (ie. test & html5audio for mp3 files, etc.)
-        \core\plugininfo\media::set_enabled_plugins('test,html5video,html5audio,swf');
+        \core\plugininfo\media::set_enabled_plugins('test,html5video,html5audio');
         $manager = core_media_manager::instance();
 
         // Test media formats that can be played by 2 or more players.
@@ -234,13 +228,11 @@ class core_medialib_testcase extends advanced_testcase {
                     $this->assertStringContainsString($test, $textwithlink);
                     $this->assertStringNotContainsString($html5video, $textwithlink);
                     $this->assertStringContainsString($html5audio, $textwithlink);
-                    $this->assertStringNotContainsString($swf, $textwithlink);
                     $this->assertStringContainsString($link, $textwithlink);
 
                     $this->assertStringContainsString($test, $textwithoutlink);
                     $this->assertStringNotContainsString($html5video, $textwithoutlink);
                     $this->assertStringContainsString($html5audio, $textwithoutlink);
-                    $this->assertStringNotContainsString($swf, $textwithoutlink);
                     $this->assertStringNotContainsString($link, $textwithoutlink);
                     break;
 
@@ -248,13 +240,11 @@ class core_medialib_testcase extends advanced_testcase {
                     $this->assertStringContainsString($test, $textwithlink);
                     $this->assertStringContainsString($html5video, $textwithlink);
                     $this->assertStringNotContainsString($html5audio, $textwithlink);
-                    $this->assertStringNotContainsString($swf, $textwithlink);
                     $this->assertStringContainsString($link, $textwithlink);
 
                     $this->assertStringContainsString($test, $textwithoutlink);
                     $this->assertStringContainsString($html5video, $textwithoutlink);
                     $this->assertStringNotContainsString($html5audio, $textwithoutlink);
-                    $this->assertStringNotContainsString($swf, $textwithoutlink);
                     $this->assertStringNotContainsString($link, $textwithoutlink);
                     break;
 
@@ -266,10 +256,9 @@ class core_medialib_testcase extends advanced_testcase {
 
     /**
      * Test for embed_url.
-     * Check SWF works including the special option required to enable it
+     * SWF shouldn't be converted to objects because media_swf has been removed.
      */
     public function test_embed_url_swf() {
-        \core\plugininfo\media::set_enabled_plugins('swf');
         $manager = core_media_manager::instance();
 
         // Without any options...
@@ -280,7 +269,7 @@ class core_medialib_testcase extends advanced_testcase {
         // ...and with the 'no it's safe, I checked it' option.
         $url = new moodle_url('http://example.org/test.swf');
         $t = $manager->embed_url($url, '', 0, 0, array(core_media_manager::OPTION_TRUSTED => true));
-        $this->assertStringContainsString('</object>', $t);
+        $this->assertStringNotContainsString('</object>', $t);
     }
 
     /**
index 2da990d..c53d435 100644 (file)
@@ -59,8 +59,8 @@ class primary_test extends \advanced_testcase {
     public function test_setting_initialise_provider() {
         return [
             'Testing as a guest user' => ['guest', ['courses']],
-            'Testing as an admin' => ['admin', ['myhome', 'courses', 'siteadminnode']],
-            'Testing as a regular user' => ['user', ['myhome', 'courses']]
+            'Testing as an admin' => ['admin', ['home', 'myhome', 'courses', 'siteadminnode']],
+            'Testing as a regular user' => ['user', ['home', 'myhome', 'courses']]
         ];
     }
 }
index cf038f4..32eceb1 100644 (file)
@@ -89,6 +89,8 @@ information provided here is intended especially for developers.
   DB call on every request.
 * As the message_jabber notification plugin has been moved to the plugins database, the XMPPHP library (aka Jabber) has been
 completely removed from Moodle core too.
+* The SWF media player has been completely removed (The Flash Player was deprecated in 2017 and officially discontinued
+on 31 December 2020).
 
 === 3.11.2 ===
 * For security reasons, filelib has been updated so all requests now use emulated redirects.
index a00d143..2a90aa4 100644 (file)
@@ -352,7 +352,7 @@ function core_login_get_return_url() {
     if ($urltogo == ($CFG->wwwroot . '/')) {
         $homepage = get_home_page();
         // Go to my-moodle page instead of site homepage if defaulthomepage set to homepage_my.
-        if ($homepage == HOMEPAGE_MY && !is_siteadmin() && !isguestuser()) {
+        if ($homepage === HOMEPAGE_MY && !isguestuser()) {
             if ($urltogo == $CFG->wwwroot or $urltogo == $CFG->wwwroot.'/' or $urltogo == $CFG->wwwroot.'/index.php') {
                 $urltogo = $CFG->wwwroot.'/my/';
             }
index 2b24d86..7038d25 100644 (file)
@@ -66,7 +66,8 @@ final class core_media_manager {
      * Option: Enable players which are only suitable for use when we trust the
      * user who embedded the content.
      *
-     * At present, this option enables the SWF player.
+     * In the past, this option enabled the SWF player (which was removed).
+     * However, this setting will remain because it might be used by third-party plugins.
      *
      * To enable, set value to true.
      */
diff --git a/media/player/swf/classes/plugin.php b/media/player/swf/classes/plugin.php
deleted file mode 100644 (file)
index 939a6e4..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Main class for plugin 'media_swf'
- *
- * @package   media_swf
- * @copyright 2016 Marina Glancy
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Media player for Flash SWF files.
- *
- * This player contains additional security restriction: it will only be used
- * if you add option core_media_player_swf::ALLOW = true.
- *
- * Code should only set this option if it has verified that the data was
- * embedded by a trusted user (e.g. in trust text).
- *
- * @package   media_swf
- * @copyright 2016 Marina Glancy
- * @author    2011 The Open University
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class media_swf_plugin extends core_media_player {
-    public function embed($urls, $name, $width, $height, $options) {
-        self::pick_video_size($width, $height);
-
-        $firsturl = reset($urls);
-        $url = $firsturl->out(true);
-
-        $fallback = core_media_player::PLACEHOLDER;
-        $output = <<<OET
-<span class="mediaplugin mediaplugin_swf">
-  <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$width" height="$height">
-    <param name="movie" value="$url" />
-    <param name="autoplay" value="true" />
-    <param name="loop" value="false" />
-    <param name="controller" value="true" />
-    <param name="scale" value="aspect" />
-    <param name="base" value="." />
-    <param name="allowscriptaccess" value="never" />
-    <param name="allowfullscreen" value="true" />
-<!--[if !IE]><!-->
-    <object type="application/x-shockwave-flash" data="$url" width="$width" height="$height">
-      <param name="controller" value="true" />
-      <param name="autoplay" value="true" />
-      <param name="loop" value="false" />
-      <param name="scale" value="aspect" />
-      <param name="base" value="." />
-      <param name="allowscriptaccess" value="never" />
-      <param name="allowfullscreen" value="true" />
-<!--<![endif]-->
-$fallback
-<!--[if !IE]><!-->
-    </object>
-<!--<![endif]-->
-  </object>
-</span>
-OET;
-
-        return $output;
-    }
-
-    public function get_supported_extensions() {
-        return array('.swf');
-    }
-
-    public function list_supported_urls(array $urls, array $options = array()) {
-        // Not supported unless the creator is trusted.
-        if (empty($options[core_media_manager::OPTION_TRUSTED])) {
-            return array();
-        }
-        return parent::list_supported_urls($urls, $options);
-    }
-
-    /**
-     * Default rank
-     * @return int
-     */
-    public function get_rank() {
-        return 30;
-    }
-}
diff --git a/media/player/swf/classes/privacy/provider.php b/media/player/swf/classes/privacy/provider.php
deleted file mode 100644 (file)
index 579b329..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Privacy provider implementation for media_swf.
- *
- * @package    media_swf
- * @copyright  2018 Mihail Geshoski <mihail@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace media_swf\privacy;
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Privacy provider implementation for media_swf.
- *
- * @copyright  2018 Mihail Geshoski <mihail@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class provider implements \core_privacy\local\metadata\null_provider {
-
-    /**
-     * Get the language string identifier with the component's language
-     * file to explain why this plugin stores no data.
-     *
-     * @return  string
-     */
-    public static function get_reason() : string {
-        return 'privacy:metadata';
-    }
-}
diff --git a/media/player/swf/lang/en/media_swf.php b/media/player/swf/lang/en/media_swf.php
deleted file mode 100644 (file)
index c5862a0..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Strings for plugin 'media_swf'
- *
- * @package   media_swf
- * @copyright 2016 Marina Glancy
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-$string['pluginname'] = 'Flash animation';
-$string['pluginname_help'] = 'For security reasons this format is only embedded within trusted text.';
-$string['privacy:metadata'] = 'The Flash animation media plugin does not store any personal data.';
diff --git a/media/player/swf/pix/icon.png b/media/player/swf/pix/icon.png
deleted file mode 100644 (file)
index 01a9d3c..0000000
Binary files a/media/player/swf/pix/icon.png and /dev/null differ
diff --git a/media/player/swf/tests/player_test.php b/media/player/swf/tests/player_test.php
deleted file mode 100644 (file)
index e5d2f96..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Test classes for handling embedded media.
- *
- * @package media_swf
- * @copyright 2016 Marina Glancy
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Test script for media embedding.
- *
- * @package media_swf
- * @copyright 2016 Marina Glancy
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class media_swf_testcase extends advanced_testcase {
-
-    /**
-     * Pre-test setup. Preserves $CFG.
-     */
-    public function setUp(): void {
-        global $CFG;
-        parent::setUp();
-
-        // Reset $CFG and $SERVER.
-        $this->resetAfterTest();
-
-        // We need trusttext for embedding swf.
-        $CFG->enabletrusttext = true;
-
-        // Consistent initial setup: all players disabled.
-        \core\plugininfo\media::set_enabled_plugins('swf');
-
-        // Pretend to be using Firefox browser (must support ogg for tests to work).
-        core_useragent::instance(true, 'Mozilla/5.0 (X11; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0 ');
-    }
-
-
-    /**
-     * Test that plugin is returned as enabled media plugin.
-     */
-    public function test_is_installed() {
-        $sortorder = \core\plugininfo\media::get_enabled_plugins();
-        $this->assertEquals(['swf' => 'swf'], $sortorder);
-    }
-
-    /**
-     * Test embedding without media filter (for example for displaying file resorce).
-     */
-    public function test_embed_url() {
-        global $CFG;
-
-        $url = new moodle_url('http://example.org/1.swf');
-
-        $manager = core_media_manager::instance();
-        $embedoptions = array(
-            core_media_manager::OPTION_TRUSTED => true,
-            core_media_manager::OPTION_BLOCK => true,
-        );
-
-        $this->assertTrue($manager->can_embed_url($url, $embedoptions));
-        $content = $manager->embed_url($url, 'Test & file', 0, 0, $embedoptions);
-
-        $this->assertMatchesRegularExpression('~mediaplugin_swf~', $content);
-        $this->assertMatchesRegularExpression('~</object>~', $content);
-        $this->assertMatchesRegularExpression('~width="' . $CFG->media_default_width . '" height="' .
-            $CFG->media_default_height . '"~', $content);
-
-        // Repeat sending the specific size to the manager.
-        $content = $manager->embed_url($url, 'New file', 123, 50, $embedoptions);
-        $this->assertMatchesRegularExpression('~width="123" height="50"~', $content);
-
-        // Not working without trust!
-        $embedoptions = array(
-            core_media_manager::OPTION_BLOCK => true,
-        );
-        $this->assertFalse($manager->can_embed_url($url, $embedoptions));
-        $content = $manager->embed_url($url, 'Test & file', 0, 0, $embedoptions);
-        $this->assertDoesNotMatchRegularExpression('~mediaplugin_swf~', $content);
-    }
-
-    /**
-     * Test that mediaplugin filter replaces a link to the supported file with media tag.
-     *
-     * filter_mediaplugin is enabled by default.
-     */
-    public function test_embed_link() {
-        global $CFG;
-        $url = new moodle_url('http://example.org/some_filename.swf');
-        $text = html_writer::link($url, 'Watch this one');
-        $content = format_text($text, FORMAT_HTML, ['trusted' => true]);
-
-        $this->assertMatchesRegularExpression('~mediaplugin_swf~', $content);
-        $this->assertMatchesRegularExpression('~</object>~', $content);
-        $this->assertMatchesRegularExpression('~width="' . $CFG->media_default_width . '" height="' .
-            $CFG->media_default_height . '"~', $content);
-
-        // Not working without trust!
-        $content = format_text($text, FORMAT_HTML);
-        $this->assertDoesNotMatchRegularExpression('~mediaplugin_swf~', $content);
-    }
-
-    /**
-     * Test that mediaplugin filter adds player code on top of <video> tags.
-     *
-     * filter_mediaplugin is enabled by default.
-     */
-    public function test_embed_media() {
-        global $CFG;
-        $url = new moodle_url('http://example.org/some_filename.swf');
-        $trackurl = new moodle_url('http://example.org/some_filename.vtt');
-        $text = '<video controls="true"><source src="'.$url.'"/>' .
-            '<track src="'.$trackurl.'">Unsupported text</video>';
-        $content = format_text($text, FORMAT_HTML, ['trusted' => true]);
-
-        $this->assertMatchesRegularExpression('~mediaplugin_swf~', $content);
-        $this->assertMatchesRegularExpression('~</object>~', $content);
-        $this->assertMatchesRegularExpression('~width="' . $CFG->media_default_width . '" height="' .
-            $CFG->media_default_height . '"~', $content);
-        // Video tag, unsupported text and tracks are removed.
-        $this->assertDoesNotMatchRegularExpression('~</video>~', $content);
-        $this->assertDoesNotMatchRegularExpression('~<source\b~', $content);
-        $this->assertDoesNotMatchRegularExpression('~Unsupported text~', $content);
-        $this->assertDoesNotMatchRegularExpression('~<track\b~i', $content);
-
-        // Video with dimensions and source specified as src attribute without <source> tag.
-        $text = '<video controls="true" width="123" height="35" src="'.$url.'">Unsupported text</video>';
-        $content = format_text($text, FORMAT_HTML, ['trusted' => true]);
-        $this->assertMatchesRegularExpression('~mediaplugin_swf~', $content);
-        $this->assertMatchesRegularExpression('~</object>~', $content);
-        $this->assertMatchesRegularExpression('~width="123" height="35"~', $content);
-    }
-}
diff --git a/media/player/swf/version.php b/media/player/swf/version.php
deleted file mode 100644 (file)
index 38de7ed..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Version details
- *
- * @package   media_swf
- * @copyright 2016 Marina Glancy
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$plugin->version   = 2021052500;  // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2021052500;  // Requires this Moodle version.
-$plugin->component = 'media_swf'; // Full name of the plugin (used for diagnostics).
index 4142369..ebed5fa 100644 (file)
@@ -19,6 +19,7 @@ Feature: Exporting and importing feedbacks
     And the following "activities" exist:
       | activity   | name                | course | idnumber    |
       | feedback   | Learning experience | C1     | feedback0   |
+    And I change window size to "large"
 
   Scenario: Export sample feedback and compare with the fixture
     When I am on the "Learning experience" "feedback activity" page logged in as teacher
index 8209620..2e63a3b 100644 (file)
@@ -380,8 +380,9 @@ function url_get_final_display_type($url) {
         }
     }
 
-    static $download = array('application/zip', 'application/x-tar', 'application/g-zip',     // binary formats
-                             'application/pdf', 'text/html');  // these are known to cause trouble for external links, sorry
+    // Binaries and other formats that are known to cause trouble for external links.
+    static $download = ['application/zip', 'application/x-tar', 'application/g-zip',
+                        'application/pdf', 'text/html', 'document/unknown'];
     static $embed    = array('image/gif', 'image/jpeg', 'image/png', 'image/svg+xml',         // images
                              'application/x-shockwave-flash', 'video/x-flv', 'video/x-ms-wm', // video formats
                              'video/quicktime', 'video/mpeg', 'video/mp4',
index 92be5f3..3728f18 100644 (file)
@@ -21,6 +21,7 @@ Feature: Add blocks to dashboard page
   Scenario: Add blocks to page
     When I press "Customise this page"
     And I add the "Latest announcements" block
+    And I press "Stop customising this page"
     Then I should see "Latest announcements" in the "Latest announcements" "block"
     And I should see "Latest badges" in the "Latest badges" "block"
     And I should see "Calendar" in the "Calendar" "block"
diff --git a/my/tests/behat/welcome.feature b/my/tests/behat/welcome.feature
new file mode 100644 (file)
index 0000000..c2db287
--- /dev/null
@@ -0,0 +1,32 @@
+@core @core_my
+Feature: Welcome message
+  In order to welcome new or existing user
+  As a user
+  I will see welcome message when I log into moodle
+
+  Scenario: Log in and being redirected to course page
+    Given the following "users" exist:
+      | username | password | firstname | lastname | email            |
+      | wf       | test     | Fei       | Wang     | fei@example.com  |
+    And the following "courses" exist:
+      | fullname | shortname |
+      | Math 101 | M1O1      |
+    When I am on "Math 101" course homepage
+    And I should see "You are not logged in" in the "page-footer" "region"
+    And I set the field "Username" to "wf"
+    And I set the field "Password" to "test"
+    And I press "Log in"
+    And I should see "Math 101" in the "page-header" "region"
+    And I should not see "Welcome, Fei!" in the "page-header" "region"
+    And I follow "Dashboard" in the user menu
+    Then I should see "Welcome, Fei!" in the "page-header" "region"
+
+  @javascript
+  Scenario: Log in and being redirected to default home page
+    When I log in as "admin"
+    And I should see "You are logged in as Admin User" in the "page-footer" "region"
+    And I should see "Welcome, Admin!" in the "page-header" "region"
+    And I log out
+    And I should see "You are not logged in" in the "page-footer" "region"
+    And I log in as "admin"
+    Then I should see "Welcome back, Admin!" in the "page-header" "region"
index 2718370..f2ee175 100644 (file)
@@ -27,7 +27,7 @@ $string['noauthtoken'] = 'An authentication token has not been received from Goo
 $string['nooauthcredentials'] = 'OAuth credentials required.';
 $string['nooauthcredentials_help'] = 'To use the Google Drive portfolio plugin you must configure OAuth credentials in the portfolio settings.';
 $string['nosessiontoken'] = 'A session token does not exist preventing export to google.';
-$string['oauthinfo'] = '<p>To use this plugin, you must register your site with Google, as described in the documentation <a href="{$a->docsurl}">Google OAuth 2.0 setup</a>.</p><p>As part of the registration process, you will need to enter the following URL as \'Authorized Redirect URIs\':</p><p>{$a->callbackurl}</p><p>Once registered, you will be provided with a client ID and secret which can be used to configure all Google Drive and Picasa plugins.</p>';
+$string['oauthinfo'] = '<p>To use this plugin, you must register your site with Google, as described in the documentation <a href="{$a->docsurl}">Google OAuth 2.0 setup</a>.</p><p>As part of the registration process, you will need to enter the following URL as \'Authorized Redirect URIs\':</p><p>{$a->callbackurl}</p><p>Once registered, you will be provided with a client ID and secret which can be used to configure all Google Drive plugins.</p>';
 $string['pluginname'] = 'Google Drive';
 $string['privacy:metadata'] = 'This plugin sends data externally to a linked Google account. It does not store data locally.';
 $string['privacy:metadata:data'] = 'Personal data passed through from the portfolio subsystem.';
diff --git a/portfolio/picasa/classes/privacy/provider.php b/portfolio/picasa/classes/privacy/provider.php
deleted file mode 100644 (file)
index 4f2fafe..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Privacy class for requesting user data.
- *
- * @package    portfolio_picasa
- * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-namespace portfolio_picasa\privacy;
-
-defined('MOODLE_INTERNAL') || die();
-
-use core_privacy\local\metadata\collection;
-
-/**
- * Provider for the portfolio_picasa plugin.
- *
- * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class provider implements
-        // This portfolio plugin does not store any data itself.
-        // It has no database tables, and it purely acts as a conduit, sending data externally.
-        \core_privacy\local\metadata\provider,
-        \core_portfolio\privacy\portfolio_provider {
-
-    /**
-     * Returns meta data about this system.
-     *
-     * @param   collection $collection The initialised collection to add items to.
-     * @return  collection     A listing of user data stored through this system.
-     */
-    public static function get_metadata(collection $collection) : collection {
-        return $collection->add_external_location_link('picasa.google.com', ['data' => 'privacy:metadata:data'],
-                                                       'privacy:metadata');
-    }
-
-    /**
-     * Export all portfolio data from each portfolio plugin for the specified userid and context.
-     *
-     * @param   int $userid The user to export.
-     * @param   \context $context The context to export.
-     * @param   array $subcontext The subcontext within the context to export this information to.
-     * @param   array $linkarray The weird and wonderful link array used to display information for a specific item
-     */
-    public static function export_portfolio_user_data(int $userid, \context $context, array $subcontext, array $linkarray) {
-    }
-
-    /**
-     * Delete all user information for the provided context.
-     *
-     * @param  \context $context The context to delete user data for.
-     */
-    public static function delete_portfolio_for_context(\context $context) {
-    }
-
-    /**
-     * Delete all user information for the provided user and context.
-     *
-     * @param  int $userid The user to delete
-     * @param  \context $context The context to refine the deletion.
-     */
-    public static function delete_portfolio_for_user(int $userid, \context $context) {
-    }
-}
diff --git a/portfolio/picasa/db/upgrade.php b/portfolio/picasa/db/upgrade.php
deleted file mode 100644 (file)
index 430343b..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * @param int $oldversion the version we are upgrading from
- * @return bool result
- */
-function xmldb_portfolio_picasa_upgrade($oldversion) {
-    global $CFG;
-
-    // Automatically generated Moodle v3.6.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.7.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.8.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.9.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    return true;
-}
diff --git a/portfolio/picasa/lang/en/portfolio_picasa.php b/portfolio/picasa/lang/en/portfolio_picasa.php
deleted file mode 100644 (file)
index d447c94..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Strings for component 'portfolio_picasa', language 'en', branch 'MOODLE_20_STABLE'
- *
- * @package   portfolio_picasa
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-$string['clientid'] = 'Client ID';
-$string['noauthtoken'] = 'An authentication token has not been received from Google. Please ensure you are allowing Moodle to access your Google account.';
-$string['nooauthcredentials'] = 'OAuth credentials required.';
-$string['nooauthcredentials_help'] = 'To use the Picasa portfolio plugin you must configure OAuth credentials in the portfolio settings.';
-$string['oauthinfo'] = '<p>To use this plugin, you must register your site with Google, as described in the documentation <a href="{$a->docsurl}">Google OAuth 2.0 setup</a>.</p><p>As part of the registration process, you will need to enter the following URL as \'Authorized Redirect URIs\':</p><p>{$a->callbackurl}</p><p>Once registered, you will be provided with a client ID and secret which can be used to configure all Google Drive and Picasa plugins.</p>';
-$string['pluginname'] = 'Picasa';
-$string['privacy:metadata'] = 'This plugin sends data externally to a linked Picasa account. It does not store data locally.';
-$string['privacy:metadata:data'] = 'Personal data passed through from the portfolio subsystem.';
-$string['sendfailed'] = 'The file {$a} failed to transfer to Picasa';
-$string['secret'] = 'Secret';
diff --git a/portfolio/picasa/lib.php b/portfolio/picasa/lib.php
deleted file mode 100644 (file)
index d760b12..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Picasa Portfolio Plugin
- *
- * @author Dan Poltawski <talktodan@gmail.com>
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- */
-require_once($CFG->libdir.'/portfolio/plugin.php');
-require_once($CFG->libdir.'/googleapi.php');
-
-class portfolio_plugin_picasa extends portfolio_plugin_push_base {
-    private $googleoauth = null;
-
-    public function supported_formats() {
-        return array(PORTFOLIO_FORMAT_IMAGE, PORTFOLIO_FORMAT_VIDEO);
-    }
-
-    public static function get_name() {
-        return get_string('pluginname', 'portfolio_picasa');
-    }
-
-    public function prepare_package() {
-        // We send the files as they are, no prep required.
-        return true;
-    }
-
-    public function get_interactive_continue_url() {
-        return 'http://picasaweb.google.com/';
-    }
-
-    public function expected_time($callertime) {
-        // We're forcing this to be run 'interactively' because the plugin
-        // does not support running in cron.
-        return PORTFOLIO_TIME_LOW;
-    }
-
-    public function send_package() {
-        if (!$this->googleoauth) {
-            throw new portfolio_plugin_exception('noauthtoken', 'portfolio_picasa');
-        }
-
-        $picasa = new google_picasa($this->googleoauth);
-        foreach ($this->exporter->get_tempfiles() as $file) {
-
-            if (!$picasa->send_file($file)) {
-                throw new portfolio_plugin_exception('sendfailed', 'portfolio_picasa', $file->get_filename());
-            }
-        }
-    }
-
-    public function steal_control($stage) {
-        if ($stage != PORTFOLIO_STAGE_CONFIG) {
-            return false;
-        }
-
-        $this->initialize_oauth();
-
-        if ($this->googleoauth->is_logged_in()) {
-            return false;
-        } else {
-            return $this->googleoauth->get_login_url();
-        }
-    }
-
-    public function post_control($stage, $params) {
-        if ($stage != PORTFOLIO_STAGE_CONFIG) {
-            return;
-        }
-
-        $this->initialize_oauth();
-        if ($this->googleoauth->is_logged_in()) {
-            return false;
-        } else {
-            return $this->googleoauth->get_login_url();
-        }
-    }
-
-    public static function has_admin_config() {
-        return true;
-    }
-
-    public static function allows_multiple_instances() {
-        return false;
-    }
-
-    public static function get_allowed_config() {
-        return array('clientid', 'secret');
-    }
-
-    public static function admin_config_form(&$mform) {
-        $a = new stdClass;
-        $a->docsurl = get_docs_url('Google_OAuth_2.0_setup');
-        $a->callbackurl = google_oauth::callback_url()->out(false);
-
-        $mform->addElement('static', null, '', get_string('oauthinfo', 'portfolio_picasa', $a));
-
-        $mform->addElement('text', 'clientid', get_string('clientid', 'portfolio_picasa'));
-        $mform->setType('clientid', PARAM_RAW_TRIMMED);
-        $mform->addElement('text', 'secret', get_string('secret', 'portfolio_picasa'));
-        $mform->setType('secret', PARAM_RAW_TRIMMED);
-
-        $strrequired = get_string('required');
-        $mform->addRule('clientid', $strrequired, 'required', null, 'client');
-        $mform->addRule('secret', $strrequired, 'required', null, 'client');
-    }
-
-    private function initialize_oauth() {
-        $returnurl = new moodle_url('/portfolio/add.php');
-        $returnurl->param('postcontrol', 1);
-        $returnurl->param('id', $this->exporter->get('id'));
-        $returnurl->param('sesskey', sesskey());
-
-        $clientid = $this->get_config('clientid');
-        $secret = $this->get_config('secret');
-
-        $this->googleoauth = new google_oauth($clientid, $secret, $returnurl, google_picasa::REALM);
-    }
-
-    public function instance_sanity_check() {
-        $clientid = $this->get_config('clientid');
-        $secret = $this->get_config('secret');
-
-        // If there is no oauth config (e.g. plugins upgraded from < 2.3 then
-        // there will be no config and this plugin should be disabled.
-        if (empty($clientid) or empty($secret)) {
-            return 'nooauthcredentials';
-        }
-        return 0;
-    }
-}
diff --git a/portfolio/picasa/tests/privacy_provider_test.php b/portfolio/picasa/tests/privacy_provider_test.php
deleted file mode 100644 (file)
index 8cd8d71..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Privacy provider tests.
- *
- * @package    portfolio_picasa
- * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Privacy provider tests class.
- *
- * @copyright  2018 Jake Dallimore <jrhdallimore@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class portfolio_picasa_privacy_provider_test extends \core_privacy\tests\provider_testcase {
-
-    /**
-     *  Verify that a collection of metadata is returned for this component and that it just links to an external location.
-     */
-    public function test_get_metadata() {
-        $collection = new \core_privacy\local\metadata\collection('portfolio_picasa');
-        $collection = \portfolio_picasa\privacy\provider::get_metadata($collection);
-        $this->assertNotEmpty($collection);
-        $items = $collection->get_collection();
-        $this->assertEquals(1, count($items));
-        $this->assertInstanceOf(\core_privacy\local\metadata\types\external_location::class, $items[0]);
-    }
-}
diff --git a/portfolio/picasa/version.php b/portfolio/picasa/version.php
deleted file mode 100644 (file)
index a777592..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Version details
- *
- * @package    portfolio
- * @subpackage picasa
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$plugin->version   = 2021052500;        // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2021052500;        // Requires this Moodle version.
-$plugin->component = 'portfolio_picasa'; // Full name of the plugin (used for diagnostics).
-$plugin->cron      = 0;
index eda575d..9582880 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /portfolio/ portfolio system,
 information provided here is intended especially for developers.
 
+=== 4.0 ===
+
+* The portfolio_picasa has been completely removed (Picasa is discontinued since 2016).
+
 === 3.7 ===
 
 * The portfolio_cron() function has been removed. Please use portfolio_cron_task scheduled task instead.
diff --git a/report/security/lang/en/deprecated.txt b/report/security/lang/en/deprecated.txt
new file mode 100644 (file)
index 0000000..cc725af
--- /dev/null
@@ -0,0 +1,4 @@
+check_mediafilterswf_details,report_security
+check_mediafilterswf_error,report_security
+check_mediafilterswf_name,report_security
+check_mediafilterswf_ok,report_security
index 2916386..e031725 100644 (file)
@@ -82,10 +82,6 @@ $string['check_guestrole_error'] = 'The guest role "{$a}" is incorrectly defined
 $string['check_guestrole_name'] = 'Guest role';
 $string['check_guestrole_notset'] = 'Guest role is not set.';
 $string['check_guestrole_ok'] = 'Guest role definition is OK.';
-$string['check_mediafilterswf_details'] = '<p>Automatic swf embedding is very dangerous - any registered user may launch an XSS attack against other server users. Please disable it on production servers.</p>';
-$string['check_mediafilterswf_error'] = 'Flash media filter is enabled - this is very dangerous for the majority of servers.';
-$string['check_mediafilterswf_name'] = 'Enabled .swf media filter';
-$string['check_mediafilterswf_ok'] = 'Flash media filter is not enabled.';
 $string['check_nodemodules_details'] = '<p>The directory <code>{$a->path}</code> contains Node.js modules and their dependencies, typically installed by the NPM utility. These modules may be needed for local Moodle development, such as for using the grunt framework. They are not needed to run a Moodle site in production and they can contain potentially dangerous code exposing your site to remote attacks.</p><p>It is strongly recommended to remove the directory if the site is available via a public URL, or at least prohibit web access to it in your webserver configuration.</p>';
 $string['check_nodemodules_info'] = 'The node_modules directory should not be present on public sites.';
 $string['check_nodemodules_name'] = 'Node.js modules directory';
@@ -147,3 +143,9 @@ $string['pluginname'] = 'Security checks';
 $string['security:view'] = 'View security report';
 $string['timewarning'] = 'Data processing may take a long time, please be patient...';
 $string['privacy:metadata'] = 'The Security overview plugin does not store any personal data.';
+
+// Deprecated since Moodle 4.0.
+$string['check_mediafilterswf_details'] = '<p>Automatic swf embedding is very dangerous - any registered user may launch an XSS attack against other server users. Please disable it on production servers.</p>';
+$string['check_mediafilterswf_error'] = 'Flash media filter is enabled - this is very dangerous for the majority of servers.';
+$string['check_mediafilterswf_name'] = 'Enabled .swf media filter';
+$string['check_mediafilterswf_ok'] = 'Flash media filter is not enabled.';
diff --git a/repository/picasa/classes/privacy/provider.php b/repository/picasa/classes/privacy/provider.php
deleted file mode 100644 (file)
index 885e6f9..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Privacy Subsystem implementation for repository_picasa.
- *
- * @package    repository_picasa
- * @copyright  2018 Zig Tan <zig@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-namespace repository_picasa\privacy;
-
-use core_privacy\local\metadata\collection;
-use core_privacy\local\request\approved_contextlist;
-use core_privacy\local\request\approved_userlist;
-use core_privacy\local\request\context;
-use core_privacy\local\request\contextlist;
-use core_privacy\local\request\userlist;
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Privacy Subsystem for repository_picasa implementing metadata and plugin providers.
- *
- * @copyright  2018 Zig Tan <zig@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class provider implements
-        \core_privacy\local\metadata\provider,
-        \core_privacy\local\request\core_userlist_provider,
-        \core_privacy\local\request\plugin\provider {
-
-    /**
-     * Returns meta data about this system.
-     *
-     * @param   collection $collection The initialised collection to add items to.
-     * @return  collection     A listing of user data stored through this system.
-     */
-    public static function get_metadata(collection $collection) : collection {
-        $collection->add_external_location_link(
-            'picasa.google.com',
-            [
-                'search_text' => 'privacy:metadata:repository_picasa:searchtext'
-            ],
-            'privacy:metadata:repository_picasa'
-        );
-
-        return $collection;
-    }
-
-    /**
-     * Get the list of contexts that contain user information for the specified user.
-     *
-     * @param   int $userid The user to search.
-     * @return  contextlist   $contextlist  The contextlist containing the list of contexts used in this plugin.
-     */
-    public static function get_contexts_for_userid(int $userid) : contextlist {
-        return new contextlist();
-    }
-
-    /**
-     * Get the list of users who have data within a context.
-     *
-     * @param   userlist    $userlist   The userlist containing the list of users who have data in this context/plugin combination.
-     */
-    public static function get_users_in_context(userlist $userlist) {
-    }
-
-    /**
-     * Export all user data for the specified user, in the specified contexts.
-     *
-     * @param   approved_contextlist $contextlist The approved contexts to export information for.
-     */
-    public static function export_user_data(approved_contextlist $contextlist) {
-    }
-
-    /**
-     * Delete all data for all users in the specified context.
-     *
-     * @param   context $context The specific context to delete data for.
-     */
-    public static function delete_data_for_all_users_in_context(\context $context) {
-    }
-
-    /**
-     * Delete all user data for the specified user, in the specified contexts.
-     *
-     * @param   approved_contextlist $contextlist The approved contexts and user information to delete information for.
-     */
-    public static function delete_data_for_user(approved_contextlist $contextlist) {
-    }
-
-    /**
-     * Delete multiple users within a single context.
-     *
-     * @param   approved_userlist       $userlist The approved context and user information to delete information for.
-     */
-    public static function delete_data_for_users(approved_userlist $userlist) {
-    }
-}
diff --git a/repository/picasa/db/access.php b/repository/picasa/db/access.php
deleted file mode 100644 (file)
index 16a7c5b..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Plugin capabilities.
- *
- * @package    repository_picasa
- * @copyright  2009 Dan Poltawski
- * @author     Dan Poltawski <talktodan@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$capabilities = array(
-
-    'repository/picasa:view' => array(
-        'captype' => 'read',
-        'contextlevel' => CONTEXT_MODULE,
-        'archetypes' => array(
-            'user' => CAP_ALLOW
-        )
-    )
-);
diff --git a/repository/picasa/db/upgrade.php b/repository/picasa/db/upgrade.php
deleted file mode 100644 (file)
index b0d2f7e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * @param int $oldversion the version we are upgrading from
- * @return bool result
- */
-function xmldb_repository_picasa_upgrade($oldversion) {
-    global $CFG;
-
-    // Automatically generated Moodle v3.6.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.7.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.8.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    // Automatically generated Moodle v3.9.0 release upgrade line.
-    // Put any upgrade step following this.
-
-    return true;
-}
diff --git a/repository/picasa/lang/en/repository_picasa.php b/repository/picasa/lang/en/repository_picasa.php
deleted file mode 100644 (file)
index 598f8a1..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Strings for component 'repository_picasa', language 'en', branch 'MOODLE_20_STABLE'
- *
- * @package   repository_picasa
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-$string['clientid'] = 'Client ID';
-$string['configplugin'] = 'Picasa repository configuration';
-$string['oauthinfo'] = '<p>To use this plugin, you must register your site with Google, as described in the documentation <a href="{$a->docsurl}">Google OAuth 2.0 setup</a>.</p><p>As part of the registration process, you will need to enter the following URL as \'Authorized Redirect URIs\':</p><p>{$a->callbackurl}</p><p>Once registered, you will be provided with a client ID and secret which can be used to configure all Google Drive and Picasa plugins.</p>';
-$string['picasa:view'] = 'View picasa repository';
-$string['pluginname'] = 'Picasa web album';
-$string['secret'] = 'Secret';
-$string['privacy:metadata:repository_picasa'] = 'The Picasa web album repository plugin does not store any personal data, but does transmit user data from Moodle to the remote system.';
-$string['privacy:metadata:repository_picasa:searchtext'] = 'The Picasa repository user search text query.';
diff --git a/repository/picasa/lib.php b/repository/picasa/lib.php
deleted file mode 100644 (file)
index 362289b..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * This plugin is used to access picasa pictures
- *
- * @since Moodle 2.0
- * @package    repository_picasa
- * @copyright  2009 Dan Poltawski <talktodan@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-require_once($CFG->dirroot . '/repository/lib.php');
-require_once($CFG->libdir.'/googleapi.php');
-
-/**
- * Picasa Repository Plugin
- *
- * @since Moodle 2.0
- * @package    repository
- * @subpackage picasa
- * @copyright  2009 Dan Poltawski
- * @author     Dan Poltawski <talktodan@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class repository_picasa extends repository {
-    private $googleoauth = null;
-
-    public function __construct($repositoryid, $context = SYSCONTEXTID, $options = array()) {
-        parent::__construct($repositoryid, $context, $options);
-
-        $returnurl = new moodle_url('/repository/repository_callback.php');
-        $returnurl->param('callback', 'yes');
-        $returnurl->param('repo_id', $this->id);
-        $returnurl->param('sesskey', sesskey());
-
-        $clientid = get_config('picasa', 'clientid');
-        $secret = get_config('picasa', 'secret');
-        $this->googleoauth = new google_oauth($clientid, $secret, $returnurl, google_picasa::REALM);
-
-        $this->check_login();
-    }
-
-    public function check_login() {
-        return $this->googleoauth->is_logged_in();
-    }
-
-    public function print_login() {
-        $url = $this->googleoauth->get_login_url();
-
-        if ($this->options['ajax']) {
-            $popup = new stdClass();
-            $popup->type = 'popup';
-            $popup->url = $url->out(false);
-            return array('login' => array($popup));
-        } else {
-            echo '<a target="_blank" href="'.$url->out(false).'">'.get_string('login', 'repository').'</a>';
-        }
-    }
-
-    public function get_listing($path='', $page = '') {
-        $picasa = new google_picasa($this->googleoauth);
-
-        $ret = array();
-        $ret['dynload'] = true;
-        $ret['manage'] = google_picasa::MANAGE_URL;
-        $ret['list'] = $picasa->get_file_list($path);
-        $ret['path'] = array((object)array('name'=>get_string('home'), 'path' => ''));
-        if ($path) {
-            $ret['path'][] = (object)array('name'=>$picasa->get_last_album_name(), 'path' => $path);
-        }
-        return $ret;
-    }
-
-    public function search($search_text, $page = 0) {
-        $picasa = new google_picasa($this->googleoauth);
-
-        $ret = array();
-        $ret['manage'] = google_picasa::MANAGE_URL;
-        $ret['list'] =  $picasa->do_photo_search($search_text);
-        return $ret;
-    }
-
-    public function logout() {
-        $this->googleoauth->log_out();
-        return parent::logout();
-    }
-
-    public function supported_filetypes() {
-        return array('web_image');
-    }
-    public function supported_returntypes() {
-        return (FILE_INTERNAL | FILE_EXTERNAL);
-    }
-
-    public static function get_type_option_names() {
-        return array('clientid', 'secret', 'pluginname');
-    }
-
-    public static function type_config_form($mform, $classname = 'repository') {
-        $a = new stdClass;
-        $a->docsurl = get_docs_url('Google_OAuth_2.0_setup');
-        $a->callbackurl = google_oauth::callback_url()->out(false);
-
-        $mform->addElement('static', null, '', get_string('oauthinfo', 'repository_picasa', $a));
-
-        parent::type_config_form($mform);
-        $mform->addElement('text', 'clientid', get_string('clientid', 'repository_picasa'));
-        $mform->setType('clientid', PARAM_RAW_TRIMMED);
-        $mform->addElement('text', 'secret', get_string('secret', 'repository_picasa'));
-        $mform->setType('secret', PARAM_RAW_TRIMMED);
-
-        $strrequired = get_string('required');
-        $mform->addRule('clientid', $strrequired, 'required', null, 'client');
-        $mform->addRule('secret', $strrequired, 'required', null, 'client');
-    }
-}
-
-// Icon for this plugin retrieved from http://www.iconspedia.com/icon/picasa-2711.html
-// Where the license is said documented to be Free.
diff --git a/repository/picasa/pix/icon.png b/repository/picasa/pix/icon.png
deleted file mode 100644 (file)
index a8ec410..0000000
Binary files a/repository/picasa/pix/icon.png and /dev/null differ
diff --git a/repository/picasa/tests/generator/lib.php b/repository/picasa/tests/generator/lib.php
deleted file mode 100644 (file)
index c8fe186..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Picasa repository data generator
- *
- * @package    repository_picasa
- * @category   test
- * @copyright  2013 Frédéric Massart
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-/**
- * Picasa repository data generator class
- *
- * @package    repository_picasa
- * @category   test
- * @copyright  2013 Frédéric Massart
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class repository_picasa_generator extends testing_repository_generator {
-
-    /**
-     * Fill in type record defaults.
-     *
-     * @param array $record
-     * @return array
-     */
-    protected function prepare_type_record(array $record) {
-        $record = parent::prepare_type_record($record);
-        if (!isset($record['clientid'])) {
-            $record['clientid'] = 'clientid';
-        }
-        if (!isset($record['secret'])) {
-            $record['secret'] = 'secret';
-        }
-        return $record;
-    }
-
-}
diff --git a/repository/picasa/version.php b/repository/picasa/version.php
deleted file mode 100644 (file)
index f844098..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Version details
- *
- * @package    repository
- * @subpackage picasa
- * @copyright  2009 Dan Poltawski
- * @author     Dan Poltawski <talktodan@gmail.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-$plugin->version   = 2021052500;        // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2021052500;        // Requires this Moodle version.
-$plugin->component = 'repository_picasa'; // Full name of the plugin (used for diagnostics).
index a94ef34..3405817 100644 (file)
@@ -46,7 +46,7 @@ class core_repository_generator_testcase extends advanced_testcase {
 
         // All the repository types.
         $all = array('boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem', 'flickr',
-            'flickr_public', 'googledocs', 'local', 'nextcloud', 'merlot', 'picasa', 'recent', 's3', 'upload', 'url',
+            'flickr_public', 'googledocs', 'local', 'nextcloud', 'merlot', 'recent', 's3', 'upload', 'url',
             'user', 'webdav', 'wikimedia', 'youtube');
 
         // The ones enabled during installation.
index 339d05e..c9e32b8 100644 (file)
@@ -3,6 +3,9 @@ information provided here is intended especially for developers. Full
 details of the repository API are available on Moodle docs:
 http://docs.moodle.org/dev/Repository_API
 
+=== 4.0 ===
+* The repository_picasa has been completely removed (Picasa is discontinued since 2016).
+
 === 3.11 ===
 * The Google Drive repository now includes a new rest API function 'shared_drives_list', which can be used to fetch
   a list of existing shared drives.
index 6fa7a92..6a4ff57 100644 (file)
@@ -10,7 +10,9 @@ Feature: Upload files
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
     And I log in as "admin"
-    When I follow "Private files"
+    And I press "Customise this page"
+    And I add the "Private files" block if not present
+    When I follow "Manage private files..."
     And I upload "lib/tests/fixtures/empty.txt" file to "Files" filemanager
     Then I should see "1" elements in "Files" filemanager
     And I should see "empty.txt" in the "div.fp-content" "css_element"
index ad35a0d..88bc99b 100644 (file)
@@ -232,6 +232,17 @@ body:not(.editing)  .sitetopic ul.section {
     }
 }
 
+.course-content .section.dropready {
+
+    &.main.drop-down {
+        border-bottom: 2px solid $dropzone-border;
+    }
+
+    li.activity.dropready.drop-down {
+        border-bottom: 2px solid $dropzone-border;
+    }
+}
+
 .section .activity .activityinstance .groupinglabel {
     padding-left: 30px;
 }
@@ -354,7 +365,7 @@ body:not(.editing)  .sitetopic ul.section {
     list-style: none;
 
     li.section {
-        margin-top: $spacer;
+        padding-top: $spacer;
         padding-bottom: $spacer;
         .content {
             margin: 0;
index 0855200..0ff63a4 100644 (file)
     }
 }
 
+.path-grade-report-grader {
+    span.gradepass {
+        color: $success;
+    }
+    span.gradefail {
+        color: $danger;
+    }
+}
+
 // Rubrics
 #page-grade-grading-manage {
     #activemethodselector {
index 9a78ed6..68ea0c8 100644 (file)
@@ -102,8 +102,11 @@ $iconsizes: map-merge((
 }
 
 .icons-collapse-expand {
+    display: flex;
+    align-items: center;
     .expanded-icon {
-        display: block;
+        display: flex;
+        align-items: center;
     }
 
     .collapsed-icon {
@@ -116,7 +119,8 @@ $iconsizes: map-merge((
         }
 
         .collapsed-icon {
-            display: block;
+            display: flex;
+            align-items: center;
         }
     }
 }
index 355a379..4d206d1 100644 (file)
@@ -71,7 +71,7 @@ $content-header-footer-height: $region-header-height + $region-footer-height;
 .count-container {
     padding: 2px;
     border-radius: 2px;
-    background-color: red;
+    background-color: $danger;
     color: white;
     font-size: 11px;
     line-height: 11px;
@@ -189,7 +189,7 @@ $content-header-footer-height: $region-header-height + $region-footer-height;
     .count-container {
         padding: 2px;
         border-radius: 2px;
-        background-color: red;
+        background-color: $danger;
         color: white;
         font-size: 11px;
         line-height: 11px;
index 79447ab..b0d35fc 100644 (file)
@@ -12077,17 +12077,19 @@ body.dragging .dragging {
 .helplink .icon {
   margin-left: 0.5rem; }
 
-.icons-collapse-expand .expanded-icon {
-  display: block; }
-
-.icons-collapse-expand .collapsed-icon {
-  display: none; }
-
-.icons-collapse-expand.collapsed .expanded-icon {
-  display: none; }
-
-.icons-collapse-expand.collapsed .collapsed-icon {
-  display: block; }
+.icons-collapse-expand {
+  display: flex;
+  align-items: center; }
+  .icons-collapse-expand .expanded-icon {
+    display: flex;
+    align-items: center; }
+  .icons-collapse-expand .collapsed-icon {
+    display: none; }
+  .icons-collapse-expand.collapsed .expanded-icon {
+    display: none; }
+  .icons-collapse-expand.collapsed .collapsed-icon {
+    display: flex;
+    align-items: center; }
 
 /* admin.less */
 .formtable tbody th {
@@ -13455,6 +13457,12 @@ body:not(.editing) .sitetopic ul.section {
       border-bottom: 0;
       padding-bottom: 0; }
 
+.course-content .section.dropready.main.drop-down {
+  border-bottom: 2px solid #212529; }
+
+.course-content .section.dropready li.activity.dropready.drop-down {
+  border-bottom: 2px solid #212529; }
+
 .section .activity .activityinstance .groupinglabel {
   padding-left: 30px; }
 
@@ -13552,7 +13560,7 @@ body:not(.editing) .sitetopic ul.section {
   list-style: none; }
   .course-content ul.topics li.section,
   .course-content ul.weeks li.section {
-    margin-top: 1rem;
+    padding-top: 1rem;
     padding-bottom: 1rem; }
     .course-content ul.topics li.section .content,
     .course-content ul.weeks li.section .content {
@@ -18468,6 +18476,12 @@ p.arrow_button {
   width: 100%;
   clear: both; }
 
+.path-grade-report-grader span.gradepass {
+  color: #357a32; }
+
+.path-grade-report-grader span.gradefail {
+  color: #ca3120; }
+
 #page-grade-grading-manage #activemethodselector label {
   display: inline-block; }
 
@@ -19054,7 +19068,7 @@ body {
 .count-container {
   padding: 2px;
   border-radius: 2px;
-  background-color: red;
+  background-color: #ca3120;
   color: white;
   font-size: 11px;
   line-height: 11px;
@@ -19142,7 +19156,7 @@ body {
 .navbar .count-container {
   padding: 2px;
   border-radius: 2px;
-  background-color: red;
+  background-color: #ca3120;
   color: white;
   font-size: 11px;
   line-height: 11px;
index 44c2d1d..be431bc 100644 (file)
@@ -12298,17 +12298,19 @@ body.dragging .dragging {
 .helplink .icon {
   margin-left: 0.5rem; }
 
-.icons-collapse-expand .expanded-icon {
-  display: block; }
-
-.icons-collapse-expand .collapsed-icon {
-  display: none; }
-
-.icons-collapse-expand.collapsed .expanded-icon {
-  display: none; }
-
-.icons-collapse-expand.collapsed .collapsed-icon {
-  display: block; }
+.icons-collapse-expand {
+  display: flex;
+  align-items: center; }
+  .icons-collapse-expand .expanded-icon {
+    display: flex;
+    align-items: center; }
+  .icons-collapse-expand .collapsed-icon {
+    display: none; }
+  .icons-collapse-expand.collapsed .expanded-icon {
+    display: none; }
+  .icons-collapse-expand.collapsed .collapsed-icon {
+    display: flex;
+    align-items: center; }
 
 /* admin.less */
 .formtable tbody th {
@@ -13677,6 +13679,12 @@ body:not(.editing) .sitetopic ul.section {
       border-bottom: 0;
       padding-bottom: 0; }
 
+.course-content .section.dropready.main.drop-down {
+  border-bottom: 2px solid #212529; }
+
+.course-content .section.dropready li.activity.dropready.drop-down {
+  border-bottom: 2px solid #212529; }
+
 .section .activity .activityinstance .groupinglabel {
   padding-left: 30px; }
 
@@ -13774,7 +13782,7 @@ body:not(.editing) .sitetopic ul.section {
   list-style: none; }
   .course-content ul.topics li.section,
   .course-content ul.weeks li.section {
-    margin-top: 1rem;
+    padding-top: 1rem;
     padding-bottom: 1rem; }
     .course-content ul.topics li.section .content,
     .course-content ul.weeks li.section .content {
@@ -18708,6 +18716,12 @@ p.arrow_button {
   width: 100%;
   clear: both; }
 
+.path-grade-report-grader span.gradepass {
+  color: #357a32; }
+
+.path-grade-report-grader span.gradefail {
+  color: #ca3120; }
+
 #page-grade-grading-manage #activemethodselector label {
   display: inline-block; }
 
@@ -19243,7 +19257,7 @@ body {
 .count-container {
   padding: 2px;
   border-radius: 2px;
-  background-color: red;
+  background-color: #ca3120;
   color: white;
   font-size: 11px;
   line-height: 11px;
@@ -19331,7 +19345,7 @@ body {
 .navbar .count-container {
   padding: 2px;
   border-radius: 2px;
-  background-color: red;
+  background-color: #ca3120;
   color: white;
   font-size: 11px;
   line-height: 11px;
index 7b61cb0..3928046 100644 (file)
@@ -24,7 +24,8 @@
         "contextheader": "context_header_html",
         "hasnavbar": false,
         "navbar": "navbar_if_available",
-        "courseheader": "course_header_html"
+        "courseheader": "course_header_html",
+        "welcomemessage": "welcomemessage"
     }
 }}
 <header id="page-header" class="row">
@@ -56,5 +57,6 @@
                 </div>
             </div>
         </div>
+        {{> core/welcome }}
     </div>
-</header>
\ No newline at end of file
+</header>
index dc48407..e5cc771 100644 (file)
@@ -6,7 +6,6 @@
         "lib/tests/behat/action_menu.feature",
         "blocks/tests/behat/hide_blocks.feature",
         "blocks/tests/behat/move_blocks.feature",
-        "repository/upload/tests/behat/upload_file.feature",
         "course/format/tests/behat/course_courseindex.feature"
     ]
 }
index 87eb486..d55badd 100644 (file)
@@ -44,7 +44,8 @@ use \core_privacy\local\request\approved_userlist;
 class provider implements
         \core_privacy\local\metadata\provider,
         \core_privacy\local\request\core_userlist_provider,
-        \core_privacy\local\request\subsystem\provider {
+        \core_privacy\local\request\subsystem\provider,
+        \core_privacy\local\request\user_preference_provider {
 
     /**
      * Returns information about the user data stored in this component.
@@ -177,6 +178,11 @@ class provider implements
         $collection->add_database_table('user_preferences', $userpreferences, 'privacy:metadata:user_preferences');
         $collection->add_subsystem_link('core_files', [], 'privacy:metadata:filelink');
 
+        $collection->add_user_preference(
+            'core_user_welcome',
+            'privacy:metadata:user_preference:core_user_welcome'
+        );
+
         return $collection;
     }
 
@@ -537,4 +543,23 @@ class provider implements
             writer::with_context($context)->export_data([get_string('privacy:sessionpath', 'user')], $sessiondata);
         }
     }
+
+    /**
+     * Export all user preferences for the plugin.
+     *
+     * @param   int $userid The userid of the user whose data is to be exported.
+     */
+    public static function export_user_preferences(int $userid) {
+        $userwelcomepreference = get_user_preferences('core_user_welcome', null, $userid);
+
+        if ($userwelcomepreference !== null) {
+            writer::export_user_preference(
+                'core_user',
+                'core_user_welcome',
+                $userwelcomepreference,
+                get_string('privacy:metadata:user_preference:core_user_welcome', 'core_user')
+            );
+        }
+    }
+
 }
index 17acd89..fe32af4 100644 (file)
@@ -27,8 +27,7 @@ Feature: Viewing the list of cohorts to enrol in a course
       | moodle/cohort:manage | Prohibit |
       | moodle/cohort:view   | Prohibit |
     And I log out
-    And I log in as "teacher1"
-    And I am on "Course 1" course homepage
+    And I am on the "Course 1" course page logged in as teacher1
     And I navigate to course participants
     When I press "Enrol users"
     Then I should not see "Select cohorts"
@@ -40,8 +39,7 @@ Feature: Viewing the list of cohorts to enrol in a course
       | name        | Test cohort name        |
       | idnumber    | 1337                    |
       | description | Test cohort description |
-    And I log in as "teacher1"
-    And I am on "Course 1" course homepage
+    And I am on the "Course 1" course page logged in as teacher1
     And I navigate to course participants
     When I press "Enrol users"
     Then I should see "Select cohorts"
@@ -49,8 +47,7 @@ Feature: Viewing the list of cohorts to enrol in a course
 
   @javascript
   Scenario: Check we do not show the cohorts field if there are none present
-    Given I log in as "teacher1"
-    And I am on "Course 1" course homepage
+    Given I am on the "Course 1" course page logged in as teacher1
     And I navigate to course participants
     When I press "Enrol users"
     Then I should not see "Select cohorts"
index a82add7..917a007 100644 (file)
@@ -20,6 +20,11 @@ Feature: Set the site home page and dashboard as the default home page
     Given I log in as "admin"
     And I am on site homepage
     And I turn editing mode on
+    And I add the "Navigation" block if not present
+    And I configure the "Navigation" block
+    And I set the following fields to these values:
+      | Page contexts | Display throughout the entire site |
+    And I press "Save changes"
     And I add the "Administration" block if not present
     And I configure the "Administration" block
     And I set the following fields to these values:
@@ -31,16 +36,16 @@ Feature: Set the site home page and dashboard as the default home page
     And I am on site homepage
     And I follow "Make this my home page"
     And I should not see "Make this my home page"
-#    The following lines should be changed once MDL-72110 is resolved.
-#    And I am on "Course 1" course homepage
-#    And "Home" "text" should exist in the ".breadcrumb" "css_element"
+    And I am on "Course 1" course homepage
+    And I should see "Home" in the "Navigation" "block"
+    And I should not see "Site home" in the "Navigation" "block"
     And I am on site homepage
     And I follow "Dashboard"
     And I follow "Make this my home page"
     And I should not see "Make this my home page"
-#    The following lines should be changed once MDL-72110 is resolved.
-#    And I am on "Course 1" course homepage
-#    Then "Dashboard" "text" should exist in the ".breadcrumb" "css_element"
+    And I am on "Course 1" course homepage
+    Then I should not see "Home" in the "Navigation" "block"
+    And I should see "Site home" in the "Navigation" "block"
 
   Scenario: User cannot configure their preferred default home page unless allowed by admin
     Given I log in as "user1"
index 4135a3e..3f2bf45 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2021091700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2021091700.04;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.
 $release  = '4.0dev (Build: 20210917)'; // Human-friendly version name