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'),
* @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;
};
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;
};
* @param {JQuery|Element} element
* @param {Number} cmid
* @param {Number} sectionreturn
+ * @return {Promise} the refresh promise
*/
var refreshModule = function(element, cmid, sectionreturn) {
const activityElement = $(element);
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();
+ });
+ });
};
/**
// 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.
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';
// 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']`,
// Array to save dettached elements during element resorting.
this.dettachedCms = {};
this.dettachedSections = {};
+ // Index of sections and cms components.
+ this.sections = {};
+ this.cms = {};
}
/**
});
}
+ /**
+ * 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.
*
{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},
];
}
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.
}
}
+ /**
+ * 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.
*
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);
+ }
}
}
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
}
}
+ $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);
// 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) {
$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;
}
$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' => '',
$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++;
$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++;
"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}}
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}}
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,
}
/**
- * 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
*/
--- /dev/null
+@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"
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"
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 |
| 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
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"
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 => {
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;
// 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());
// End page.
echo html_writer::end_tag('ul');
-print $OUTPUT->footer();
\ No newline at end of file
+print $OUTPUT->footer();
return $text;
}
- // Check SWF permissions.
+ // Check permissions.
$this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
// Looking for tags.
*/
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;
}
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());
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') {
$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;
}
$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
// 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>";
}
}
. '" 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>";
}
}
}
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)) {
$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);
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;
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']);
$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';
$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';
// 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.';
+
proceedtocourse,core_enrol
coursepage,core_admin
invalidpersistenterror,core_competency
+mediapluginswf,core_admin
+mediapluginswfnote,core_admin
$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';
$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)';
$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}!
$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';
if ($this->validatefunction) {
return call_user_func($this->validatefunction, $data);
} else {
+ if ($data < 0) {
+ return get_string('errorsetting', 'admin');
+ }
return '';
}
}
}
$seconds = (int)($data['v']*$data['u']);
- if ($seconds < 0) {
- return get_string('errorsetting', 'admin');
- }
// Validate the new setting.
$error = $this->validate_setting($seconds);
$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;
}
/**
// 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.
// 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;
}
* @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':
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';
}
}
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.
*
*
* @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');
// 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);
}
/**
* @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.
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']);
}
}
new environment\publicpaths(),
new environment\configrw(),
new environment\preventexecpath(),
- new security\mediafilterswf(),
new security\embed(),
new security\openprofiles(),
new security\crawlers(),
return $checks;
}
}
-
+++ /dev/null
-<?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);
- }
-}
-
'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'),
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.
'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',
'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',
}
}
-
'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',
),
'media' => array(
- 'html5audio', 'html5video', 'swf', 'videojs', 'vimeo', 'youtube'
+ 'html5audio', 'html5video', 'videojs', 'vimeo', 'youtube'
),
'message' => array(
),
'portfolio' => array(
- 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
+ 'boxnet', 'download', 'flickr', 'googledocs', 'mahara'
),
'profilefield' => array(
'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'
),
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;
+ }
}
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);
}
'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.
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;
}
}
}
-/**
- * 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
*
* @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
$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);
}
"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">
</div>
</div>
</div>
+ {{> core/welcome }}
</div>
</header>
--- /dev/null
+{{!
+ 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}}
$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');
\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));
}
/**
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';
$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.
$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;
$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;
/**
* 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...
// ...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);
}
/**
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']]
];
}
}
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.
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/';
}
* 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.
*/
+++ /dev/null
-<?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;
- }
-}
+++ /dev/null
-<?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';
- }
-}
+++ /dev/null
-<?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.';
+++ /dev/null
-<?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);
- }
-}
+++ /dev/null
-<?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).
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
}
}
- 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',
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"
--- /dev/null
+@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"
$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.';
+++ /dev/null
-<?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) {
- }
-}
+++ /dev/null
-<?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;
-}
+++ /dev/null
-<?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';
+++ /dev/null
-<?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;
- }
-}
+++ /dev/null
-<?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]);
- }
-}
+++ /dev/null
-<?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;
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.
--- /dev/null
+check_mediafilterswf_details,report_security
+check_mediafilterswf_error,report_security
+check_mediafilterswf_name,report_security
+check_mediafilterswf_ok,report_security
$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';
$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.';
+++ /dev/null
-<?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) {
- }
-}
+++ /dev/null
-<?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
- )
- )
-);
+++ /dev/null
-<?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;
-}
+++ /dev/null
-<?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.';
+++ /dev/null
-<?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.
+++ /dev/null
-<?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;
- }
-
-}
+++ /dev/null
-<?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).
// 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.
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.
| 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"
}
}
+.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;
}
list-style: none;
li.section {
- margin-top: $spacer;
+ padding-top: $spacer;
padding-bottom: $spacer;
.content {
margin: 0;
}
}
+.path-grade-report-grader {
+ span.gradepass {
+ color: $success;
+ }
+ span.gradefail {
+ color: $danger;
+ }
+}
+
// Rubrics
#page-grade-grading-manage {
#activemethodselector {
}
.icons-collapse-expand {
+ display: flex;
+ align-items: center;
.expanded-icon {
- display: block;
+ display: flex;
+ align-items: center;
}
.collapsed-icon {
}
.collapsed-icon {
- display: block;
+ display: flex;
+ align-items: center;
}
}
}
.count-container {
padding: 2px;
border-radius: 2px;
- background-color: red;
+ background-color: $danger;
color: white;
font-size: 11px;
line-height: 11px;
.count-container {
padding: 2px;
border-radius: 2px;
- background-color: red;
+ background-color: $danger;
color: white;
font-size: 11px;
line-height: 11px;
.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 {
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; }
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 {
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; }
.count-container {
padding: 2px;
border-radius: 2px;
- background-color: red;
+ background-color: #ca3120;
color: white;
font-size: 11px;
line-height: 11px;
.navbar .count-container {
padding: 2px;
border-radius: 2px;
- background-color: red;
+ background-color: #ca3120;
color: white;
font-size: 11px;
line-height: 11px;
.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 {
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; }
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 {
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; }
.count-container {
padding: 2px;
border-radius: 2px;
- background-color: red;
+ background-color: #ca3120;
color: white;
font-size: 11px;
line-height: 11px;
.navbar .count-container {
padding: 2px;
border-radius: 2px;
- background-color: red;
+ background-color: #ca3120;
color: white;
font-size: 11px;
line-height: 11px;
"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">
</div>
</div>
</div>
+ {{> core/welcome }}
</div>
-</header>
\ No newline at end of file
+</header>
"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"
]
}
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.
$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;
}
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')
+ );
+ }
+ }
+
}
| 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"
| 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"
@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"
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:
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"
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