Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7beda01cae | |||
| 5124b6747c | |||
| 09912bfefa | |||
| c9e8aa0406 | |||
| 2e024f304a | |||
| 58ced4add0 | |||
| 6a877d438b | |||
| 42ba0492e2 | |||
| 77f85ed287 | |||
| 9bc769a610 | |||
| d6c4c0fbbd | |||
| cd097f117b | |||
| 7727745bf7 | |||
| 852e31dbc4 | |||
| ef2a1934a0 | |||
| d9a6577410 | |||
| 34d7edf079 | |||
| 797f03228b | |||
| 825e621264 | |||
| aac60d73a1 | |||
| 43c2c25645 | |||
| 5e4559c85a | |||
| 6255f92d66 | |||
| df2e6c1cd4 | |||
| ae6826953e | |||
| 688c9b13d4 | |||
| e93fd454a2 | |||
| ba64c15814 | |||
| 6a5a787844 | |||
| cf76d1bf9f | |||
| 862a9fb24c | |||
| 71cdfae287 | |||
| bdf525a43e | |||
| f2c1e6c114 | |||
| 44e3cb05e5 | |||
| 964ad9d264 | |||
| b576b068bd | |||
| fbc51cd591 | |||
| 53675693b4 | |||
| 35118869c7 | |||
| ee8408c4d5 | |||
| d2e27ec8bb | |||
| ed53d81dc7 | |||
| 440edc5e42 | |||
| f5867c078d | |||
| 62d48a1d47 | |||
| dcdeb47f39 | |||
| 0fdd514f11 | |||
| b9ad54bad8 | |||
| aaf231551a | |||
| e80192f606 | |||
| 997713f43e | |||
| 6abfde47dc |
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* MoodleNet callback.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core\moodlenet\moodlenet_client;
|
||||
use core\oauth2\api;
|
||||
|
||||
require_once(__DIR__ . '/../config.php');
|
||||
require_login();
|
||||
|
||||
// Parameters.
|
||||
$issuerid = required_param('issuerid', PARAM_INT);
|
||||
$error = optional_param('error', '', PARAM_RAW);
|
||||
$message = optional_param('error_description', null, PARAM_RAW);
|
||||
|
||||
// Headers to make it not cacheable.
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
|
||||
|
||||
$PAGE->set_context(context_system::instance());
|
||||
$PAGE->set_url('/admin/moodlenet_oauth2_callback.php');
|
||||
$PAGE->set_pagelayout('popup');
|
||||
|
||||
// Wait as long as it takes for this script to finish.
|
||||
core_php_time_limit::raise();
|
||||
|
||||
$issuer = api::get_issuer($issuerid);
|
||||
$returnurl = new moodle_url('/admin/moodlenet_oauth2_callback.php');
|
||||
$returnurl->param('issuerid', $issuerid);
|
||||
$returnurl->param('callback', 'yes');
|
||||
$returnurl->param('sesskey', sesskey());
|
||||
$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE_RESOURCE, true);
|
||||
$oauthclient->is_logged_in(); // Will upgrade the auth code to a token.
|
||||
|
||||
echo $OUTPUT->header();
|
||||
$PAGE->requires->js_call_amd('core/moodlenet/oauth2callback', 'init', [$error, $message]);
|
||||
echo $OUTPUT->footer();
|
||||
@@ -33,6 +33,11 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
new lang_string('enablecourserelativedates', 'core_admin'),
|
||||
new lang_string('enablecourserelativedates_desc', 'core_admin'), 0));
|
||||
|
||||
// Sharing to MoodleNet setting.
|
||||
$temp->add(new admin_setting_configcheckbox('enablesharingtomoodlenet',
|
||||
new lang_string('enablesharingtomoodlenet', 'core_admin'),
|
||||
new lang_string('enablesharingtomoodlenet_desc', 'core_admin'), 0));
|
||||
|
||||
$ADMIN->add('experimental', $temp);
|
||||
|
||||
// "debugging" settingpage
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file gives information about MoodleNet.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
if ($hassiteconfig) {
|
||||
if (!empty($CFG->enablesharingtomoodlenet)) {
|
||||
if (!$ADMIN->locate('moodlenet')) {
|
||||
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
|
||||
}
|
||||
|
||||
// Outbound settings page.
|
||||
$settings = new admin_settingpage('moodlenetoutbound', new lang_string('moodlenet:outboundsettings', 'moodle'));
|
||||
$ADMIN->add('moodlenet', $settings);
|
||||
|
||||
// Get all the issuers.
|
||||
$issuers = \core\oauth2\api::get_all_issuers();
|
||||
$oauth2services = [
|
||||
'' => new lang_string('none', 'admin'),
|
||||
];
|
||||
foreach ($issuers as $issuer) {
|
||||
// Get the enabled issuer with the service type is MoodleNet only.
|
||||
if ($issuer->get('servicetype') == 'moodlenet' && $issuer->get('enabled')) {
|
||||
$oauth2services[$issuer->get('id')] = s($issuer->get('name'));
|
||||
}
|
||||
}
|
||||
|
||||
$url = new \moodle_url('/admin/tool/oauth2/issuers.php');
|
||||
|
||||
$settings->add(new admin_setting_configselect('moodlenet/oauthservice', new lang_string('issuer', 'auth_oauth2'),
|
||||
new lang_string('moodlenet:configoauthservice', 'moodle', $url->out()), '', $oauth2services));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,52 +8,52 @@ Feature: An administrator can manage Block plugins
|
||||
Scenario: An administrator can control the enabled state of block plugins using JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
When I click on "Disable the Latest badges plugin" "link"
|
||||
Then I should see "The Latest badges plugin has been disabled"
|
||||
And "Disable the Latest badges plugin" "link" should not exist
|
||||
But "Enable the Latest badges plugin" "link" should exist
|
||||
When I click on "Enable the Latest badges plugin" "link"
|
||||
Then I should see "The Latest badges plugin has been enabled"
|
||||
And "Enable the Latest badges plugin" "link" should not exist
|
||||
But "Disable the Latest badges plugin" "link" should exist
|
||||
When I click on "Disable Latest badges" "link"
|
||||
Then I should see "Latest badges disabled."
|
||||
And "Disable Latest badges" "link" should not exist
|
||||
But "Enable Latest badges" "link" should exist
|
||||
When I click on "Enable Latest badges" "link"
|
||||
Then I should see "Latest badges enabled."
|
||||
And "Enable Latest badges" "link" should not exist
|
||||
But "Disable Latest badges" "link" should exist
|
||||
|
||||
Scenario: An administrator can control the enabled state of block plugins without JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
When I click on "Disable the Latest badges plugin" "link"
|
||||
Then I should see "The Latest badges plugin has been disabled"
|
||||
And "Disable the Latest badges plugin" "link" should not exist
|
||||
But "Enable the Latest badges plugin" "link" should exist
|
||||
When I click on "Enable the Latest badges plugin" "link"
|
||||
Then I should see "The Latest badges plugin has been enabled"
|
||||
And "Enable the Latest badges plugin" "link" should not exist
|
||||
But "Disable the Latest badges plugin" "link" should exist
|
||||
When I click on "Disable Latest badges" "link"
|
||||
Then I should see "Latest badges disabled."
|
||||
And "Disable Latest badges" "link" should not exist
|
||||
But "Enable Latest badges" "link" should exist
|
||||
When I click on "Enable Latest badges" "link"
|
||||
Then I should see "Latest badges enabled."
|
||||
And "Enable Latest badges" "link" should not exist
|
||||
But "Disable Latest badges" "link" should exist
|
||||
|
||||
@javascript
|
||||
Scenario: An administrator can control the protected state of block plugins using JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
When I click on "Protect instances of the Latest badges block" "link"
|
||||
Then I should see "The Latest badges block is now protected"
|
||||
And "Protect instances of the Latest badges block" "link" should not exist
|
||||
But "Unprotect instances of the Latest badges block" "link" should exist
|
||||
And "Protect instances of the Activities block" "link" should exist
|
||||
When I click on "Unprotect instances of the Latest badges block" "link"
|
||||
Then I should see "The Latest badges block is no longer protected"
|
||||
And "Unprotect instances of the Activities block" "link" should not exist
|
||||
But "Protect instances of the Latest badges block" "link" should exist
|
||||
And "Protect instances of the Activities block" "link" should exist
|
||||
When I click on "Protect instances of Latest badges" "link"
|
||||
Then I should see "Latest badges block instances are protected."
|
||||
And "Protect instances of Latest badges" "link" should not exist
|
||||
But "Unprotect instances of Latest badges" "link" should exist
|
||||
And "Protect instances of Activities" "link" should exist
|
||||
When I click on "Unprotect instances of Latest badges" "link"
|
||||
Then I should see "Latest badges block instances are unprotected."
|
||||
And "Unprotect instances of Activities" "link" should not exist
|
||||
But "Protect instances of Latest badges" "link" should exist
|
||||
And "Protect instances of Activities" "link" should exist
|
||||
|
||||
Scenario: An administrator can control the protected state of block plugins without JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
When I click on "Protect instances of the Latest badges block" "link"
|
||||
Then I should see "The Latest badges block is now protected"
|
||||
And "Protect instances of the Latest badges block" "link" should not exist
|
||||
But "Unprotect instances of the Latest badges block" "link" should exist
|
||||
And "Protect instances of the Activities block" "link" should exist
|
||||
When I click on "Unprotect instances of the Latest badges block" "link"
|
||||
Then I should see "The Latest badges block is no longer protected"
|
||||
And "Unprotect instances of the Activities block" "link" should not exist
|
||||
But "Protect instances of the Latest badges block" "link" should exist
|
||||
And "Protect instances of the Activities block" "link" should exist
|
||||
When I click on "Protect instances of Latest badges" "link"
|
||||
Then I should see "Latest badges block instances are protected."
|
||||
And "Protect instances of Latest badges" "link" should not exist
|
||||
But "Unprotect instances of Latest badges" "link" should exist
|
||||
And "Protect instances of Activities" "link" should exist
|
||||
When I click on "Unprotect instances of Latest badges" "link"
|
||||
Then I should see "Latest badges block instances are unprotected."
|
||||
And "Unprotect instances of Activities" "link" should not exist
|
||||
But "Protect instances of Latest badges" "link" should exist
|
||||
And "Protect instances of Activities" "link" should exist
|
||||
|
||||
@@ -8,23 +8,23 @@ Feature: An administrator can manage Media plugins
|
||||
Scenario: An administrator can control the enabled state of media plugins using JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Media players > Manage media players" in site administration
|
||||
When I click on "Disable the YouTube plugin" "link"
|
||||
Then I should see "The YouTube plugin has been disabled"
|
||||
And "Disable the YouTube plugin" "link" should not exist
|
||||
But "Enable the YouTube plugin" "link" should exist
|
||||
When I click on "Enable the YouTube plugin" "link"
|
||||
Then I should see "The YouTube plugin has been enabled"
|
||||
And "Enable the YouTube plugin" "link" should not exist
|
||||
But "Disable the YouTube plugin" "link" should exist
|
||||
When I click on "Disable YouTube" "link"
|
||||
Then I should see "YouTube disabled."
|
||||
And "Disable YouTube" "link" should not exist
|
||||
But "Enable YouTube" "link" should exist
|
||||
When I click on "Enable YouTube" "link"
|
||||
Then I should see "YouTube enabled."
|
||||
And "Enable YouTube" "link" should not exist
|
||||
But "Disable YouTube" "link" should exist
|
||||
|
||||
Scenario: An administrator can control the enabled state of media plugins without JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Media players > Manage media players" in site administration
|
||||
When I click on "Disable the YouTube plugin" "link"
|
||||
Then I should see "The YouTube plugin has been disabled"
|
||||
And "Disable the YouTube plugin" "link" should not exist
|
||||
But "Enable the YouTube plugin" "link" should exist
|
||||
When I click on "Enable the YouTube plugin" "link"
|
||||
Then I should see "The YouTube plugin has been enabled"
|
||||
And "Enable the YouTube plugin" "link" should not exist
|
||||
But "Disable the YouTube plugin" "link" should exist
|
||||
When I click on "Disable YouTube" "link"
|
||||
Then I should see "YouTube disabled."
|
||||
And "Disable YouTube" "link" should not exist
|
||||
But "Enable YouTube" "link" should exist
|
||||
When I click on "Enable YouTube" "link"
|
||||
Then I should see "YouTube enabled."
|
||||
And "Enable YouTube" "link" should not exist
|
||||
But "Disable YouTube" "link" should exist
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
@core @core_admin
|
||||
Feature: MoodleNet outbound configuration
|
||||
In order to send activity/resource to MoodleNet
|
||||
As a Moodle administrator
|
||||
I need to set outbound configuration
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
|
||||
Scenario: Share to MoodleNet experimental flag
|
||||
Given I navigate to "Development > Experimental" in site administration
|
||||
Then "Enable sharing to MoodleNet" "field" should exist
|
||||
And the field "Enable sharing to MoodleNet" matches value "0"
|
||||
|
||||
Scenario: Outbound configuration without experimental flag enable yet
|
||||
Given I navigate to "MoodleNet" in site administration
|
||||
Then I should not see "MoodleNet outbound settings"
|
||||
|
||||
Scenario: Outbound configuration without OAuth 2 service setup yet
|
||||
Given the following config values are set as admin:
|
||||
| enablesharingtomoodlenet | 1 |
|
||||
When I navigate to "MoodleNet" in site administration
|
||||
Then I should see "MoodleNet outbound settings"
|
||||
And I click on "MoodleNet outbound settings" "link"
|
||||
And the field "OAuth 2 service" matches value "None"
|
||||
And I should see "Select a MoodleNet OAuth 2 service to enable sharing to that MoodleNet site. If the service doesn't exist yet, you will need to create it."
|
||||
And I click on "create" "link"
|
||||
And I should see "OAuth 2 services"
|
||||
|
||||
Scenario: Outbound configuration with OAuth 2 service setup
|
||||
Given a MoodleNet mock server is configured
|
||||
And the following config values are set as admin:
|
||||
| enablesharingtomoodlenet | 1 |
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I press "Custom"
|
||||
And I should see "Create new service: Custom"
|
||||
And I set the following fields to these values:
|
||||
| Name | Testing custom service |
|
||||
| Client ID | thisistheclientid |
|
||||
| Client secret | supersecret |
|
||||
And I press "Save changes"
|
||||
When I navigate to "MoodleNet > MoodleNet outbound settings" in site administration
|
||||
Then the field "OAuth 2 service" matches value "None"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
And I press "MoodleNet"
|
||||
And I should see "Create new service: MoodleNet"
|
||||
And I change the MoodleNet field "Service base URL" to mock server
|
||||
And I press "Save changes"
|
||||
And I navigate to "MoodleNet > MoodleNet outbound settings" in site administration
|
||||
And the "OAuth 2 service" "field" should be enabled
|
||||
And I should see "MoodleNet" in the "OAuth 2 service" "select"
|
||||
And I should not see "Testing custom service" in the "OAuth 2 service" "select"
|
||||
@@ -1,4 +1,4 @@
|
||||
@core @core_admin
|
||||
@core @core_admin @core_webservice
|
||||
Feature: Web service user settings
|
||||
In order to configure authorised users for a web service
|
||||
As an admin
|
||||
@@ -29,3 +29,13 @@ Feature: Web service user settings
|
||||
Then I should see "User One" in the ".alloweduserlist" "css_element"
|
||||
And I should see "1@example.org" in the ".alloweduserlist" "css_element"
|
||||
And I should see "Kermit" in the ".alloweduserlist" "css_element"
|
||||
|
||||
@javascript
|
||||
Scenario: Add a function to a web service
|
||||
When I log in as "admin"
|
||||
And I navigate to "Server > Web services > External services" in site administration
|
||||
And I click on "Functions" "link" in the "Silly service" "table_row"
|
||||
And I follow "Add functions"
|
||||
And I set the field "Name" to "core_blog_get_entries"
|
||||
And I click on "Add functions" "button" in the "#fgroup_id_buttonar" "css_element"
|
||||
Then I should see "core_blog_get_entries" in the "generaltable" "table"
|
||||
|
||||
@@ -14,11 +14,11 @@ Feature: I can apply presets
|
||||
Scenario: Applying Starter Moodle preset changes status and settings
|
||||
# Checking the settings before applying Full Moodle preset (we're only testing one of each type).
|
||||
Given I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And "Disable the Chat plugin" "icon" should exist in the "Chat" "table_row"
|
||||
And "Disable Chat" "icon" should exist in the "Chat" "table_row"
|
||||
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
|
||||
And "Hide" "icon" should exist in the "Restriction by grouping" "table_row"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
And "Disable the Logged in user plugin" "icon" should exist in the "Logged in user" "table_row"
|
||||
And "Disable Logged in user" "icon" should exist in the "Logged in user" "table_row"
|
||||
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
|
||||
And "Disable" "icon" should exist in the "Social format" "table_row"
|
||||
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
|
||||
@@ -109,11 +109,11 @@ Feature: I can apply presets
|
||||
And the field "Enable badges" matches value "0"
|
||||
And the field "Enable competencies" matches value "0"
|
||||
And I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And "Disable the Chat plugin" "icon" should not exist in the "Chat" "table_row"
|
||||
And "Disable Chat" "icon" should not exist in the "Chat" "table_row"
|
||||
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
|
||||
And "Hide" "icon" should not exist in the "Restriction by grouping" "table_row"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
And "Disable the Logged in user plugin" "icon" should not exist in the "Logged in user" "table_row"
|
||||
And "Disable Logged in user" "icon" should not exist in the "Logged in user" "table_row"
|
||||
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
|
||||
And "Disable" "icon" should not exist in the "Social format" "table_row"
|
||||
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
|
||||
|
||||
@@ -20,11 +20,11 @@ Feature: I can revert changes after a load
|
||||
And the field "Enable badges" matches value "0"
|
||||
And the field "Enable competencies" matches value "0"
|
||||
And I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And "Disable the Chat plugin" "icon" should not exist in the "Chat" "table_row"
|
||||
And "Disable Chat" "icon" should not exist in the "Chat" "table_row"
|
||||
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
|
||||
And "Hide" "icon" should not exist in the "Restriction by grouping" "table_row"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
And "Disable the Logged in user plugin" "icon" should not exist in the "Logged in user" "table_row"
|
||||
And "Disable Logged in user" "icon" should not exist in the "Logged in user" "table_row"
|
||||
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
|
||||
And "Disable" "icon" should not exist in the "Social format" "table_row"
|
||||
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
|
||||
@@ -39,11 +39,11 @@ Feature: I can revert changes after a load
|
||||
Then the field "Enable badges" matches value "1"
|
||||
And the field "Enable competencies" matches value "1"
|
||||
And I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And "Disable the Chat plugin" "icon" should exist in the "Chat" "table_row"
|
||||
And "Disable Chat" "icon" should exist in the "Chat" "table_row"
|
||||
And I navigate to "Plugins > Availability restrictions > Manage restrictions" in site administration
|
||||
And "Hide" "icon" should exist in the "Restriction by grouping" "table_row"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
And "Disable the Logged in user plugin" "icon" should exist in the "Logged in user" "table_row"
|
||||
And "Disable Logged in user" "icon" should exist in the "Logged in user" "table_row"
|
||||
And I navigate to "Plugins > Course formats > Manage course formats" in site administration
|
||||
And "Disable" "icon" should exist in the "Social format" "table_row"
|
||||
And I navigate to "Plugins > Question behaviours > Manage question behaviours" in site administration
|
||||
|
||||
@@ -47,9 +47,8 @@ $string['coursesize_2'] = 'M (~100MB; create in ~2 minutes)';
|
||||
$string['coursesize_3'] = 'L (~1GB; create in ~30 minutes)';
|
||||
$string['coursesize_4'] = 'XL (~10GB; create in ~2 hours)';
|
||||
$string['coursesize_5'] = 'XXL (~20GB; create in ~4 hours)';
|
||||
$string['additionalmodules'] = 'Additional modules';
|
||||
$string['additionalmodules_help'] = 'We normally create standard modules in the course (like forum, page or label),
|
||||
but you can select any other module that implements the course_backend_generator_create_activity function.';
|
||||
$string['additionalmodules'] = 'Additional activities';
|
||||
$string['additionalmodules_help'] = 'Select more activities that implement the course_backend_generator_create_activity function to include in the test course.';
|
||||
$string['coursewithoutusers'] = 'The selected course has no users';
|
||||
$string['createcourse'] = 'Create course';
|
||||
$string['createtestplan'] = 'Create test plan';
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['autoenablenotification'] = '<p>In Moodle 4.0 onwards, the <a href="https://moodle.net/">MoodleNet</a> integration is enabled by default in Advanced features. Users with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their courses.</p><p>If desired, an alternative MoodleNet instance may be specified in the <a href="{$a->settingslink}">MoodleNet settings</a>.</p>';
|
||||
$string['autoenablenotification'] = '<p>In Moodle 4.0 onwards, the <a href="https://moodle.net/">MoodleNet</a> integration is enabled by default in Advanced features. Users with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their courses.</p><p>If desired, an alternative MoodleNet instance may be specified in the <a href="{$a->settingslink}">MoodleNet inbound settings</a>.</p>';
|
||||
$string['autoenablenotification_subject'] = 'Default MoodleNet setting changed.';
|
||||
$string['addingaresource'] = 'Adding content from MoodleNet';
|
||||
$string['aria:enterprofile'] = "Enter your MoodleNet profile ID";
|
||||
@@ -38,7 +38,7 @@ $string['defaultmoodlenet_desc'] = 'The URL of the MoodleNet instance available
|
||||
$string['defaultmoodlenetname'] = "MoodleNet instance name";
|
||||
$string['defaultmoodlenetnamevalue'] = 'MoodleNet Central';
|
||||
$string['defaultmoodlenetname_desc'] = 'The name of the MoodleNet instance available via the activity chooser.';
|
||||
$string['enablemoodlenet'] = 'Enable MoodleNet integration';
|
||||
$string['enablemoodlenet'] = 'Enable MoodleNet integration (inbound)';
|
||||
$string['enablemoodlenet_desc'] = 'If enabled, a user with the capability to create and manage activities can browse MoodleNet via the activity chooser and import MoodleNet resources into their course. In addition, a user with the capability to restore backups can select a backup file on MoodleNet and restore it into Moodle.';
|
||||
$string['errorduringdownload'] = 'An error occurred while downloading the file: {$a}';
|
||||
$string['forminfo'] = 'Your MoodleNet profile ID will be automatically saved in your profile on this site.';
|
||||
@@ -55,7 +55,7 @@ $string['missinginvalidpostdata'] = 'The resource information from MoodleNet is
|
||||
If this happens repeatedly, please contact the site administrator.';
|
||||
$string['mnetprofile'] = 'MoodleNet profile';
|
||||
$string['mnetprofiledesc'] = '<p>Enter your MoodleNet profile details here to be redirected to your profile while visiting MoodleNet.</p>';
|
||||
$string['moodlenetsettings'] = 'MoodleNet settings';
|
||||
$string['moodlenetsettings'] = 'MoodleNet inbound settings';
|
||||
$string['moodlenetnotenabled'] = 'The MoodleNet integration must be enabled in Site administration / MoodleNet before resource imports can be processed.';
|
||||
$string['notification'] = 'You are about to import the content "{$a->name} ({$a->type})" into your site. Select the course in which it should be added, or <a href="{$a->cancellink}">cancel</a>.';
|
||||
$string['removedmnetprofilenotification'] = 'Due to recent changes on the MoodleNet platform, any users who previously saved their MoodleNet profile ID on the site will need to enter a MoodleNet profile ID in the new format in order to authenticate on the MoodleNet platform.';
|
||||
|
||||
@@ -35,7 +35,9 @@ if ($hassiteconfig) {
|
||||
|
||||
// Create a MoodleNet category.
|
||||
if (get_config('tool_moodlenet', 'enablemoodlenet')) {
|
||||
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
|
||||
if (!$ADMIN->locate('moodlenet')) {
|
||||
$ADMIN->add('root', new admin_category('moodlenet', get_string('pluginname', 'tool_moodlenet')));
|
||||
}
|
||||
// Our settings page.
|
||||
$settings = new admin_settingpage('tool_moodlenet', get_string('moodlenetsettings', 'tool_moodlenet'));
|
||||
$ADMIN->add('moodlenet', $settings);
|
||||
|
||||
@@ -196,7 +196,7 @@ class external_service_functions_form extends moodleform {
|
||||
//we add the descriptions to the functions
|
||||
foreach ($functions as $functionid => $functionname) {
|
||||
//retrieve full function information (including the description)
|
||||
$function = external_api::external_function_info($functionname);
|
||||
$function = \core_external\external_api::external_function_info($functionname);
|
||||
if (empty($function->deprecated)) {
|
||||
$functions[$functionid] = $function->name . ':' . $function->description;
|
||||
} else {
|
||||
|
||||
@@ -228,11 +228,26 @@ class backpack_api2p1 {
|
||||
$msg['status'] = \core\output\notification::NOTIFY_SUCCESS;
|
||||
$msg['message'] = get_string('addedtobackpack', 'badges');
|
||||
} else {
|
||||
$statuserror = $response->status->error;
|
||||
if (is_array($statuserror)) {
|
||||
if ($response) {
|
||||
// Although the specification defines that status error is a string, some providers, like Badgr, are wrongly
|
||||
// returning an array. It has been reported, but adding this extra check doesn't hurt, just in case.
|
||||
$statuserror = implode($statuserror);
|
||||
// returning an array. It has been reported, but adding these extra checks doesn't hurt, just in case.
|
||||
if (
|
||||
property_exists($response, 'status') &&
|
||||
is_object($response->status) &&
|
||||
property_exists($response->status, 'error')
|
||||
) {
|
||||
$statuserror = $response->status->error;
|
||||
if (is_array($statuserror)) {
|
||||
$statuserror = implode($statuserror);
|
||||
}
|
||||
} else if (property_exists($response, 'error')) {
|
||||
$statuserror = $response->error;
|
||||
if (property_exists($response, 'message')) {
|
||||
$statuserror .= '. Message: ' . $response->message;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$statuserror = 'Empty response';
|
||||
}
|
||||
$data = [
|
||||
'badgename' => $data['assertion']['badge']['name'],
|
||||
|
||||
@@ -17,7 +17,7 @@ Feature: Add the glossary random block when main feature is enabled
|
||||
|
||||
Scenario: The glossary random block cannot be added when glossary module is disabled
|
||||
Given I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And I click on "Disable the Glossary plugin" "icon" in the "Glossary" "table_row"
|
||||
And I click on "Disable Glossary" "icon" in the "Glossary" "table_row"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
When I click on "Add a block" "link"
|
||||
Then I should not see "Random glossary entry"
|
||||
|
||||
@@ -15,7 +15,7 @@ Feature: Add the glossary random block when main feature is disabled
|
||||
And I add the "Random glossary entry" block to the default region with:
|
||||
| Title | Random glossary entry |
|
||||
When I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And I click on "Disable the Glossary plugin" "icon" in the "Glossary" "table_row"
|
||||
And I click on "Disable Glossary" "icon" in the "Glossary" "table_row"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
Then I should see "Random glossary entry"
|
||||
|
||||
@@ -29,7 +29,7 @@ Feature: Add the glossary random block when main feature is disabled
|
||||
And I click on "Cancel" "button" in the "Delete block?" "dialogue"
|
||||
And I should see "Random glossary entry"
|
||||
When I navigate to "Plugins > Activity modules > Manage activities" in site administration
|
||||
And I click on "Disable the Glossary plugin" "icon" in the "Glossary" "table_row"
|
||||
And I click on "Disable Glossary" "icon" in the "Glossary" "table_row"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I open the "Random glossary entry" blocks action menu
|
||||
And I click on "Delete Random glossary entry block" "link" in the "Random glossary entry" "block"
|
||||
|
||||
@@ -94,7 +94,7 @@ $string['zero_request_title'] = 'Request your first course';
|
||||
$string['zero_request_intro'] = 'Need help getting started? Check out the <a href="{$a->dochref}" title="{$a->doctitle}" target="{$a->doctarget}">Moodle documentation</a> or take your first steps with our <a href="{$a->quickhref}" title="{$a->quicktitle}" target="{$a->quicktarget}">Quickstart guide</a>.';
|
||||
$string['zero_nocourses_title'] = 'Create your first course';
|
||||
$string['zero_nocourses_intro'] = 'Need help getting started? Check out the <a href="{$a->dochref}" title="{$a->doctitle}" target="{$a->doctarget}">Moodle documentation</a> or take your first steps with our Quickstart guide.';
|
||||
$string['zero_createcourses_intro'] = 'Once you enrol in a course, it will appear here. To view all courses on this site, go to Manage courses.';
|
||||
$string['zero_createcourses_intro'] = 'Once you enrol in a course, it will appear here.';
|
||||
$string['zero_nomanagecourses_intro'] = 'Once you enrol in a course, it will appear here.';
|
||||
|
||||
// Deprecated since Moodle 4.0.
|
||||
|
||||
@@ -47,7 +47,7 @@ Feature: Zero state on my overview block
|
||||
| shortname | C1 |
|
||||
When I am on the "My courses" page logged in as "manager"
|
||||
Then I should see "You're not enrolled in any course"
|
||||
Then I should see "To view all courses on this site, go to Manage courses."
|
||||
Then I should see "Once you enrol in a course, it will appear here."
|
||||
And "Manage courses" "button" should exist
|
||||
And "Create course" "button" should exist
|
||||
And I click on "Create course" "button"
|
||||
|
||||
@@ -7,7 +7,7 @@ Feature: Adding and configuring YouTube block
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Plugins > Blocks > Manage blocks" in site administration
|
||||
And I click on "Enable the YouTube plugin" "icon" in the "YouTube" "table_row"
|
||||
And I click on "Enable YouTube" "icon" in the "YouTube" "table_row"
|
||||
|
||||
@javascript
|
||||
Scenario: Category options are not available (except default) in the block settings if the YouTube API key is not set.
|
||||
|
||||
@@ -40,7 +40,7 @@ $string['indentation_help'] = 'Allow teachers, and other users with the manage a
|
||||
$string['section0name'] = 'General';
|
||||
$string['sectionavailability_title'] = 'Week availability';
|
||||
$string['sectiondelete_title'] = 'Delete week?';
|
||||
$string['sectionmove_title'] = 'Move section';
|
||||
$string['sectionmove_title'] = 'Move week';
|
||||
$string['sectionname'] = 'Week';
|
||||
$string['sectionsavailability'] = 'Weeks availability';
|
||||
$string['sectionsavailability_title'] = 'Weeks availability';
|
||||
|
||||
+2
-2
@@ -1764,8 +1764,8 @@ class grade_structure {
|
||||
|
||||
if ($menuitems) {
|
||||
$menu = new action_menu($menuitems);
|
||||
$icon = $OUTPUT->pix_icon('i/dropdown', get_string('actions'));
|
||||
$extraclasses = 'btn btn-icon icon-size-2 bg-secondary d-flex align-items-center justify-content-center';
|
||||
$icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
|
||||
$extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
|
||||
$menu->set_menu_trigger($icon, $extraclasses);
|
||||
$menu->set_menu_left();
|
||||
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -31,6 +31,7 @@ import {renderForPromise, replaceNodeContents} from 'core/templates';
|
||||
const selectors = {
|
||||
component: '.user-search',
|
||||
courseid: '[data-region="courseid"]',
|
||||
resetPageButton: '[data-action="resetpage"]',
|
||||
};
|
||||
const component = document.querySelector(selectors.component);
|
||||
const courseID = component.querySelector(selectors.courseid).dataset.courseid;
|
||||
@@ -81,10 +82,10 @@ export default class UserSearch extends GradebookSearchClass {
|
||||
*/
|
||||
async renderDropdown() {
|
||||
const {html, js} = await renderForPromise('gradereport_grader/search/resultset', {
|
||||
users: this.getMatchedResults(),
|
||||
users: this.getMatchedResults().slice(0, 5),
|
||||
hasusers: this.getMatchedResults().length > 0,
|
||||
total: this.getDatasetSize(),
|
||||
found: this.getMatchedResults().length,
|
||||
matches: this.getMatchedResults().length,
|
||||
showing: this.getMatchedResults().slice(0, 5).length,
|
||||
searchterm: this.getSearchTerm(),
|
||||
selectall: this.selectAllResultsLink(),
|
||||
});
|
||||
@@ -135,6 +136,7 @@ export default class UserSearch extends GradebookSearchClass {
|
||||
this.getPreppedSearchTerm(),
|
||||
`<span class="font-weight-bold">${this.getSearchTerm()}</span>`
|
||||
);
|
||||
user.matchingField = `${user.matchingField} (${user.email})`;
|
||||
user.link = this.selectOneLink(user.id);
|
||||
break;
|
||||
}
|
||||
@@ -153,6 +155,9 @@ export default class UserSearch extends GradebookSearchClass {
|
||||
if (e.target === this.getHTMLElements().currentViewAll && e.button === 0) {
|
||||
window.location = this.selectAllResultsLink();
|
||||
}
|
||||
if (e.target.closest(selectors.resetPageButton)) {
|
||||
window.location = e.target.closest(selectors.resetPageButton).href;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,11 +185,18 @@ export default class UserSearch extends GradebookSearchClass {
|
||||
}
|
||||
}
|
||||
if (document.activeElement === this.getHTMLElements().clearSearchButton) {
|
||||
this.closeSearch();
|
||||
this.closeSearch(true);
|
||||
break;
|
||||
}
|
||||
if (e.target.closest(selectors.resetPageButton)) {
|
||||
window.location = e.target.closest(selectors.resetPageButton).href;
|
||||
break;
|
||||
}
|
||||
if (e.target.closest('.dropdown-item')) {
|
||||
e.preventDefault();
|
||||
window.location = e.target.closest('.dropdown-item').href;
|
||||
break;
|
||||
}
|
||||
e.preventDefault();
|
||||
window.location = e.target.closest('.dropdown-item').href;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,14 +222,18 @@ export default class {
|
||||
|
||||
/**
|
||||
* When called, close the dropdown and reset the input field attributes.
|
||||
*
|
||||
* @param {Boolean} clear Conditionality clear the input box.
|
||||
*/
|
||||
closeSearch() {
|
||||
closeSearch(clear = false) {
|
||||
this.toggleDropdown();
|
||||
// Hide the "clear" search button search bar.
|
||||
this.clearSearchButton.classList.add('d-none');
|
||||
// Clear the entered search query in the search bar and hide the search results container.
|
||||
this.setSearchTerms('');
|
||||
this.searchInput.value = "";
|
||||
if (clear) {
|
||||
// Clear the entered search query in the search bar and hide the search results container.
|
||||
this.setSearchTerms('');
|
||||
this.searchInput.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -379,7 +383,7 @@ export default class {
|
||||
}
|
||||
// The "clear search" button is triggered.
|
||||
if (e.target.closest(this.selectors.clearSearch) && e.button === 0) {
|
||||
this.closeSearch();
|
||||
this.closeSearch(true);
|
||||
this.searchInput.focus({preventScroll: true});
|
||||
}
|
||||
// User may have accidentally clicked off the dropdown and wants to reopen it.
|
||||
@@ -418,7 +422,7 @@ export default class {
|
||||
case 'Tab':
|
||||
// If the current focus is on clear search, then check if viewall exists then around tab to it.
|
||||
if (e.target.closest(this.selectors.clearSearch)) {
|
||||
if (this.currentViewAll) {
|
||||
if (this.currentViewAll && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
this.currentViewAll.focus({preventScroll: true});
|
||||
} else {
|
||||
|
||||
@@ -101,7 +101,7 @@ class get_users_in_report extends external_api {
|
||||
$userpicture->size = 0; // Size f2.
|
||||
$user->profileimageurlsmall = $userpicture->get_url($PAGE)->out(false);
|
||||
return $user;
|
||||
}, $report->load_users());
|
||||
}, $report->load_users(true));
|
||||
sort($users);
|
||||
|
||||
return [
|
||||
|
||||
@@ -92,9 +92,11 @@ class action_bar extends \core_grades\output\action_bar {
|
||||
$data['initialselector'] = $initialselector->export_for_template($output);
|
||||
$data['groupselector'] = $gradesrenderer->group_selector($course);
|
||||
|
||||
$resetlink = new moodle_url('/grade/report/grader/index.php', ['id' => $courseid]);
|
||||
$searchinput = $OUTPUT->render_from_template('gradereport_grader/search/searchinput', [
|
||||
'currentvalue' => $this->usersearch,
|
||||
'courseid' => $courseid,
|
||||
'resetlink' => $resetlink->out(false),
|
||||
]);
|
||||
$searchdropdown = new gradebook_dropdown(
|
||||
true,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
$string['clearsearch'] = 'Clear searched users';
|
||||
$string['collapsedcolumns'] = 'Collapsed columns <span class="badge badge-pill badge-primary ml-1" data-collapse="count">{$a}</span>';
|
||||
$string['eventgradereportviewed'] = 'Grader report viewed';
|
||||
$string['grader:manage'] = 'Manage the grader report';
|
||||
@@ -46,12 +47,11 @@ $string['privacy:metadata:preference:grade_report_studentsperpage'] = 'The numbe
|
||||
$string['privacy:request:preference:grade_report_grader_collapsed_categories'] = 'You have some gradebook categories collapsed in the "{$a->name}" course';
|
||||
$string['reopencolumn'] = 'Reopen {$a} column';
|
||||
$string['summarygrader'] = 'A table with the names of students in the first column, with assessable activities grouped by course and category across the top.';
|
||||
$string['showingxofy'] = 'Showing {$a->found} of {$a->total}';
|
||||
$string['useractivitygrade'] = '{$a} grade';
|
||||
$string['overriddengrade'] = 'Overridden grade';
|
||||
$string['advancedgrading'] = 'View {$a} results';
|
||||
$string['cellactions'] = 'Cell actions';
|
||||
$string['viewallresults'] = 'View all results for "{$a}"';
|
||||
$string['viewallresults'] = 'View all results ({$a})';
|
||||
$string['viewresultsuser'] = 'View results for {$a}';
|
||||
|
||||
// Deprecated since Moodle 4.2.
|
||||
|
||||
@@ -389,8 +389,10 @@ class grade_report_grader extends grade_report {
|
||||
|
||||
/**
|
||||
* pulls out the userids of the users to be display, and sorts them
|
||||
*
|
||||
* @param bool $allusers If we are getting the users within the report, we want them all irrespective of paging.
|
||||
*/
|
||||
public function load_users() {
|
||||
public function load_users(bool $allusers = false) {
|
||||
global $CFG, $DB;
|
||||
|
||||
if (!empty($this->users)) {
|
||||
@@ -465,7 +467,8 @@ class grade_report_grader extends grade_report {
|
||||
$this->groupwheresql
|
||||
ORDER BY $sort";
|
||||
// We never work with unlimited result. Limit the number of records by MAX_STUDENTS_PER_PAGE if no other limit is specified.
|
||||
$studentsperpage = $this->get_students_per_page() ?: static::MAX_STUDENTS_PER_PAGE;
|
||||
$studentsperpage = ($this->get_students_per_page() && !$allusers) ?
|
||||
$this->get_students_per_page() : static::MAX_STUDENTS_PER_PAGE;
|
||||
$this->users = $DB->get_records_sql($sql, $params, $studentsperpage * $this->page, $studentsperpage);
|
||||
|
||||
if (empty($this->users)) {
|
||||
|
||||
@@ -234,7 +234,8 @@
|
||||
pointer-events: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
.path-grade-report-grader .usersearchwidget button {
|
||||
.path-grade-report-grader .usersearchwidget button,
|
||||
.path-grade-report-grader .usersearchwidget a {
|
||||
pointer-events: initial;
|
||||
}
|
||||
.path-grade-report-grader .usersearchdropdown {
|
||||
|
||||
@@ -45,10 +45,10 @@
|
||||
{{/profileimageurl}}
|
||||
</div>
|
||||
<div class="d-block pr-3 w-75">
|
||||
<span class="d-flex w-100 p-0 font-weight-bold">
|
||||
<span class="d-block w-100 p-0 text-truncate font-weight-bold">
|
||||
{{fullname}}
|
||||
</span>
|
||||
<span class="d-flex w-100 pull-left text-truncate small" aria-hidden="true">
|
||||
<span class="d-block w-100 pull-left text-truncate small" aria-hidden="true">
|
||||
{{{matchingField}}}
|
||||
</span>
|
||||
<span class="sr-only" aria-label="{{#str}}usermatchedon, core_grades{{/str}}">{{matchingFieldName}}</span>
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
"link": "http://foo.bar/grade/report/grader/index.php?id=42&userid=3"
|
||||
}
|
||||
],
|
||||
"found": 2,
|
||||
"total": 20,
|
||||
"matches": 20,
|
||||
"selectall": "https://foo.bar/grade/report/grader/index.php?id=2&searchvalue=abe",
|
||||
"searchterm": "Foo",
|
||||
"hasusers": true
|
||||
@@ -53,17 +52,16 @@
|
||||
}}
|
||||
<div class="d-flex flex-column mh-100 h-100">
|
||||
{{#hasusers}}
|
||||
<span class="small d-block px-4 my-2">{{#str}}showingxofy, gradereport_grader, {"found": "{{found}}", "total": "{{total}}"}{{/str}}</span>
|
||||
<ul class="searchresultitemscontainer d-flex flex-column mw-100 position-relative py-2 list-group h-100 mh-100 mx-0" role="listbox" data-region="search-result-items-container" tabindex="-1">
|
||||
{{#users}}
|
||||
{{>gradereport_grader/search/resultitem}}
|
||||
{{/users}}
|
||||
<li class="w-100 result-row p-1 border-top bottom-0 position-sticky" role="none" id="result-row-{{id}}">
|
||||
<a role="option" class="dropdown-item d-flex small p-3" id="select-all" href="{{{selectall}}}" tabindex="-1">
|
||||
{{#str}}viewallresults, gradereport_grader, {{matches}}{{/str}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="unsearchablecontentcontainer p-1">
|
||||
<a role="button" id="select-all" href="{{{selectall}}}" class="small d-block p-4" tabindex="-1">
|
||||
{{#str}}viewallresults, gradereport_grader, {{searchterm}}{{/str}}
|
||||
</a>
|
||||
</div>
|
||||
{{/hasusers}}
|
||||
{{^hasusers}}
|
||||
<span class="small d-block px-4 my-4">{{#str}} noresultsfor, core_grades, {{searchterm}}{{/str}}</span>
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
Example context (json):
|
||||
{
|
||||
"currentvalue": "bar",
|
||||
"courseid": 2
|
||||
"courseid": 2,
|
||||
"resetlink": "grade/report/grader/index.php?id=2"
|
||||
}
|
||||
}}
|
||||
<span class="d-none" data-region="courseid" data-courseid="{{courseid}}" aria-hidden="true"></span>
|
||||
@@ -34,6 +35,9 @@
|
||||
{{/str}}{{/label}}
|
||||
{{$value}}{{{currentvalue}}}{{/value}}
|
||||
{{/ core/search_input_auto }}
|
||||
<a class="ml-2 btn btn-link border-0" href="{{resetlink}}" data-action="resetpage" role="link" aria-label="{{#str}}clearsearch, gradereport_grader{{/str}}">
|
||||
{{#str}}clear{{/str}}
|
||||
</a>
|
||||
{{/currentvalue}}
|
||||
{{^currentvalue}}
|
||||
{{< core/search_input_auto }}
|
||||
|
||||
@@ -49,7 +49,7 @@ Feature: Within the grader report, test that we can search for users
|
||||
| -1- |
|
||||
| Teacher 1 |
|
||||
When I set the field "Search users" to "Turtle"
|
||||
And I wait until "View all results for \"Turtle\"" "button" exists
|
||||
And I wait until "View all results (1)" "option_role" exists
|
||||
And "Turtle Manatee" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "User Example" "list_item" should not exist in the ".user-search" "css_element"
|
||||
And I click on "Turtle Manatee" "list_item"
|
||||
@@ -66,14 +66,14 @@ Feature: Within the grader report, test that we can search for users
|
||||
| User Test |
|
||||
| Dummy User |
|
||||
And I set the field "Search users" to "Turt"
|
||||
And I wait until "View all results for \"Turt\"" "button" exists
|
||||
And I wait until "View all results (1)" "option_role" exists
|
||||
And I click on "Clear search input" "button" in the ".user-search" "css_element"
|
||||
And "View all results for \"Turt\"" "button" should not be visible
|
||||
And "View all results (1)" "option_role" should not be visible
|
||||
|
||||
Scenario: A teacher can search the grader report to find specified users
|
||||
# Case: Standard search.
|
||||
Given I set the field "Search users" to "Dummy"
|
||||
And I wait until "View all results for \"Dummy\"" "button" exists
|
||||
And I wait until "View all results (1)" "option_role" exists
|
||||
And I click on "Dummy User" "option_role"
|
||||
And I wait until the page is ready
|
||||
And the following should exist in the "user-grades" table:
|
||||
@@ -104,11 +104,15 @@ Feature: Within the grader report, test that we can search for users
|
||||
|
||||
# Case: Multiple users found and select only one result.
|
||||
Then I set the field "Search users" to "User"
|
||||
And I wait until "View all results for \"User\"" "button" exists
|
||||
And I wait until "View all results (3)" "option_role" exists
|
||||
And "Dummy User" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "User Example" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "User Test" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "Turtle Manatee" "list_item" should not exist in the ".user-search" "css_element"
|
||||
# Check if the matched field names (by lines) includes some identifiable info to help differentiate similar users.
|
||||
And "User (student2@example.com)" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "User (student3@example.com)" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "User (student4@example.com)" "list_item" should exist in the ".user-search" "css_element"
|
||||
And I click on "Dummy User" "list_item"
|
||||
And I wait until the page is ready
|
||||
And the following should exist in the "user-grades" table:
|
||||
@@ -125,12 +129,11 @@ Feature: Within the grader report, test that we can search for users
|
||||
# Business case: When searching with multiple partial matches, show the matches in the dropdown + a "View all results for (Bob)"
|
||||
# Business case cont. When pressing enter with multiple partial matches, behave like when you select the "View all results for (Bob)"
|
||||
# Case: Multiple users found and select all partial matches.
|
||||
# TODO: Need to look at maybe adding the users email or something in the case multiple matches exist?
|
||||
And I set the field "Search users" to "User"
|
||||
And I wait until "View all results for \"User\"" "button" exists
|
||||
And I wait until "View all results (3)" "option_role" exists
|
||||
# Dont need to check if all users are in the dropdown, we checked that earlier in this test.
|
||||
And "View all results for \"User\"" "button" should exist
|
||||
And I click on "View all results for \"User\"" "button"
|
||||
And "View all results (3)" "option_role" should exist
|
||||
And I click on "View all results (3)" "option_role"
|
||||
And I wait until the page is ready
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- |
|
||||
@@ -142,15 +145,24 @@ Feature: Within the grader report, test that we can search for users
|
||||
| Teacher 1 |
|
||||
| Student 1 |
|
||||
| Turtle Manatee |
|
||||
And I click on "Clear" "link" in the ".user-search" "css_element"
|
||||
And I wait until the page is ready
|
||||
And the following should exist in the "user-grades" table:
|
||||
| -1- |
|
||||
| Turtle Manatee |
|
||||
| Student 1 |
|
||||
| User Example |
|
||||
| User Test |
|
||||
| Dummy User |
|
||||
|
||||
Scenario: A teacher can quickly tell that a search is active on the current table
|
||||
Given I set the field "Search users" to "Turtle"
|
||||
And I wait until "View all results for \"Turtle\"" "button" exists
|
||||
And I wait until "View all results (1)" "option_role" exists
|
||||
And I click on "Turtle Manatee" "list_item"
|
||||
And I wait until the page is ready
|
||||
# The search input remains in the field on reload this is in keeping with other search implementations.
|
||||
When the field "Search users" matches value "Turtle"
|
||||
And "View all results for \"Turtle\"" "link" should not exist
|
||||
And "View all results (1)" "link" should not exist
|
||||
# Test if we can then further retain the turtle result set and further filter from there.
|
||||
Then I set the field "Search users" to "Turtle plagiarism"
|
||||
And "Turtle Manatee" "list_item" should not exist
|
||||
@@ -158,10 +170,10 @@ Feature: Within the grader report, test that we can search for users
|
||||
|
||||
Scenario: A teacher can search for values besides the users' name
|
||||
Given I set the field "Search users" to "student5@example.com"
|
||||
And I wait until "View all results for \"student5@example.com\"" "button" exists
|
||||
And I wait until "View all results (1)" "option_role" exists
|
||||
And "Turtle Manatee" "list_item" should exist
|
||||
And I set the field "Search users" to "@example.com"
|
||||
And I wait until "View all results for \"@example.com\"" "button" exists
|
||||
And I wait until "View all results (5)" "option_role" exists
|
||||
# Note: All learners match this email & showing emails is current default.
|
||||
And "Dummy User" "list_item" should exist in the ".user-search" "css_element"
|
||||
And "User Example" "list_item" should exist in the ".user-search" "css_element"
|
||||
@@ -238,11 +250,11 @@ Feature: Within the grader report, test that we can search for users
|
||||
And I press the down key
|
||||
And the focused element is "Student 1" "option_role"
|
||||
And I press the end key
|
||||
And the focused element is "Turtle Manatee" "option_role"
|
||||
And the focused element is "View all results (5)" "option_role"
|
||||
And I press the home key
|
||||
And the focused element is "Student 1" "option_role"
|
||||
And I press the up key
|
||||
And the focused element is "Turtle Manatee" "option_role"
|
||||
And the focused element is "View all results (5)" "option_role"
|
||||
And I press the down key
|
||||
And the focused element is "Student 1" "option_role"
|
||||
And I press the escape key
|
||||
@@ -250,14 +262,20 @@ Feature: Within the grader report, test that we can search for users
|
||||
Then I set the field "Search users" to "Goodmeme"
|
||||
And I press the down key
|
||||
And the focused element is "Search users" "field"
|
||||
|
||||
And I navigate to "View > Grader report" in the course gradebook
|
||||
And I set the field "Search users" to "ABC"
|
||||
And I wait until "Turtle Manatee" "option_role" exists
|
||||
And I press the down key
|
||||
And the focused element is "Student 1" "option_role"
|
||||
|
||||
# Lets check the tabbing order.
|
||||
And I set the field "Search users" to "ABC"
|
||||
And I wait "3" seconds
|
||||
And I wait until "View all results for \"ABC\"" "button" exists
|
||||
And I wait until "View all results (5)" "option_role" exists
|
||||
And I press the tab key
|
||||
And the focused element is "Clear search input" "button"
|
||||
And I press the tab key
|
||||
And the focused element is "View all results for \"ABC\"" "button"
|
||||
And the focused element is "View all results (5)" "option_role"
|
||||
And I press the tab key
|
||||
And the focused element is ".search-widget[data-searchtype='group'] [data-toggle='dropdown']" "css_element"
|
||||
# Ensure we can interact with the input & clear search options with the keyboard.
|
||||
@@ -307,3 +325,20 @@ Feature: Within the grader report, test that we can search for users
|
||||
# Begin the search checking if we are adhering the filters.
|
||||
When I set the field "Search users" to "Turtle"
|
||||
Then "Turtle Manatee" "list_item" should not exist in the ".user-search" "css_element"
|
||||
|
||||
Scenario: As a teacher I can dynamically find users whilst ignoring pagination
|
||||
Given "42" "users" exist with the following data:
|
||||
| username | students[count] |
|
||||
| firstname | Student |
|
||||
| lastname | s[count] |
|
||||
| email | students[count]@example.com |
|
||||
And "42" "course enrolments" exist with the following data:
|
||||
| user | students[count] |
|
||||
| course | C1 |
|
||||
| role |student |
|
||||
And I reload the page
|
||||
And the field "perpage" matches value "20"
|
||||
When I set the field "Search users" to "42"
|
||||
# One of the users' phone numbers also matches.
|
||||
And I wait until "View all results (2)" "link" exists
|
||||
Then "Student s42" "list_item" should exist in the ".user-search" "css_element"
|
||||
|
||||
@@ -408,7 +408,8 @@ class grade extends tablelike implements selectable_items, filterable_items {
|
||||
$menuitems[] = new \action_menu_link_secondary($url, null, $title);
|
||||
$menu = new \action_menu($menuitems);
|
||||
$icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
|
||||
$menu->set_menu_trigger($icon);
|
||||
$extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
|
||||
$menu->set_menu_trigger($icon, $extraclasses);
|
||||
$menu->set_menu_left();
|
||||
|
||||
return $OUTPUT->render($menu);
|
||||
|
||||
@@ -261,7 +261,8 @@ class user extends tablelike implements selectable_items {
|
||||
$menuitems[] = new \action_menu_link_secondary($url, null, $title);
|
||||
$menu = new \action_menu($menuitems);
|
||||
$icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
|
||||
$menu->set_menu_trigger($icon);
|
||||
$extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
|
||||
$menu->set_menu_trigger($icon, $extraclasses);
|
||||
$menu->set_menu_left();
|
||||
|
||||
return $OUTPUT->render($menu);
|
||||
|
||||
@@ -121,7 +121,7 @@ if ($itemtype == 'user') {
|
||||
true, null, null, $report->screen->item, $actionbar);
|
||||
} else {
|
||||
print_grade_page_head($course->id, 'report', 'singleview', $reportname, false, $button,
|
||||
true, null, null, null, $actionbar, false);
|
||||
true, null, null, null, $actionbar);
|
||||
}
|
||||
|
||||
if ($data = data_submitted()) {
|
||||
|
||||
@@ -73,36 +73,35 @@
|
||||
<div class="container-fluid tertiary-navigation full-width-bottom-border">
|
||||
<div class="row">
|
||||
{{#generalnavselector}}
|
||||
<div class="navitem">
|
||||
<div class="navitem order-1">
|
||||
{{>core/tertiary_navigation_selector}}
|
||||
</div>
|
||||
<div class="navitem-divider"></div>
|
||||
<div class="navitem-divider d-none d-sm-flex order-1"></div>
|
||||
{{/generalnavselector}}
|
||||
{{#groupselector}}
|
||||
<div class="navitem">
|
||||
<div class="navitem order-2">
|
||||
{{{.}}}
|
||||
</div>
|
||||
<div class="navitem-divider"></div>
|
||||
<div class="navitem-divider d-none d-sm-flex order-2"></div>
|
||||
{{/groupselector}}
|
||||
{{#itemselector}}
|
||||
<div class="navitem">
|
||||
<div class="navitem order-3">
|
||||
{{{.}}}
|
||||
</div>
|
||||
<div class="navitem-divider"></div>
|
||||
<div class="navitem-divider d-none d-sm-flex order-3"></div>
|
||||
{{/itemselector}}
|
||||
{{#pagetoggler}}
|
||||
<div class="d-flex ml-auto">
|
||||
<div class="d-flex row ml-0 ml-sm-auto order-4 px-0">
|
||||
{{#bulkactions}}
|
||||
<div class="navitem-divider"></div>
|
||||
<div class="navitem">{{{bulkactions}}}</div>
|
||||
{{#js}}
|
||||
require(['gradereport_singleview/bulkactions'], function(bulkactions) {
|
||||
bulkactions.init();
|
||||
});
|
||||
{{/js}}
|
||||
<div class="d-flex navitem ml-0 ml-sm-auto mr-3">{{{bulkactions}}}</div>
|
||||
{{#js}}
|
||||
require(['gradereport_singleview/bulkactions'], function(bulkactions) {
|
||||
bulkactions.init();
|
||||
});
|
||||
{{/js}}
|
||||
<div class="navitem-divider d-none d-sm-flex"></div>
|
||||
{{/bulkactions}}
|
||||
<div class="navitem-divider"></div>
|
||||
<div class="navitem">
|
||||
<div class="d-flex navitem ml-0 mr-auto mr-sm-0 ml-sm-auto">
|
||||
{{>gradereport_singleview/page_toggler}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
>
|
||||
<div class="align-items-center d-flex">
|
||||
{{#selectedoption}}
|
||||
<div class="d-block pr-3">
|
||||
<div class="d-block pr-3 text-truncate">
|
||||
<span class="d-block small">
|
||||
{{#str}} selectagrade, gradereport_singleview {{/str}}
|
||||
</span>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"gradeselectactive": "true"
|
||||
}
|
||||
}}
|
||||
<div class="page-toggler d-flex">
|
||||
<div class="page-toggler d-block d-sm-flex text-nowrap">
|
||||
{{#displaylabel}}
|
||||
<p class="my-auto mr-3 text-uppercase">{{#str}}viewby, gradereport_singleview{{/str}}</p>
|
||||
{{/displaylabel}}
|
||||
|
||||
@@ -840,15 +840,17 @@ class user extends grade_report {
|
||||
}
|
||||
$this->gradeitemsdata[] = $gradeitemdata;
|
||||
}
|
||||
|
||||
$parent = $gradeobject->load_parent_category();
|
||||
if ($gradeobject->is_category_item()) {
|
||||
$parent = $parent->load_parent_category();
|
||||
}
|
||||
|
||||
// We collect the aggregation hints whether they are hidden or not.
|
||||
if ($this->showcontributiontocoursetotal) {
|
||||
$hint['grademax'] = $gradegrade->grade_item->grademax;
|
||||
$hint['grademin'] = $gradegrade->grade_item->grademin;
|
||||
$hint['grade'] = $gradeval;
|
||||
$parent = $gradeobject->load_parent_category();
|
||||
if ($gradeobject->is_category_item()) {
|
||||
$parent = $parent->load_parent_category();
|
||||
}
|
||||
$hint['parent'] = $parent->load_grade_item()->id;
|
||||
$this->aggregationhints[$gradegrade->itemid] = $hint;
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ class gradereport_user_renderer extends plugin_renderer_base {
|
||||
|
||||
$navigationdata = [];
|
||||
|
||||
$users = [];
|
||||
while ($userdata = $gui->next_user()) {
|
||||
$users[$userdata->user->id] = $userdata->user;
|
||||
}
|
||||
@@ -153,6 +154,11 @@ class gradereport_user_renderer extends plugin_renderer_base {
|
||||
$arraykeys = array_keys($users);
|
||||
$keynumber = array_search($userid, $arraykeys);
|
||||
|
||||
// Without a valid user or users list, there's nothing to render.
|
||||
if ($keynumber === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Determine directionality so that icons can be modified to suit language.
|
||||
$previousarrow = right_to_left() ? 'right' : 'left';
|
||||
$nextarrow = right_to_left() ? 'left' : 'right';
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<span class="userinitials"></span>
|
||||
{{/image}}
|
||||
</div>
|
||||
<div class="selected-option-info d-block pr-3">
|
||||
<div class="selected-option-info d-block pr-3 text-truncate">
|
||||
<span class="selected-option-text p-0 font-weight-bold">
|
||||
{{text}}
|
||||
</span>
|
||||
|
||||
@@ -178,7 +178,7 @@ class group extends base {
|
||||
// Visibility column.
|
||||
$columns[] = (new column(
|
||||
'visibility',
|
||||
new lang_string('visibility', 'core_group'),
|
||||
new lang_string('visibilityshort', 'core_group'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
@@ -205,7 +205,7 @@ class group extends base {
|
||||
// Participation column.
|
||||
$columns[] = (new column(
|
||||
'participation',
|
||||
new lang_string('participation', 'core_group'),
|
||||
new lang_string('participationshort', 'core_group'),
|
||||
$this->get_entity_name()
|
||||
))
|
||||
->add_joins($this->get_joins())
|
||||
@@ -296,7 +296,7 @@ class group extends base {
|
||||
$filters[] = (new filter(
|
||||
select::class,
|
||||
'visibility',
|
||||
new lang_string('visibility', 'core_group'),
|
||||
new lang_string('visibilityshort', 'core_group'),
|
||||
$this->get_entity_name(),
|
||||
"{$groupsalias}.visibility"
|
||||
))
|
||||
@@ -312,7 +312,7 @@ class group extends base {
|
||||
$filters[] = (new filter(
|
||||
boolean_select::class,
|
||||
'participation',
|
||||
new lang_string('participation', 'core_group'),
|
||||
new lang_string('participationshort', 'core_group'),
|
||||
$this->get_entity_name(),
|
||||
"{$groupsalias}.participation"
|
||||
))
|
||||
|
||||
@@ -15,13 +15,13 @@ Feature: Backup and restore a course containing groups
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber | visibility | participation |
|
||||
| Visible to all/Participation | C1 | VP | 0 | 1 |
|
||||
| Visible to members/Participation | C1 | MP | 1 | 1 |
|
||||
| See own membership | C1 | O | 2 | 0 |
|
||||
| Not visible | C1 | N | 3 | 0 |
|
||||
| Visible to all/Non-Participation | C1 | VN | 0 | 0 |
|
||||
| Visible to members/Non-Participation | C1 | MN | 1 | 0 |
|
||||
| name | course | idnumber | visibility | participation |
|
||||
| Visible to everyone/Participation | C1 | VP | 0 | 1 |
|
||||
| Only visible to members/Participation | C1 | MP | 1 | 1 |
|
||||
| Only see own membership | C1 | O | 2 | 0 |
|
||||
| Not visible | C1 | N | 3 | 0 |
|
||||
| Visible to everyone/Non-Participation | C1 | VN | 0 | 0 |
|
||||
| Only visible to members/Non-Participation | C1 | MN | 1 | 0 |
|
||||
And I log in as "admin"
|
||||
And I backup "Course 1" course using this options:
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
@@ -35,14 +35,14 @@ Feature: Backup and restore a course containing groups
|
||||
And I press "Edit group settings"
|
||||
Then the following fields match these values:
|
||||
| Group ID number | <idnumber> |
|
||||
| Group visibility | <visibility> |
|
||||
| Allow activity participation | <participation> |
|
||||
| Group membership visibility | <visibility> |
|
||||
| Show group in dropdown menu for activities in group mode | <participation> |
|
||||
|
||||
Examples:
|
||||
| group | idnumber | visibility | participation |
|
||||
| Visible to all/Participation | VP | 0 | 1 |
|
||||
| Visible to members/Participation | MP | 1 | 1 |
|
||||
| See own membership | O | 2 | 0 |
|
||||
| Not visible | N | 3 | 0 |
|
||||
| Visible to all/Non-Participation | VN | 0 | 0 |
|
||||
| Visible to members/Non-Participation | MN | 1 | 0 |
|
||||
| group | idnumber | visibility | participation |
|
||||
| Visible to everyone/Participation | VP | 0 | 1 |
|
||||
| Only visible to members/Participation | MP | 1 | 1 |
|
||||
| Only see own membership | O | 2 | 0 |
|
||||
| Not visible | N | 3 | 0 |
|
||||
| Visible to everyone/Non-Participation | VN | 0 | 0 |
|
||||
| Only visible to members/Non-Participation | MN | 1 | 0 |
|
||||
|
||||
@@ -31,13 +31,13 @@ Feature: Private groups
|
||||
| student7 | C1 | student |
|
||||
| student8 | C1 | student |
|
||||
And the following "groups" exist:
|
||||
| name | course | idnumber | visibility | participation |
|
||||
| Visible to all/Participation | C1 | VP | 0 | 1 |
|
||||
| Visible to members/Participation | C1 | MP | 1 | 1 |
|
||||
| See own membership | C1 | O | 2 | 0 |
|
||||
| Not visible | C1 | N | 3 | 0 |
|
||||
| Visible to all/Non-Participation | C1 | VN | 0 | 0 |
|
||||
| Visible to members/Non-Participation | C1 | MN | 1 | 0 |
|
||||
| name | course | idnumber | visibility | participation |
|
||||
| Visible to everyone/Participation | C1 | VP | 0 | 1 |
|
||||
| Only visible to members/Participation | C1 | MP | 1 | 1 |
|
||||
| Only see own membership | C1 | O | 2 | 0 |
|
||||
| Not visible | C1 | N | 3 | 0 |
|
||||
| Visible to everyone/Non-Participation | C1 | VN | 0 | 0 |
|
||||
| Only visible to members/Non-Participation | C1 | MN | 1 | 0 |
|
||||
And the following "group members" exist:
|
||||
| user | group |
|
||||
| student1 | VP |
|
||||
@@ -53,62 +53,62 @@ Feature: Private groups
|
||||
| student7 | O |
|
||||
| student8 | N |
|
||||
|
||||
Scenario: Participants in "Visible to all" groups see their membership and other members:
|
||||
Scenario: Participants in "Visible to everyone" groups see their membership and other members:
|
||||
Given I log in as "student1"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Participants"
|
||||
Then the following should exist in the "participants" table:
|
||||
| First name / Surname | Groups |
|
||||
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 2 | No groups |
|
||||
| Student 3 | No groups |
|
||||
| Student 4 | No groups |
|
||||
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 6 | No groups |
|
||||
| Student 7 | No groups |
|
||||
| Student 8 | No groups |
|
||||
|
||||
Scenario: Participants in "Visible to members" groups see their membership and other members, plus "Visible to all"
|
||||
Scenario: Participants in "Only visible to members" groups see their membership and other members, plus "Visible to everyone"
|
||||
Given I log in as "student2"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Participants"
|
||||
Then the following should exist in the "participants" table:
|
||||
| First name / Surname | Groups |
|
||||
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 2 | Visible to members/Non-Participation, Visible to members/Participation |
|
||||
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 2 | Only visible to members/Non-Participation, Only visible to members/Participation |
|
||||
| Student 3 | No groups |
|
||||
| Student 4 | No groups |
|
||||
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 6 | Visible to members/Non-Participation, Visible to members/Participation |
|
||||
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 6 | Only visible to members/Non-Participation, Only visible to members/Participation |
|
||||
| Student 7 | No groups |
|
||||
| Student 8 | No groups |
|
||||
|
||||
Scenario: Participants in "See own membership" groups see their membership but not other members, plus "Visible to all"
|
||||
Scenario: Participants in "Only see own membership" groups see their membership but not other members, plus "Visible to everyone"
|
||||
Given I log in as "student3"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Participants"
|
||||
Then the following should exist in the "participants" table:
|
||||
| First name / Surname | Groups |
|
||||
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 2 | No groups |
|
||||
| Student 3 | See own membership |
|
||||
| Student 3 | Only see own membership |
|
||||
| Student 4 | No groups |
|
||||
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 6 | No groups |
|
||||
| Student 7 | No groups |
|
||||
| Student 8 | No groups |
|
||||
|
||||
Scenario: Participants in "Not visible" groups do not see that group, do see "Visible to all"
|
||||
Scenario: Participants in "Not visible" groups do not see that group, do see "Visible to everyone"
|
||||
Given I log in as "student4"
|
||||
And I am on "Course 1" course homepage
|
||||
When I follow "Participants"
|
||||
Then the following should exist in the "participants" table:
|
||||
| First name / Surname | Groups |
|
||||
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 2 | No groups |
|
||||
| Student 3 | No groups |
|
||||
| Student 4 | No groups |
|
||||
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 6 | No groups |
|
||||
| Student 7 | No groups |
|
||||
| Student 8 | No groups |
|
||||
@@ -119,11 +119,11 @@ Feature: Private groups
|
||||
When I follow "Participants"
|
||||
Then the following should exist in the "participants" table:
|
||||
| First name / Surname | Groups |
|
||||
| Student 1 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 2 | Visible to members/Non-Participation, Visible to members/Participation |
|
||||
| Student 3 | See own membership |
|
||||
| Student 1 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 2 | Only visible to members/Non-Participation, Only visible to members/Participation |
|
||||
| Student 3 | Only see own membership |
|
||||
| Student 4 | Not visible |
|
||||
| Student 5 | Visible to all/Non-Participation, Visible to all/Participation |
|
||||
| Student 6 | Visible to members/Non-Participation, Visible to members/Participation |
|
||||
| Student 7 | See own membership |
|
||||
| Student 5 | Visible to everyone/Non-Participation, Visible to everyone/Participation |
|
||||
| Student 6 | Only visible to members/Non-Participation, Only visible to members/Participation |
|
||||
| Student 7 | Only see own membership |
|
||||
| Student 8 | Not visible |
|
||||
|
||||
@@ -148,14 +148,14 @@ Feature: Automatic updating of groups and groupings
|
||||
Given I set the field "groups" to "Group (with ID)"
|
||||
And I press "Edit group settings"
|
||||
And "visibility" "select" should exist
|
||||
And the field "Group visibility" matches value "Visible to all"
|
||||
And the field "Group membership visibility" matches value "Visible to everyone"
|
||||
And the "participation" "checkbox" should be enabled
|
||||
And the field "Allow activity participation" matches value "1"
|
||||
And the field "Show group in dropdown menu for activities in group mode" matches value "1"
|
||||
When the following "group members" exist:
|
||||
| user | group |
|
||||
| teacher1 | An ID |
|
||||
And I reload the page
|
||||
Then "visibility" "select" should not exist
|
||||
And "Visible to all" "text" should exist
|
||||
And "Visible to everyone" "text" should exist
|
||||
And the "participation" "checkbox" should be disabled
|
||||
And the field "Allow activity participation" matches value "1"
|
||||
And the field "Show group in dropdown menu for activities in group mode" matches value "1"
|
||||
|
||||
@@ -180,7 +180,7 @@ class groups_test extends core_reportbuilder_testcase {
|
||||
$this->assertEquals('G101', $groupidnumber);
|
||||
$this->assertEquals(format_text($group->description), $groupdescription);
|
||||
$this->assertEquals('S', $groupenrolmentkey);
|
||||
$this->assertEquals('Visible to all', $groupvisibility);
|
||||
$this->assertEquals('Visible to everyone', $groupvisibility);
|
||||
$this->assertEquals('Yes', $groupparticipation);
|
||||
$this->assertEmpty($grouppicture);
|
||||
$this->assertNotEmpty($grouptimecreated);
|
||||
@@ -231,7 +231,7 @@ class groups_test extends core_reportbuilder_testcase {
|
||||
], false],
|
||||
'Filter group visibility' => ['group:visibility', [
|
||||
'group:visibility_operator' => select::EQUAL_TO,
|
||||
'group:visibility_value' => 0, // Visible to all.
|
||||
'group:visibility_value' => 0, // Visible to everyone.
|
||||
], true],
|
||||
'Filter group visibility (no match)' => ['group:visibility', [
|
||||
'group:visibility_operator' => select::EQUAL_TO,
|
||||
|
||||
+10
-10
@@ -14,30 +14,30 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Testing the H5P helper.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @category test
|
||||
* @copyright 2019 Sara Arjona <sara@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace core_h5p;
|
||||
|
||||
use advanced_testcase;
|
||||
use core_h5p\local\library\autoloader;
|
||||
|
||||
/**
|
||||
* Test class covering the H5P helper.
|
||||
*
|
||||
* @package core_h5p
|
||||
* @category test
|
||||
* @copyright 2019 Sara Arjona <sara@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @covers \core_h5p\helper
|
||||
*/
|
||||
class helper_test extends \advanced_testcase {
|
||||
|
||||
/**
|
||||
* Register the H5P autoloader
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
autoloader::register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the behaviour of get_display_options().
|
||||
*
|
||||
|
||||
@@ -73,7 +73,7 @@ $string['pathssubdataroot'] = '<p>Um diretório em que Moodle armazenará todo o
|
||||
<p>Este diretório deve ser legível e gravável pelo usuário do servidor web (geralmente "www-data \',\' nobody \', ou\' apache\'). </p>
|
||||
<p>Não deve ser diretamente acessível através da web. </p>
|
||||
<p>Se o diretório não existir atualmente, o processo de instalação tentará criá-lo. </p>';
|
||||
$string['pathssubdirroot'] = '<p>Caminho completo do diretório para instalação do Moddle.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Caminho completo do diretório para instalação do Moodle.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>O endereço completo em que Moodle pode ser acessado ou seja, o endereço que os usuários vão digitar na barra de endereços do seu navegador para acessar Moodle. </p>
|
||||
<p>Não é possível acessar Moodle usando múltiplos endereços. Se o seu site é acessível através de múltiplos endereços, em seguida, escolher o mais fácil e configurar um redirecionamento permanente para cada um dos outros endereços. </p>
|
||||
<p>Se o seu site é acessível tanto a partir da Internet e, a partir de uma rede interna (às vezes chamado de Intranet), em seguida, use o endereço público aqui. </p>
|
||||
|
||||
+17
-15
@@ -95,13 +95,13 @@ $string['blockediplist'] = 'Blocked IP List';
|
||||
$string['blockinstances'] = 'Instances';
|
||||
$string['blockmultiple'] = 'Multiple';
|
||||
$string['blockprotect'] = 'Protect instances';
|
||||
$string['blockprotectblock'] = 'Protect instances of the {$a} block';
|
||||
$string['blockprotected'] = 'The {$a} block is now protected';
|
||||
$string['blockprotectblock'] = 'Protect instances of {$a}';
|
||||
$string['blockprotected'] = '{$a} block instances are protected.';
|
||||
$string['blockprotect_help'] = 'If you lock a particular type of block, then no-one will be able to add or delete instances. (You can, of course, unlock again if you need to edit instances.)
|
||||
|
||||
This is intended to protect blocks like the navigation and settings which are very hard to get back if accidentally deleted.';
|
||||
$string['blockunprotectblock'] = 'Unprotect instances of the {$a} block';
|
||||
$string['blockunprotected'] = 'The {$a} block is no longer protected';
|
||||
$string['blockunprotectblock'] = 'Unprotect instances of {$a}';
|
||||
$string['blockunprotected'] = '{$a} block instances are unprotected.';
|
||||
$string['blocksettings'] = 'Manage blocks';
|
||||
$string['bloglevel'] = 'Blog visibility';
|
||||
$string['bookmarkadded'] = 'Bookmark added.';
|
||||
@@ -145,7 +145,7 @@ $string['cliupgradecompletenomaintenanceupgrade'] = 'To purge remaining caches a
|
||||
php admin/cli/purge_caches.php --filter
|
||||
php admin/cli/purge_caches.php --other
|
||||
|
||||
It is recommended to perform these purges in isolation, with a gap between commands, to reduce load spikes on the web server.';
|
||||
You should perform these purges in isolation, with a gap between commands, to reduce load spikes on the web server.';
|
||||
$string['cliupgradedefault'] = 'New setting: {$a}';
|
||||
$string['cliupgradedefaultheading'] = 'Setting new default values';
|
||||
$string['cliupgradedefaultverbose'] = 'New setting: {$a->name}, Default value: {$a->defaultsetting}';
|
||||
@@ -366,7 +366,7 @@ $string['configsectionsecurity'] = 'Security';
|
||||
$string['configsectionstats'] = 'Statistics';
|
||||
$string['configsectionuser'] = 'User';
|
||||
$string['configsecureforms'] = 'Moodle can use an additional level of security when accepting data from web forms. If this is enabled, then the browser\'s HTTP_REFERER variable is checked against the current form address. In a very few cases this can cause problems if the user is using a firewall (eg Zonealarm) configured to strip HTTP_REFERER from their web traffic. Symptoms are getting \'stuck\' on a form. If your users are having problems with the login page (for example) you might want to disable this setting, although it might leave your site more open to brute-force password attacks. If in doubt, leave this set to \'Yes\'.';
|
||||
$string['configservicespage'] = 'Users will be sent to this URL when clicking on \'Services and support\'. If the field is left blank the user will be redirect to the \'Find your Moodle partner\' official page.';
|
||||
$string['configservicespage'] = 'Enter the URL of a services and support page or leave empty to link to Moodle services on moodle.com.';
|
||||
$string['configsessioncookie'] = 'This setting customises the name of the cookie used for Moodle sessions. This is optional, and only useful to avoid cookies being confused when there is more than one copy of Moodle running within the same web site.';
|
||||
$string['configsessioncookiedomain'] = 'This allows you to change the domain that the Moodle cookies are available from. This is useful for Moodle customisations (e.g. authentication or enrolment plugins) that need to share Moodle session information with a web application on another subdomain. <strong>WARNING: it is strongly recommended to leave this setting at the default (empty) - an incorrect value will prevent all logins to the site.</strong>';
|
||||
$string['configsessioncookiepath'] = 'If you need to change where browsers send the Moodle cookies, you can change this setting to specify a subdirectory of your web site. Otherwise the default \'/\' should be fine.';
|
||||
@@ -402,7 +402,7 @@ $string['configstripalltitletags'] = 'Uncheck this setting to allow HTML tags in
|
||||
$string['configsupportavailability'] = 'Determines who has access to contact site support from the footer.';
|
||||
$string['configsupportemail'] = 'If SMTP is configured on this site and a support page is not set, this email address will receive messages submitted through the support form. If sending fails, the email address will be displayed to logged-in users.';
|
||||
$string['configsupportname'] = 'The name of the person or other entity providing support via the support form or support page.';
|
||||
$string['configsupportpage'] = 'A link to this page will be provided for users to contact the site support. If the field is left blank then a link to a support form will be provided instead.';
|
||||
$string['configsupportpage'] = 'Enter the URL of a support page or leave empty to link to a contact form.';
|
||||
$string['configtempdatafoldercleanup'] = 'Remove temporary data files from the data folder that are older than the selected time.';
|
||||
$string['configthemedesignermode'] = 'Normally all theme images and style sheets are cached in browsers and on the server for a very long time, for performance. If you are designing themes or developing code then you probably want to turn this mode on so that you are not served cached versions. Warning: this will make your site slower for all users! Alternatively, you can also reset the theme caches manually from the Theme selection page.';
|
||||
$string['configthemelist'] = 'Leave this blank to allow any valid theme to be used. If you want to shorten the theme menu, you can specify a comma-separated list of names here (Don\'t use spaces!).
|
||||
@@ -458,7 +458,7 @@ $string['cron_enabled'] = 'Enable cron';
|
||||
$string['cron_enabled_desc'] = 'Cron should normally be enabled, however this setting allows it to be disabled temporarily, for example before a server restart. If disabled, the system is prevented from starting new background tasks. Note that the cron should not be disabled for a long time, as this will prevent important functionality from working.';
|
||||
$string['cron_help'] = 'The cron.php script runs a number of tasks at different scheduled intervals, such as sending forum post notification emails. The script should be run regularly - ideally every minute.';
|
||||
$string['cron_keepalive'] = 'Keep alive';
|
||||
$string['cron_keepalive_desc'] = 'The amount of time to keep polling for additional tasks. This setting is useful for ensuring that cron is always running.<br><br>If you use dedicated task runners then you should set this value to an 0, otherwise we recommend setting this to around a value similar to your adhoc task concurrency limit. Longer values should be avoided and the maximum value is 15 minutes.';
|
||||
$string['cron_keepalive_desc'] = 'The length of time to keep polling for additional tasks. This setting is for ensuring that cron is always running. If you use dedicated task runners, set it to 0. Otherwise, set it to a value similar to the adhoc task concurrency limit. Avoid longer times. The maximum time is 15 minutes.';
|
||||
$string['cron_link'] = 'admin/cron';
|
||||
$string['cronclionly'] = 'Cron execution via command line only';
|
||||
$string['cronerrorclionly'] = 'Sorry, internet access to this page has been disabled by the administrator.';
|
||||
@@ -528,7 +528,7 @@ $string['devicedetectregexexpression'] = 'Regular expression';
|
||||
$string['devicedetectregexvalue'] = 'Return value';
|
||||
$string['devicetype'] = 'Device type';
|
||||
$string['disabled'] = 'Disabled';
|
||||
$string['disableplugin'] = 'Disable the {$a} plugin';
|
||||
$string['disableplugin'] = 'Disable {$a}';
|
||||
$string['disableuserimages'] = 'Disable user profile images';
|
||||
$string['displayerrorswarning'] = 'Enabling the PHP setting <em>display_errors</em> is not recommended on production sites because some error messages may reveal sensitive information about your server.';
|
||||
$string['displayloginfailures'] = 'Display login failures';
|
||||
@@ -603,10 +603,12 @@ $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a us
|
||||
$string['enablemobilewebservice'] = 'Enable web services for mobile devices';
|
||||
$string['enablepdfexportfont'] = 'Enable PDF fonts';
|
||||
$string['enablepdfexportfont_desc'] = 'If your site has courses in different languages which need other fonts in generated PDF files, you can provide the option to set the font in the course settings. You need to specify available fonts in $CFG->pdfexportfont in config.php.';
|
||||
$string['enableplugin'] = 'Enable the {$a} plugin';
|
||||
$string['enableplugin'] = 'Enable {$a}';
|
||||
$string['enablerecordcache'] = 'Enable record cache';
|
||||
$string['enablerssfeeds'] = 'Enable RSS feeds';
|
||||
$string['enablesearchareas'] = 'Enable search areas';
|
||||
$string['enablesharingtomoodlenet'] = 'Enable sharing to MoodleNet (outbound)';
|
||||
$string['enablesharingtomoodlenet_desc'] = 'Enable users to share course content to a configured MoodleNet instance if they have the relevant capability.';
|
||||
$string['enablestats'] = 'Enable statistics';
|
||||
$string['enabletrusttext'] = 'Enable trusted content';
|
||||
$string['enableuserfeedback'] = 'Enable feedback about this software';
|
||||
@@ -1014,8 +1016,8 @@ $string['pleaserefreshregistration'] = 'Your site is registered. Registration la
|
||||
$string['pleaserefreshregistrationunknown'] = 'Your site has been registered but the registration date is unknown. Please update your registration using the \'Update registration\' button or ensure that the \'Site registration\' scheduled task is enabled so your registration is automatically updated.';
|
||||
$string['pleaserefreshregistrationnewdata'] = 'Registration information has been changed. Please confirm it using the \'Update registration\' button.';
|
||||
$string['plugin'] = 'Plugin';
|
||||
$string['plugin_disabled'] = 'The {$a} plugin has been disabled';
|
||||
$string['plugin_enabled'] = 'The {$a} plugin has been enabled';
|
||||
$string['plugin_disabled'] = '{$a} disabled.';
|
||||
$string['plugin_enabled'] = '{$a} enabled.';
|
||||
$string['plugins'] = 'Plugins';
|
||||
$string['pluginscheck'] = 'Plugin dependencies check';
|
||||
$string['pluginscheckfailed'] = 'Dependencies check failed for {$a->pluginslist}';
|
||||
@@ -1250,7 +1252,7 @@ $string['selecttheme'] = 'Select theme for {$a} device';
|
||||
$string['server'] = 'Server';
|
||||
$string['serverchecks'] = 'Server checks';
|
||||
$string['serverlimit'] = 'Server limit';
|
||||
$string['servicespage'] = 'Link to \'Services and support\'';
|
||||
$string['servicespage'] = 'Services and support link';
|
||||
$string['sessionautostartwarning'] = '<p>Serious configuration error detected, please notify server administrator.</p><p> To operate properly, Moodle requires that administrator changes PHP settings.</p><p><code>session.auto_start</code> must be set to <code>off</code>.</p><p>This setting is controlled by editing <code>php.ini</code>, Apache/IIS <br />configuration or <code>.htaccess</code> file on the server.</p>';
|
||||
$string['sessioncookie'] = 'Cookie prefix';
|
||||
$string['sessioncookiedomain'] = 'Cookie domain';
|
||||
@@ -1332,7 +1334,7 @@ $string['supportemail'] = 'Support email';
|
||||
$string['supportemailsubject'] = 'Site support request - {$a}';
|
||||
$string['supportavailability'] = 'Support availability';
|
||||
$string['supportname'] = 'Support name';
|
||||
$string['supportpage'] = 'Support page';
|
||||
$string['supportpage'] = 'Contact site support link';
|
||||
$string['suspenduser'] = 'Suspend user account';
|
||||
$string['switchlang'] = 'Switch lang directory';
|
||||
$string['systempaths'] = 'System paths';
|
||||
@@ -1583,7 +1585,7 @@ $string['xmlrpcwebserviceenabled'] = 'It has been detected that the XML-RPC Web
|
||||
$string['yuicomboloading'] = 'YUI combo loading';
|
||||
$string['ziprequired'] = 'The Zip PHP extension is now required by Moodle, info-ZIP binaries or PclZip library are not used anymore.';
|
||||
$string['manageqbanks'] = 'Manage question bank plugins';
|
||||
$string['modassignmentinuse'] = 'It has been detected that your site is still using the Assignment 2.2 plugin. You may resolve this before upgrading by 1) Backing up your Assignment 2.2 activities and restoring them as new Assignment activities; or 2) Deleting the data from the assignment tables in the database.';
|
||||
$string['modassignmentinuse'] = 'Your site is still using the Assignment 2.2 plugin. Before upgrading you must 1) backup any Assignment 2.2 activities that you want to keep and restore them as Assignment activities, and 2) delete all Assignment 2.2 data from the database.';
|
||||
|
||||
|
||||
$string['caching'] = 'Caching';
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ $string['defaultweight_help'] = 'The default weight allows you to choose roughly
|
||||
$string['deletecheck'] = 'Delete {$a} block?';
|
||||
$string['deletecheck_modal'] = 'Delete block?';
|
||||
$string['deleteblock'] = 'Delete {$a} block';
|
||||
$string['deleteblockcheck'] = 'Are you sure that you want to delete this block titled {$a}?';
|
||||
$string['deleteblockcheck'] = 'This will delete the block {$a}.';
|
||||
$string['deleteblockinprogress'] = 'Block {$a} removal in progress...';
|
||||
$string['deleteblockwarning'] = '<p>You are about to delete a block that appears elsewhere.</p><p>Original block location: {$a->location}<br />Display on page types: {$a->pagetype}</p><p>Are you sure you want to continue?</p>';
|
||||
$string['hideblock'] = 'Hide {$a} block';
|
||||
|
||||
@@ -33,15 +33,15 @@ $string['bulkcancel'] = 'Close bulk edit';
|
||||
$string['bulkselection'] = '{$a} selected';
|
||||
$string['bulkselection_plural'] = '{$a} selected';
|
||||
$string['cmavailability'] = 'Activity availability';
|
||||
$string['cmdelete_info'] = 'This will delete "{$a->name}" and any user data it contains';
|
||||
$string['cmdelete_info'] = 'This will delete {$a->name} and any user data it contains.';
|
||||
$string['cmdelete_title'] = 'Delete activity?';
|
||||
$string['cmsdelete'] = 'Delete activities';
|
||||
$string['cmsdelete_info'] = 'This will delete {$a->count} activities and any user data they contain';
|
||||
$string['cmsdelete_info'] = 'This will delete {$a->count} activities and any user data they contain.';
|
||||
$string['cmsdelete_title'] = 'Delete selected activities?';
|
||||
$string['cmsduplicate'] = 'Duplicate activities';
|
||||
$string['cmsmove'] = 'Move activities';
|
||||
$string['cmmove_title'] = 'Move activity';
|
||||
$string['cmmove_info'] = 'Move "{$a}" activity after';
|
||||
$string['cmmove_info'] = 'Move {$a} after';
|
||||
$string['cmsmove_title'] = 'Move selected activities';
|
||||
$string['cmsmove_info'] = 'Move {$a} activities after';
|
||||
$string['courseindex'] = 'Course index';
|
||||
@@ -49,12 +49,12 @@ $string['nobulkaction'] = 'No bulk actions available';
|
||||
$string['preference:coursesectionspreferences'] = 'Section user preferences for course {$a}';
|
||||
$string['privacy:metadata:preference:coursesectionspreferences'] = 'Section user preferences like collapsed and expanded.';
|
||||
$string['sectionavailability_title'] = 'Section availability';
|
||||
$string['sectiondelete_info'] = 'This will delete "{$a->name}" and all the activities it contains.';
|
||||
$string['sectiondelete_info'] = 'This will delete {$a->name} and all the activities it contains.';
|
||||
$string['sectiondelete_title'] = 'Delete section?';
|
||||
$string['sectionsavailability'] = 'Sections availability';
|
||||
$string['sectionsavailability_title'] = 'Sections availability';
|
||||
$string['sectionsdelete'] = 'Delete sections';
|
||||
$string['sectionmove_info'] = 'Move "{$a}" after';
|
||||
$string['sectionmove_info'] = 'Move {$a} after';
|
||||
$string['sectionmove_title'] = 'Move section';
|
||||
$string['sectionsdelete_info'] = 'This will delete {$a->count} sections and all the activities they contain.';
|
||||
$string['sectionsdelete_title'] = 'Delete selected sections?';
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ $string['errorthresholdlow'] = 'Notification threshold must be at least 1 day.';
|
||||
$string['errorwithbulkoperation'] = 'There was an error while processing your bulk enrolment change.';
|
||||
$string['eventuserenrolmentcreated'] = 'User enrolled in course';
|
||||
$string['eventuserenrolmentdeleted'] = 'User unenrolled from course';
|
||||
$string['eventuserenrolmentupdated'] = 'User unenrolment updated';
|
||||
$string['eventuserenrolmentupdated'] = 'User enrolment updated';
|
||||
$string['expirynotify'] = 'Notify before enrolment expires';
|
||||
$string['expirynotify_help'] = 'This setting determines whether enrolment expiry notification messages are sent.';
|
||||
$string['expirynotifyall'] = 'Enroller and enrolled user';
|
||||
|
||||
+3
-1
@@ -240,7 +240,7 @@ $string['duplicateparaminsql'] = 'ERROR: duplicate parameter name in query';
|
||||
$string['duplicaterolename'] = 'There is already a role with this name!';
|
||||
$string['duplicateroleshortname'] = 'There is already a role with this short name!';
|
||||
$string['duplicateusername'] = 'Duplicate username - skipping record';
|
||||
$string['editedpagenotfound'] = 'Could not determine the current page. Please try refreshing the page and repeating the operation.';
|
||||
$string['editedpagenotfound'] = 'The system couldn\'t determine the page you are on. Please refresh the page and try again.';
|
||||
$string['emailfail'] = 'Emailing failed';
|
||||
$string['encryption_encryptfailed'] = 'Encryption failed';
|
||||
$string['encryption_decryptfailed'] = 'Decryption failed';
|
||||
@@ -426,6 +426,8 @@ $string['moduledisable'] = 'This module ({$a}) has been disabled for this partic
|
||||
$string['moduledoesnotexist'] = 'This module does not exist';
|
||||
$string['moduleinstancedoesnotexist'] = 'The instance of this module does not exist';
|
||||
$string['modulemissingcode'] = 'Module {$a} is missing the code needed to perform this function';
|
||||
$string['moodlenet:invalidshareformat'] = 'Invalid MoodleNet share format';
|
||||
$string['moodlenet:usernotconfigured'] = 'You do not have permission to share content to MoodleNet, or your account is incorrectly configured.';
|
||||
$string['movecatcontentstoroot'] = 'Moving the category content to root is not allowed. You must move the contents to an existing category!';
|
||||
$string['movecatcontentstoselected'] = 'Some category content cannot be moved into the selected category.';
|
||||
$string['movecategorynotpossible'] = 'You cannot move category \'{$a}\' into the selected category.';
|
||||
|
||||
+15
-25
@@ -174,14 +174,9 @@ $string['nummembers'] = 'Members per group';
|
||||
$string['mygroups'] = 'My groups';
|
||||
$string['othergroups'] = 'Other groups';
|
||||
$string['overview'] = 'Overview';
|
||||
$string['participation'] = 'Allow activity participation';
|
||||
$string['participation_help'] = 'If enabled, members can select this group when participating in an activity using Separate Groups
|
||||
or Visible Groups mode.
|
||||
|
||||
This setting is only applicable if the Group visibility is set to "Visible to all" or "Visible to members". Participation is
|
||||
disabled otherwise.
|
||||
|
||||
This setting cannot be edited once a group has members.';
|
||||
$string['participation'] = 'Show group in dropdown menu for activities in group mode';
|
||||
$string['participation_help'] = 'Should group members be able to select this group for activities in separate or visible groups mode? (Only applicable if group membership is visible to everyone or only visible to members.)';
|
||||
$string['participationshort'] = 'Participation';
|
||||
$string['potentialmembers'] = 'Potential members: {$a}';
|
||||
$string['potentialmembs'] = 'Potential members';
|
||||
$string['printerfriendly'] = 'Printer-friendly display';
|
||||
@@ -206,25 +201,20 @@ $string['toomanygroups'] = 'Insufficient users to populate this number of groups
|
||||
$string['usercount'] = 'User count';
|
||||
$string['usercounttotal'] = 'User count ({$a})';
|
||||
$string['usergroupmembership'] = 'Selected user\'s membership:';
|
||||
$string['visibility'] = 'Group visibility';
|
||||
$string['visibility_help'] = 'Controls the visibility of membership to this group.
|
||||
$string['visibility'] = 'Group membership visibility';
|
||||
$string['visibility_help'] = '* Visible to everyone - all course participants can view who is in the group
|
||||
* Only visible to members - course participants not in the group can’t view the group or its members
|
||||
* Only see own membership - a user can see they are in the group but can’t view other group members
|
||||
* Hidden - only teachers can view the group and its members
|
||||
|
||||
If "Visible to all" is set, all users can see when a user is a member of this group (default).
|
||||
Users with the view hidden groups capability can always view group membership.
|
||||
|
||||
If "Visible to members" is set, only members of this group can see when another user is a member.
|
||||
|
||||
If "See own membership" is set, users can see that they are in this group, but cannot see that other users are members of the group.
|
||||
|
||||
If "Membership is hidden" is set, users cannot see that they or anyone else are members of the group.
|
||||
|
||||
Users with moodle/course:viewhiddengroups will always be able to see group membership.
|
||||
|
||||
This setting cannot be edited once a group has members.
|
||||
';
|
||||
$string['visibilityall'] = 'Visible to all';
|
||||
$string['visibilitymembers'] = 'Visible to members';
|
||||
$string['visibilityown'] = 'See own membership';
|
||||
$string['visibilitynone'] = 'Membership is hidden';
|
||||
Note that you can\'t change this setting if the group has members.';
|
||||
$string['visibilityshort'] = 'Visibility';
|
||||
$string['visibilityall'] = 'Visible to everyone';
|
||||
$string['visibilitymembers'] = 'Only visible to members';
|
||||
$string['visibilityown'] = 'Only see own membership';
|
||||
$string['visibilitynone'] = 'Hidden';
|
||||
$string['memberofgroup'] = 'Group member of: {$a}';
|
||||
|
||||
// Deprecated since Moodle 3.11.
|
||||
|
||||
@@ -1365,6 +1365,27 @@ $string['moodledocslink'] = 'Help and documentation';
|
||||
$string['moodleversion'] = 'Moodle version';
|
||||
$string['moodlerelease'] = 'Moodle release';
|
||||
$string['moodleservicesandsupport'] = 'Services and support';
|
||||
$string['moodlenet:cannotconnecttoserver'] = 'Cannot connect to MoodleNet server';
|
||||
$string['moodlenet:configoauthservice'] = 'Select a MoodleNet OAuth 2 service to enable sharing to that MoodleNet site. If the service doesn\'t exist yet, you will need to <a href="{$a}">create</a> it.';
|
||||
$string['moodlenet:eventresourceexported'] = 'Resource exported';
|
||||
$string['moodlenet:gotomoodlenet'] = 'Go to MoodleNet drafts';
|
||||
$string['moodlenet:issuerisnotauthorized'] = 'MoodleNet issuer is not authorized';
|
||||
$string['moodlenet:issuerisnotenabled'] = 'MoodleNet issuer is not enabled';
|
||||
$string['moodlenet:issuerisnotset'] = 'MoodleNet issuer is not set at site administration';
|
||||
$string['moodlenet:outboundsettings'] = 'MoodleNet outbound settings';
|
||||
$string['moodlenet:sharenotice'] = 'You are sharing this to MoodleNet as a {$a}';
|
||||
$string['moodlenet:sharefailtitle'] = 'Something went wrong';
|
||||
$string['moodlenet:sharefailtext'] = 'There was an error sharing your content to MoodleNet.<br>Please try again later.';
|
||||
$string['moodlenet:sharefailtextwithsitesupport'] = 'There was an error sharing your content to MoodleNet.<br>Please try again later or <a href="{$a}">contact site support</a>.';
|
||||
$string['moodlenet:sharefilesizelimitexceeded'] = 'The size of the resource being shared ({$a->filesize} bytes) exceeds the limit of {$a->filesizelimit} bytes.';
|
||||
$string['moodlenet:sharesuccesstitle'] = 'Saved to MoodleNet drafts';
|
||||
$string['moodlenet:sharesuccesstext'] = "Almost done! Visit your drafts in MoodleNet to finish sharing your content.";
|
||||
$string['moodlenet:sharetomoodlenet'] = 'Share to MoodleNet';
|
||||
$string['moodlenet:sharetyperesource'] = 'resource';
|
||||
$string['moodlenet:sharingstatus'] = 'Sharing to MoodleNet';
|
||||
$string['moodlenet:sharinglargefile'] = "Large files can take some time.";
|
||||
$string['moodlenet:sharingto'] = 'Sharing to: ';
|
||||
$string['moodlenet:packagingandsending'] = 'Packaging your file and sending to MoodleNet...';
|
||||
$string['more'] = 'more';
|
||||
$string['morehelp'] = 'More help';
|
||||
$string['morehelpaboutmodule'] = 'More help about the {$a} activity';
|
||||
@@ -1970,6 +1991,7 @@ $string['setmode'] = 'Set mode';
|
||||
$string['setpassword'] = 'Set password';
|
||||
$string['setpasswordinstructions'] = 'Please enter your new password below, then save changes.';
|
||||
$string['settings'] = 'Settings';
|
||||
$string['share'] = 'Share';
|
||||
$string['shortname'] = 'Short name'; /* @deprecated MDL-34652 - Use shortnamecourse or shortnameuser or some own context specific string. */
|
||||
$string['shortnamecollisionwarning'] = '[*] = This shortname is already in use by a course and will need to be changed upon approval';
|
||||
$string['shortnamecourse'] = 'Course short name';
|
||||
|
||||
@@ -460,10 +460,10 @@ $string['settingsformultipletries'] = 'Multiple tries';
|
||||
$string['showhidden'] = 'Also show old questions';
|
||||
$string['showmarkandmax'] = 'Show mark and max';
|
||||
$string['showmaxmarkonly'] = 'Show max mark only';
|
||||
$string['showquestiontext'] = 'Show question text in the question list';
|
||||
$string['showquestiontext_full'] = 'Full display';
|
||||
$string['showquestiontext'] = 'Show question text in the question list?';
|
||||
$string['showquestiontext_full'] = 'Yes, with images, media, etc.';
|
||||
$string['showquestiontext_off'] = 'No';
|
||||
$string['showquestiontext_plain'] = 'Text only';
|
||||
$string['showquestiontext_plain'] = 'Yes, text only';
|
||||
$string['shown'] = 'Shown';
|
||||
$string['shownumpartscorrect'] = 'Show the number of correct responses';
|
||||
$string['shownumpartscorrectwhenfinished'] = 'Show the number of correct responses once the question has finished';
|
||||
|
||||
@@ -309,6 +309,7 @@ $string['manager'] = 'Manager';
|
||||
$string['managerdescription'] = 'Managers can access courses and modify them, but usually do not participate in them.';
|
||||
$string['manageroles'] = 'Manage roles';
|
||||
$string['maybeassignedin'] = 'Context types where this role may be assigned';
|
||||
$string['moodlenet:shareactivity'] = 'Share activities to MoodleNet';
|
||||
$string['morethan'] = 'More than {$a}';
|
||||
$string['multipleroles'] = 'Multiple roles';
|
||||
$string['my:manageblocks'] = 'Manage Dashboard page blocks';
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ $string['privacy:metadata:component'] = 'The component name in frankenstyle';
|
||||
$string['privacy:metadata:itemid'] = 'The item ID of the state';
|
||||
$string['privacy:metadata:registration'] = 'The xAPI registration UUID';
|
||||
$string['privacy:metadata:statedata'] = 'JSON object with the state data';
|
||||
$string['privacy:metadata:stateid'] = 'The xAPI state id.';
|
||||
$string['privacy:metadata:stateid'] = 'The xAPI state ID';
|
||||
$string['privacy:metadata:timecreated'] = 'The time when the state element was created';
|
||||
$string['privacy:metadata:timemodified'] = 'The last time state was updated';
|
||||
$string['privacy:metadata:userid'] = 'The ID of the user who belongs the state ';
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
define("core/moodlenet/oauth2callback",["exports","core/prefetch","core/notification","core/str"],(function(_exports,_prefetch,_notification,_str){var obj;
|
||||
/**
|
||||
* A module to handle the OAuth2 callback for MoodleNet.
|
||||
*
|
||||
* @module core/moodlenet/oauth2callback
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_prefetch=(obj=_prefetch)&&obj.__esModule?obj:{default:obj};_exports.init=(error,errorDescription)=>{_prefetch.default.prefetchStrings("moodle",["moodlenet:sharefailtitle","error"]),((error,errorDescription)=>{window.opener?(window.opener.moodleNetAuthorize(error,errorDescription),setTimeout((()=>{window.close()}),300)):(0,_notification.alert)((0,_str.get_string)("error","moodle"),(0,_str.get_string)("moodlenet:sharefailtitle","moodle"))})(error,errorDescription)}}));
|
||||
|
||||
//# sourceMappingURL=oauth2callback.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"oauth2callback.min.js","sources":["../../src/moodlenet/oauth2callback.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A module to handle the OAuth2 callback for MoodleNet.\n *\n * @module core/moodlenet/oauth2callback\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Prefetch from \"core/prefetch\";\nimport {alert} from 'core/notification';\nimport {get_string as getString} from 'core/str';\n\n/**\n * Handle the OAuth2 callback for MoodleNet.\n *\n * @param {String} error Error\n * @param {String} errorDescription Error description\n */\nconst handleCallback = (error, errorDescription) => {\n if (window.opener) {\n // Call the MoodleNet Authorization again in the opener window.\n window.opener.moodleNetAuthorize(error, errorDescription);\n // Close the authorization popup.\n // We need to use setTimeout here because the Behat 'I press \"x\" and switch to main window' step expects the popup to still\n // be visible after clicking the button. Otherwise, it will throw a webdriver error.\n setTimeout(() => {\n // Close the authorization popup.\n window.close();\n }, 300);\n } else {\n alert(getString('error', 'moodle'), getString('moodlenet:sharefailtitle', 'moodle'));\n }\n};\n\n/**\n * Initialize.\n *\n * @param {String} error Error\n * @param {String} errorDescription Error description\n */\nexport const init = (error, errorDescription) => {\n Prefetch.prefetchStrings('moodle', ['moodlenet:sharefailtitle', 'error']);\n handleCallback(error, errorDescription);\n};\n"],"names":["error","errorDescription","prefetchStrings","window","opener","moodleNetAuthorize","setTimeout","close","handleCallback"],"mappings":";;;;;;;;4JAwDoB,CAACA,MAAOC,sCACfC,gBAAgB,SAAU,CAAC,2BAA4B,UAvB7C,EAACF,MAAOC,oBACvBE,OAAOC,QAEPD,OAAOC,OAAOC,mBAAmBL,MAAOC,kBAIxCK,YAAW,KAEPH,OAAOI,UACR,+BAEG,mBAAU,QAAS,WAAW,mBAAU,2BAA4B,YAY9EC,CAAeR,MAAOC"}
|
||||
@@ -0,0 +1,3 @@
|
||||
define("core/moodlenet/send_activity_modal",["exports","core/modal","core/modal_registry"],(function(_exports,_modal,_modal_registry){var _class;function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),_modal_registry=_interopRequireDefault(_modal_registry);const SendActivityModal=(_defineProperty(_class=class extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}},"TYPE","core/moodlenet/send_activity_modal"),_defineProperty(_class,"TEMPLATE","core/moodlenet/send_activity_modal_base"),_class);_modal_registry.default.register(SendActivityModal.TYPE,SendActivityModal,SendActivityModal.TEMPLATE);var _default=SendActivityModal;return _exports.default=_default,_exports.default}));
|
||||
|
||||
//# sourceMappingURL=send_activity_modal.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"send_activity_modal.min.js","sources":["../../src/moodlenet/send_activity_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Send activity modal for MoodleNet.\n *\n * @module core/moodlenet/send_activity_modal\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Modal from 'core/modal';\nimport ModalRegistry from 'core/modal_registry';\n\nconst SendActivityModal = class extends Modal {\n static TYPE = 'core/moodlenet/send_activity_modal';\n static TEMPLATE = 'core/moodlenet/send_activity_modal_base';\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n};\n\nModalRegistry.register(SendActivityModal.TYPE, SendActivityModal, SendActivityModal.TEMPLATE);\n\nexport default SendActivityModal;\n"],"names":["SendActivityModal","Modal","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","register","TYPE","TEMPLATE"],"mappings":"2jBA2BMA,0CAAoB,cAAcC,eAIpCC,+BAEUA,8BAGDC,2BACAC,iCATK,wEACI,2EAYRC,SAASL,kBAAkBM,KAAMN,kBAAmBA,kBAAkBO,uBAErEP"}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
define("core/moodlenet/send_resource",["exports","core/config","core/modal_factory","core/notification","core/str","core/prefetch","core/templates","core/moodlenet/service","core/moodlenet/send_activity_modal"],(function(_exports,_config,_modal_factory,_notification,_str,_prefetch,Templates,MoodleNetService,_send_activity_modal){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
|
||||
/**
|
||||
* A module to handle Share operations of the MoodleNet.
|
||||
*
|
||||
* @module core/moodlenet/send_resource
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_config=_interopRequireDefault(_config),_modal_factory=_interopRequireDefault(_modal_factory),_prefetch=_interopRequireDefault(_prefetch),Templates=_interopRequireWildcard(Templates),MoodleNetService=_interopRequireWildcard(MoodleNetService),_send_activity_modal=_interopRequireDefault(_send_activity_modal);let currentModal,siteSupportUrl,issuerId,courseId,cmId,shareFormat;const responseFromMoodleNet=function(status){let resourceUrl=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const $modal=currentModal.getModal(),modal=$modal[0];modal.querySelector(".modal-header").classList.add("no-border"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_done",{success:status,sitesupporturl:siteSupportUrl})),status&&(currentModal.setFooter(Templates.render("core/moodlenet/send_activity_modal_footer_view",{resourseurl:resourceUrl})),currentModal.showFooter())},handleAuthorization=issuerId=>MoodleNetService.authorizationCheck(issuerId,courseId).then((async data=>data.status?((issuerId,cmId,shareFormat)=>{const modal=currentModal.getModal()[0];modal.querySelector(".modal-header").classList.remove("no-border"),modal.querySelector(".modal-header").classList.add("no-header-text"),currentModal.setBody(Templates.render("core/moodlenet/send_activity_modal_packaging",{})),currentModal.hideFooter(),MoodleNetService.sendActivity(issuerId,cmId,shareFormat).then((async data=>{const status=data.status,resourceUrl=data.resourceurl;return responseFromMoodleNet(status,resourceUrl)})).catch(_notification.exception)})(issuerId,cmId,shareFormat):(window.moodleNetAuthorize=(error,errorDescription)=>{""==error?handleAuthorization(issuerId):"access_denied"!==error&&(0,_notification.alert)("Authorization error","Error: "+error+"<br><br>Error description: "+errorDescription,"Cancel")},window.open(data.loginurl,"moodlenet_auth","location=0,status=0,width=".concat(550,",height=").concat(550,",scrollbars=yes"))))).catch(_notification.exception);_exports.init=()=>{_prefetch.default.prefetchTemplates(["core/moodlenet/send_activity_modal_base","core/moodlenet/send_activity_modal_packaging","core/moodlenet/send_activity_modal_done","core/moodlenet/send_activity_modal_footer_view"]),document.addEventListener("click",(e=>{const shareAction=e.target.closest('[data-action="sendtomoodlenet"]'),sendAction=e.target.closest('.moodlenet-action-buttons [data-action="share"]');if(shareAction){e.preventDefault();const type=shareAction.getAttribute("data-type"),shareType=shareAction.getAttribute("data-sharetype"),cmId=_config.default.contextInstanceId;"activity"==type&&MoodleNetService.getActivityInformation(cmId).then((async data=>data.status?(siteSupportUrl=data.supportpageurl,issuerId=data.issuerid,_modal_factory.default.create({type:_send_activity_modal.default.TYPE,large:!0,templateContext:{activitytype:data.type,activityname:data.name,sharetype:await(0,_str.get_string)("moodlenet:sharetype"+shareType,"moodle"),server:data.server}}).then((modal=>(currentModal=modal,modal.show(),modal))).catch(_notification.exception)):(0,_notification.addNotification)({message:data.warnings[0].message,type:"error"}))).catch(_notification.exception)}sendAction&&(e.preventDefault(),courseId=_config.default.courseId,cmId=_config.default.contextInstanceId,shareFormat=0,handleAuthorization(issuerId))}))}}));
|
||||
|
||||
//# sourceMappingURL=send_resource.min.js.map
|
||||
File diff suppressed because one or more lines are too long
+11
@@ -0,0 +1,11 @@
|
||||
define("core/moodlenet/service",["exports","core/ajax"],(function(_exports,_ajax){var obj;
|
||||
/**
|
||||
* A javascript module to handle MoodleNet ajax actions.
|
||||
*
|
||||
* @module core/moodlenet/service
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.sendActivity=_exports.getActivityInformation=_exports.authorizationCheck=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getActivityInformation=cmId=>{const request={methodname:"core_moodlenet_get_share_info_activity",args:{cmid:cmId}};return _ajax.default.call([request])[0]};_exports.sendActivity=(issuerId,cmId,shareFormat)=>{const request={methodname:"core_moodlenet_send_activity",args:{issuerid:issuerId,cmid:cmId,shareformat:shareFormat}};return _ajax.default.call([request])[0]};_exports.authorizationCheck=(issuerId,courseId)=>{const request={methodname:"core_moodlenet_auth_check",args:{issuerid:issuerId,courseid:courseId}};return _ajax.default.call([request])[0]}}));
|
||||
|
||||
//# sourceMappingURL=service.min.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"service.min.js","sources":["../../src/moodlenet/service.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * A javascript module to handle MoodleNet ajax actions.\n *\n * @module core/moodlenet/service\n * @copyright 2023 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 4.2\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Get the activity information by course module id.\n *\n * @param {Integer} cmId The course module id.\n * @return {promise}\n */\nexport const getActivityInformation = (cmId) => {\n const request = {\n methodname: 'core_moodlenet_get_share_info_activity',\n args: {\n cmid: cmId\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Send the activity to Moodlenet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} cmId The course module ID.\n * @param {Integer} shareFormat The share format.\n * @return {promise}\n */\nexport const sendActivity = (issuerId, cmId, shareFormat) => {\n const request = {\n methodname: 'core_moodlenet_send_activity',\n args: {\n issuerid: issuerId,\n cmid: cmId,\n shareformat: shareFormat,\n }\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Check if the user is already authorized with MoodleNet.\n *\n * @param {Integer} issuerId The OAuth 2 issuer ID.\n * @param {Integer} courseId The course ID.\n * @return {promise}\n */\nexport const authorizationCheck = (issuerId, courseId) => {\n const request = {\n methodname: 'core_moodlenet_auth_check',\n args: {\n issuerid: issuerId,\n courseid: courseId,\n }\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["cmId","request","methodname","args","cmid","Ajax","call","issuerId","shareFormat","issuerid","shareformat","courseId","courseid"],"mappings":";;;;;;;;0OAgCuCA,aAC7BC,QAAU,CACZC,WAAY,yCACZC,KAAM,CACFC,KAAMJ,cAIPK,cAAKC,KAAK,CAACL,UAAU,0BAWJ,CAACM,SAAUP,KAAMQ,qBACnCP,QAAU,CACZC,WAAY,+BACZC,KAAM,CACFM,SAAUF,SACVH,KAAMJ,KACNU,YAAaF,qBAIdH,cAAKC,KAAK,CAACL,UAAU,gCAUE,CAACM,SAAUI,kBACnCV,QAAU,CACZC,WAAY,4BACZC,KAAM,CACFM,SAAUF,SACVK,SAAUD,kBAIXN,cAAKC,KAAK,CAACL,UAAU"}
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* A module to handle the OAuth2 callback for MoodleNet.
|
||||
*
|
||||
* @module core/moodlenet/oauth2callback
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/
|
||||
|
||||
import Prefetch from "core/prefetch";
|
||||
import {alert} from 'core/notification';
|
||||
import {get_string as getString} from 'core/str';
|
||||
|
||||
/**
|
||||
* Handle the OAuth2 callback for MoodleNet.
|
||||
*
|
||||
* @param {String} error Error
|
||||
* @param {String} errorDescription Error description
|
||||
*/
|
||||
const handleCallback = (error, errorDescription) => {
|
||||
if (window.opener) {
|
||||
// Call the MoodleNet Authorization again in the opener window.
|
||||
window.opener.moodleNetAuthorize(error, errorDescription);
|
||||
// Close the authorization popup.
|
||||
// We need to use setTimeout here because the Behat 'I press "x" and switch to main window' step expects the popup to still
|
||||
// be visible after clicking the button. Otherwise, it will throw a webdriver error.
|
||||
setTimeout(() => {
|
||||
// Close the authorization popup.
|
||||
window.close();
|
||||
}, 300);
|
||||
} else {
|
||||
alert(getString('error', 'moodle'), getString('moodlenet:sharefailtitle', 'moodle'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*
|
||||
* @param {String} error Error
|
||||
* @param {String} errorDescription Error description
|
||||
*/
|
||||
export const init = (error, errorDescription) => {
|
||||
Prefetch.prefetchStrings('moodle', ['moodlenet:sharefailtitle', 'error']);
|
||||
handleCallback(error, errorDescription);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Send activity modal for MoodleNet.
|
||||
*
|
||||
* @module core/moodlenet/send_activity_modal
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/
|
||||
|
||||
import Modal from 'core/modal';
|
||||
import ModalRegistry from 'core/modal_registry';
|
||||
|
||||
const SendActivityModal = class extends Modal {
|
||||
static TYPE = 'core/moodlenet/send_activity_modal';
|
||||
static TEMPLATE = 'core/moodlenet/send_activity_modal_base';
|
||||
|
||||
registerEventListeners() {
|
||||
// Call the parent registration.
|
||||
super.registerEventListeners();
|
||||
|
||||
// Register to close on save/cancel.
|
||||
this.registerCloseOnSave();
|
||||
this.registerCloseOnCancel();
|
||||
}
|
||||
};
|
||||
|
||||
ModalRegistry.register(SendActivityModal.TYPE, SendActivityModal, SendActivityModal.TEMPLATE);
|
||||
|
||||
export default SendActivityModal;
|
||||
@@ -0,0 +1,189 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* A module to handle Share operations of the MoodleNet.
|
||||
*
|
||||
* @module core/moodlenet/send_resource
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/
|
||||
|
||||
import Config from 'core/config';
|
||||
import ModalFactory from 'core/modal_factory';
|
||||
import {alert as displayAlert, addNotification, exception as displayException} from 'core/notification';
|
||||
import {get_string as getString} from 'core/str';
|
||||
import Prefetch from "core/prefetch";
|
||||
import * as Templates from 'core/templates';
|
||||
import * as MoodleNetService from 'core/moodlenet/service';
|
||||
import SendActivityModal from 'core/moodlenet/send_activity_modal';
|
||||
|
||||
const TYPE_ACTIVITY = "activity";
|
||||
|
||||
let currentModal;
|
||||
let siteSupportUrl;
|
||||
let issuerId;
|
||||
let courseId;
|
||||
let cmId;
|
||||
let shareFormat;
|
||||
|
||||
/**
|
||||
* Handle send to MoodleNet.
|
||||
*
|
||||
* @param {int} issuerId The OAuth 2 issuer ID.
|
||||
* @param {int} cmId The course module ID.
|
||||
* @param {int} shareFormat The share format.
|
||||
*/
|
||||
const sendToMoodleNet = (issuerId, cmId, shareFormat) => {
|
||||
const $modal = currentModal.getModal();
|
||||
const modal = $modal[0];
|
||||
modal.querySelector('.modal-header').classList.remove('no-border');
|
||||
modal.querySelector('.modal-header').classList.add('no-header-text');
|
||||
|
||||
currentModal.setBody(Templates.render('core/moodlenet/send_activity_modal_packaging', {}));
|
||||
currentModal.hideFooter();
|
||||
|
||||
MoodleNetService.sendActivity(issuerId, cmId, shareFormat).then(async(data) => {
|
||||
const status = data.status;
|
||||
const resourceUrl = data.resourceurl;
|
||||
return responseFromMoodleNet(status, resourceUrl);
|
||||
}).catch(displayException);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle response from MoodleNet.
|
||||
*
|
||||
* @param {boolean} status Response status. True if successful.
|
||||
* @param {String} resourceUrl Resource URL.
|
||||
*/
|
||||
const responseFromMoodleNet = (status, resourceUrl = '') => {
|
||||
const $modal = currentModal.getModal();
|
||||
const modal = $modal[0];
|
||||
modal.querySelector('.modal-header').classList.add('no-border');
|
||||
currentModal.setBody(Templates.render('core/moodlenet/send_activity_modal_done', {
|
||||
success: status,
|
||||
sitesupporturl: siteSupportUrl,
|
||||
}));
|
||||
|
||||
if (status) {
|
||||
currentModal.setFooter(Templates.render('core/moodlenet/send_activity_modal_footer_view', {
|
||||
resourseurl: resourceUrl,
|
||||
}));
|
||||
currentModal.showFooter();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle authorization with MoodleNet server.
|
||||
*
|
||||
* @param {int} issuerId The OAuth 2 issuer ID.
|
||||
* @return {promise}
|
||||
*/
|
||||
const handleAuthorization = (issuerId) => {
|
||||
const windowsizewidth = 550;
|
||||
const windowsizeheight = 550;
|
||||
|
||||
// Check if the user is authorized with MoodleNet or not.
|
||||
return MoodleNetService.authorizationCheck(issuerId, courseId).then(async(data) => {
|
||||
if (!data.status) {
|
||||
// Not yet authorized.
|
||||
// Declare moodleNetAuthorize variable, so we can call it later in the callback.
|
||||
window.moodleNetAuthorize = (error, errorDescription) => {
|
||||
// This will be called by the callback after the authorization is successful.
|
||||
if (error == '') {
|
||||
handleAuthorization(issuerId);
|
||||
} else if (error !== 'access_denied') {
|
||||
displayAlert(
|
||||
'Authorization error',
|
||||
'Error: ' + error + '<br><br>Error description: ' + errorDescription,
|
||||
'Cancel'
|
||||
);
|
||||
}
|
||||
};
|
||||
// Open the login url of the OAuth 2 issuer for user to login into MoodleNet and authorize.
|
||||
return window.open(data.loginurl, 'moodlenet_auth',
|
||||
`location=0,status=0,width=${windowsizewidth},height=${windowsizeheight},scrollbars=yes`);
|
||||
} else {
|
||||
// Already authorized.
|
||||
return sendToMoodleNet(issuerId, cmId, shareFormat);
|
||||
}
|
||||
}).catch(displayException);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register events.
|
||||
*/
|
||||
const registerEventListeners = () => {
|
||||
document.addEventListener('click', e => {
|
||||
const shareAction = e.target.closest('[data-action="sendtomoodlenet"]');
|
||||
const sendAction = e.target.closest('.moodlenet-action-buttons [data-action="share"]');
|
||||
if (shareAction) {
|
||||
e.preventDefault();
|
||||
const type = shareAction.getAttribute('data-type');
|
||||
const shareType = shareAction.getAttribute('data-sharetype');
|
||||
const cmId = Config.contextInstanceId;
|
||||
if (type == TYPE_ACTIVITY) {
|
||||
MoodleNetService.getActivityInformation(cmId).then(async(data) => {
|
||||
if (data.status) {
|
||||
siteSupportUrl = data.supportpageurl;
|
||||
issuerId = data.issuerid;
|
||||
const modalPromise = ModalFactory.create({
|
||||
type: SendActivityModal.TYPE,
|
||||
large: true,
|
||||
templateContext: {
|
||||
'activitytype': data.type,
|
||||
'activityname': data.name,
|
||||
'sharetype': await getString('moodlenet:sharetype' + shareType, 'moodle'),
|
||||
'server': data.server,
|
||||
}
|
||||
});
|
||||
return modalPromise.then(modal => {
|
||||
currentModal = modal;
|
||||
modal.show();
|
||||
return modal;
|
||||
}).catch(displayException);
|
||||
} else {
|
||||
return addNotification({
|
||||
message: data.warnings[0].message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}).catch(displayException);
|
||||
}
|
||||
}
|
||||
|
||||
if (sendAction) {
|
||||
e.preventDefault();
|
||||
courseId = Config.courseId;
|
||||
cmId = Config.contextInstanceId;
|
||||
shareFormat = 0;
|
||||
handleAuthorization(issuerId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize.
|
||||
*/
|
||||
export const init = () => {
|
||||
Prefetch.prefetchTemplates([
|
||||
'core/moodlenet/send_activity_modal_base',
|
||||
'core/moodlenet/send_activity_modal_packaging',
|
||||
'core/moodlenet/send_activity_modal_done',
|
||||
'core/moodlenet/send_activity_modal_footer_view',
|
||||
]);
|
||||
registerEventListeners();
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* A javascript module to handle MoodleNet ajax actions.
|
||||
*
|
||||
* @module core/moodlenet/service
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
* @since 4.2
|
||||
*/
|
||||
|
||||
import Ajax from 'core/ajax';
|
||||
|
||||
/**
|
||||
* Get the activity information by course module id.
|
||||
*
|
||||
* @param {Integer} cmId The course module id.
|
||||
* @return {promise}
|
||||
*/
|
||||
export const getActivityInformation = (cmId) => {
|
||||
const request = {
|
||||
methodname: 'core_moodlenet_get_share_info_activity',
|
||||
args: {
|
||||
cmid: cmId
|
||||
}
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the activity to Moodlenet.
|
||||
*
|
||||
* @param {Integer} issuerId The OAuth 2 issuer ID.
|
||||
* @param {Integer} cmId The course module ID.
|
||||
* @param {Integer} shareFormat The share format.
|
||||
* @return {promise}
|
||||
*/
|
||||
export const sendActivity = (issuerId, cmId, shareFormat) => {
|
||||
const request = {
|
||||
methodname: 'core_moodlenet_send_activity',
|
||||
args: {
|
||||
issuerid: issuerId,
|
||||
cmid: cmId,
|
||||
shareformat: shareFormat,
|
||||
}
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the user is already authorized with MoodleNet.
|
||||
*
|
||||
* @param {Integer} issuerId The OAuth 2 issuer ID.
|
||||
* @param {Integer} courseId The course ID.
|
||||
* @return {promise}
|
||||
*/
|
||||
export const authorizationCheck = (issuerId, courseId) => {
|
||||
const request = {
|
||||
methodname: 'core_moodlenet_auth_check',
|
||||
args: {
|
||||
issuerid: issuerId,
|
||||
courseid: courseId,
|
||||
}
|
||||
};
|
||||
|
||||
return Ajax.call([request])[0];
|
||||
};
|
||||
@@ -154,6 +154,11 @@
|
||||
"allowedlevel2": true,
|
||||
"allowedspread": true
|
||||
},
|
||||
"moodlenet": {
|
||||
"component": "core",
|
||||
"allowedlevel2": false,
|
||||
"allowedspread": false
|
||||
},
|
||||
"navigation": {
|
||||
"component": "core",
|
||||
"allowedlevel2": true,
|
||||
|
||||
@@ -740,9 +740,10 @@ abstract class base implements \IteratorAggregate {
|
||||
debugging('Number of event data fields must not be changed in event classes', DEBUG_DEVELOPER);
|
||||
}
|
||||
$encoded = json_encode($this->data['other']);
|
||||
// The comparison here is not set to strict as whole float numbers will be converted to integers through JSON encoding /
|
||||
// decoding and send an unwanted debugging message.
|
||||
if ($encoded === false or $this->data['other'] != json_decode($encoded, true)) {
|
||||
// The comparison here is not set to strict. We just need to check if the data is compatible with the JSON encoding
|
||||
// or not and we don't need to worry about how the data is encoded. Because in some cases, the data can contain
|
||||
// objects, and objects can be converted to a different format during encoding and decoding.
|
||||
if ($encoded === false) {
|
||||
debugging('other event data must be compatible with json encoding', DEBUG_DEVELOPER);
|
||||
}
|
||||
if ($this->data['userid'] and !is_number($this->data['userid'])) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\event;
|
||||
|
||||
/**
|
||||
* MoodleNet send attempt event.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class moodlenet_resource_exported extends \core\event\base {
|
||||
|
||||
/**
|
||||
* Set basic properties for the event.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init(): void {
|
||||
$this->data['crud'] = 'c';
|
||||
|
||||
// Used by teachers, but not for direct educational value to their students.
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the localised general event name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name(): string {
|
||||
return get_string('moodlenet:eventresourceexported');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the non-localised event description.
|
||||
* This description format is designed to work for both single activity and course sharing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
$outcome = $this->other['success'] ? 'successfully shared' : 'failed to share';
|
||||
$cmids = implode("', '", $this->other['cmids']);
|
||||
|
||||
$description = "The user with id '{$this->userid}' {$outcome} activities to MoodleNet with the " .
|
||||
"following course module ids, from context with id '{$this->data['contextid']}': '{$cmids}'.";
|
||||
|
||||
return rtrim($description, ", '");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns relevant URL.
|
||||
* @return \moodle_url
|
||||
*/
|
||||
public function get_url() {
|
||||
return new \moodle_url($this->other['resourceurl']);
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\external;
|
||||
|
||||
use context_course;
|
||||
use core\moodlenet\moodlenet_client;
|
||||
use core\moodlenet\utilities;
|
||||
use core\oauth2\api;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use core_external\external_warnings;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* The external API to check whether a user has authorized for a given MoodleNet OAuth 2 issuer.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class moodlenet_auth_check extends external_api {
|
||||
|
||||
/**
|
||||
* Returns description of parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
|
||||
'courseid' => new external_value(PARAM_INT, 'Course ID', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* External function to check if the user is already authorized with MoodleNet.
|
||||
*
|
||||
* @param int $issuerid Issuer Id.
|
||||
* @param int $courseid The course ID that contains the activity which being shared
|
||||
* @return array
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute(int $issuerid, int $courseid): array {
|
||||
global $USER;
|
||||
[
|
||||
'issuerid' => $issuerid,
|
||||
'courseid' => $courseid,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'issuerid' => $issuerid,
|
||||
'courseid' => $courseid,
|
||||
]);
|
||||
|
||||
// Check capability.
|
||||
$coursecontext = context_course::instance($courseid);
|
||||
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
|
||||
if (!$usercanshare) {
|
||||
return self::return_errors($courseid, 'errorpermission',
|
||||
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
|
||||
}
|
||||
|
||||
// Get the issuer.
|
||||
$issuer = api::get_issuer($issuerid);
|
||||
// Validate the issuer and check if it is enabled or not.
|
||||
if (!utilities::is_valid_instance($issuer)) {
|
||||
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('invalidparameter', 'debug'));
|
||||
}
|
||||
|
||||
$returnurl = new moodle_url('/admin/moodlenet_oauth2_callback.php');
|
||||
$returnurl->param('issuerid', $issuerid);
|
||||
$returnurl->param('callback', 'yes');
|
||||
$returnurl->param('sesskey', sesskey());
|
||||
|
||||
// Get the OAuth Client.
|
||||
if (!$oauthclient = api::get_user_oauth_client($issuer, $returnurl, moodlenet_client::API_SCOPE_CREATE_RESOURCE, true)) {
|
||||
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
|
||||
}
|
||||
|
||||
$status = false;
|
||||
$warnings = [];
|
||||
$loginurl = '';
|
||||
|
||||
if (!$oauthclient->is_logged_in()) {
|
||||
$loginurl = $oauthclient->get_login_url()->out(false);
|
||||
} else {
|
||||
$status = true;
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $status,
|
||||
'loginurl' => $loginurl,
|
||||
'warnings' => $warnings,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the data returned from the external function.
|
||||
*
|
||||
* @return external_single_structure
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'loginurl' => new external_value(PARAM_RAW, 'Login url'),
|
||||
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
|
||||
'warnings' => new external_warnings(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle return error.
|
||||
*
|
||||
* @param int $itemid Item id
|
||||
* @param string $warningcode Warning code
|
||||
* @param string $message Message
|
||||
* @return array
|
||||
*/
|
||||
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
|
||||
$warnings[] = [
|
||||
'item' => $itemid,
|
||||
'warningcode' => $warningcode,
|
||||
'message' => $message,
|
||||
];
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
'loginurl' => '',
|
||||
'warnings' => $warnings,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\external;
|
||||
|
||||
use context_course;
|
||||
use core\moodlenet\utilities;
|
||||
use core\oauth2\api;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use core_external\external_warnings;
|
||||
|
||||
/**
|
||||
* The external API to het the activity information for MoodleNet sharing.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class moodlenet_get_share_info_activity extends external_api {
|
||||
|
||||
/**
|
||||
* Returns description of parameters.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'The cmid of the activity', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* External function to get the activity information.
|
||||
*
|
||||
* @param int $cmid The course module id.
|
||||
* @return array
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute(int $cmid): array {
|
||||
global $CFG, $USER;
|
||||
|
||||
[
|
||||
'cmid' => $cmid
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid
|
||||
]);
|
||||
|
||||
// Get course module.
|
||||
$coursemodule = get_coursemodule_from_id(false, $cmid);
|
||||
if (!$coursemodule) {
|
||||
return self::return_errors($cmid, 'errorgettingactivityinformation', get_string('invalidcoursemodule', 'error'));
|
||||
}
|
||||
|
||||
// Get course.
|
||||
$course = get_course($coursemodule->course);
|
||||
|
||||
// Check capability.
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
|
||||
if (!$usercanshare) {
|
||||
return self::return_errors($cmid, 'errorpermission',
|
||||
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
|
||||
}
|
||||
|
||||
$warnings = [];
|
||||
$supporturl = '';
|
||||
$issuerid = get_config('moodlenet', 'oauthservice');
|
||||
|
||||
if (empty($issuerid)) {
|
||||
return self::return_errors(0, 'errorissuernotset', get_string('moodlenet:issuerisnotset', 'moodle'));
|
||||
}
|
||||
|
||||
if ($CFG->supportavailability && $CFG->supportavailability != CONTACT_SUPPORT_DISABLED) {
|
||||
if (!empty($CFG->supportpage)) {
|
||||
$supporturl = $CFG->supportpage;
|
||||
} else {
|
||||
$supporturl = $CFG->wwwroot . '/user/contactsitesupport.php';
|
||||
}
|
||||
}
|
||||
|
||||
// Get the issuer.
|
||||
$issuer = api::get_issuer($issuerid);
|
||||
// Validate the issuer and check if it is enabled or not.
|
||||
if (!utilities::is_valid_instance($issuer)) {
|
||||
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('moodlenet:issuerisnotenabled', 'moodle'));
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'name' => $coursemodule->name,
|
||||
'type' => get_string('modulename', $coursemodule->modname),
|
||||
'server' => $issuer->get_display_name(),
|
||||
'supportpageurl' => $supporturl,
|
||||
'issuerid' => $issuerid,
|
||||
'warnings' => $warnings
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the data returned from the external function.
|
||||
*
|
||||
* @return external_single_structure
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'name' => new external_value(PARAM_TEXT, 'Activity name'),
|
||||
'type' => new external_value(PARAM_TEXT, 'Activity type'),
|
||||
'server' => new external_value(PARAM_TEXT, 'MoodleNet server'),
|
||||
'supportpageurl' => new external_value(PARAM_URL, 'Support page URL'),
|
||||
'issuerid' => new external_value(PARAM_INT, 'MoodleNet issuer id'),
|
||||
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
|
||||
'warnings' => new external_warnings()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle return error.
|
||||
*
|
||||
* @param int $itemid Item id.
|
||||
* @param string $warningcode Warning code.
|
||||
* @param string $message Message.
|
||||
* @param int $issuerid Issuer id.
|
||||
* @return array
|
||||
*/
|
||||
protected static function return_errors(int $itemid, string $warningcode, string $message, int $issuerid = 0): array {
|
||||
$warnings[] = [
|
||||
'item' => $itemid,
|
||||
'warningcode' => $warningcode,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
'name' => '',
|
||||
'type' => '',
|
||||
'server' => '',
|
||||
'supportpageurl' => '',
|
||||
'issuerid' => $issuerid,
|
||||
'warnings' => $warnings
|
||||
];
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\external;
|
||||
|
||||
use context_course;
|
||||
use core\http_client;
|
||||
use core\moodlenet\activity_sender;
|
||||
use core\moodlenet\moodlenet_client;
|
||||
use core\moodlenet\utilities;
|
||||
use core\oauth2\api;
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use core_external\external_warnings;
|
||||
use moodle_url;
|
||||
|
||||
/**
|
||||
* The external API to send activity to MoodleNet.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Huong Nguyen <huongnv13@gmail.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class moodlenet_send_activity extends external_api {
|
||||
|
||||
/**
|
||||
* Describes the parameters for sending the activity.
|
||||
*
|
||||
* @return external_function_parameters
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'issuerid' => new external_value(PARAM_INT, 'OAuth 2 issuer ID', VALUE_REQUIRED),
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module ID', VALUE_REQUIRED),
|
||||
'shareformat' => new external_value(PARAM_INT, 'Share format', VALUE_REQUIRED),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* External function to send the activity to MoodleNet.
|
||||
*
|
||||
* @param int $issuerid The MoodleNet OAuth 2 issuer ID
|
||||
* @param int $cmid The course module ID of the activity that being shared
|
||||
* @param int $shareformat The share format being used, as defined by \core\moodlenet\activity_sender
|
||||
* @return array
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute(int $issuerid, int $cmid, int $shareformat): array {
|
||||
global $CFG, $USER;
|
||||
|
||||
[
|
||||
'issuerid' => $issuerid,
|
||||
'cmid' => $cmid,
|
||||
'shareformat' => $shareformat,
|
||||
] = self::validate_parameters(self::execute_parameters(), [
|
||||
'issuerid' => $issuerid,
|
||||
'cmid' => $cmid,
|
||||
'shareformat' => $shareformat,
|
||||
]);
|
||||
|
||||
// Check capability.
|
||||
[$course] = get_course_and_cm_from_cmid($cmid);
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$usercanshare = utilities::can_user_share($coursecontext, $USER->id);
|
||||
if (!$usercanshare) {
|
||||
return self::return_errors($cmid, 'errorpermission',
|
||||
get_string('nopermissions', 'error', get_string('moodlenet:sharetomoodlenet', 'moodle')));
|
||||
}
|
||||
|
||||
// Check format.
|
||||
if (!in_array($shareformat, [activity_sender::SHARE_FORMAT_BACKUP])) {
|
||||
return self::return_errors($shareformat, 'errorinvalidformat', get_string('invalidparameter', 'debug'));
|
||||
}
|
||||
|
||||
// Get the issuer.
|
||||
$issuer = api::get_issuer($issuerid);
|
||||
// Validate the issuer and check if it is enabled or not.
|
||||
if (!utilities::is_valid_instance($issuer)) {
|
||||
return self::return_errors($issuerid, 'errorissuernotenabled', get_string('invalidparameter', 'debug'));
|
||||
}
|
||||
|
||||
// Get the OAuth Client.
|
||||
if (!$oauthclient = api::get_user_oauth_client(
|
||||
$issuer,
|
||||
new moodle_url($CFG->wwwroot),
|
||||
moodlenet_client::API_SCOPE_CREATE_RESOURCE
|
||||
)) {
|
||||
return self::return_errors($issuerid, 'erroroauthclient', get_string('invalidparameter', 'debug'));
|
||||
}
|
||||
|
||||
// Check login state.
|
||||
if (!$oauthclient->is_logged_in()) {
|
||||
return self::return_errors($issuerid, 'erroroauthclient', get_string('moodlenet:issuerisnotauthorized', 'moodle'));
|
||||
}
|
||||
|
||||
// Get the HTTP Client.
|
||||
$client = new http_client();
|
||||
|
||||
// Share activity.
|
||||
try {
|
||||
$moodlenetclient = new moodlenet_client($client, $oauthclient);
|
||||
$activitysender = new activity_sender($cmid, $USER->id, $moodlenetclient, $oauthclient, $shareformat);
|
||||
$result = $activitysender->share_activity();
|
||||
if (empty($result['drafturl'])) {
|
||||
return self::return_errors($result['responsecode'], 'errorsendingactivity',
|
||||
get_string('moodlenet:cannotconnecttoserver', 'moodle'));
|
||||
}
|
||||
} catch (\moodle_exception $e) {
|
||||
return self::return_errors(0, 'errorsendingactivity', $e->getMessage());
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => true,
|
||||
'resourceurl' => $result['drafturl'],
|
||||
'warnings' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the data returned from the external function.
|
||||
*
|
||||
* @return external_single_structure
|
||||
* @since Moodle 4.2
|
||||
*/
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'status' => new external_value(PARAM_BOOL, 'Status: true if success'),
|
||||
'resourceurl' => new external_value(PARAM_URL, 'Resource URL from MoodleNet'),
|
||||
'warnings' => new external_warnings(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle return error.
|
||||
*
|
||||
* @param int $itemid Item id
|
||||
* @param string $warningcode Warning code
|
||||
* @param string $message Message
|
||||
* @return array
|
||||
*/
|
||||
protected static function return_errors(int $itemid, string $warningcode, string $message): array {
|
||||
$warnings[] = [
|
||||
'item' => $itemid,
|
||||
'warningcode' => $warningcode,
|
||||
'message' => $message,
|
||||
];
|
||||
|
||||
return [
|
||||
'status' => false,
|
||||
'resourceurl' => '',
|
||||
'warnings' => $warnings,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\moodlenet;
|
||||
|
||||
use backup;
|
||||
use backup_controller;
|
||||
use backup_root_task;
|
||||
use cm_info;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
|
||||
/**
|
||||
* Packager to prepare appropriate backup of an activity to share to MoodleNet.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Raquel Ortega <raquel.ortega@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class activity_packager {
|
||||
|
||||
/** @var backup_controller $controller */
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param cm_info $cminfo context module information about the resource being packaged.
|
||||
* @param int $userid The ID of the user performing the packaging.
|
||||
*/
|
||||
public function __construct(
|
||||
protected cm_info $cminfo,
|
||||
protected int $userid
|
||||
) {
|
||||
// Check backup/restore support.
|
||||
if (!plugin_supports('mod', $cminfo->modname , FEATURE_BACKUP_MOODLE2)) {
|
||||
throw new \coding_exception("Cannot backup module $cminfo->modname. This module doesn't support the backup feature.");
|
||||
}
|
||||
|
||||
$this->cminfo = $cminfo;
|
||||
$this->controller = new backup_controller (
|
||||
backup::TYPE_1ACTIVITY,
|
||||
$cminfo->id,
|
||||
backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_NO,
|
||||
backup::MODE_GENERAL,
|
||||
$userid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the backup file using appropriate setting overrides and return relevant information.
|
||||
*
|
||||
* @return array Array containing packaged file and stored_file object describing it.
|
||||
* Array in the format [storedfile => stored_file object, filecontents => raw file].
|
||||
*/
|
||||
public function get_package(): array {
|
||||
$alltasksettings = $this->get_all_task_settings();
|
||||
|
||||
// Override relevant settings to remove user data when packaging to share to MoodleNet.
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_users', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_role_assignments', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_blocks', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_comments', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_badges', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_userscompletion', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_logs', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_grade_histories', 0);
|
||||
$this->override_task_setting($alltasksettings, 'setting_root_groups', 0);
|
||||
|
||||
return $this->package();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all backup settings available for override.
|
||||
*
|
||||
* @return array the associative array of taskclass => settings instances.
|
||||
*/
|
||||
protected function get_all_task_settings(): array {
|
||||
$tasksettings = [];
|
||||
foreach ($this->controller->get_plan()->get_tasks() as $task) {
|
||||
$taskclass = get_class($task);
|
||||
$tasksettings[$taskclass] = $task->get_settings();
|
||||
}
|
||||
return $tasksettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override a backup task setting with a given value.
|
||||
*
|
||||
* @param array $alltasksettings All task settings.
|
||||
* @param string $settingname The name of the setting to be overridden (task class name format).
|
||||
* @param int $settingvalue Value to be given to the setting.
|
||||
* @return void
|
||||
*/
|
||||
protected function override_task_setting(array $alltasksettings, string $settingname, int $settingvalue): void {
|
||||
if (empty($rootsettings = $alltasksettings[backup_root_task::class])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($rootsettings as $setting) {
|
||||
$name = $setting->get_ui_name();
|
||||
if ($name == $settingname && $settingvalue != $setting->get_value()) {
|
||||
$setting->set_value($settingvalue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Package the activity identified by CMID.
|
||||
*
|
||||
* @return array Array containing packaged file and stored_file object describing it.
|
||||
* Array in the format [storedfile => stored_file object, filecontents => raw file].
|
||||
* @throws \moodle_exception.
|
||||
*/
|
||||
protected function package(): array {
|
||||
// Execute the backup and fetch the result.
|
||||
$this->controller->execute_plan();
|
||||
$result = $this->controller->get_results();
|
||||
// Controller no longer required.
|
||||
$this->controller->destroy();
|
||||
|
||||
if (!isset($result['backup_destination'])) {
|
||||
throw new \moodle_exception('Failed to package activity.');
|
||||
}
|
||||
|
||||
$backupfile = $result['backup_destination'];
|
||||
|
||||
if (!$backupfile->get_contenthash()) {
|
||||
throw new \moodle_exception('Failed to package activity (invalid file).');
|
||||
}
|
||||
|
||||
// Create the location we want to copy this file to.
|
||||
$time = time();
|
||||
$fr = [
|
||||
'contextid' => \context_course::instance($this->cminfo->course)->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'moodlenet_resource',
|
||||
'filename' => $this->cminfo->modname . '_backup.mbz',
|
||||
// Add timestamp to itemid to make it unique, to avoid any collisions.
|
||||
'itemid' => $this->cminfo->id . $time,
|
||||
'timemodified' => $time,
|
||||
];
|
||||
|
||||
// Create the local file based on the backup.
|
||||
$fs = get_file_storage();
|
||||
$packagedfiledata = [
|
||||
'storedfile' => $fs->create_file_from_storedfile($fr, $backupfile),
|
||||
];
|
||||
|
||||
// Delete the backup now it has been created in the file area.
|
||||
$backupfile->delete();
|
||||
|
||||
if (!$packagedfiledata['storedfile']) {
|
||||
throw new \moodle_exception("Failed to copy backup file to moodlenet_activity area.");
|
||||
}
|
||||
|
||||
// Ensure we can handle files at the upper end of the limit supported by MoodleNet.
|
||||
raise_memory_limit(activity_sender::MAX_FILESIZE);
|
||||
|
||||
// Get the actual file content.
|
||||
$packagedfiledata['filecontents'] = $packagedfiledata['storedfile']->get_content();
|
||||
|
||||
return $packagedfiledata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\moodlenet;
|
||||
|
||||
use cm_info;
|
||||
use core\event\moodlenet_resource_exported;
|
||||
use core\oauth2\client;
|
||||
use moodle_exception;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* API for sharing Moodle LMS activities to MoodleNet instances.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class activity_sender {
|
||||
/**
|
||||
* @var int Backup share format - the content is being shared as a Moodle backup file.
|
||||
*/
|
||||
public const SHARE_FORMAT_BACKUP = 0;
|
||||
|
||||
/**
|
||||
* @var int Maximum upload file size (1.07 GB).
|
||||
*/
|
||||
public const MAX_FILESIZE = 1070000000;
|
||||
|
||||
/**
|
||||
* @var cm_info The context module info object for the activity being shared.
|
||||
*/
|
||||
protected cm_info $cminfo;
|
||||
|
||||
/**
|
||||
* @var stdClass The course where the activity is located.
|
||||
*/
|
||||
protected stdClass $course;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param int $cmid The course module ID of the activity being shared.
|
||||
* @param int $userid The user ID who is sharing the activity.
|
||||
* @param moodlenet_client $moodlenetclient The moodlenet_client object used to perform the share.
|
||||
* @param client $oauthclient The OAuth 2 client for the MoodleNet instance.
|
||||
* @param int $shareformat The data format to share in. Defaults to a Moodle backup (SHARE_FORMAT_BACKUP).
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function __construct(
|
||||
int $cmid,
|
||||
protected int $userid,
|
||||
protected moodlenet_client $moodlenetclient,
|
||||
protected client $oauthclient,
|
||||
protected int $shareformat = self::SHARE_FORMAT_BACKUP
|
||||
) {
|
||||
|
||||
[$this->course, $this->cminfo] = get_course_and_cm_from_cmid($cmid);
|
||||
|
||||
if (!in_array($shareformat, $this->get_allowed_share_formats())) {
|
||||
throw new moodle_exception('moodlenet:invalidshareformat');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Share an activity/resource to MoodleNet.
|
||||
*
|
||||
* @return array The HTTP response code from MoodleNet and the MoodleNet draft resource URL (URL empty string on fail).
|
||||
* Format: ['responsecode' => 201, 'drafturl' => 'https://draft.mnurl/here']
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public function share_activity(): array {
|
||||
global $DB;
|
||||
|
||||
$accesstoken = '';
|
||||
$resourceurl = '';
|
||||
$issuer = $this->oauthclient->get_issuer();
|
||||
|
||||
// Check user can share to the requested MoodleNet instance.
|
||||
$coursecontext = \context_course::instance($this->course->id);
|
||||
$usercanshare = utilities::can_user_share($coursecontext, $this->userid);
|
||||
|
||||
if ($usercanshare && utilities::is_valid_instance($issuer) && $this->oauthclient->is_logged_in()) {
|
||||
$accesstoken = $this->oauthclient->get_accesstoken()->token;
|
||||
}
|
||||
|
||||
// Throw an exception if the user is not currently set up to be able to share to MoodleNet.
|
||||
if (!$accesstoken) {
|
||||
throw new moodle_exception('moodlenet:usernotconfigured');
|
||||
}
|
||||
|
||||
// Attempt to prepare and send the resource if validation has passed and we have an OAuth 2 token.
|
||||
|
||||
// Prepare file in requested format.
|
||||
$filedata = $this->prepare_share_contents();
|
||||
|
||||
// If we have successfully prepared a file to share of permitted size, share it to MoodleNet.
|
||||
if (!empty($filedata['storedfile'])) {
|
||||
|
||||
// Avoid sending a file larger than the defined limit.
|
||||
$filesize = $filedata['storedfile']->get_filesize();
|
||||
if ($filesize > self::MAX_FILESIZE) {
|
||||
$filedata['storedfile']->delete();
|
||||
throw new moodle_exception('moodlenet:sharefilesizelimitexceeded', 'core', '',
|
||||
[
|
||||
'filesize' => $filesize,
|
||||
'filesizelimit' => self::MAX_FILESIZE,
|
||||
]);
|
||||
}
|
||||
|
||||
// MoodleNet only accept plaintext descriptions.
|
||||
$resourcedescription = $DB->get_field($this->cminfo->modname, 'intro', ['id' => $this->cminfo->instance]);
|
||||
$resourcedescription = strip_tags($resourcedescription);
|
||||
$resourcedescription = format_text(
|
||||
$resourcedescription,
|
||||
FORMAT_PLAIN,
|
||||
['context' => $coursecontext]
|
||||
);
|
||||
|
||||
$response = $this->moodlenetclient->create_resource_from_file($filedata, $this->cminfo->name, $resourcedescription);
|
||||
$responsecode = $response->getStatusCode();
|
||||
|
||||
$responsebody = json_decode($response->getBody());
|
||||
$resourceurl = $responsebody->homepage ?? '';
|
||||
|
||||
// TODO: Store consumable information about completed share - to be completed in MDL-77296.
|
||||
|
||||
// Delete the generated file now it is no longer required.
|
||||
// (It has either been sent, or failed - retries not currently supported).
|
||||
$filedata['storedfile']->delete();
|
||||
}
|
||||
|
||||
// Log every attempt to share (and whether or not it was successful).
|
||||
$this->log_event($coursecontext, $this->cminfo->id, $resourceurl, $responsecode);
|
||||
|
||||
return [
|
||||
'responsecode' => $responsecode,
|
||||
'drafturl' => $resourceurl,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for sharing, in the format specified.
|
||||
*
|
||||
* @return array Array of metadata about the file, as well as a stored_file object for the file.
|
||||
*/
|
||||
protected function prepare_share_contents(): array {
|
||||
|
||||
switch ($this->shareformat) {
|
||||
case self::SHARE_FORMAT_BACKUP:
|
||||
default:
|
||||
// If sharing the activity as a backup, prepare the packaged backup.
|
||||
$packager = new activity_packager($this->cminfo, $this->userid);
|
||||
$filedata = $packager->get_package();
|
||||
break;
|
||||
};
|
||||
|
||||
return $filedata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an event to the admin logs for an outbound share attempt.
|
||||
*
|
||||
* @param \context $coursecontext The course context being shared from.
|
||||
* @param int $cmid The CMID of the activity being shared.
|
||||
* @param string $resourceurl The URL of the draft resource if it was created.
|
||||
* @param int $responsecode The HTTP response code describing the outcome of the attempt.
|
||||
* @return void
|
||||
*/
|
||||
protected function log_event(
|
||||
\context $coursecontext,
|
||||
int $cmid,
|
||||
string $resourceurl,
|
||||
int $responsecode
|
||||
): void {
|
||||
$event = moodlenet_resource_exported::create([
|
||||
'context' => $coursecontext,
|
||||
'other' => [
|
||||
'cmids' => [$cmid],
|
||||
'resourceurl' => $resourceurl,
|
||||
'success' => ($responsecode == 201),
|
||||
],
|
||||
]);
|
||||
$event->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of supported share formats.
|
||||
*
|
||||
* @return array Array of supported share format values.
|
||||
*/
|
||||
protected function get_allowed_share_formats(): array {
|
||||
|
||||
return [
|
||||
self::SHARE_FORMAT_BACKUP,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?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/>.
|
||||
|
||||
namespace core\moodlenet;
|
||||
|
||||
use core\http_client;
|
||||
use core\oauth2\client;
|
||||
|
||||
/**
|
||||
* MoodleNet client which handles direct outbound communication with MoodleNet instances.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class moodlenet_client {
|
||||
/**
|
||||
* @var string MoodleNet resource creation endpoint URI.
|
||||
*/
|
||||
protected const API_CREATE_RESOURCE_URI = '/.pkg/@moodlenet/ed-resource/basic/v1/create';
|
||||
|
||||
/**
|
||||
* @var string MoodleNet scope for creating resources.
|
||||
*/
|
||||
public const API_SCOPE_CREATE_RESOURCE = '@moodlenet/ed-resource:write.own';
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param http_client $httpclient The httpclient object being used to perform the share.
|
||||
* @param client $oauthclient The OAuth 2 client for the MoodleNet site being shared to.
|
||||
*/
|
||||
public function __construct(
|
||||
protected http_client $httpclient,
|
||||
protected client $oauthclient,
|
||||
) {
|
||||
// All properties promoted, nothing further required.
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a resource on MoodleNet which includes a file.
|
||||
*
|
||||
* @param array $filedata The file data in the format [storedfile => stored_file object, filecontents => raw file].
|
||||
* @param string $resourcename The name of the resource being shared.
|
||||
* @param string $resourcedescription A description of the resource being shared.
|
||||
* @return Psr\Http\Message\ResponseInterface The HTTP client response from MoodleNet.
|
||||
*/
|
||||
public function create_resource_from_file(array $filedata, string $resourcename, $resourcedescription) {
|
||||
// This may take a long time if a lot of data is being shared.
|
||||
\core_php_time_limit::raise();
|
||||
|
||||
$moodleneturl = $this->oauthclient->get_issuer()->get('baseurl');
|
||||
$apiurl = rtrim($moodleneturl, '/') . self::API_CREATE_RESOURCE_URI;
|
||||
|
||||
$requestdata = $this->prepare_file_share_request_data($filedata, $resourcename, $resourcedescription);
|
||||
|
||||
return $this->httpclient->request('POST', $apiurl, $requestdata);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the request data required for sharing a file to MoodleNet.
|
||||
* This creates an array in the format used by \core\httpclient options to send a multipart request.
|
||||
*
|
||||
* @param array $filedata An array of data relating to the file being shared (as prepared by ::prepare_share_contents).
|
||||
* @param string $resourcename The name of the resource being shared.
|
||||
* @param string $resourcedescription A description of the resource being shared.
|
||||
* @return array Data in the format required to send a file to MoodleNet using \core\httpclient.
|
||||
*/
|
||||
protected function prepare_file_share_request_data(array $filedata, string $resourcename, $resourcedescription): array {
|
||||
return [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $this->oauthclient->get_accesstoken()->token,
|
||||
],
|
||||
'multipart' => [
|
||||
[
|
||||
'name' => 'metadata',
|
||||
'contents' => json_encode([
|
||||
'name' => $resourcename,
|
||||
'description' => $resourcedescription,
|
||||
]),
|
||||
'headers' => [
|
||||
'Content-Disposition' => 'form-data; name="."',
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'filecontents',
|
||||
'contents' => $filedata['filecontents'],
|
||||
'headers' => [
|
||||
'Content-Disposition' => 'form-data; name=".resource"; filename="' .
|
||||
$filedata['storedfile']->get_filename() . '"',
|
||||
'Content-Type' => $filedata['storedfile']->get_mimetype(),
|
||||
'Content-Transfer-Encoding' => 'binary',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace core\moodlenet;
|
||||
|
||||
use context_course;
|
||||
use core\oauth2\issuer;
|
||||
|
||||
/**
|
||||
* Class containing static utilities (such as various checks) required by the MoodleNet API.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2023 Michael Hawkins <michaelh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class utilities {
|
||||
/**
|
||||
* Check whether the specified issuer is configured as a MoodleNet instance that can be shared to.
|
||||
*
|
||||
* @param issuer $issuer The OAuth 2 issuer being validated.
|
||||
* @return bool true if the issuer is enabled and available to share to.
|
||||
*/
|
||||
public static function is_valid_instance(issuer $issuer): bool {
|
||||
global $CFG;
|
||||
|
||||
$issuerid = $issuer->get('id');
|
||||
$allowedissuer = get_config('moodlenet', 'oauthservice');
|
||||
|
||||
return ($CFG->enablesharingtomoodlenet && $issuerid == $allowedissuer && $issuer->get('enabled') &&
|
||||
$issuer->get('servicetype') == 'moodlenet');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a user has the capabilities required to share activities from a given course to MoodleNet.
|
||||
*
|
||||
* @param context_course $coursecontext Course context where the activity would be shared from.
|
||||
* @param int $userid The user ID being checked.
|
||||
* @return boolean
|
||||
*/
|
||||
public static function can_user_share(context_course $coursecontext, int $userid): bool {
|
||||
return (has_capability('moodle/moodlenet:shareactivity', $coursecontext, $userid) &&
|
||||
has_capability('moodle/backup:backupactivity', $coursecontext, $userid));
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,7 @@ class secondary extends view {
|
||||
'backup' => 9,
|
||||
'restore' => 10,
|
||||
'competencybreakdown' => 11,
|
||||
'sendtomoodlenet' => 16,
|
||||
],
|
||||
self::TYPE_CUSTOM => [
|
||||
'advgrading' => 2,
|
||||
|
||||
@@ -76,8 +76,8 @@ class api {
|
||||
/**
|
||||
* Create one of the standard issuers.
|
||||
*
|
||||
* @param string $type One of google, facebook, microsoft, nextcloud or imsobv2p1
|
||||
* @param string|false $baseurl Baseurl (only required for nextcloud and imsobv2p1)
|
||||
* @param string $type One of google, facebook, microsoft, MoodleNet, nextcloud or imsobv2p1
|
||||
* @param string|false $baseurl Baseurl (only required for nextcloud, imsobv2p1 and moodlenet)
|
||||
* @return \core\oauth2\issuer
|
||||
*/
|
||||
public static function create_standard_issuer($type, $baseurl = false) {
|
||||
@@ -92,6 +92,10 @@ class api {
|
||||
if (!$baseurl) {
|
||||
throw new moodle_exception('Nextcloud service type requires the baseurl parameter.');
|
||||
}
|
||||
case 'moodlenet':
|
||||
if (!$baseurl) {
|
||||
throw new moodle_exception('MoodleNet service type requires the baseurl parameter.');
|
||||
}
|
||||
case 'google':
|
||||
case 'facebook':
|
||||
case 'microsoft':
|
||||
|
||||
@@ -76,7 +76,11 @@ class moodlenet implements issuer_interface {
|
||||
}
|
||||
|
||||
$endpointscreated = 0;
|
||||
$configreader = new auth_server_config_reader(new http_client());
|
||||
$config = [];
|
||||
if (defined('BEHAT_SITE_RUNNING')) {
|
||||
$config['verify'] = false;
|
||||
}
|
||||
$configreader = new auth_server_config_reader(new http_client($config));
|
||||
try {
|
||||
$config = $configreader->read_configuration(new \moodle_url($baseurl));
|
||||
|
||||
@@ -150,7 +154,11 @@ class moodlenet implements issuer_interface {
|
||||
'scope' => $scopes
|
||||
];
|
||||
|
||||
$client = new http_client();
|
||||
$config = [];
|
||||
if (defined('BEHAT_SITE_RUNNING')) {
|
||||
$config['verify'] = false;
|
||||
}
|
||||
$client = new http_client($config);
|
||||
$request = new Request(
|
||||
'POST',
|
||||
$url,
|
||||
|
||||
@@ -43,6 +43,9 @@ class user_field_mapping extends persistent {
|
||||
* @return array
|
||||
*/
|
||||
private static function get_user_fields() {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/user/profile/lib.php');
|
||||
|
||||
return array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username'], get_profile_field_names());
|
||||
}
|
||||
|
||||
|
||||
@@ -2681,4 +2681,14 @@ $capabilities = array(
|
||||
'contextlevel' => CONTEXT_SYSTEM,
|
||||
'archetypes' => [],
|
||||
],
|
||||
|
||||
// Allow users to share activities to MoodleNet.
|
||||
'moodle/moodlenet:shareactivity' => [
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_COURSE,
|
||||
'archetypes' => [
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
@@ -3042,6 +3042,24 @@ $functions = array(
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_moodlenet_send_activity' => [
|
||||
'classname' => 'core\external\moodlenet_send_activity',
|
||||
'description' => 'Send activity to MoodleNet',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_moodlenet_get_share_info_activity' => [
|
||||
'classname' => 'core\external\moodlenet_get_share_info_activity',
|
||||
'description' => 'Get information about an activity being shared',
|
||||
'type' => 'read',
|
||||
'ajax' => true,
|
||||
],
|
||||
'core_moodlenet_auth_check' => [
|
||||
'classname' => 'core\external\moodlenet_auth_check',
|
||||
'description' => 'Check a user has authorized for a given MoodleNet site',
|
||||
'type' => 'write',
|
||||
'ajax' => true,
|
||||
],
|
||||
);
|
||||
|
||||
$services = array(
|
||||
|
||||
@@ -28,8 +28,8 @@ $string['settings'] = 'General settings';
|
||||
$string['privacy:reason'] = 'The TinyMCE editor does not store any preferences or user data.';
|
||||
$string['branding'] = 'TinyMCE branding';
|
||||
$string['branding_desc'] = 'Support TinyMCE by displaying the logo in the bottom corner of the text editor. The logo links to the TinyMCE website.';
|
||||
$string['plugin_enabled'] = 'The {$a} plugin has been enabled.';
|
||||
$string['plugin_disabled'] = 'The {$a} plugin has been disabled.';
|
||||
$string['plugin_enabled'] = '{$a} enabled.';
|
||||
$string['plugin_disabled'] = '{$a} disabled.';
|
||||
$string['tiny:hash'] = '#';
|
||||
$string['tiny:accessibility'] = 'Accessibility';
|
||||
$string['tiny:action'] = 'Action';
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
$string['browserepositories'] = 'Browse repositories...';
|
||||
$string['createlink'] = 'Create link';
|
||||
$string['enterurl'] = 'Enter a URL';
|
||||
$string['helplinktext'] = 'Link';
|
||||
$string['openinnewwindow'] = 'Open in new window';
|
||||
$string['pluginname'] = 'Tiny link';
|
||||
$string['link'] = 'Link';
|
||||
|
||||
@@ -8,23 +8,23 @@ Feature: An administrator can manage TinyMCE subplugins
|
||||
Scenario: An administrator can control the enabled state of TinyMCE subplugins using JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Text editors > TinyMCE editor > General settings" in site administration
|
||||
When I click on "Disable the Tiny equation editor plugin" "link"
|
||||
Then I should see "The Tiny equation editor plugin has been disabled"
|
||||
And "Disable the Tiny equation editor plugin" "link" should not exist
|
||||
But "Enable the Tiny equation editor plugin" "link" should exist
|
||||
When I click on "Enable the Tiny equation editor plugin" "link"
|
||||
Then I should see "The Tiny equation editor plugin has been enabled"
|
||||
And "Enable the Tiny equation editor plugin" "link" should not exist
|
||||
But "Disable the Tiny equation editor plugin" "link" should exist
|
||||
When I click on "Disable Tiny equation editor" "link"
|
||||
Then I should see "Tiny equation editor disabled."
|
||||
And "Disable Tiny equation editor" "link" should not exist
|
||||
But "Enable Tiny equation editor" "link" should exist
|
||||
When I click on "Enable Tiny equation editor" "link"
|
||||
Then I should see "Tiny equation editor enabled."
|
||||
And "Enable Tiny equation editor" "link" should not exist
|
||||
But "Disable Tiny equation editor" "link" should exist
|
||||
|
||||
Scenario: An administrator can control the enabled state of TinyMCE subplugins without JavaScript
|
||||
Given I am logged in as "admin"
|
||||
And I navigate to "Plugins > Text editors > TinyMCE editor > General settings" in site administration
|
||||
When I click on "Disable the Tiny equation editor plugin" "link"
|
||||
Then I should see "The Tiny equation editor plugin has been disabled"
|
||||
And "Disable the Tiny equation editor plugin" "link" should not exist
|
||||
But "Enable the Tiny equation editor plugin" "link" should exist
|
||||
When I click on "Enable the Tiny equation editor plugin" "link"
|
||||
Then I should see "The Tiny equation editor plugin has been enabled"
|
||||
And "Enable the Tiny equation editor plugin" "link" should not exist
|
||||
But "Disable the Tiny equation editor plugin" "link" should exist
|
||||
When I click on "Disable Tiny equation editor" "link"
|
||||
Then I should see "Tiny equation editor disabled."
|
||||
And "Disable Tiny equation editor" "link" should not exist
|
||||
But "Enable Tiny equation editor" "link" should exist
|
||||
When I click on "Enable Tiny equation editor" "link"
|
||||
Then I should see "Tiny equation editor enabled."
|
||||
And "Enable Tiny equation editor" "link" should not exist
|
||||
But "Disable Tiny equation editor" "link" should exist
|
||||
|
||||
+18
-1
@@ -23,6 +23,7 @@
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use core\moodlenet\utilities;
|
||||
use core_contentbank\contentbank;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
@@ -4799,7 +4800,7 @@ class settings_navigation extends navigation_node {
|
||||
* @return navigation_node|false
|
||||
*/
|
||||
protected function load_module_settings() {
|
||||
global $CFG;
|
||||
global $CFG, $USER;
|
||||
|
||||
if (!$this->page->cm && $this->context->contextlevel == CONTEXT_MODULE && $this->context->instanceid) {
|
||||
$cm = get_coursemodule_from_id(false, $this->context->instanceid, 0, false, MUST_EXIST);
|
||||
@@ -4880,6 +4881,22 @@ class settings_navigation extends navigation_node {
|
||||
$function($this, $modulenode);
|
||||
}
|
||||
|
||||
// Send to MoodleNet.
|
||||
$usercanshare = utilities::can_user_share($this->context->get_course_context(), $USER->id);
|
||||
$issuerid = get_config('moodlenet', 'oauthservice');
|
||||
$issuer = \core\oauth2\api::get_issuer($issuerid);
|
||||
$isvalidinstance = utilities::is_valid_instance($issuer);
|
||||
if ($usercanshare && $isvalidinstance) {
|
||||
$this->page->requires->js_call_amd('core/moodlenet/send_resource', 'init');
|
||||
$action = new action_link(new moodle_url(''), '', null, [
|
||||
'data-action' => 'sendtomoodlenet',
|
||||
'data-type' => 'activity',
|
||||
'data-sharetype' => 'resource',
|
||||
]);
|
||||
$modulenode->add(get_string('moodlenet:sharetomoodlenet', 'moodle'),
|
||||
$action, self::TYPE_SETTING, null, 'exportmoodlenet')->set_force_into_more_menu(true);
|
||||
}
|
||||
|
||||
// Remove the module node if there are no children.
|
||||
if ($modulenode->children->count() <= 0) {
|
||||
$modulenode->remove();
|
||||
|
||||
+2
-2
@@ -726,11 +726,11 @@ abstract class oauth2_client extends curl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access token.
|
||||
* Get access token object.
|
||||
*
|
||||
* This is just a getter to read the private property.
|
||||
*
|
||||
* @return string
|
||||
* @return stdClass
|
||||
*/
|
||||
public function get_accesstoken() {
|
||||
return $this->accesstoken;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/moodlenet/send_activity_modal_base
|
||||
Modal to send activity to MoodleNet
|
||||
Example context (json):
|
||||
{
|
||||
"uniqid": 123456,
|
||||
"activitytype": "assignment",
|
||||
"activityname": "Test assignment",
|
||||
"sharetype": "resource"
|
||||
}
|
||||
}}
|
||||
<div class="modal moodle-has-zindex" data-region="modal-container" aria-hidden="true" role="dialog">
|
||||
<div class="modal-dialog moodlenet-share-dialog" role="document" data-region="modal" aria-labelledby="{{uniqid}}-moodlenet-modal-title" tabindex="0">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header no-border" data-region="header">
|
||||
<div id="{{uniqid}}-moodlenet-modal-title" class="modal-title" data-region="title">
|
||||
<h5 class="sr-only modal-title">{{#str}} moodlenet:sharetomoodlenet, moodle {{/str}}</h5>
|
||||
<div class="d-flex moodlenet-share-moodlenetinfo">
|
||||
<span class="moodlenet-logo">{{#pix}} moodlenet, moodle, {{#str}} moodlenet:sharetomoodlenet, moodle {{/str}} {{/pix}}</span>
|
||||
<span class="moodlenet-title">{{#str}} moodlenet:sharetomoodlenet, moodle {{/str}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="close" data-action="hide" aria-label="{{#cleanstr}}closebuttontitle{{/cleanstr}}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" data-region="body">
|
||||
<div class="moodlenet-share-modal-content">
|
||||
<div class="moodlenet-share-activity-info">
|
||||
<span class="moodlenet-activity-type text-uppercase">{{activitytype}}</span>
|
||||
<span class="moodlenet-activity-name">{{activityname}}</span>
|
||||
</div>
|
||||
<hr class="moodlenet-share-activity-info-hr">
|
||||
<div class="moodlenet-share-notice">
|
||||
{{#pix}} e/help, core, {{#str}} moodlenet:sharenotice, moodle, {{sharetype}} {{/str}}{{/pix}} {{#str}} moodlenet:sharenotice, moodle, {{sharetype}} {{/str}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" data-region="footer">
|
||||
{{> core/moodlenet/send_activity_modal_footer_share }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template core/moodlenet/send_activity_modal_done
|
||||
This template renders done state for share modal.
|
||||
Example context (json):
|
||||
{
|
||||
"success": true,
|
||||
"sitesupporturl": "https://moodle.test/contactsitesupport.php"
|
||||
}
|
||||
}}
|
||||
<div class="moodlenet-share-modal-content text-center">
|
||||
<div class="moodlenet-circle-status{{#success}} success{{/success}}{{^success}} fail{{/success}}">
|
||||
<span class="status-icon pt-5{{#success}} text-success{{/success}}{{^success}} text-danger{{/success}}">
|
||||
{{#success}}
|
||||
{{#pix}} t/check, core {{/pix}}
|
||||
{{/success}}
|
||||
{{^success}}
|
||||
{{#pix}} fp/cross, theme {{/pix}}
|
||||
{{/success}}
|
||||
</span>
|
||||
<span class="status-text font-weight-bold h5 pt-5">
|
||||
{{#success}}
|
||||
{{#str}} moodlenet:sharesuccesstitle, moodle {{/str}}
|
||||
{{/success}}
|
||||
{{^success}}
|
||||
{{#str}} moodlenet:sharefailtitle, moodle {{/str}}
|
||||
{{/success}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="result pt-2">
|
||||
<span class="d-block h6 font-weight-normal pt-1 result-text">
|
||||
{{#success}}
|
||||
{{#str}} moodlenet:sharesuccesstext, moodle {{/str}}
|
||||
{{/success}}
|
||||
{{^success}}
|
||||
{{#sitesupporturl}}
|
||||
{{#str}} moodlenet:sharefailtextwithsitesupport, moodle, {{sitesupporturl}} {{/str}}
|
||||
{{/sitesupporturl}}
|
||||
{{^sitesupporturl}}
|
||||
{{#str}} moodlenet:sharefailtext, moodle {{/str}}
|
||||
{{/sitesupporturl}}
|
||||
{{/success}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
{{!
|
||||
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/moodlenet/send_activity_modal_footer_share
|
||||
Render the footer for sharing activity
|
||||
Example context (json):
|
||||
{
|
||||
"server": "MoodleNet Local"
|
||||
}
|
||||
}}
|
||||
<div class="moodlenet-share-to">
|
||||
{{#str}} moodlenet:sharingto, moodle {{/str}}<span class="font-weight-bold">{{server}}</span>
|
||||
</div>
|
||||
<div class="moodlenet-action-buttons">
|
||||
<button type="button" class="btn btn-secondary" data-action="cancel">{{#str}} cancel, moodle {{/str}}</button>
|
||||
<button type="button" class="btn btn-primary" data-action="share">{{#str}} share, moodle {{/str}}</button>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
{{!
|
||||
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/moodlenet/send_activity_modal_footer_view
|
||||
Render the footer for sharing activity
|
||||
Example context (json):
|
||||
{
|
||||
"resourseurl": "https://moodlenet.test/draft/activity.mbz"
|
||||
}
|
||||
}}
|
||||
<div class="moodlenet-action-buttons">
|
||||
<a class="btn btn-primary" href="{{resourseurl}}" target="_blank">
|
||||
{{#str}} moodlenet:gotomoodlenet, moodle {{/str}}
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
{{!
|
||||
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/moodlenet/send_activity_modal_packaging
|
||||
This template renders the packaging stage for MoodleNet.
|
||||
Example context (json):
|
||||
{
|
||||
}
|
||||
}}
|
||||
<div class="moodlenet-share-modal-content text-center p-5">
|
||||
<div class="font-weight-bold h3 p-2">{{#str}} moodlenet:sharingstatus, moodle {{/str}}</div>
|
||||
<div class="h5 font-weight-normal p-2 test-share-large">{{#str}} moodlenet:sharinglargefile, moodle {{/str}}</div>
|
||||
<div class="p-5">
|
||||
{{> core/loading }}
|
||||
</div>
|
||||
<div class="h5 font-weight-normal p-2">{{#str}} moodlenet:packagingandsending, moodle {{/str}}</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user